39. People Counter with EEPROM

Objective

The task is to build a human counter system using a microcontroller with two push buttons, one to increment and one to decrement the count, and display the value on a serial terminal (e.g., PuTTY or Arduino IDE).

Requirements

  • Increment and decrement the count using two buttons.
  • Display the current count on the serial terminal.
  • Save the count in EEPROM so it persists after reset or power loss.
  • On startup, read and display the last saved count from EEPROM.

Why Use EEPROM?

EEPROM is a non-volatile memory that retains data without power, making it ideal for storing the count value so it remains available even after a restart.

Hardware Setup

Connect two push-button switches to GPIO pins using either a pull-up or pull-down configuration for reliable input detection.

This task can be implemented on the following microcontrollers:

  1. ESP32
  2. Arduino UNO

We are using the ESP32 DevKitC v4 development board and programming it using the Arduino IDE.

  • Before uploading, make sure to select “ESP32 Dev Module” as the board to ensure correct settings and compatibility.

ESP32 does not have a real EEPROM chip inside. Instead, part of its Flash memory is used to simulate EEPROM behavior.

Hardware Connection

Switch Interfacing with an internal pullup resistor
1) Increment switch → GPIO Pin 15

2)Decrement Switch → GPIO Pin 16

Circuit Connection

ESP32-Human-counter

Code

#include <EEPROM.h>

#define INC_BUTTON_PIN 15  // increment button (active LOW, INPUT_PULLUP)
#define DEC_BUTTON_PIN 16  // decrement button (active LOW, INPUT_PULLUP)

// Map switches to indices 0,1 for the debounce arrays (exact pattern you gave)
uint8_t switchPins[2] = { INC_BUTTON_PIN, DEC_BUTTON_PIN };

static const int EEPROM_BYTES = 4;
static const int COUNT_ADDR = 0;  // always write/read the 32-bit count at 0

uint32_t humanCount = 0;

uint8_t debounceDelay = 50;                    // ms
unsigned long lastDebounceTime[2] = { 0, 0 };  // one per switch
uint8_t lastButtonState[2] = { HIGH, HIGH };   // previous sampled state
uint8_t ButtonState[2] = { HIGH, HIGH };       // debounced state

// Check if a button has been pressed with debouncing
bool isButtonPressed(uint8_t switchIndex) {
  int reading = digitalRead(switchPins[switchIndex]);

  // If the button state has changed, reset the debounce timer
  if (reading != lastButtonState[switchIndex]) {
    lastDebounceTime[switchIndex] = millis();
  }
  lastButtonState[switchIndex] = reading;

  // If the state has remained stable for the debounce period (50 ms)
  if ((millis() - lastDebounceTime[switchIndex]) > debounceDelay) {
    if (reading != ButtonState[switchIndex]) {
      ButtonState[switchIndex] = reading;

      // Active LOW → pressed when LOW
      if (ButtonState[switchIndex] == LOW) {
        return true;  // valid press detected
      }
    }
  }
  return false;  // no valid press detected
}

void saveCount() {
  EEPROM.put(COUNT_ADDR, humanCount);  // write 4 bytes at address 0
  EEPROM.commit();                     // persist to flash on ESP32
}

void loadCount() {
  uint32_t val;
  EEPROM.get(COUNT_ADDR, val);
  // Fresh-erased bytes read as 0xFFFFFFFF
  if (val == 0xFFFFFFFFu) {
    humanCount = 0;
    saveCount();  // initialize persistent storage
  } else {
    humanCount = val;
  }
}

void setup() {
  Serial.begin(115200);

  // Start EEPROM emulation with 4 bytes total
  EEPROM.begin(EEPROM_BYTES);

  // Buttons with internal pull-ups (active LOW)
  pinMode(INC_BUTTON_PIN, INPUT_PULLUP);
  pinMode(DEC_BUTTON_PIN, INPUT_PULLUP);

  loadCount();

  Serial.println("Human Counter System Started!");
  Serial.print("Initial Count: ");
  Serial.println(humanCount);
}

void loop() {
  // Increment count
  if (isButtonPressed(0)) {  // index 0 → INC_BUTTON_PIN
    humanCount++;
    saveCount();  // write on every valid press (no 100 ms throttle)
    Serial.print("Count Incremented: ");
    Serial.println(humanCount);
  }

  // Decrement count
  if (isButtonPressed(1)) {  // index 1 → DEC_BUTTON_PIN
    if (humanCount > 0) {
      humanCount--;
      saveCount();
      Serial.print("Count Decremented: ");
      Serial.println(humanCount);
    }
  }
}

