Project 9 : IR Sensor Object Counter with ESP32 & LCD

IR Sensor Object Counter with ESP32 & LCD

This project uses an ESP32, an IR sensor, and an I2C LCD to count the number of objects passing in front of the sensor. It includes debouncing for accuracy, serial commands for testing and resetting, and a real-time LCD display.

1. How It Works

  • The IR sensor detects an object when its output goes LOW.
  • The ESP32 increments the count when an object is detected and removed.
  • The LCD display shows the current object count and sensor state.
  • Serial commands (reset, test, scan) allow you to reset counts, test the sensor, or scan for I2C devices.

2. Components Required

  1. ESP32 development board
  2. IR obstacle sensor module
  3. I2C 16x2 LCD (common address: 0x27)
  4. Jumper wires
  5. Breadboard (optional)

3. Circuit Diagram (Text-Based)

IR Sensor       → ESP32
VCC (3.3V/5V)   → 3.3V or 5V pin
GND             → GND
OUT             → GPIO4

LCD (I2C)       → ESP32
VCC             → 5V
GND             → GND
SDA             → GPIO21
SCL             → GPIO22

LED             → ESP32
+ve (Long)      → GPIO2
-ve(Short) → GND

Buzzer          → ESP32
+ve (Long)      → GPIO5
-ve(Short) → GND

4. Install Required Libraries

Before uploading the code, install the LiquidCrystal_I2C library.

Open Arduino IDE

Go to Sketch → Include Library → Manage Libraries

Search for LiquidCrystal_I2C by Frank de Brabander

Click Install

5. Upload the Code

Paste your code into the Arduino IDE, select:

Board: ESP32 Dev Module

Port: your ESP32 COM port

---------------------------------------------------------------------------------------

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

// Pin definitions
#define IR_SENSOR_PIN 4 // GPIO4 for IR sensor output
#define SDA_PIN 21 // GPIO21 for I2C SDA
#define SCL_PIN 22 // GPIO22 for I2C SCL
#define BUZZER_PIN 5 // GPIO5 for passive buzzer
#define LED_PIN 2 // GPIO2 for LED

// LCD configuration
#define LCD_ADDRESS 0x27 // I2C address of LCD (common addresses: 0x27, 0x3F)
#define LCD_COLUMNS 16
#define LCD_ROWS 2

// Create LCD object
LiquidCrystal_I2C lcd(LCD_ADDRESS, LCD_COLUMNS, LCD_ROWS);

// Variables for object counting
int objectCount = 0;
bool lastStableSensorState = HIGH; // Last stable state after debouncing
bool currentSensorState = HIGH;
bool lastRawSensorState = HIGH; // For detecting changes
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50; // 50ms debounce delay

// Variables for display update
unsigned long lastDisplayUpdate = 0;
unsigned long displayUpdateInterval = 200; // Update display every 200ms

// Variables for buzzer and LED control
unsigned long buzzerStartTime = 0;
unsigned long ledStartTime = 0;
bool buzzerActive = false;
bool ledActive = false;
unsigned long buzzerDuration = 300; // Buzzer beep duration in milliseconds
unsigned long ledDuration = 500; // LED glow duration in milliseconds
int buzzerFrequency = 2500; // Buzzer frequency in Hz

void setup() {
// Initialize Serial communication
Serial.begin(115200);
Serial.println("IR Object Counter Starting...");

// Initialize I2C communication
Wire.begin(SDA_PIN, SCL_PIN);

// Initialize LCD
lcd.init();
lcd.backlight();

// Clear the LCD
lcd.clear();

// Display initial message
lcd.setCursor(0, 0);
lcd.print("Object Counter");
lcd.setCursor(0, 1);
lcd.print("Count: 0");

// Initialize pins
pinMode(IR_SENSOR_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);

// Ensure buzzer and LED are off initially
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, LOW);

// Initial sensor reading
lastRawSensorState = digitalRead(IR_SENSOR_PIN);
lastStableSensorState = lastRawSensorState;

