Built-in project · Arduino Uno R3

Memory math game on an LCD and an IR remote, on an Arduino Uno

A sum flashes on a 16x2 LCD for two seconds, then you key the answer in on an IR remote (receiver on 12). An RGB LED on 9, 6 and 5 and a buzzer on 3 mark it right or wrong. The whole build, an Arduino Uno and 8 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 40 minutes to build and run. Runs in your browser. Free, and no account needed.

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

The circuit runs here once the page has loaded.

What you'll learn

  • Decoding NEC infrared remote codes
  • Driving a 16x2 LCD in 4 bit mode
  • Making random questions with random
  • Mixing colors on an RGB LED with analogWrite
  • Moving between a menu and a game with state variables
  • Difficulty: Intermediate
  • Time: About 40 minutes
  • Board: Arduino Uno R3
  • Parts: 10

How it works

Press 1, 2 or 3 on the IR remote to pick a level. A question appears on the LCD for two seconds, then clears. Level 1 is a number to remember, level 2 an addition and level 3 a multiplication.

Type your answer from memory with the number keys. The remote's VOL- key sends it, EQ rubs out one digit, and ST/REPT clears the line. A right answer lights the RGB LED green and plays a high beep. A wrong one lights it red, plays a low beep and shows the real answer.

A round is five questions. At the end the screen shows your score out of 5, the piezo plays one rising note for each right answer, and the menu returns.

Parts list

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

Kits that can build it

These 5 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 12
Resistor, 220 Ω (3) pin 1Arduino Uno R3 pin 9
Character LCD, 16x2 pin D4Arduino Uno R3 pin 8
Character LCD, 16x2 pin D5Arduino Uno R3 pin 7
Resistor, 220 Ω (2) pin 1Arduino Uno R3 pin 6
Resistor, 220 Ω (1) pin 1Arduino Uno R3 pin 5
Character LCD, 16x2 pin D7Arduino Uno R3 pin 4
Piezo speaker pin 1Arduino Uno R3 pin 3
Character LCD, 16x2 pin RSArduino Uno R3 pin 2
Character LCD, 16x2 pin VDDArduino Uno R3 pin 5V
Character LCD, 16x2 pin AArduino Uno R3 pin 5V
Infrared receiver, VS1838B pin VCCArduino Uno R3 pin 5V
Character LCD, 16x2 pin VSSArduino Uno R3 pin GND
Character LCD, 16x2 pin V0Arduino Uno R3 pin GND
Character LCD, 16x2 pin RWArduino Uno R3 pin GND
Character LCD, 16x2 pin KArduino Uno R3 pin GND
RGB LED pin CArduino Uno R3 pin GND
Infrared receiver, VS1838B pin GNDArduino Uno R3 pin GND
Piezo speaker pin 2Arduino Uno R3 pin GND
Character LCD, 16x2 pin D6Arduino Uno R3 pin A2
Character LCD, 16x2 pin EArduino Uno R3 pin A3
Resistor, 220 Ω (3) pin 2RGB LED pin R
Resistor, 220 Ω (2) pin 2RGB LED pin G
Resistor, 220 Ω (1) pin 2RGB LED pin B
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
// Memory math: a sum flashes on the LCD for two seconds, then you key in the
// answer on the IR remote from memory.
//
//   LCD RS -> pin 2   E -> A3   D4 -> pin 8   D5 -> pin 7   D6 -> A2   D7 -> pin 4
//   IR receiver OUT -> pin 12
//   RGB LED red -> pin 9, green -> pin 6, blue -> pin 5, each through 220 ohm
//   passive buzzer -> pin 3
//
// Press 1, 2 or 3 on the remote to pick a level:
//   1  remember a number
//   2  remember an addition and work it out
//   3  remember a multiplication and work it out
// Each round has five questions. Type the answer with the number keys, then:
//   VOL-     (the "+" key on the simulated handset) to send it
//   EQ       (the "100+" key) to rub out the last digit
//   ST/REPT  (the "200+" key) to clear the line
// Both handsets send the same codes; only the printing on the keys differs.

#define DECODE_NEC
#include <IRremote.hpp>
#include <LiquidCrystal.h>

LiquidCrystal lcd(2, A3, 8, 7, A2, 4);  // RS, E, D4, D5, D6, D7

const int RECEIVER_PIN = 12;
const int RED_PIN = 9;
const int GREEN_PIN = 6;
const int BLUE_PIN = 5;
const int BUZZER = 3;
const int QUESTIONS = 5;

const byte KEY_OK = 0x15;
const byte KEY_BACK = 0x19;
const byte KEY_CLEAR = 0x0D;
const byte DIGIT_KEYS[10] = {0x16, 0x0C, 0x18, 0x5E, 0x08, 0x1C, 0x5A, 0x42, 0x52, 0x4A};