Code Explanation

  • setup();
    •  Initializes Serial communication, starts EEPROM emulation, sets button pins as INPUT_PULLUP, loads the saved count from EEPROM, and prints the initial value.
  • loop();
    • Continuously checks both buttons using isButtonPressed() to increment or decrement the counter, saves the updated count, and prints it.
  • isButtonPressed(uint8_t switchIndex);
    • Implements 50 ms software debounce; returns true only when a button press is stable and confirmed (active-LOW).
  • saveCount();
    • Writes the 32-bit humanCount value to EEPROM address 0 and commits it to Flash for permanent storage.
  • loadCount();
    • Reads the stored 32-bit counter from EEPROM; if memory is blank (0xFFFFFFFF), initializes it to 0 and saves it.

Precautions when Allocating Flash to EEPROM

  1. Don’t exceed Flash partition size
    • The emulated EEPROM uses the “app data” area of Flash and is safe up to about 4096 bytes. Allocating more (e.g., EEPROM.begin(8000)) can corrupt other Flash data or cause runtime errors.
  2. Write infrequently
    • Each Flash cell supports a limited number of writes (~10k).
      • Avoid continuous writing in loop().
      • Use EEPROM.commit() only when values actually change.
  3. Call EEPROM.commit() after writing
    • Without this, your changes stay only in RAM and disappear after a restart.
  4. Avoid overlapping with other Flash uses

We are using the Arduino UNO development board and programming it using the Arduino IDE.

  • Before uploading, make sure to select “Arduino UNO” as the board to ensure correct settings and compatibility.

Hardware Connection

Switch Interfacing with an internal pullup resistor
1) Increment switch → GPIO Pin 2

2)Decrement Switch → GPIO Pin 3

Circuit Connection

Arduino-Human-counter-circuit

Firmware Implementation

We are going to use EEPROM.h library to implement this task. In Arduino UNO, there is a separate 1KB EEPROM chip available.

Code

#include <EEPROM.h>

// Pin Definitions
#define INC_BUTTON_PIN 2  // Button to increment count
#define DEC_BUTTON_PIN 3  // Button to decrement count

// Variables
int humanCount;               // Current human count
const int eepromAddress = 0;  // EEPROM address to store the count

void setup() {
  Serial.begin(9600);
  initiateCount();

  // Configure button pins
  pinMode(INC_BUTTON_PIN, INPUT_PULLUP);
  pinMode(DEC_BUTTON_PIN, INPUT_PULLUP);

  Serial.println("Human Counter System Started!");
  Serial.print("Initial Count: ");
  Serial.println(humanCount);
}


void loop() {
  static bool incButtonState = HIGH;
  static bool decButtonState = HIGH;


  // Read button states with debounce
  bool incButtonPressed = debounceButton(INC_BUTTON_PIN, incButtonState);
  bool decButtonPressed = debounceButton(DEC_BUTTON_PIN, decButtonState);


  // Increment count
  if (incButtonPressed) {
    humanCount++;
    updateEEPROM();
    Serial.print("Count Incremented: ");
    Serial.println(humanCount);
  }

  // Decrement count
  if (decButtonPressed && humanCount > 0) {
    humanCount--;
    updateEEPROM();
    Serial.print("Count Decremented: ");
    Serial.println(humanCount);
  }
}


bool debounceButton(int pin, bool &lastState) {
  bool currentState = digitalRead(pin);
  if (lastState != currentState) {
    delay(50);  // Debounce delay
    if (digitalRead(pin) == currentState) {
      lastState = currentState;
      return currentState == LOW;  // Return true if button is pressed
    }
  }
  return false;
}


void updateEEPROM() {
  EEPROM.write(eepromAddress, humanCount);
}

void initiateCount() {
  humanCount = EEPROM.read(eepromAddress);
  if (humanCount == 255) {
    humanCount = 0;
    updateEEPROM();
  }
}

Code Explanation

  • setup();
     Initializes Serial, reads saved count from EEPROM, sets button pins as inputs, and displays the initial count.
  • loop();
     Continuously checks button presses to increase or decrease the count and updates the EEPROM after every change.
  • debounceButton();
     Filters out false button signals caused by mechanical bouncing and returns true only on a valid press.
  • updateEEPROM();
     Saves the current human count to EEPROM memory so it’s retained after reset or power-off.
  • initiateCount();
     Reads the stored count from EEPROM at startup and resets it to zero if uninitialized.

Precautions

  • Be careful while writing to the EEPROM, as it has limited write cycles.
  • Writing time is 3.3ms per byte; consider this while writing your code.

Output

Output Video