ESP32-C3 Super Mini Relay Timer

In this project, I will guide you through building a customizable countdown timer using the ESP32-C3 microcontroller, a 0.96-inch OLED display, a rotary encoder, and a relay module. This project is perfect for controlling devices that require timed intervals, such as lamps, fans, or any other appliances.

Components Required

  1. ESP32-C3 Super Mini (Affiliate): https://s.click.aliexpress.com/e/_DdbQYtJ
  2. 0.96-inch OLED Display (128×64 pixels) (Affiliate): https://s.click.aliexpress.com/e/_Dm4UE3J
  3. KY-040 Rotary Encoder (Affiliate): https://s.click.aliexpress.com/e/_DdyBNk1
  4. Relay Module 1 channel 3.3V red (Affiliate): https://s.click.aliexpress.com/e/_DCtP7d7
  5. Breadboard and jumper wires (Affiliate): https://s.click.aliexpress.com/e/_Dl5kuk1

Esp32-C3 Super Mini Pinout

 

Testing: Circuit Diagram and Connections

Here is how you need to wire the components to the ESP32-C3:

Rotary Encoder Pins:

  • CLK: Connected to GPIO 2.
  • DT: Connected to GPIO 3.
  • SW: Connected to GPIO 1.

OLED Display: Uses I2C communication.

  • SDA: Connect to GPIO 8.
  • SCL: Connect to GPIO 9.

Relay Module:

  • IN: Connected to GPIO 0.
  • VCC and GND: Connected to 5V(External Power Supply) and ground of the ESP32-C3.

 

 

 

 

Functionality Overview

  1. Rotary Encoder:
    • The encoder is used to select the timer value (0–60 minutes).
    • Turning the encoder adjusts the timer duration in one-minute increments.
  2. OLED Display:
    • Displays the selected timer duration during setup.
    • Shows the remaining time once the countdown starts.
  3. Relay Module:
    • When the countdown starts, the relay is triggered to turn on the connected appliance.
    • After the timer completes, the relay is turned off.
  4. Push Button:
    • The button (on the rotary encoder) starts the countdown after the time is selected.
    • During the countdown, the button is disabled to avoid accidental restarts.

Code Explanation

Let’s break down the code used to make this project work.

#include <U8g2lib.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define I2C_ADDRESS 0x3C

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

const int CLK = 2;
const int DT = 3;
const int SW = 1;
const int RELAY_PIN = 0;

int encoderValue = 0;
int lastCLKState;
int lastDTState;
bool buttonPressed = false;
bool timerRunning = false;
unsigned long countdownStartTime;
unsigned long countdownDuration = 0;

void setup() {
  initializePins();
  initializeSerial();
  initializeDisplay();
}

void loop() {
  handleEncoder();
  handleCountdown();
  handleButton();
  delay(1);
}

void initializePins() {
  pinMode(CLK, INPUT);
  pinMode(DT, INPUT);
  pinMode(SW, INPUT_PULLUP);
  pinMode(RELAY_PIN, OUTPUT);
  lastCLKState = digitalRead(CLK);
  lastDTState = digitalRead(DT);
}

void initializeSerial() {
  Serial.begin(115200);
}

void initializeDisplay() {
  u8g2.begin();
  updateDisplayTime();
}

void handleEncoder() {
  int currentStateCLK = digitalRead(CLK);
  int currentStateDT = digitalRead(DT);

  if (currentStateCLK != lastCLKState && currentStateCLK == HIGH) {
    encoderValue += (currentStateDT == LOW) ? 1 : -1;
    encoderValue = constrain(encoderValue, 0, 60);
    
    Serial.print("Encoder Value: ");
    Serial.println(encoderValue);

    updateDisplayTime();
    countdownDuration = encoderValue * 60;
  }

  lastCLKState = currentStateCLK;
  lastDTState = currentStateDT;
}

void updateDisplayTime() {
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_ncenB08_tr);
  u8g2.setCursor(15, 15);
  u8g2.print("Select the Time:");
  u8g2.setFont(u8g2_font_ncenB14_tr);
  u8g2.setCursor(45, 35);
  u8g2.print(encoderValue);
  u8g2.print(" min");
  u8g2.sendBuffer();
}

void handleCountdown() {
  if (timerRunning) {
    unsigned long elapsedTime = millis() - countdownStartTime;
    unsigned long remainingTime = (countdownDuration * 1000) - elapsedTime;

    if (elapsedTime < countdownDuration * 1000) {
      displayCountdown(remainingTime);
    } else {
      endCountdown();
    }
  }
}

void displayCountdown(unsigned long remainingTime) {
  int minutes = remainingTime / (60 * 1000);
  int seconds = (remainingTime % (60 * 1000)) / 1000;

  Serial.print("Remaining Time: ");
  Serial.print(minutes);
  Serial.print(" minutes and ");
  Serial.print(seconds);  
  Serial.println(" seconds");

  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_ncenB08_tr);
  u8g2.setCursor(15, 15);
  u8g2.print("Remaining Time: ");
  u8g2.setFont(u8g2_font_ncenB14_tr);
  u8g2.setCursor(35, 35);
  u8g2.print(minutes);
  u8g2.print("m ");
  u8g2.print(seconds);
  u8g2.print("s");
  u8g2.sendBuffer();
}

