Built-in project · Arduino Uno R3

Guess the number: IR remote and an I2C LCD, on an Arduino Uno

The board picks a number from 0 to 99; type guesses on the remote (IR receiver on pin 2), press PLAY/PAUSE to check, and the I2C LCD narrows the range until you find it. The whole build, an Arduino Uno and 3 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Intermediate About 20 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $36. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • How an infrared receiver decodes NEC remote commands
  • Turning a command code into a button name with a switch
  • Building a number from typed digits
  • Narrowing a range, like binary search
  • Showing text on an I2C LCD
  • Difficulty: Intermediate
  • Time: About 20 minutes
  • Board: Arduino Uno R3
  • Parts: 5

How it works

The Uno picks a secret number from 1 to 98 and shows a range on the LCD, starting at 0 < ? < 99. The VS1838B infrared receiver on pin 2 listens for the remote. The LCD is on the I2C pins A4 and A5.

Press up to two digit buttons on the remote and the screen shows your guess. Press PLAY/PAUSE to submit it. The sketch says Too big or Too small, and the range closes in around the secret number.

A correct guess shows You got it! and the number. Press CH, the middle button of the top row, to start a new game.

Parts list

5 parts, plus the jumper wires. They retail for about $36; on Mokxi they're free.

Kits that can build it

These 2 starter kits have every part in the circuit. Each kit's page lists what else it can build.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Infrared receiver, VS1838B pin OUTArduino Uno R3 pin 2
Infrared receiver, VS1838B pin VCCArduino Uno R3 pin 5V
Character LCD with I2C backpack pin VCCArduino Uno R3 pin 5V
Infrared receiver, VS1838B pin GNDArduino Uno R3 pin GND
Character LCD with I2C backpack pin GNDArduino Uno R3 pin GND
Character LCD with I2C backpack pin SDAArduino Uno R3 pin A4
Character LCD with I2C backpack pin SCLArduino Uno R3 pin A5
Infrared remote, 21 buttons pin IRInfrared receiver, VS1838B pin IR

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Guess the number: the board thinks of a number, the remote guesses it.
//
//   IR receiver OUT -> pin 2   VCC -> 5V   GND -> GND
//   I2C LCD GND -> GND   VCC -> 5V   SDA -> A4   SCL -> A5
//
// Type up to two digits on the remote, then press PLAY/PAUSE to guess. The
// display shows the range the number is in, which closes in after every
// guess. Press CH (the middle button of the top row) to start a new game.
//
// Click buttons on the remote on the canvas.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// The simulated remote speaks NEC, so only that decoder is compiled in.
#define DECODE_NEC
#include <IRremote.hpp>

// The remote's buttons by the command each one sends.
const char *keyOf(uint8_t command) {
  switch (command) {
    case 0x16: return "0";
    case 0x0C: return "1";
    case 0x18: return "2";
    case 0x5E: return "3";
    case 0x08: return "4";
    case 0x1C: return "5";
    case 0x5A: return "6";
    case 0x42: return "7";
    case 0x52: return "8";
    case 0x4A: return "9";
    case 0x45: return "CH-";
    case 0x46: return "CH";
    case 0x47: return "CH+";
    case 0x44: return "PREV";
    case 0x40: return "NEXT";
    case 0x43: return "PLAY/PAUSE";
    case 0x07: return "VOL-";
    case 0x15: return "VOL+";
    case 0x09: return "EQ";
    case 0x19: return "100+";
    case 0x0D: return "200+";
    default: return "";
  }
}

const int RECEIVER = 2;

LiquidCrystal_I2C lcd(0x27, 16, 2);
int target = 0;
int lower = 0;
int upper = 99;
int guess = 0;
int digits = 0;

void showRange(const char *line) {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(line);
  lcd.setCursor(0, 1);
  lcd.print(lower);
  lcd.print(" < ? < ");
  lcd.print(upper);
}

void newGame() {
  target = random(1, 99);
  lower = 0;
  upper = 99;
  guess = 0;
  digits = 0;
  showRange("Guess Number!");
  Serial.println("new game: a number from 1 to 98");
}

void setup() {
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();
  randomSeed(analogRead(A0));
  IrReceiver.begin(RECEIVER, ENABLE_LED_FEEDBACK);
  Serial.println("Mokxi Uno: guess the number with the remote");
  newGame();
}

void loop() {
  if (!IrReceiver.decode()) return;
  bool repeat = IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT;
  const char *key = keyOf(IrReceiver.decodedIRData.command);
  IrReceiver.resume();
  if (repeat || key[0] == '\0') return;

  if (strcmp(key, "CH") == 0) {
    newGame();
  } else if (key[0] >= '0' && key[0] <= '9' && key[1] == '\0') {
    if (digits < 2) {
      guess = guess * 10 + (key[0] - '0');
      digits++;
    }
    lcd.clear();
    lcd.print("Your guess: ");
    lcd.print(guess);
  } else if (strcmp(key, "PLAY/PAUSE") == 0 && digits > 0) {
    Serial.print("guess ");
    Serial.println(guess);
    if (guess == target) {
      lcd.clear();
      lcd.print("You got it!");
      lcd.setCursor(0, 1);
      lcd.print("It was ");
      lcd.print(target);
      Serial.println("correct!");
    } else {
      if (guess > target && guess < upper) upper = guess;
      if (guess < target && guess > lower) lower = guess;
      showRange(guess > target ? "Too big" : "Too small");
    }
    guess = 0;
    digits = 0;
  }
}