Serial.println("System initialized successfully!");
Serial.println("Place objects in front of the IR sensor to count them.");
Serial.print("Initial sensor state: ");
Serial.println(lastStableSensorState ? "HIGH (no object)" : "LOW (object detected)");
}

void loop() {
// Read current sensor state
currentSensorState = digitalRead(IR_SENSOR_PIN);

// Check if sensor reading has changed
if (currentSensorState != lastRawSensorState) {
// Reset debounce timer when reading changes
lastDebounceTime = millis();
lastRawSensorState = currentSensorState;
}

// If the sensor reading has been stable for the debounce delay
if ((millis() - lastDebounceTime) > debounceDelay) {
// Check if the stable state is different from our last stable state
if (currentSensorState != lastStableSensorState) {
// Object detected (sensor goes from HIGH to LOW)
if (currentSensorState == LOW && lastStableSensorState == HIGH) {
objectCount++;
Serial.println("Object detected! Count: " + String(objectCount));

// Activate buzzer and LED
activateBuzzerAndLED();

// Immediate display update for responsiveness
updateDisplay();
}
// Object removed (sensor goes from LOW to HIGH)
else if (currentSensorState == HIGH && lastStableSensorState == LOW) {
Serial.println("Object removed.");
}

// Update the stable state
lastStableSensorState = currentSensorState;
}
}

// Handle buzzer timing
handleBuzzer();

// Handle LED timing
handleLED();

// Update display periodically
if (millis() - lastDisplayUpdate > displayUpdateInterval) {
updateDisplay();
lastDisplayUpdate = millis();
}

// Check for reset command from Serial
if (Serial.available() > 0) {
String command = Serial.readString();
command.trim();

if (command.equalsIgnoreCase("reset") || command.equalsIgnoreCase("r")) {
resetCounter();
}
else if (command.equalsIgnoreCase("test") || command.equalsIgnoreCase("t")) {
testSensor();
}
else if (command.equalsIgnoreCase("scan") || command.equalsIgnoreCase("s")) {
scanI2CDevices();
}
else if (command.equalsIgnoreCase("testbuzzer") || command.equalsIgnoreCase("tb")) {
testBuzzer();
}
else if (command.equalsIgnoreCase("testled") || command.equalsIgnoreCase("tl")) {
testLED();
}
}

// Small delay to prevent excessive CPU usage
delay(10);
}

void activateBuzzerAndLED() {
// Start buzzer
buzzerActive = true;
buzzerStartTime = millis();

// Start LED
ledActive = true;
ledStartTime = millis();
digitalWrite(LED_PIN, HIGH);

Serial.println("Buzzer and LED activated");
}

void handleBuzzer() {
if (buzzerActive) {
unsigned long currentTime = millis();

// Generate tone for passive buzzer
if (currentTime - buzzerStartTime < buzzerDuration) {
// Create square wave for the buzzer
static unsigned long lastToggle = 0;
unsigned long toggleInterval = 1000000 / (buzzerFrequency * 2); // in microseconds

if (micros() - lastToggle >= toggleInterval) {
static bool buzzerState = false;
buzzerState = !buzzerState;
digitalWrite(BUZZER_PIN, buzzerState ? HIGH : LOW);
lastToggle = micros();
}
} else {
// Turn off buzzer
digitalWrite(BUZZER_PIN, LOW);
buzzerActive = false;
}
}
}

void handleLED() {
if (ledActive) {
if (millis() - ledStartTime >= ledDuration) {
digitalWrite(LED_PIN, LOW);
ledActive = false;
}
}
}

void updateDisplay() {
// Update the count on LCD
lcd.setCursor(0, 1);
lcd.print("Count: ");
lcd.print(objectCount);

// Clear any extra digits (if count went from 3 digits to 2, etc.)
if (objectCount < 10) {
lcd.print(" ");
} else if (objectCount < 100) {
lcd.print(" ");
}

// Additional information on the display
lcd.setCursor(12, 0);
if (digitalRead(IR_SENSOR_PIN) == LOW) {
lcd.print("OBJ"); // Object detected
} else {
lcd.print(" "); // No object
}

// Show sensor state and activity indicators
lcd.setCursor(12, 1);
lcd.print(digitalRead(IR_SENSOR_PIN) ? "H" : "L");

// Show LED status
lcd.setCursor(14, 1);
lcd.print(ledActive ? "*" : " ");
}