void endCountdown() {
  Serial.println("Countdown complete");
  timerRunning = false;
  Serial.println("Input enabled");

  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_ncenB08_tr);
  u8g2.setCursor(0, 10);
  u8g2.print("Countdown Complete!");
  u8g2.sendBuffer();

  digitalWrite(RELAY_PIN, LOW);
}

void handleButton() {
  if (digitalRead(SW) == LOW && !buttonPressed && !timerRunning) {
    startCountdown();
  } else if (digitalRead(SW) == HIGH && buttonPressed) {
    buttonPressed = false;
  }
}

void startCountdown() {
  buttonPressed = true;
  timerRunning = true;
  countdownStartTime = millis();

  Serial.print("Countdown started for ");
  Serial.print(countdownDuration / 60);
  Serial.print(" minutes and ");
  Serial.print(countdownDuration % 60);
  Serial.println(" seconds");

  digitalWrite(RELAY_PIN, HIGH);
}

1. Including the U8g2 Library for OLED Control

The code starts by including the U8g2lib library, which helps in managing the OLED display:

#include <U8g2lib.h>

This library provides functions to draw text and graphics on the screen. The display is initialized with this line:

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

2. Pin Definitions

The ESP32-C3 GPIO pins for the rotary encoder and relay are defined as follows:

const int CLK = 2;
const int DT = 3;
const int SW = 1;
const int RELAY_PIN = 0;

3. Setup Function

During the setup phase, the code initializes the pins, the OLED display, and the serial communication:

void setup() {
  initializePins();
  initializeSerial();
  initializeDisplay();
}

The pins are set up using:

void initializePins() {
  pinMode(CLK, INPUT);
  pinMode(DT, INPUT);
  pinMode(SW, INPUT_PULLUP);
  pinMode(RELAY_PIN, OUTPUT);
  lastCLKState = digitalRead(CLK);
  lastDTState = digitalRead(DT);
}

4. Handling the Rotary Encoder

The handleEncoder() function is responsible for reading the rotary encoder’s state and adjusting the timer value accordingly:

void handleEncoder() {
  int currentStateCLK = digitalRead(CLK);
  int currentStateDT = digitalRead(DT);

  if (currentStateCLK != lastCLKState && currentStateCLK == HIGH) {
    encoderValue += (currentStateDT == LOW) ? 1 : -1;
    encoderValue = constrain(encoderValue, 0, 60);
    updateDisplayTime();
    countdownDuration = encoderValue * 60;
  }
  lastCLKState = currentStateCLK;
}

This part ensures that when you rotate the encoder, the time value is updated between 0 and 60 minutes.

5. Updating the Display

The selected time is shown on the OLED using updateDisplayTime():

void updateDisplayTime() {
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_ncenB08_tr);
  u8g2.setCursor(15, 15);
  u8g2.print("Select the Time:");
  u8g2.setFont(u8g2_font_ncenB14_tr);
  u8g2.setCursor(45, 35);
  u8g2.print(encoderValue);
  u8g2.print(" min");
  u8g2.sendBuffer();
}

6. Starting and Handling the Countdown

Once the button is pressed, the countdown begins:

void handleButton() {
  if (digitalRead(SW) == LOW && !buttonPressed && !timerRunning) {
    startCountdown();
  } else if (digitalRead(SW) == HIGH && buttonPressed) {
    buttonPressed = false;
  }
}

When the countdown is running, the display shows the remaining time:

void displayCountdown(unsigned long remainingTime) {
  int minutes = remainingTime / (60 * 1000);
  int seconds = (remainingTime % (60 * 1000)) / 1000;
  u8g2.clearBuffer();
  u8g2.setCursor(15, 15);
  u8g2.print("Remaining Time: ");
  u8g2.setFont(u8g2_font_ncenB14_tr);
  u8g2.setCursor(35, 35);
  u8g2.print(minutes);
  u8g2.print("m ");
  u8g2.print(seconds);
  u8g2.print("s");
  u8g2.sendBuffer();
}

Once the countdown is complete, the relay turns off:

void endCountdown() {
  timerRunning = false;
  u8g2.clearBuffer();
  u8g2.setCursor(0, 10);
  u8g2.print("Countdown Complete!");
  u8g2.sendBuffer();
  digitalWrite(RELAY_PIN, LOW);
}

Final Thoughts

This simple countdown timer project can be expanded to control various devices with just a few changes. It’s highly customizable and provides hands-on experience working with ESP32-C3, OLED displays, and rotary encoders. You can adapt it to different time ranges or add more features like pause functionality, sound alarms, or even Wi-Fi control.