Built-in project · Arduino Nano

Electronic voting machine on an Arduino Nano

Electronic voting machine on an Arduino Nano: four buttons to vote, a result button for every count and the winner on an LCD, and the votes kept in EEPROM. The whole build, an Arduino Nano and 7 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.

Beginner Runs in your browser. Free, and no account needed.

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

Electronic voting machine · Arduino Nanolive0.000 s 0.00x
Press Run, vote with the colored buttons, then press the white one
The circuit itself, running here on the simulator. Press what can be pressed; click anything else to open it in the editor.

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 Nano
// Electronic voting machine: four buttons to vote, one to see the result,
// and a 16x2 I2C LCD, on an Arduino Nano.
//
//   vote buttons   red -> D2, blue -> D3, green -> D4, yellow -> D5, each to GND
//   result button  D6 to GND
//   LCD            SDA -> A4, SCL -> A5, VCC -> 5 V, GND -> GND (address 0x27)
//   piezo          D12 and GND
//
// The class votes for a team color. Press a color to vote for it: the
// machine beeps, says thank you, and ignores every button for two seconds,
// so one press is one vote and the next voter steps up.
//
// Press the result button to see every count and the winner for five
// seconds. Hold it for three seconds to clear all the votes and start a new
// election.
//
// # The votes survive a power cut
//
// Every vote is written to the EEPROM, the Nano's own memory that keeps its
// contents with the power off. When the sketch starts it reads the counts
// back, so pulling the plug halfway through the vote loses nothing. A blank
// EEPROM reads 255 in every byte, so a marker byte says whether the counts
// there were written by this sketch at all.
//
// # One press, one vote
//
// A button is read as "pressed" only on the change from up to down, and 30 ms
// of quiet after a change is needed before the next one counts. Holding a
// button down, or the contacts bouncing, can never add a second vote.

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

const uint8_t VOTE[4] = {2, 3, 4, 5};
const char *const TEAM[4] = {"Red", "Blue", "Green", "Yellow"};
const uint8_t RESULT = 6;
const uint8_t PIEZO = 12;

const int MARKER_ADDRESS = 8;  // the four counts take bytes 0 to 7
const uint8_t MARKER = 0x5A;
const unsigned long LOCK_MS = 2000;
const unsigned long SHOW_MS = 5000;
const unsigned long CLEAR_HOLD_MS = 3000;

LiquidCrystal_I2C lcd(0x27, 16, 2);

unsigned int votes[4];
bool lastState[5];             // the four vote buttons, then the result button
unsigned long changedAt[5];
unsigned long busyUntil = 0;   // the screen shows a message until then
unsigned long resultDownAt = 0;
bool resultHeld = false;

unsigned int total() {
  return votes[0] + votes[1] + votes[2] + votes[3];
}

void saveVotes() {
  for (uint8_t i = 0; i < 4; i++) EEPROM.put(i * 2, votes[i]);
  EEPROM.write(MARKER_ADDRESS, MARKER);
}

void loadVotes() {
  if (EEPROM.read(MARKER_ADDRESS) != MARKER) {
    for (uint8_t i = 0; i < 4; i++) votes[i] = 0;
    saveVotes();
    return;
  }
  for (uint8_t i = 0; i < 4; i++) EEPROM.get(i * 2, votes[i]);
}

void showWaiting() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Press to vote");
  lcd.setCursor(0, 1);
  lcd.print("Votes so far: ");
  lcd.print(total());
}

void castVote(uint8_t team) {
  votes[team]++;
  saveVotes();
  tone(PIEZO, 1047, 150);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Vote: ");
  lcd.print(TEAM[team]);
  lcd.setCursor(0, 1);
  lcd.print("Thank you!");
  Serial.print("vote for ");
  Serial.print(TEAM[team]);
  Serial.print(", ");
  Serial.print(total());
  Serial.println(total() == 1 ? " vote in all" : " votes in all");
  busyUntil = millis() + LOCK_MS;
}