void showTyped();

int level = 0;
int question = 0;
int score = 0;
long answer = 0;
char typed[8];
int typedLength = 0;

void color(int r, int g, int b) {
  analogWrite(RED_PIN, r);
  analogWrite(GREEN_PIN, g);
  analogWrite(BLUE_PIN, b);
}

// The remote's next key press, or -1 if none (repeats are ignored).
int nextKey() {
  if (!IrReceiver.decode()) return -1;
  bool repeat = IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT;
  int command = IrReceiver.decodedIRData.command;
  IrReceiver.resume();
  return repeat ? -1 : command;
}

int digitOf(int key) {
  for (int d = 0; d < 10; d++) {
    if (DIGIT_KEYS[d] == key) return d;
  }
  return -1;
}

void showMenu() {
  level = 0;
  color(0, 0, 60);
  lcd.clear();
  lcd.print("Memory math");
  lcd.setCursor(0, 1);
  lcd.print("Level 1, 2 or 3?");
  Serial.println("pick a level: 1, 2 or 3");
}

void ask() {
  long a, b;
  lcd.clear();
  lcd.print("Q");
  lcd.print(question + 1);
  lcd.print(" remember:");
  lcd.setCursor(0, 1);
  if (level == 1) {
    answer = random(1000, 100000);
    lcd.print(answer);
  } else if (level == 2) {
    a = random(10, 1000);
    b = random(10, 1000);
    answer = a + b;
    lcd.print(a);
    lcd.print(" + ");
    lcd.print(b);
  } else {
    a = random(2, 100);
    b = random(2, 100);
    answer = a * b;
    lcd.print(a);
    lcd.print(" x ");
    lcd.print(b);
  }
  Serial.print("question ");
  Serial.println(question + 1);
  delay(2000);
  typedLength = 0;
  typed[0] = '\0';
  showTyped();
}

void showTyped() {
  lcd.clear();
  lcd.print("Your answer:");
  lcd.setCursor(0, 1);
  lcd.print(typed);
}

void check() {
  long given = atol(typed);
  lcd.clear();
  if (typedLength > 0 && given == answer) {
    score++;
    lcd.print("Right!");
    color(0, 160, 0);
    tone(BUZZER, 880, 300);
    Serial.println("right");
  } else {
    lcd.print("Not quite. It was");
    lcd.setCursor(0, 1);
    lcd.print(answer);
    color(160, 0, 0);
    tone(BUZZER, 110, 500);
    Serial.println("wrong");
  }
  delay(1500);
  color(0, 0, 0);
  question++;
  if (question < QUESTIONS) {
    ask();
    return;
  }
  lcd.clear();
  lcd.print("Round over");
  lcd.setCursor(0, 1);
  lcd.print("Score: ");
  lcd.print(score);
  lcd.print(" of 5");
  Serial.print("score ");
  Serial.println(score);
  for (int i = 0; i < score; i++) {
    tone(BUZZER, 523 + 131 * i, 150);
    delay(200);
  }
  delay(2000);
  showMenu();
}

void setup() {
  pinMode(RED_PIN, OUTPUT);
  pinMode(GREEN_PIN, OUTPUT);
  pinMode(BLUE_PIN, OUTPUT);
  Serial.begin(9600);
  lcd.begin(16, 2);
  IrReceiver.begin(RECEIVER_PIN);
  randomSeed(analogRead(A0));
  Serial.println("Mokxi Uno: memory math on the IR remote.");
  showMenu();
}

void loop() {
  int key = nextKey();
  if (key < 0) return;
  int d = digitOf(key);
  if (level == 0) {
    if (d >= 1 && d <= 3) {
      level = d;
      question = 0;
      score = 0;
      Serial.print("level ");
      Serial.println(level);
      ask();
    }
    return;
  }
  if (d >= 0 && typedLength < 7) {
    typed[typedLength++] = '0' + d;
    typed[typedLength] = '\0';
    showTyped();
  } else if (key == KEY_BACK && typedLength > 0) {
    typed[--typedLength] = '\0';
    showTyped();
  } else if (key == KEY_CLEAR) {
    typedLength = 0;
    typed[0] = '\0';
    showTyped();
  } else if (key == KEY_OK) {
    check();
  }
}

Code walkthrough

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

nextKey sketch.ino

Checks whether the IR receiver has decoded a button. It saves the command code, calls resume so the receiver can listen again, and returns the code. Repeats from a held button return -1 so one press counts once.

// The remote's next key press, or -1 if none (repeats are ignored).
int nextKey() {
  if (!IrReceiver.decode()) return -1;
  bool repeat = IrReceiver.decodedIRData.flags & IRDATA_FLAGS_IS_REPEAT;
  int command = IrReceiver.decodedIRData.command;
  IrReceiver.resume();
  return repeat ? -1 : command;
}

