Built-in project · Arduino Uno R3

Number guess on an Arduino Uno

A 4x3 telephone keypad and a 16x2 LCD on an Uno: guess the number, and the display says higher or lower. The whole build, an Arduino Uno and 2 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 25 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

  • Scanning a 4x3 phone keypad on seven pins
  • Showing text on a 16x2 character LCD
  • Seeding random numbers from a human reaction time
  • Building a number from typed digits
  • Giving feedback with HIGHER and LOWER
  • Difficulty: Intermediate
  • Time: About 25 minutes
  • Board: Arduino Uno R3
  • Parts: 4

How it works

The board picks a secret number from 1 to 100. You type a guess on the phone keypad and press #. The LCD says HIGHER or LOWER, and * erases what you typed. A right guess shows how many goes it took, then a new number is chosen.

The random number is seeded from micros() when you press the first key. That is your reaction time in microseconds, so each session starts differently. Seeding in setup would give the same number every run.

Parts list

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

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
Keypad, 4x3 telephone pin R1Arduino Uno R3 pin 12
Keypad, 4x3 telephone pin R2Arduino Uno R3 pin 11
Keypad, 4x3 telephone pin R3Arduino Uno R3 pin 10
Keypad, 4x3 telephone pin R4Arduino Uno R3 pin 9
Keypad, 4x3 telephone pin C1Arduino Uno R3 pin 8
Keypad, 4x3 telephone pin C2Arduino Uno R3 pin 7
Keypad, 4x3 telephone pin C3Arduino Uno R3 pin 6
Character LCD, 16x2 pin VDDArduino Uno R3 pin 5V
Character LCD, 16x2 pin AArduino 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
Character LCD, 16x2 pin RSArduino Uno R3 pin A0
Character LCD, 16x2 pin EArduino Uno R3 pin A1
Character LCD, 16x2 pin D4Arduino Uno R3 pin A2
Character LCD, 16x2 pin D5Arduino Uno R3 pin A3
Character LCD, 16x2 pin D6Arduino Uno R3 pin A4
Character LCD, 16x2 pin D7Arduino Uno R3 pin A5

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
// Number guess: the board thinks of a number, you type it on a phone keypad.
//
//   pins 12, 11, 10, 9     keypad rows R1 to R4
//   pins 8, 7, 6           keypad columns C1 to C3
//   A0  LCD RS             A1  LCD E
//   A2, A3, A4, A5         LCD D4 to D7   (RW and V0 to GND)
//
// The pad is the twelve-key one off a telephone:
//
//     1  2  3
//     4  5  6
//     7  8  9
//     *  0  #
//
// Type a number between 1 and 100 and press `#` to send it. The display says
// HIGHER or LOWER, and `*` rubs out what you have typed. Get it right and it
// tells you how many goes it took and thinks of another.
//
// # Where the number comes from
//
// A board has no dice and no clock that is different on each run, so seeding
// the generator in `setup()` gives the same "random" number every time, which
// is the classic bug in every guessing game written on a microcontroller. This
// one seeds from `micros()` at the moment the first key of the game is pressed.
// That reading is a person's reaction time in microseconds, and nothing on the
// board can predict it.
//
// # Reading the pad
//
// Twelve keys on seven pins and no chip: each key shorts its row to its column,
// so the sketch scans: one row driven low at a time, the three columns read
// with their pull-ups on. mokxi_keypad.h does the scan and the debounce and
// hands back one character per press. It never drives a row high, because two
// keys held in one column would put one driven output across another.

#include "mokxi_keypad.h"
#include "mokxi_lcd1602.h"

const uint8_t ROWS[4] = {12, 11, 10, 9};
const uint8_t COLS[3] = {8, 7, 6};

Keypad4x3 pad(ROWS, COLS);
Lcd1602 lcd(A0, A1, A2, A3, A4, A5);

const int LOWEST = 1;
const int HIGHEST = 100;

int secret = 0;
int entry = 0;
int tries = 0;
bool seeded = false;

static void newGame()
{
  secret = (int)random(LOWEST, HIGHEST + 1);
  entry = 0;
  tries = 0;
}

static void showEntry(const char *note)
{
  lcd.setCursor(0, 0);
  lcd.print(note);
  lcd.padLine();
  lcd.setCursor(0, 1);
  lcd.print("Guess: ");
  if (entry > 0) {
    lcd.print(entry);
  } else {
    lcd.write('_');
  }
  lcd.padLine();
}