void showResults() {
  // Who has the most? A tie is said as a tie.
  unsigned int best = 0;
  for (uint8_t i = 0; i < 4; i++) best = max(best, votes[i]);
  lcd.clear();
  for (uint8_t i = 0; i < 4; i++) {
    lcd.setCursor((i % 2) * 8, i / 2);
    lcd.print(TEAM[i][0]);  // R, B, G and Y
    lcd.print(':');
    lcd.print(votes[i]);
  }
  Serial.print("results: ");
  for (uint8_t i = 0; i < 4; i++) {
    Serial.print(TEAM[i]);
    Serial.print(' ');
    Serial.print(votes[i]);
    Serial.print(i < 3 ? ", " : "\n");
  }
  uint8_t leaders = 0;
  for (uint8_t i = 0; i < 4; i++) {
    if (votes[i] == best) leaders++;
  }
  if (best == 0) {
    Serial.println("no votes yet");
  } else if (leaders == 1) {
    for (uint8_t i = 0; i < 4; i++) {
      if (votes[i] != best) continue;
      Serial.print("winner: ");
      Serial.println(TEAM[i]);
      lcd.setCursor(12, 0);
      lcd.print("WIN");
      lcd.setCursor(12, 1);
      lcd.print(TEAM[i][0]);
    }
  } else {
    Serial.print("a tie between ");
    Serial.print(leaders);
    Serial.println(" teams");
    lcd.setCursor(12, 0);
    lcd.print("TIE");
  }
  busyUntil = millis() + SHOW_MS;
}

void clearVotes() {
  for (uint8_t i = 0; i < 4; i++) votes[i] = 0;
  saveVotes();
  tone(PIEZO, 440, 400);
  lcd.clear();
  lcd.print("Votes cleared");
  Serial.println("votes cleared: a new election");
  busyUntil = millis() + LOCK_MS;
}

// Has button `i` on `pin` just gone down?
bool pressed(uint8_t i, uint8_t pin, unsigned long now) {
  bool state = digitalRead(pin);
  bool down = false;
  if (state != lastState[i] && now - changedAt[i] > 30) {
    down = state == LOW;
    lastState[i] = state;
    changedAt[i] = now;
  }
  return down;
}

void setup() {
  Serial.begin(115200);
  for (uint8_t i = 0; i < 4; i++) pinMode(VOTE[i], INPUT_PULLUP);
  pinMode(RESULT, INPUT_PULLUP);
  pinMode(PIEZO, OUTPUT);
  for (uint8_t i = 0; i < 5; i++) lastState[i] = HIGH;
  lcd.init();
  lcd.backlight();
  loadVotes();
  Serial.print("Voting machine ready: ");
  Serial.print(total());
  Serial.println(" votes stored");
  showWaiting();
}

void loop() {
  unsigned long now = millis();
  bool busy = (long)(busyUntil - now) > 0;

  for (uint8_t i = 0; i < 4; i++) {
    // Read every button every time, so a press during the lock is not saved
    // up and counted the moment it ends.
    if (pressed(i, VOTE[i], now) && !busy) castVote(i);
  }

  if (pressed(4, RESULT, now)) {
    resultDownAt = now;
    resultHeld = true;
    showResults();
  }
  if (resultHeld && lastState[4] == HIGH) resultHeld = false;
  if (resultHeld && now - resultDownAt >= CLEAR_HOLD_MS) {
    resultHeld = false;
    clearVotes();
  }

  // Back to "press to vote" when a message has been up long enough.
  static bool wasBusy = false;
  busy = (long)(busyUntil - now) > 0;
  if (wasBusy && !busy) showWaiting();
  wasBusy = busy;
}

Parts list

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

How it is wired

16 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.

  • Arduino Nano pin 2; Arcade button (5) pin 2
  • Arduino Nano pin 3; Arcade button (4) pin 2
  • Arduino Nano pin 4; Arcade button (3) pin 2
  • Arduino Nano pin 5; Arcade button (2) pin 2
  • Arduino Nano pin 6; Arcade button (1) pin 2
  • Arduino Nano pin 12; Piezo speaker pin 1
  • Arduino Nano pin A5; Character LCD with I2C backpack pin SCL
  • Arduino Nano pin A4; Character LCD with I2C backpack pin SDA
  • Ground: Arcade button (1) pin 1
  • Ground: Arcade button (2) pin 1
  • Ground: Arcade button (3) pin 1
  • Ground: Arcade button (4) pin 1
  • Ground: Arcade button (5) pin 1
  • Ground: Piezo speaker pin 2
  • Ground: Character LCD with I2C backpack pin GND
  • 5 V: Character LCD with I2C backpack pin VCC

Change it and keep it

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