void resetCounter() {
objectCount = 0;
Serial.println("Counter reset to 0");

// Turn off buzzer and LED
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, LOW);
buzzerActive = false;
ledActive = false;

// Clear and reinitialize display
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Object Counter");
updateDisplay();

// Brief confirmation message
lcd.setCursor(0, 1);
lcd.print("Reset! ");
delay(1000);
updateDisplay();
}

// Function to test sensor readings
void testSensor() {
Serial.println("Testing sensor for 10 seconds...");
Serial.println("Watch the readings (LOW = object detected, HIGH = no object):");

unsigned long testStart = millis();
while (millis() - testStart < 10000) {
int reading = digitalRead(IR_SENSOR_PIN);
Serial.print("Sensor: ");
Serial.print(reading ? "HIGH" : "LOW");
Serial.print(" (Raw: ");
Serial.print(reading);
Serial.println(")");
delay(500);
}
Serial.println("Test complete");
}

// Function to test buzzer
void testBuzzer() {
Serial.println("Testing buzzer...");
activateBuzzerAndLED();
Serial.println("Buzzer test initiated");
}

// Function to test LED
void testLED() {
Serial.println("Testing LED...");
digitalWrite(LED_PIN, HIGH);
delay(1000);
digitalWrite(LED_PIN, LOW);
Serial.println("LED test complete");
}

// Function to scan for I2C devices (useful for debugging)
void scanI2CDevices() {
Serial.println("Scanning for I2C devices...");
byte error, address;
int nDevices = 0;

for(address = 1; address < 127; address++) {
Wire.beginTransmission(address);
error = Wire.endTransmission();

if (error == 0) {
Serial.print("I2C device found at address 0x");
if (address < 16) {
Serial.print("0");
}
Serial.println(address, HEX);
nDevices++;
}
}

if (nDevices == 0) {
Serial.println("No I2C devices found");
} else {
Serial.println("Scan complete");
}
}

// Function to test LCD (call this in setup if needed)
void testLCD() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("LCD Test");
lcd.setCursor(0, 1);
lcd.print("Hello World!");
delay(2000);
}

----------------------------------------------------------------------------------------

Then upload.

6. Understanding the Code

Key Features

Debouncing: Avoids false counts from sensor noise.

LCD Updates: Shows count and object detection status in real time.

Serial Commands:

reset → Resets count to 0.

test → Shows sensor state for 10 seconds.

scan → Scans for connected I2C devices (helps find LCD address).

7. Testing the Project

Step 1 – Power Up

Connect ESP32 to your PC via USB.

Open Serial Monitor (115200 baud rate).

Step 2 – Initial Output

You should see:

IR Object Counter Starting...
System initialized successfully!
Place objects in front of the IR sensor to count them.
Initial sensor state: HIGH (no object)

Step 3 – Object Detection

Pass your hand or object in front of the sensor.

Count should increase when the object passes and is removed.

LCD will update automatically.


8. Troubleshooting

LCD not displaying text?

Run the scan command in Serial Monitor — note the detected I2C address.

Change #define LCD_ADDRESS 0x27 to the detected address.

Count increasing randomly?

Increase debounceDelay in the code (default is 50 ms).

No count changes?

Check IR sensor wiring and power.

Use test command to verify if sensor is giving HIGH/LOW values.

9. Example Output

Serial Monitor:

Object detected! Count: 1
Object removed.
Object detected! Count: 2

LCD Display:

Object Counter   OBJ
Count: 2         L

(OBJ means object detected, H/L shows sensor HIGH or LOW.)

10. Extensions

You can enhance the project by:

Adding a buzzer when an object is detected.

Storing the count in EEPROM so it’s not lost after reset.

Sending count data to IoT platforms like Blynk or MQTT.


Back to blog

Leave a comment