void setup()
{
  pad.begin();
  lcd.begin();
  lcd.print("Number guess");
  lcd.setCursor(0, 1);
  lcd.print("1 to 100, # ends");
}

void loop()
{
  char key = pad.pressed();
  if (key == 0) {
    return;
  }

  // The first press of the session is what the generator is seeded from: a
  // person's reaction time, which nothing on the board can work out for itself.
  if (!seeded) {
    randomSeed(micros());
    seeded = true;
    newGame();
    showEntry("Think of a go");
  }

  if (key == '*') {
    entry = 0;
    showEntry("Cleared");
    return;
  }

  if (key >= '0' && key <= '9') {
    if (entry < 100) {
      entry = entry * 10 + (key - '0');
    }
    showEntry("Typing");
    return;
  }

  if (key != '#') {
    return;
  }

  if (entry < LOWEST || entry > HIGHEST) {
    entry = 0;
    showEntry("1 to 100 only");
    return;
  }

  tries++;
  if (entry < secret) {
    entry = 0;
    showEntry("HIGHER");
    return;
  }
  if (entry > secret) {
    entry = 0;
    showEntry("LOWER");
    return;
  }

  lcd.setCursor(0, 0);
  lcd.print("Got it: ");
  lcd.print(secret);
  lcd.padLine();
  lcd.setCursor(0, 1);
  lcd.print(tries);
  lcd.print(tries == 1 ? " go. Again..." : " goes. Again...");
  lcd.padLine();
  delay(2000);
  newGame();
  showEntry("New number");
}

Code walkthrough

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

newGame sketch.ino

Picks a new secret number between LOWEST and HIGHEST with random, and resets the typed entry and the try count to zero.

static void newGame()
{
  secret = (int)random(LOWEST, HIGHEST + 1);
  entry = 0;
  tries = 0;
}

showEntry sketch.ino

Redraws the display. The top line shows a note such as HIGHER or Typing. The bottom line shows Guess: followed by your number, or an underscore when nothing is typed. padLine clears the rest of each line.

static void showEntry(const char *note)
{
  lcd.setCursor(0, 0);
  lcd.print(note);
  lcd.padLine();
  lcd.setCursor(0, 1);
  lcd.print("Guess: ");
  if (entry > 0) {
    lcd.print(entry);
  } else {
    lcd.write('_');
  }
  lcd.padLine();
}

loop sketch.ino

Waits for a key. The first key seeds the generator and starts a game. Digits build the entry. A * clears it. A # checks the range, counts a try, and compares the guess with the secret.

void loop()
{
  char key = pad.pressed();
  if (key == 0) {
    return;
  }

  // The first press of the session is what the generator is seeded from: a
  // person's reaction time, which nothing on the board can work out for itself.
  if (!seeded) {
    randomSeed(micros());
    seeded = true;
    newGame();
    showEntry("Think of a go");
  }

  if (key == '*') {
    entry = 0;
    showEntry("Cleared");
    return;
  }

  if (key >= '0' && key <= '9') {
    if (entry < 100) {
      entry = entry * 10 + (key - '0');
    }
    showEntry("Typing");
    return;
  // ... 36 more lines in the editor

Troubleshooting

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

  • The LCD shows nothing or only blocks This project
    Check RS on A0, E on A1, and D4 to D7 on A2 to A5, and tie RW and V0 to ground.
  • Pressing # says 1 to 100 only This project
    The guess must be from LOWEST to HIGHEST. An empty entry or a number over 100 is rejected.
  • Pressing one key reports several keys or the wrong key. Keypad
    Keep the row and column pin order right. Scan by driving one column low and reading the rows with pull-ups. Use the Keypad library with the correct pin lists, and match the pin map to your wiring.
  • Keys do not register or float. Keypad
    Rows need pull-ups, either INPUT_PULLUP or external resistors. Give each key press a few milliseconds of debounce and check for loose wires on all seven pins.
  • 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.
  • Upload fails or nothing runs after wiring pins 0 and 1 to a module. Arduino Uno R3
    Pins 0 and 1 are the USB serial port. Move the module to other pins, or unplug it while uploading. Also check the right board is selected.
  • Pins misbehave or the board resets when you power motors or servos from it. Arduino Uno R3
    Each I/O pin gives about 20 mA, 40 mA max. Power motors and servos from their own supply and join its ground to the Uno's GND.

Change it and keep it

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