Pet Feeder with OLED Countdown using ESP32-CAM
Pet Feeder with OLED Countdown using ESP32-CAM

Pet Feeder with OLED Countdown using ESP32-CAM

Table of Contents

01 Overview

Having a pet is like having another person that is dependent on the owner. Many of us are too busy that sometimes we forget to feed our pets and leave them hungry. This project allows us to build a pet feeder when it detects motions. It will also show a countdown and captures an image of the pet when it is feeding.

This project uses ESP32-CAM to capture an image when the PIR sensor detects motion. When motion is detected, the display module (LCD module for version 1 and OLED module for version 2) will display a cooldown timer and activates the Servo motor to open the pet food container and close it afterwards.

We have created two versions of the project so that pet owners will know which one they would like the best:

Version 1: Pet Feeder with LCD Countdown using ESP32-CAM

This version uses a 16x2 LCD Module to display the text that motion has been detected and the servo dispenses the pet food for 3 seconds.

Version 2: Pet Feeder with OLED Countdown using ESP32-CAM and ESP32

This is a refined version of the version 1 but instead of the LCD module, we used OLED display and added an image capturing so that owners will gets a picture of their pet eating. This version also used two microcontrollers; ESP32-CAM to control the PIR sensor and to snap the picture, and the ESP32 to control the servo motor and OLED display.

02 Hardware and Software Components

Hardware Components

Component Description
ESP32-CAM ESP32-CAM is a small size camera development board based on ESP32, set WIFI+ Bluetooth solution in one body, the development board can work independently as a minimum system. and is perfect for IoT devices requiring a camera with advanced functions like image tracking and recognition.
MG996R Servo Motor The MG996R is a strong, reliable servo motor that's perfect for projects like robot arms, RC cars, or anything that needs precise and powerful movement. It runs on 4.8 to 7.2 volts and uses standard PWM signals, making it easy to control with most microcontrollers like Arduino or ESP32.
Jumper Wires Jumper wires are insulated wires with connector pins used to connect power, ground, and control signals between the project components without soldering.
Breadboard A breadboard is a reusable, solderless prototyping board used to assemble and test electronic circuits. Its connected rows and power rails make it easy to connect components using jumper wires.
LCD Module - Version 1 An LCD module is a display component used to show text or numerical data in embedded electronics projects, most commonly in a 16x2 format (16 characters over 2 lines).
Mini PIR Sensor - Version 1 The mini PIR (Passive Infrared) sensor is a small motion-detecting module that senses infrared radiation emitted by warm objects like humans or animals. It's commonly used in security systems, smart devices, and automation projects to detect movement within a certain range.
ESP32 - Version 2 The ESP32 is a versatile microcontroller with built-in Wi-Fi and Bluetooth, ideal for IoT, home automation, robotics, and wearable projects. It features a dual-core processor, multiple input/output pins, and supports various interfaces.
PIR Sensor - Version 2 A PIR (Passive Infrared) sensor detects motion by sensing the infrared radiation emitted by warm objects like humans and animals. It is widely used in security systems, lighting automation, and IoT projects to trigger actions such as alarms, lights, or camera captures when motion is detected.
OLED Module - Version 2 OLED (Organic Light-Emitting Diode) is a self-light-emitting technology composed of a thin, multi-layered organic film placed between an anode and cathode. In contrast to LCD technology, OLED does not require a backlight.

Software Used

Software Component Description
Arduino IDE Software used to write, compile, and upload the code to the ESP32 and ESP32-CAM boards.

03 Application Discussion

ESP32-CAM

ESP32-CAM is a small size camera development board based on ESP32, set WIFI+ Bluetooth solution in one body, the development board can work independently as a minimum system. and is perfect for IoT devices requiring a camera with advanced functions like image tracking and recognition.

Specifications