Code walkthrough

The key functions of the sketch, and what each one does.

keyOf sketch.ino

Each remote button sends a command code. This function turns that code into a name such as "7", "CH" or "PLAY/PAUSE". It returns an empty string for codes it does not know, and loop ignores those.

// The remote's buttons by the command each one sends.
const char *keyOf(uint8_t command) {
  switch (command) {
    case 0x16: return "0";
    case 0x0C: return "1";
    case 0x18: return "2";
    case 0x5E: return "3";
    case 0x08: return "4";
    case 0x1C: return "5";
    case 0x5A: return "6";
    case 0x42: return "7";
    case 0x52: return "8";
    case 0x4A: return "9";
    case 0x45: return "CH-";
    case 0x46: return "CH";
    case 0x47: return "CH+";
    case 0x44: return "PREV";
    case 0x40: return "NEXT";
    case 0x43: return "PLAY/PAUSE";
    case 0x07: return "VOL-";
    case 0x15: return "VOL+";
    case 0x09: return "EQ";
    case 0x19: return "100+";
    case 0x0D: return "200+";
    default: return "";
  }
}

showRange sketch.ino

Clears the LCD and writes a message on the top row. The bottom row shows the current limits, like 20 < ? < 60, from the lower and upper variables.

void showRange(const char *line) {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(line);
  lcd.setCursor(0, 1);
  lcd.print(lower);
  lcd.print(" < ? < ");
  lcd.print(upper);
}

newGame sketch.ino

Picks a new target with random(1, 99), resets the range to 0 and 99, and clears the typed guess. Then it shows the starting range.

void newGame() {
  target = random(1, 99);
  lower = 0;
  upper = 99;
  guess = 0;
  digits = 0;
  showRange("Guess Number!");
  Serial.println("new game: a number from 1 to 98");
}

loop sketch.ino

Waits for the receiver to decode a button, and skips repeats and unknown keys. A digit adds to guess, up to two digits. PLAY/PAUSE compares the guess to target, then either wins or tightens lower or upper.

void loop() {
  if (!IrReceiver.decode()) return;
  bool repeat = IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT;
  const char *key = keyOf(IrReceiver.decodedIRData.command);
  IrReceiver.resume();
  if (repeat || key[0] == '\0') return;

  if (strcmp(key, "CH") == 0) {
    newGame();
  } else if (key[0] >= '0' && key[0] <= '9' && key[1] == '\0') {
    if (digits < 2) {
      guess = guess * 10 + (key[0] - '0');
      digits++;
    }
    lcd.clear();
    lcd.print("Your guess: ");
    lcd.print(guess);
  } else if (strcmp(key, "PLAY/PAUSE") == 0 && digits > 0) {
    Serial.print("guess ");
    Serial.println(guess);
    if (guess == target) {
      lcd.clear();
      lcd.print("You got it!");
      lcd.setCursor(0, 1);
      lcd.print("It was ");
      lcd.print(target);
      Serial.println("correct!");
    } else {
  // ... 8 more lines in the editor

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • Nothing happens when you click remote buttons This project
    Check the receiver's OUT pin goes to pin 2, and that the remote's IR pin is joined to the receiver's IR pin.
  • The LCD is blank This project
    SDA goes to A4 and SCL goes to A5. The sketch expects the display at address 0x27.
  • A third digit is ignored This project
    Guesses are limited to two digits. Press PLAY/PAUSE to submit, then type the next guess.
  • No signal is seen, or the module gets hot, because the three legs are in the wrong order. Infrared receiver
    On the VS1838B, with the dome facing you, the legs are usually OUT, GND, VCC from left to right. Check your module's marking before powering it. Supply is 2.7 to 5.5 V.
  • A plain LED blinking at the receiver is never detected. Infrared receiver
    The receiver only responds to light modulated at about 38 kHz. Send with a library such as IRremote, which makes the carrier. OUT idles high and goes low during a burst, so decode that inverted signal.
  • Button codes come out wrong or do not match another sketch. Infrared remote
    Remotes differ. Print the raw codes for your buttons first, then compare against those values. A repeat while you hold a button is a special repeat code, not a new press.
  • Nothing is received at all. Infrared remote
    Check the receiver pin order of OUT, GND and VCC, which varies by module. Use the pin your IR library expects. Keep the receiver away from bright light and aim the remote at it.
  • The screen stays blank or shows only a row of blocks. Character LCD with I2C backpack
    The I2C address is usually 0x27 or 0x3F. Run an I2C scanner to find yours. Then turn the contrast trimmer on the backpack slowly until text appears.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.