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.
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.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Full-size breadboard
- 1 × Character LCD, 16x2
- 1 × RGB LEDIn Starter Projects Kit.
- 3 × Resistor, 220 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × Infrared receiver, VS1838B
- 1 × Infrared remote, 21 buttons
- 1 × Piezo speakerIn Starter Kit, Claw Machine Kit, Vending Machine Kit.
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 pin | Connects to |
|---|---|
| Infrared receiver, VS1838B pin OUT | Arduino Uno R3 pin 12 |
| Resistor, 220 Ω (3) pin 1 | Arduino Uno R3 pin 9 |
| Character LCD, 16x2 pin D4 | Arduino Uno R3 pin 8 |
| Character LCD, 16x2 pin D5 | Arduino Uno R3 pin 7 |
| Resistor, 220 Ω (2) pin 1 | Arduino Uno R3 pin 6 |
| Resistor, 220 Ω (1) pin 1 | Arduino Uno R3 pin 5 |
| Character LCD, 16x2 pin D7 | Arduino Uno R3 pin 4 |
| Piezo speaker pin 1 | Arduino Uno R3 pin 3 |
| Character LCD, 16x2 pin RS | Arduino Uno R3 pin 2 |
| Character LCD, 16x2 pin VDD | Arduino Uno R3 pin 5V |
| Character LCD, 16x2 pin A | Arduino Uno R3 pin 5V |
| Infrared receiver, VS1838B pin VCC | Arduino Uno R3 pin 5V |
| Character LCD, 16x2 pin VSS | Arduino Uno R3 pin GND |
| Character LCD, 16x2 pin V0 | Arduino Uno R3 pin GND |
| Character LCD, 16x2 pin RW | Arduino Uno R3 pin GND |
| Character LCD, 16x2 pin K | Arduino Uno R3 pin GND |
| RGB LED pin C | Arduino Uno R3 pin GND |
| Infrared receiver, VS1838B pin GND | Arduino Uno R3 pin GND |
| Piezo speaker pin 2 | Arduino Uno R3 pin GND |
| Character LCD, 16x2 pin D6 | Arduino Uno R3 pin A2 |
| Character LCD, 16x2 pin E | Arduino Uno R3 pin A3 |
| Resistor, 220 Ω (3) pin 2 | RGB LED pin R |
| Resistor, 220 Ω (2) pin 2 | RGB LED pin G |
| Resistor, 220 Ω (1) pin 2 | RGB LED pin B |
| Infrared remote, 21 buttons pin IR | Infrared 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.
// 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 editorcheck 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 editorloop 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 editorTroubleshooting
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 inDIGIT_KEYSmatch 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.