Category Details
Microcontroller ESP32-D0WDQ6
CPU Dual-core 32-bit Xtensa® LX6 @ up to 240 MHz
RAM 520 KB SRAM + 8 MB PSRAM
Flash Memory 4 MB
Wi-Fi 802.11 b/g/n
Bluetooth v4.2 BR/EDR + BLE
Camera Resolution (OV2640) Up to 1600x1200 (UXGA)
Camera Output Formats JPEG, BMP, Grayscale
Camera Focus Fixed focus
Camera FOV ~65°
MicroSD slot Supports up to 4–8 GB (FAT32)
Image Storage Saves captured photos in JPEG format
Input Voltage 5 V (via 5V pin)
Operating Voltage 3.3 V internally
Typical Current Usage ~160–250 mA during Wi-Fi + camera use
Usable GPIOs 9 (shared with camera functions)
Interfaces Supported UART, SPI, I2C, PWM, ADC
Built-in Flash LED White (connected to GPIO 4)
Dimensions ~27 mm × 40.5 mm
Antenna Options Onboard PCB + U.FL connector for external antenna

Pinout:

To upload code using ESP32-CAM:

MG996R Servo Motor

The MG996R is a strong, reliable servo motor that's perfect for projects like robot arms, RC cars, or anything that needs precise and powerful movement. It runs on 4.8 to 7.2 volts and uses standard PWM signals, making it easy to control with most microcontrollers like Arduino or ESP32. Thanks to its metal gears and high torque, it can handle heavier loads without losing accuracy or speed.

Specifications:

Category Details
Operating voltage 4.8V to 7.2V
Stall torque 9.4 kg·cm (4.8V), 11 kg·cm (6V)
Speed 0.20 sec/60° (4.8V), 0.16 sec/60° (6V)
Rotation range 0° to ~180°
Gear type Metal
Dimensions 40.7 x 19.7 x 42.9 mm
Weight ~55g
Signal control PWM (500–2500 µs pulse width)
Bearing type Dual ball bearings

Wire Configuration

Wire Number Wire Color Description
1 Brown Ground wire connected to the ground of system
2 Red Powers the motor typically +5V is used
3 Orange PWM signal is given in through this wire to drive the motor

Jumper Wires

Jumper wires provide temporary electrical connections between the ESP32 boards, sensors, displays, servo motor, and breadboard. Their removable connections make the circuit easy to assemble, modify, and troubleshoot.

Breadboard

A breadboard is a solderless prototyping board used to build and test the feeder circuit. It allows components and jumper wires to share electrical connections without soldering.

LCD Module - Version 1

An LCD module is a display component used to show text or numerical data in embedded electronics projects, most commonly in a 16x2 format (16 characters over 2 lines).

Specifications:

Category Details
Display 16 characters × 2 lines
Voltage 5V (some support 3.3V)
I2C address Usually 0x27 or 0x3F
Character format 5x8 dots
Size ~80mm × 36mm
Libraries LiquidCrystal, LiquidCrystal_I2C

Mini PIR Sensor - Version 1

The mini PIR (Passive Infrared) sensor is a small motion-detecting module that senses infrared radiation emitted by warm objects like humans or animals. It's commonly used in security systems, smart devices, and automation projects to detect movement within a certain range. Compact and energy-efficient, the mini PIR sensor is easy to use with microcontrollers like Arduino or ESP32 for triggering actions such as turning on lights, capturing photos, or activating alarms.

Specifications:

Category Details
Detection range 3–6 meters (varies by model)
Detection angle ~110°
Operating voltage 3.3V to 5V
Output Digital HIGH/LOW signal
Warm-up time ~10–60 seconds after powering on
Dimensions ~24mm × 32mm × 18mm (typical mini sensor)

ESP32 - Version 2

The ESP32 is a versatile microcontroller with built-in Wi-Fi and Bluetooth, ideal for IoT, home automation, robotics, and wearable projects. It features a dual-core processor, multiple input/output pins, and supports various interfaces.

Specifications:

Category Details
CPU Dual-core Xtensa® 32-bit LX6 @ up to 240 MHz
RAM 520 KB SRAM
Flash Typically 4 MB (varies by board)
Operating voltage 3.3 V
Wi-Fi 802.11 b/g/n
Bluetooth v4.2 BR/EDR and BLE
GPIO pins Up to 34 (depending on module)
Common modules ESP32-WROOM-32, ESP32-WROVER, ESP32-C3