ask sketch.ino

Makes the question for the chosen level and stores the right result in answer. It shows the question, waits two seconds, then clears the line so you have to answer from memory.

void ask() {
  long a, b;
  lcd.clear();
  lcd.print("Q");
  lcd.print(question + 1);
  lcd.print(" remember:");
  lcd.setCursor(0, 1);
  if (level == 1) {
    answer = random(1000, 100000);
    lcd.print(answer);
  } else if (level == 2) {
    a = random(10, 1000);
    b = random(10, 1000);
    answer = a + b;
    lcd.print(a);
    lcd.print(" + ");
    lcd.print(b);
  } else {
    a = random(2, 100);
    b = random(2, 100);
    answer = a * b;
    lcd.print(a);
    lcd.print(" x ");
    lcd.print(b);
  }
  Serial.print("question ");
  Serial.println(question + 1);
  delay(2000);
  // ... 4 more lines in the editor

check sketch.ino

Turns the typed text into a number with atol and compares it to answer. It scores, lights the LED, plays a tone, and moves to the next question. After five questions it shows the score and returns to the menu.

void check() {
  long given = atol(typed);
  lcd.clear();
  if (typedLength > 0 && given == answer) {
    score++;
    lcd.print("Right!");
    color(0, 160, 0);
    tone(BUZZER, 880, 300);
    Serial.println("right");
  } else {
    lcd.print("Not quite. It was");
    lcd.setCursor(0, 1);
    lcd.print(answer);
    color(160, 0, 0);
    tone(BUZZER, 110, 500);
    Serial.println("wrong");
  }
  delay(1500);
  color(0, 0, 0);
  question++;
  if (question < QUESTIONS) {
    ask();
    return;
  }
  lcd.clear();
  lcd.print("Round over");
  lcd.setCursor(0, 1);
  lcd.print("Score: ");
  // ... 11 more lines in the editor

loop sketch.ino

Gets a key and turns it into a digit with digitOf. At the menu, digits 1 to 3 start a level. During a question, digits are added to the typed answer, up to seven, and the other keys edit or send it.

void loop() {
  int key = nextKey();
  if (key < 0) return;
  int d = digitOf(key);
  if (level == 0) {
    if (d >= 1 && d <= 3) {
      level = d;
      question = 0;
      score = 0;
      Serial.print("level ");
      Serial.println(level);
      ask();
    }
    return;
  }
  if (d >= 0 && typedLength < 7) {
    typed[typedLength++] = '0' + d;
    typed[typedLength] = '\0';
    showTyped();
  } else if (key == KEY_BACK && typedLength > 0) {
    typed[--typedLength] = '\0';
    showTyped();
  } else if (key == KEY_CLEAR) {
    typedLength = 0;
    typed[0] = '\0';
    showTyped();
  } else if (key == KEY_OK) {
    check();
  // ... 2 more lines in the editor

Troubleshooting

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

  • Remote buttons do nothing. This project
    Check that the receiver's OUT pin goes to pin 12, with VCC on 5 V and GND on ground.
  • The LCD is blank or shows boxes. This project
    Check RS on pin 2, E on A3, and D4 to D7 on pins 8, 7, A2 and 4. V0 and RW need to go to ground.
  • The wrong digit appears for a key. This project
    The codes in DIGIT_KEYS match this remote. A different remote needs its own codes.
  • The screen lights up but shows only a row of solid boxes, or nothing at all. Character LCD
    Contrast pin V0 needs a voltage. Connect the middle pin of a 10 k pot to V0, with its ends on 5 V and GND. Also confirm that the code calls lcd.begin(16, 2).
  • Garbled characters or a blank display even with good contrast. Character LCD
    In 4-bit mode only D4 to D7 are used, and D0 to D3 stay unconnected. Tie RW to GND. Match the pins in LiquidCrystal(rs, en, d4, d5, d6, d7) exactly to your wiring.
  • The colors are wrong or one color never lights. RGB LED
    Check whether your LED is common cathode (shared leg to ground) or common anode (shared leg to 5 V). Common anode works backwards: LOW turns a color on.
  • One color is much brighter than the others, or the LED runs too hot. RGB LED
    Use one resistor per color leg, never one for the shared leg. Typical values are 220 ohm for red and 100 to 150 ohm for green and blue on 5 V.
  • The LED is dim, burns out, or the pin reads wrong because the resistor value is off. Resistor
    Read the color bands again. A 220 ohm to 330 ohm resistor suits a typical LED on 5 V. 10k is a common pull-up or pull-down value. Check with a multimeter if unsure.

Change it and keep it

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