ELECTRO MOTIF
Published

Wireless Smart Dimmer using ESP-NOW and RTOS

An advanced ESP32-based wireless control system leveraging ESP-NOW and FreeRTOS to achieve real-time communication, multitasking, and respon

IntermediateWork in progress2 hours10
Wireless Smart Dimmer using ESP-NOW and RTOS

Things used in this project

Hardware components

Espressif ESP32 Development Board - Developer Edition
Espressif ESP32 Development Board - Developer Edition
×2
Rotary Encoder with Push-Button
Rotary Encoder with Push-Button
×1
I2C 16x2 Arduino LCD Display Module
DFRobot I2C 16x2 Arduino LCD Display Module
×1
Jumper wires (generic)
Jumper wires (generic)
×1
USB Cable Assembly, USB Type A Plug to Micro USB Type B Plug
USB Cable Assembly, USB Type A Plug to Micro USB Type B Plug
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Code

Sender Code (Rotary Encoder)

C/C++
Reads rotary encoder input and wirelessly sends the value using ESP-NOW.
#include <esp_now.h>
#include <WiFi.h>

// Rotary Encoder Pins
#define CLK 32
#define DT  33

// Shared data
int encoderValue = 0;

// Mutex
SemaphoreHandle_t encoderMutex;

// Receiver MAC
uint8_t broadcastAddress[] = {0x04, 0x83, 0x08, 0x57, 0xBC, 0xB4};

// Structure
typedef struct struct_message {
  int encoderValue;
} struct_message;

struct_message myData;
esp_now_peer_info_t peerInfo;

// ================= CALLBACK =================
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  Serial.print("Send Status: ");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Success" : "Fail");
}

// ================= ENCODER TASK =================
void encoderTask(void *pvParameters) {

  int lastStateCLK = digitalRead(CLK);
  int currentStateCLK;

  while (1) {

    currentStateCLK = digitalRead(CLK);

    if (currentStateCLK != lastStateCLK && currentStateCLK == HIGH) {

      if (digitalRead(DT) != currentStateCLK) {
        if (xSemaphoreTake(encoderMutex, portMAX_DELAY)) {
          encoderValue++;
          xSemaphoreGive(encoderMutex);
        }
      } else {
        if (xSemaphoreTake(encoderMutex, portMAX_DELAY)) {
          encoderValue--;
          xSemaphoreGive(encoderMutex);
        }
      }

      Serial.print("Encoder: ");
      Serial.println(encoderValue);
    }

    lastStateCLK = currentStateCLK;

    vTaskDelay(1); // small delay for stability
  }
}

// ================= SEND TASK =================
void sendTask(void *pvParameters) {

  int lastSentValue = 0;

  while (1) {

    int localValue;

    // Get value safely
    if (xSemaphoreTake(encoderMutex, portMAX_DELAY)) {
      localValue = encoderValue;
      xSemaphoreGive(encoderMutex);
    }

    // Send only if value changed
    if (localValue != lastSentValue) {

      myData.encoderValue = localValue;

      esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *)&myData, sizeof(myData));

      if (result == ESP_OK) {
        Serial.println("Data Sent");
      } else {
        Serial.println("Send Error");
      }

      lastSentValue = localValue;
    }

    vTaskDelay(pdMS_TO_TICKS(50)); // control send rate
  }
}

// ================= SETUP =================
void setup() {
  Serial.begin(115200);

  pinMode(CLK, INPUT);
  pinMode(DT, INPUT);

  WiFi.mode(WIFI_STA);

  if (esp_now_init() != ESP_OK) {
    Serial.println("ESP-NOW Init Failed");
    return;
  }

  esp_now_register_send_cb(OnDataSent);

  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  peerInfo.channel = 0;
  peerInfo.encrypt = false;

  if (esp_now_add_peer(&peerInfo) != ESP_OK) {
    Serial.println("Failed to add peer");
    return;
  }

  // Create Mutex
  encoderMutex = xSemaphoreCreateMutex();

  // Create Tasks
  xTaskCreatePinnedToCore(encoderTask, "Encoder Task", 2048, NULL, 2, NULL, 0);
  xTaskCreatePinnedToCore(sendTask, "Send Task", 2048, NULL, 1, NULL, 1);
}