Pinout:

PIR Sensor - Version 2

A PIR (Passive Infrared) sensor detects motion by sensing the infrared radiation emitted by warm objects like humans and animals. It is widely used in security systems, lighting automation, and IoT projects to trigger actions such as alarms, lights, or camera captures when motion is detected.

Specifications:

Category Details
Detection range 3 to 7 meters (typical)
Detection angle ~100° to 120°
Operating voltage 3.3V to 5V
Output Digital HIGH (motion), LOW (no motion)
Warm-up time ~30 to 60 seconds after power-on
Common models HC-SR501, AM312 (mini), SR602

OLED Module - Version 2

OLED (Organic Light-Emitting Diode) is a self-light-emitting technology composed of a thin, multi-layered organic film placed between an anode and cathode. In contrast to LCD technology, OLED does not require a backlight.

Specifications:

  • Display Type: OLED (monochrome)
  • Resolution: 128 x 64 pixels
  • Interface: I2C (often SDA/SCL) or SPI (variant-dependent)
  • Viewing Angle: Nearly 180°
  • Operating Voltage: 3.3V – 5V
  • Power Consumption: ~0.06–0.08W
  • Driver IC: SSD1306 (most common)
  • Screen Size: Typically 0.96" or 1.3" diagonal
  • Color Options: Usually white, blue, or yellow

04 Hardware Setup

Version 1

Version 2

Wiring Set-up — Version 1

ESP32-CAM Pin Connected To
GPIO 16 PIR Sensor OUT
GPIO 13 (PWM) Servo Motor Signal Pin
GPIO 15 (SDA) LCD I2C SDA
GPIO 14 (SCL) LCD I2C SCL
3.3V PIR Sensor VCC
5V (External) Servo Motor VCC (use external 5V supply)
GND PIR Sensor GND, LCD GND, Servo GND

Wiring Set-up — Version 2

ESP32

ESP32 Pin Connected To
GPIO 25 (PWM) Servo Motor Signal Pin
GPIO 21 (SDA) OLED Display SDA
GPIO 22 (SCL) OLED Display SCL
3.3V OLED Display VCC
GND OLED Display GND, Servo Motor GND
5V (External) Servo Motor VCC (use external power)

ESP32-CAM

ESP32-CAM Pin Connected To
GPIO 13 PIR Sensor OUT (Motion Detection)
GPIO 32 Camera Power Down (PWDN)
GPIO 0 Camera Clock (XCLK)
GPIO 26 Camera SIOD (I2C Data)
GPIO 27 Camera SIOC (I2C Clock)
GPIO 35 Camera D7 (Y9)
GPIO 34 Camera D6 (Y8)
GPIO 39 Camera D5 (Y7)
GPIO 36 Camera D4 (Y6)
GPIO 21 Camera D3 (Y5)
GPIO 19 Camera D2 (Y4)
GPIO 18 Camera D1 (Y3)
GPIO 5 Camera D0 (Y2)
GPIO 25 Camera VSYNC
GPIO 23 Camera HREF
GPIO 22 Camera PCLK
3.3V PIR Sensor VCC
GND PIR Sensor GND, Camera GND
MicroSD Card Slot SD Card Storage

Assembly Instructions

05 Software Setup

Version 1: with LCD Countdown using ESP32-CAM

ESP32-CAM Code

Version 2: with OLED Countdown using ESP32-CAM and ESP32

ESP32-CAM Code

ESP32 Code

Board Settings

06 Code

Version 1 — ESP32-CAM Code

Arduino / C++
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ESP32Servo.h>

// === PIN ASSIGNMENTS ===
#define PIR_PIN     16    // PIR Sensor
#define SERVO_PIN   13    // Servo Motor
#define SDA_PIN     15    // LCD SDA
#define SCL_PIN     14    // LCD SCL

