Kids love calculators so i made an Arduino based calculator with a generic 4×4 keypad that uses 8 digital pins. It comes with a sticker, so it was easy to attach it to a plywood laser cut box. The only pity is that the buttons are labelled A,B,C,D; we need to use small stickers to cover them with the math operators. It works as a normal calculator, but also has a hidden function of a maths quiz inside, which gives you 60 seconds to answers as many sums as you can, keeping a high score.

IMG_20171108_141752 - Edited

I used a cheap 16×2 LCD screen that uses the standard LiquidCrystal library. It looks better than the above picture suggests.

The code is not very complicated, but not trivial either. I started from this example that works with integers, changed the display, and added a decimal point function to it, and a couple of other features to limit the numbers to the display size (16 digits per line), and prevent overflow. I am using float type numbers, so their precision is usually limited to 6-7 digits.

At startup, it functions as a normal calculator, until you enter the magic number ‘321’, then the quiz starts. The quiz asks simple math questions, and counts how many you get right in 60 seconds. The questions become increasingly difficult as your score goes up. It alternates between +, -, *, /

Below a video of the calculator function, and the quiz.

I started with a breadboard prototype as below.

IMG_20171106_161045 - Edited

IMG_20171106_161235 - Edited

As this was working well and the kids loved it, i designed a simple PCB for the Atmega328P, with a header for the 16-pin LCD screen. The buzzer sounds at startup, at the beginning and end of the quiz, and at each key press: normal sound for valid key, low beep for a wrong key.

IMG_20171106_161133 - Edited

At first i hoped to power the project with 2x 1.5V batteries, but the LCD screen needs at least 4V so i ended up with 3x AAA batteries.

IMG_20171106_165525 - Edited

The code is below:

[wordpress does not show the includes: EEPROM.h   Keypad.h   LiquidCrystal.h]

/*
   Arduino calculator with keypad and LCD, and quiz
   Tom Tobback Oct 2017

   keypad library:
  || @version 1.0
  || @author Andrew Mascolo
  || @date May 15, 2013
  || @description
  || Simple use of keypad and LCD
  taken from https://playground.arduino.cc/Main/KeypadCalculator

  use LCD 16x2 with pot
  buzzer on D13
  keypad on D2-D9
  use * as decimal point

  starts as normal calculator
  enter magic number to start quiz: 60 seconds 
  enter reset code to reset high score
*/

#include        // for high score
#include 
#include 

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, A5, A4, A3, A2);


float first = 0;
float second = 0;
float total = 0;
boolean mult = false;       // multiplication flag to avoid overflow
boolean first_decimal = false;  // does the first number have a decimal point
int first_decimal_pos = 0; // how many digits after decimal point
boolean second_decimal = false;  // does the second number have a decimal point
int second_decimal_pos = 0; // how many digits after decimal point
int digits_first = 0;
int digits_second = 0;
const long magic_number = 321;     // to enter the quiz state
const long magic_reset = 332211;   // to reset high score
boolean quiz = false;             // quiz state
int answer;                       // quiz answer
int correct_answer;               // correct quiz answer
int digits_answer = 0;            // count digits of answer
int score = 0;
unsigned long timestamp = 0;      // start of quiz
int high_score;
char customKey;
const byte ROWS = 4;
const byte COLS = 4;

char keys[ROWS][COLS] = {
  {'1', '2', '3', '+'},
  {'4', '5', '6', '-'},
  {'7', '8', '9', '*'},
  {'.', '0', '=', '/'}
};
byte rowPins[ROWS] = {2, 3, 4, 5}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {6, 7, 8, 9}; //connect to the column pinouts of the keypad

//initialize an instance of class NewKeypad
Keypad customKeypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS);

void setup()
{
  //  EEPROM.write(10, 0);            // OPTIONAL reset high score to 0
  high_score = EEPROM.read(10);
  if (high_score == 255) {
    EEPROM.write(10, 0);            // reset high score to 0
    high_score = 0;
  }

  randomSeed(analogRead(A0));
  Serial.begin(9600);
  lcd.begin(16, 2);
  lcd.setCursor(2, 0);
  lcd.print("BuffaloLabs");
  lcd.setCursor(0, 1);
  lcd.print("Calculator+Quiz");
  tone(13, 500);
  delay(100);
  tone(13, 1000);
  delay(100);
  noTone(13);
  delay(2000);
  lcd.clear();
}