// ================= LOOP =================
void loop() {}

Receiver Code (LCD + LED)

C/C++
Receives encoder data via ESP-NOW, controls LED brightness using PWM, and displays values on a 16x2 I2C LCD.
#include <esp_now.h>
#include <WiFi.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// LCD
LiquidCrystal_I2C lcd(0x27, 16, 2);

// I2C pins
#define SDA_PIN 32
#define SCL_PIN 33

// PWM
#define LED_PIN 14
#define PWM_CHANNEL 0
#define PWM_FREQ 5000
#define PWM_RESOLUTION 8

// Structure
typedef struct struct_message {
  int encoderValue;
} struct_message;

// Shared data
struct_message sharedData;

// Mutex
SemaphoreHandle_t dataMutex;

// ================= ESP-NOW CALLBACK =================
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  
  struct_message tempData;
  memcpy(&tempData, incomingData, sizeof(tempData));

  // Take mutex (ISR-safe version)
  if (xSemaphoreTakeFromISR(dataMutex, NULL) == pdTRUE) {
    sharedData = tempData;
    xSemaphoreGiveFromISR(dataMutex, NULL);
  }
}

// ================= LED TASK =================
void ledTask(void *pvParameters) {

  struct_message localData;

  while (1) {

    if (xSemaphoreTake(dataMutex, portMAX_DELAY)) {
      localData = sharedData;
      xSemaphoreGive(dataMutex);
    }

    int brightness = map(localData.encoderValue, 0, 100, 0, 255);
    brightness = constrain(brightness, 0, 255);

    ledcWrite(PWM_CHANNEL, brightness);

    Serial.print("LED Brightness: ");
    Serial.println(brightness);

    vTaskDelay(pdMS_TO_TICKS(50));
  }
}

// ================= LCD TASK =================
void lcdTask(void *pvParameters) {

  struct_message localData;

  while (1) {

    if (xSemaphoreTake(dataMutex, portMAX_DELAY)) {
      localData = sharedData;
      xSemaphoreGive(dataMutex);
    }

    int brightness = map(localData.encoderValue, 0, 100, 0, 255);

    lcd.setCursor(0, 0);
    lcd.print("Encoder:       ");
    lcd.setCursor(9, 0);
    lcd.print(localData.encoderValue);

    lcd.setCursor(0, 1);
    lcd.print("Bright:        ");
    lcd.setCursor(9, 1);
    lcd.print(brightness);

    vTaskDelay(pdMS_TO_TICKS(200));
  }
}

// ================= SETUP =================
void setup() {
  Serial.begin(115200);

  // I2C
  Wire.begin(SDA_PIN, SCL_PIN);
  lcd.init();
  lcd.backlight();
  lcd.print("System Ready");

  // PWM
  ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION);
  ledcAttachPin(LED_PIN, PWM_CHANNEL);

  // WiFi + ESP-NOW
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();

  if (esp_now_init() != ESP_OK) {
    Serial.println("ESP-NOW Init Failed");
    return;
  }

  esp_now_register_recv_cb(OnDataRecv);

  // Create Mutex
  dataMutex = xSemaphoreCreateMutex();

  // Create Tasks
  xTaskCreatePinnedToCore(ledTask, "LED Task", 2048, NULL, 1, NULL, 0);
  xTaskCreatePinnedToCore(lcdTask, "LCD Task", 2048, NULL, 1, NULL, 1);
}

// ================= LOOP =================
void loop() {}

MAC Address Code

C/C++
Retrieves the ESP32 MAC address required for ESP-NOW communication setup.
#include <WiFi.h>

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  Serial.println(WiFi.macAddress());
}

void loop() {}

Credits

ELECTRO MOTIF
5 projects • 0 followers
Electronics enthusiast focused on PCB design, power electronics, and embedded hardware. I design circuits and share hardware projects.

Comments