LiquidCrystal_I2C lcd(0x27, 16, 2);
Servo feederServo;
bool hasFed = false;
unsigned long lastFeedTime = 0;
const unsigned long cooldownPeriod = 10000; // 10 seconds cooldown

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

  // LCD Initialization
  Wire.begin(SDA_PIN, SCL_PIN);
  lcd.begin(16, 2);
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Pet Feeder Ready");
  lcd.setCursor(0, 1);
  lcd.print("Warming PIR...");

  // PIR sensor setup with internal pull-down
  pinMode(PIR_PIN, INPUT_PULLDOWN);
  delay(10000);  // Warm-up delay for PIR sensor
  Serial.println("✅ PIR Warmed up.");

  // Servo initialization
  feederServo.setPeriodHertz(50);
  feederServo.attach(SERVO_PIN);
  feederServo.write(0);  // Closed

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Waiting for pet...");
}

void loop() {
  // Debounce: read PIR twice to confirm motion
  int motion = digitalRead(PIR_PIN);
  delay(50);
  int confirm = digitalRead(PIR_PIN);
  unsigned long currentTime = millis();

  if (motion == HIGH && confirm == HIGH && !hasFed) {
    hasFed = true;
    lastFeedTime = currentTime;

    Serial.println("🎯 Motion Confirmed!");
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Pet Detected");
    lcd.setCursor(0, 1);
    lcd.print("Dispensing...");

    // Dispense food via servo
    feederServo.write(90);
    delay(1500);
    feederServo.write(0);
    delay(500);

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Feeding Done!");
    delay(3000);

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Waiting...");
  }

  // Reset feeding flag after cooldown
  if (hasFed && (currentTime - lastFeedTime > cooldownPeriod)) {
    hasFed = false;
    Serial.println("⏳ Cooldown complete. Ready for next detection.");
  }

  delay(200);
}

Version 2 — ESP32-CAM Code

Arduino / C++
#include "esp_camera.h"
#include "FS.h"
#include "SD_MMC.h"


#define PIR_PIN 13  // GPIO connected to PIR OUT


// CAMERA MODEL - AI Thinker
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27


#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22


int photoIndex = 0;
bool motionPreviouslyDetected = false;
unsigned long lastCaptureTime = 0;
const unsigned long captureCooldown = 10000; // 10 sec cooldown between photos


void startCamera() {
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer   = LEDC_TIMER_0;
  config.pin_d0       = Y2_GPIO_NUM;
  config.pin_d1       = Y3_GPIO_NUM;
  config.pin_d2       = Y4_GPIO_NUM;
  config.pin_d3       = Y5_GPIO_NUM;
  config.pin_d4       = Y6_GPIO_NUM;
  config.pin_d5       = Y7_GPIO_NUM;
  config.pin_d6       = Y8_GPIO_NUM;
  config.pin_d7       = Y9_GPIO_NUM;
  config.pin_xclk     = XCLK_GPIO_NUM;
  config.pin_pclk     = PCLK_GPIO_NUM;
  config.pin_vsync    = VSYNC_GPIO_NUM;
  config.pin_href     = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn     = PWDN_GPIO_NUM;
  config.pin_reset    = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  config.frame_size   = FRAMESIZE_SVGA; // try FRAMESIZE_VGA if needed
  config.jpeg_quality = 10;
  config.fb_count     = 1;


  esp_err_t err = esp_camera_init(&config);
   if (err != ESP_OK) {
      Serial.printf("Camera init failed with error 0x%x", err);
      while (true);
  }
}

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

  pinMode(PIR_PIN, INPUT);

  startCamera();

if (!SD_MMC.begin()) {
  Serial.println("SD Card Mount Failed");
  return;
}


if (SD_MMC.cardType() == CARD_NONE) {
  Serial.println("No SD card attached");
  return;
}


Serial.println("System ready. Waiting for motion...");
}