void loop()
{

  customKey = customKeypad.getKey();

  if (quiz) {                                 // quiz waiting for answer
    lcd.setCursor(14, 1);
    int remaining = 60 - (millis() - timestamp) / 1000;
    if (remaining < 10) lcd.print(" "); lcd.print(remaining); if (remaining == 0) { // quiz FINISHED if (score > high_score) {                       // new high score
        high_score = score;
        EEPROM.write(10, high_score);
        lcd.clear();
        lcd.print("NEW HIGH SCORE= ");
        lcd.setCursor(0, 1);
        lcd.print(high_score);
        for (int i = 1000; i < 4000; i += 5) { // siren tone(13, i); delay(1); } for (int i = 4000; i > 1000; i -= 5) {
          tone(13, i);
          delay(1);
        }
        noTone(13);
      }
      else {                                          // no new high score
        for (int i = 0; i < 10; i++) {
          tone(13, 2000);
          delay(50);
          noTone(13);
          delay(50);
        }
      }
      customKeypad.waitForKey();                      // new quiz
      beep();
      score = 0;
      showHighscore();
      correct_answer = quizQuestion();
      timestamp = millis();
    }

    switch (customKey)
    {
      case '0' ... '9':
        if (digits_answer < 4) { answer = answer * 10 + (customKey - '0'); // if integer (from ascii to numerical value) lcd.setCursor(digits_answer, 1); lcd.print(customKey); digits_answer++; beep(); Serial.println(answer, 6); } else { lowBeep(); } break; case '=': if (digits_answer > 0) {
          if (answer == correct_answer) {
            beepYes();
            score++;
          }
          else {
            beepNo();
            score--;
            if (score < 0) score = 0;
          }
          correct_answer = quizQuestion();
        }
        else {
          lowBeep();
        }
        break;
      case '+':
        lowBeep();
        break;
      case '-':
        lowBeep();
        break;
      case '*':
        lowBeep();
        break;
      case '/':
        lowBeep();
        break;
      case '.':
        lowBeep();
        break;
    }
  }

  else {                                      // normal calculator
    switch (customKey)
    {
      case '0' ... '9': // This keeps collecting the first value until a operator is pressed "+-*/"
        if (digits_first == 0) lcd.clear();
        if (digits_first < 6) { if (first_decimal) { first_decimal_pos++; float divider = pow(10, first_decimal_pos); first = first + (customKey - '0') / divider; } else { first = first * 10 + (customKey - '0'); // if integer (from ascii to numerical value) } lcd.print(customKey); digits_first++; beep(); Serial.println(first, 6); } else { lowBeep(); } if (first == magic_number) { // escape to quiz showHighscore(); correct_answer = quizQuestion(); quiz = true; timestamp = millis(); } if (first == magic_reset) { // reset quiz high score showReset(); char rst = customKeypad.waitForKey(); if (rst == '0') { EEPROM.write(10, 0); high_score = 0; longBeep(); } else { lowBeep(); } showHighscore(); correct_answer = quizQuestion(); quiz = true; timestamp = millis(); } break; case '+': if (digits_first == 0) { lowBeep(); break; } lcd.print("+"); beep(); second = SecondNumber(); // get the collected the second number lcd.print("="); beep(); total = first + second; printTotal(); break; case '-': if (digits_first == 0) { lowBeep(); break; } lcd.print("-"); beep(); second = SecondNumber(); lcd.print("="); beep(); total = first - second; printTotal(); break; case '*': if (digits_first == 0) { lowBeep(); break; } lcd.print("*"); beep(); mult = true; second = SecondNumber(); lcd.print("="); beep(); total = first * second; printTotal(); mult = false; break; case '/': if (digits_first == 0) { lowBeep(); break; } lcd.print("/"); beep(); second = SecondNumber(); lcd.print("="); beep(); second == 0 ? lcd.print("Invalid") : total = (float)first / (float)second; printTotal(); break; case '.': if (digits_first == 0) { lowBeep(); break; } if (first_decimal) { // already has decimal point lowBeep(); } else { lcd.setCursor(digits_first, 0); // add decimal point lcd.print(customKey); digits_first++; beep(); first_decimal = true; } break; case '=': lowBeep(); resetC(); lcd.clear(); break; } } } float SecondNumber() { while ( 1 ) { customKey = customKeypad.getKey(); if (customKey >= '0' && customKey <= '9') {
      if (digits_second < 6) {
        if ((mult && (digits_first + digits_second) < 10) || !mult) {
          lcd.print(customKey);
          beep();
          if (second_decimal) {
            second_decimal_pos++;
            float divider = pow(10, second_decimal_pos);
            second = second + (customKey - '0') / divider;
          }
          else {
            second = second * 10 + (customKey - '0');  // if integer    (from ascii to numerical value)
          }
          digits_second++;
          Serial.println(second, 6);
        }
        else {
          lowBeep();
        }
      }
      else {
        lowBeep();
      }
    }
    if (customKey == '=') break; //return second;

    if (customKey == '.') {
      if (second_decimal) {                    // already has decimal point
        lowBeep();
      }
      else {
        lcd.print(customKey);
        digits_second++;
        beep();
        second_decimal = true;
      }

    }
    if (customKey == '+' || customKey == '-' || customKey == '*' || customKey == '/' ) lowBeep();


  }
  return second;
}

void resetC() {
  total = 0;
  digits_first = 0; digits_second = 0;
  first = 0, second = 0;  // reset values back to zero for next use
  first_decimal = false;
  first_decimal_pos = 0;
  second_decimal = false;
  second_decimal_pos = 0;
}

void printTotal() {
  int decimalPlaces;
  float temp = total;
  for (decimalPlaces = 0; decimalPlaces < 7; decimalPlaces++) { if (temp == (long)temp) break; temp *= 10.0; // Shift left one decimal digit } Serial.println(total, decimalPlaces); lcd.setCursor(0, 1); lcd.print(total, decimalPlaces); resetC(); } void beep() { tone(13, 500); delay(10); noTone(13); } void lowBeep() { tone(13, 50); // low beep delay(10); noTone(13); } void longBeep() { tone(13, 500); // long beep delay(1000); noTone(13); } /////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////// int quizQuestion() { delay(random(0, 100)); randomSeed(analogRead(A0)); lcd.clear(); answer = 0; digits_answer = 0; int upper_limit = score / 2 + 5; first = random(score / 2 + 1, upper_limit); delay(random(0, 100)); randomSeed(analogRead(A0)); delay(random(0, 100)); int op = score % 4; // 0 1 2 3 switch (op) { case 0: lcd.print(first, 0); // float but show as int lcd.print("+"); second = random(1, upper_limit - 1); correct_answer = first + second; break; case 1: lcd.print(first, 0); // float but show as int lcd.print("-"); second = random(1, first + 1); correct_answer = first - second; break; case 2: lcd.print(first, 0); // float but show as int lcd.print("*"); if (first > 10) {
        second = 2;
      }
      else {
        second = random(0, 11);
      }
      correct_answer = first * second;
      break;
    case 3:                    // correct_answer = first >> (first * second) / second = first
      second = random(1, first + 1);
      correct_answer = first;
      lcd.print(first * second, 0);              // float but show as int
      lcd.print("/");
      break;
  }
  lcd.print(second, 0);              // float but show as int
  lcd.print("=");

  lcd.setCursor(8, 0);
  lcd.print("score:");
  if (score < 10) lcd.print(" ");
  lcd.print(score);

  lcd.setCursor(9, 1);
  lcd.print("time:");

  lcd.setCursor(0, 1);
  return correct_answer;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////


void beepYes() {
  for (int i = 0; i < 3; i++) {
    tone(13, 1000);
    delay(50);
    noTone(13);
    delay(50);
  }
}

void beepNo() {
  for (int i = 0; i < 3; i++) {
    tone(13, 100);
    delay(50);
    noTone(13);
    delay(50);
  }
}

void showHighscore() {
  lcd.clear();
  lcd.print("HIGH SCORE= ");
  lcd.print(high_score);
  lcd.setCursor(0,1);
  lcd.print("starting quiz..");
  delay(2000);
  lcd.clear();

  tone(13, 500);
  delay(100);
  tone(13, 1000);
  delay(100);
  noTone(13);
}

void showReset() {
  lcd.clear();
  lcd.print("HIGH SCORE= ");
  lcd.print(high_score);
  lcd.setCursor(0, 1);
  lcd.print("Press 0 to reset...");
}

Leave a Reply

Your email address will not be published. Required fields are marked *