void loop() {
  bool motionDetected = digitalRead(PIR_PIN) == HIGH;


  if (motionDetected && !motionPreviouslyDetected && millis() - lastCaptureTime > captureCooldown) {
    Serial.write('A');


    camera_fb_t * fb = esp_camera_fb_get();
    if (!fb) {
      Serial.println("Camera capture failed");
      return;
    }


    String path = "/photo" + String(photoIndex++) + ".jpg";
    File file = SD_MMC.open(path, FILE_WRITE);
    if (!file) {
      Serial.println("Failed to open file");
      esp_camera_fb_return(fb);
      return;
    }


    file.write(fb->buf, fb->len);
    file.close();
    esp_camera_fb_return(fb);


    Serial.println("Photo saved to: " + path);


    lastCaptureTime = millis(); // start cooldown
  }


  motionPreviouslyDetected = motionDetected;
  delay(10000); // 10 secs delay
  }

Version 2 — ESP32 Code

Arduino / C++
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP32Servo.h>

    // === OLED Setup ===
    #define SCREEN_WIDTH 128
    #define SCREEN_HEIGHT 64
    #define OLED_ADDRESS 0x3C


    Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);


    // === Servo Setup ===
    #define SERVO_PIN 25
    Servo myServo;


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


      // Initialize OLED
      if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
        Serial.println(F("OLED not found"));
        while (1);
      }


      display.clearDisplay();
      display.setTextSize(1);
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(30, 25);
      display.println("OLED Ready");
      display.display();
      delay(1000);


      // Initialize Servo
      myServo.setPeriodHertz(50);
      myServo.attach(SERVO_PIN);
      myServo.write(0);
      Serial.println("Servo Ready");
      delay(1000);
    }

    void open(){
    for (int i = 3; i > 0; i--) {
        display.clearDisplay();
        display.setTextSize(1);
        display.setCursor(25, 15);
        display.println("DISPENSING IN...");
        display.setTextSize(2);
        display.setCursor(60, 35);
        display.print(i);
        display.display();
        delay(1000);
      }


      // Dispense (Open Servo)
      myServo.write(90);
      display.clearDisplay();
      display.setTextSize(1);
      display.setCursor(34, 28);
      display.println("DISPENSING...");
      display.display();
      Serial.println("Dispensing...");
      delay(1000);


      // Countdown to Close
      for (int i = 3; i > 0; i--) {
        display.clearDisplay();
        display.setTextSize(1);
        display.setCursor(30, 15);
        display.println("CLOSING IN...");
        display.setTextSize(2);
        display.setCursor(60, 35);
        display.print(i);
        display.display();
        delay(1000);
      }


      // Close Servo
      myServo.write(0);
      display.clearDisplay();
      display.setTextSize(1);
      display.setCursor(44, 28);
      display.println("DONE!");
      display.display();
      Serial.println("Dispense Complete");
      }

      void loop() {
      byte recv=0;
      if (Serial.available()){
        recv = Serial.read();
        if (recv == 'A'){
        open();
        }
      }
    }

07 Code Breakdown

Here is what each part of the code does.

Library Used

Arduino / C++
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ESP32Servo.h>
  • Wire.h: Enables I2C communication for devices like the LCD.
  • LiquidCrystal_I2C.h: Controls a 16x2 I2C LCD display for showing status messages.
  • ESP32Servo.h: Allows the ESP32 to generate PWM signals to control a servo motor.

Constants & Macros

Arduino / C++
#define PIR_PIN     16
#define SERVO_PIN   13
#define SDA_PIN     15
#define SCL_PIN     14
  • PIR_PIN: GPIO pin for the PIR sensor (detects motion).
  • SERVO_PIN: GPIO pin controlling the servo motor for food dispensing.
  • SDA_PIN / SCL_PIN: I2C pins for communication with the LCD screen.

Global Objects & Variables

Arduino / C++
LiquidCrystal_I2C lcd(0x27, 16, 2);
Servo feederServo;
bool hasFed = false;
unsigned long lastFeedTime = 0;
const unsigned long cooldownPeriod = 10000;
  • lcd: Creates an LCD object at I2C address 0x27 for a 16x2 character display.
  • feederServo: Creates a servo motor object.
  • hasFed: Tracks if the pet has been fed already to avoid double dispensing.
  • lastFeedTime: Stores the timestamp of the last feeding.
  • cooldownPeriod: Prevents feeding again within 10 seconds.

setup() Function

Arduino / C++
void setup() {
  Serial.begin(115200);
  Wire.begin(SDA_PIN, SCL_PIN);
  lcd.begin(16, 2);
  lcd.backlight();
  lcd.print("Pet Feeder Ready");

  pinMode(PIR_PIN, INPUT_PULLDOWN);
  delay(10000);

  feederServo.setPeriodHertz(50);
  feederServo.attach(SERVO_PIN);
  feederServo.write(0);
}
  • Start Serial Communication: Used for debugging messages.
  • Initialize LCD Display: Configures SDA/SCL pins for I2C communication. Turns on LCD backlight and displays "Pet Feeder Ready".
  • PIR Sensor Warm-Up: Configures PIR sensor pin with internal pull-down resistor. Waits 10 seconds for the sensor to stabilize.
  • Initialize Servo Motor: Sets PWM frequency. Moves servo to closed position (0°) to ensure no food is dispensed at startup.

loop() Function

Arduino / C++
void loop() {
  int motion = digitalRead(PIR_PIN);
  delay(50);
  int confirm = digitalRead(PIR_PIN);

  if (motion == HIGH && confirm == HIGH && !hasFed) {
    hasFed = true;
    lastFeedTime = millis();
    feederServo.write(90);
    delay(1500);
    feederServo.write(0);
  }
}
  • Debounce PIR Sensor: Reads the PIR sensor twice with a short delay to confirm motion.
  • Trigger Feeding Sequence: If motion is confirmed and the system hasn't fed recently, it activates the servo to dispense food and displays "Pet Detected" and "Dispensing..." on LCD.
  • Cooldown Logic: Prevents multiple feedings within the 10-second cooldown period.
  • Idle Display: Shows "Waiting..." on LCD while monitoring for motion.

ESP32-CAM Code Breakdown

Arduino / C++
#include <esp_camera.h>
#include <FS.h>
#include <SD_MMC.h>

#define PIR_PIN 13

#define PWDN_GPIO_NUM 32
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22

bool motionPreviouslyDetected = false;
unsigned long lastCaptureTime = 0;
const unsigned long captureCooldown = 10000;
  • PIR_PIN: GPIO pin connected to the PIR motion sensor output.
  • Camera Pin Definitions: Assign ESP32 pins for data, clock, and control signals of the AI Thinker ESP32-CAM module.
  • photoIndex: Used to incrementally name saved photos.
  • motionPreviouslyDetected: Tracks motion state to avoid multiple captures for one motion event.
  • lastCaptureTime: Stores the timestamp of the last photo to enforce a cooldown.
  • captureCooldown: Sets a 10-second wait between photo captures.

startCamera() Function

Arduino / C++
void startCamera() {
  camera_config_t config;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_SVGA;
  config.jpeg_quality = 10;
  esp_camera_init(&config);
}
  • startCamera(): Configures and initializes the ESP32-CAM hardware, assigns all camera pins, sets resolution, pixel format, and JPEG quality, then calls esp_camera_init() to initialize the camera driver.

setup() Function

Arduino / C++
void setup() {
  Serial.begin(115200);
  pinMode(PIR_PIN, INPUT);
  startCamera();

  if (!SD_MMC.begin()) {
    Serial.println("SD Card Mount Failed");
    return;
  }

  Serial.println("System ready. Waiting for motion...");
}
  • setup(): Initializes peripherals (serial, PIR sensor, camera, SD card) and prepares the system for operation.

loop() Function

Arduino / C++
void loop() {
  bool motionDetected = digitalRead(PIR_PIN) == HIGH;

  if (motionDetected && !motionPreviouslyDetected && millis() - lastCaptureTime > captureCooldown) {
    camera_fb_t *fb = esp_camera_fb_get();
    String path = "/photo" + String(photoIndex++) + ".jpg";
    File file = SD_MMC.open(path, FILE_WRITE);
    file.write(fb->buf, fb->len);
    file.close();
    esp_camera_fb_return(fb);

    lastCaptureTime = millis();
  }

  motionPreviouslyDetected = motionDetected;
  delay(100);
}
  • Motion Detection: Reads PIR sensor state to detect movement.
  • Cooldown Check: Ensures at least 10 seconds have passed since the last photo.
  • Capture & Save Image: If motion is detected, triggers camera to capture a frame and saves the image as photoX.jpg on the SD card.
  • Motion State Update: Tracks whether motion is ongoing to avoid repeated triggers.
  • Delay: Waits briefly to prevent excessive CPU use in the loop.

ESP32 Code Breakdown

Arduino / C++
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP32Servo.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDRESS 0x3C
#define SERVO_PIN 25

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
Servo myServo;
  • Wire.h: Enables I2C communication between the ESP32 and the OLED display.
  • Adafruit_GFX.h: Provides graphics functions like drawing text, shapes, and images on the display.
  • Adafruit_SSD1306.h: Driver library to control SSD1306 OLED displays over I2C.
  • ESP32Servo.h: Allows the ESP32 to generate PWM signals to control a servo motor.
  • SCREEN_WIDTH & SCREEN_HEIGHT: Define the OLED display size as 128x64 pixels.
  • OLED_ADDRESS: Sets the OLED's I2C address (0x3C for most SSD1306 modules).
  • SERVO_PIN: Assigns GPIO pin 25 for controlling the servo motor.
  • display: Object to control the OLED display.
  • myServo: Object to control the servo motor.

void open() Function

Arduino / C++
void open() {
  for (int i = 3; i > 0; i--) {
    display.clearDisplay();
    display.print("DISPENSING IN...");
    delay(1000);
  }

  myServo.write(90);
  delay(1000);

  for (int i = 3; i > 0; i--) {
    display.clearDisplay();
    display.print("CLOSING IN...");
    delay(1000);
  }

  myServo.write(0);
  display.clearDisplay();
  display.print("DONE!");
}
  • Pre-Dispense Countdown: Displays "DISPENSING IN..." with a countdown (3...2...1) before starting.
  • Activate Servo: Rotates the servo to 90° (open position) to dispense. Shows "DISPENSING..." on the OLED and logs to Serial Monitor.
  • Post-Dispense Countdown: Displays "CLOSING IN..." with a countdown (3...2...1) before closing.
  • Deactivate Servo: Rotates the servo back to 0° (closed position). Shows "DONE!" on OLED and logs "Dispense Complete".

void loop() Function

Arduino / C++
void loop() {
  byte recv = 0;
  if (Serial.available()) {
    recv = Serial.read();
    if (recv == 'A') {
      open();
    }
  }
}
  • Serial Check: Looks for incoming data from the Serial Monitor.
  • Serial.write(): Reads the received character. If it matches 'A', it calls the open() function to start dispensing.
  • Repeat Forever: Keeps running in a loop, waiting for the next command.

08 Testing and Calibration

Test each sketch with the food container empty before placing the feeder near a pet. The checks below follow the sequence implemented in the original code.

Pre-Power Checks

  1. For Version 1, check the code assignments: PIR on GPIO 16, servo on GPIO 13, LCD SDA on GPIO 15, and LCD SCL on GPIO 14.
  2. For Version 2, check the ESP32 servo on GPIO 25, OLED SDA on GPIO 21, OLED SCL on GPIO 22, and the ESP32-CAM PIR input on GPIO 13.
  3. Power the servo from the external 5V supply and connect its ground to the controller ground.

Functional Test Procedure

Test Action Expected result
Version 1 startup Power the ESP32-CAM and open the Serial Monitor at 115200 baud. The LCD shows Pet Feeder Ready, then Warming PIR.... After 10 seconds it shows Waiting for pet....
Version 1 motion Move in front of the PIR after the warm-up delay. The PIR is read twice with a 50 ms delay. When both reads are HIGH, the LCD shows Pet Detected and Dispensing....
Version 1 feeding Test with a small amount of food. The servo moves to 90 degrees for 1.5 seconds, returns to 0 degrees, and the LCD shows Feeding Done!.
Version 1 cooldown Trigger the PIR again immediately after feeding. hasFed prevents another feed until the 10-second cooldownPeriod expires.
Version 2 startup Power the ESP32 OLED/servo board and open the Serial Monitor at 115200 baud. The OLED displays OLED Ready, the servo moves to 0 degrees, and the serial output reports Servo Ready.
Version 2 dispensing Trigger the Version 2 dispensing command from the ESP32-CAM system. open() counts down from 3, moves the servo to 90 degrees, displays DISPENSING..., counts down to close, then returns the servo to 0 degrees and displays DONE!.

Calibration Values

The following values are the starting points defined in the sketches. Change one value at a time, upload the affected sketch, and repeat the functional test.

Setting Current value When to adjust
PIR warm-up, Version 1 delay(10000) Change only if the PIR needs more time to stabilize after startup.
PIR confirmation, Version 1 delay(50) Change if the two PIR readings do not reliably confirm motion.
Servo closed position write(0) Adjust the 0-degree value until the gate closes fully without binding.
Servo open position write(90) Adjust the 90-degree value until one measured portion is released.
Version 1 feed duration delay(1500) Adjust the open time to control the amount dispensed.
Version 1 cooldown cooldownPeriod = 10000 Increase it when the PIR detects the same pet repeatedly.

Troubleshooting

  • False motion events: allow the PIR warm-up to finish, keep the sensor away from heat sources, and increase the confirmation delay only when necessary.
  • Servo resets the board: use a separate regulated 5V servo supply and verify the grounds are connected.
  • Too much or too little food: adjust the servo open angle and open time in small increments, then repeat the test with the same food type.
  • OLED remains blank: verify power, SDA/SCL wiring, and the 0x3C address.
  • Repeated feeding: confirm that hasFed is set after a successful trigger and that the cooldown timer is longer than the feeding sequence.

09 System Demonstration

Video Demonstration

Version 1:

Version 2:

10 Conclusion

This project addresses a common challenge faced by many pet owners—ensuring their pets are fed on time despite busy schedules. By integrating motion detection, automated feeding, visual countdown, and image capture, the smart pet feeder offers a reliable and efficient solution for modern pet care.

Two versions of the project were made to accommodate varying user preferences. The first version utilizes a 16x2 LCD display and a single ESP32-CAM module to detect motion and activate the servo motor for food dispensing. The second, more advanced version employs an OLED display and a dual-microcontroller setup—an ESP32-CAM for motion detection and photo capture, and a separate ESP32 for display control and servo operation. This configuration not only improves performance but also enhances user experience by providing real-time visual feedback and image documentation.

Through the use of accessible hardware components such as the ESP32-CAM, MG996R servo motor, PIR sensor, and display modules, this project demonstrates how embedded systems can be effectively applied to solve everyday problems. Ultimately, the smart pet feeder serves as a practical and scalable solution for improving pet care through automation and IoT technology.

Possible Improvements and Future Enhancements

  • Scheduled feeding: Add a clock module to dispense food at set times.
  • Remote control: Use a mobile app or web dashboard to monitor and control the feeder.
  • Food-level detection: Add a sensor to detect when the food container is empty.
  • Improved safety: Add manual controls, servo limits, and system error notifications.

11 References

  • Github: PierceBrandies - PetFeeder
  • Hackster.io - craftiarenko automatic pet feeder
  • Youtube

12 Project Authors (Code Delta)

  • Janine Ernie G. Cabuslay
  • James Robert L. Machico
  • Cheryl Lou B. Narvaez
Quality Check
  • Lukie-Mar Lonzaga
  • Miles Darren Bagnol
  • Elijah Joseph Orias
Pet Feeder with OLED Countdown using ESP32-CAM – CreateLabz
Esp32Esp32 dev boardEsp32camFeeder for petI2c 1602 lcdI2c lcdLcd moduleMotionMotion detectionMotion detectorOledOled displayPirPir sensorServoServo motor

Leave a comment

All comments are moderated before being published