Hello, I want to share my implementation of a soft on/off fade for the rbdimmer module and ask for feedback.
My goal: when turning the light on it should fade from 0 to the target brightness over about 2 seconds. When turning off it should fade down to 0. Like a premium smart bulb effect.
Here is my first attempt with a simple for-loop in loop():
#include <rbdimmerESP32.h>
rbdimmerESP32 dimmer(18, 19);
int targetPower = 0;
bool fading = false;
void fadeToTarget() {
int current = dimmer.getPower();
int step = (targetPower > current) ? 1 : -1;
while (current != targetPower) {
current += step;
dimmer.setPower(current);
delay(20); // 20ms per step = ~2 seconds for full range
}
fading = false;
}
void loop() {
if (fading) fadeToTarget();
// MQTT, WiFi, other tasks here...
}
The fade itself works perfectly — smooth transition from any level to any level. But there is a big problem: during the fade, nothing else runs. WiFi disconnects, MQTT messages are lost, the watchdog timer sometimes triggers if the fade is long enough.
It makes no sense for me to have a nice fade animation if the ESP32 becomes unresponsive for 2 seconds every time. I think I need to move this to a FreeRTOS task so the fade runs independently from loop().
UPDATE: I converted the fade to a FreeRTOS task. Here is my new version:
#include <rbdimmerESP32.h>
rbdimmerESP32 dimmer(18, 19);
TaskHandle_t fadeHandle = NULL;
struct FadeParams {
int target;
};
void fadeTask(void* params) {
FadeParams* fp = (FadeParams*)params;
int current = dimmer.getPower();
int step = (fp->target > current) ? 1 : -1;
while (current != fp->target) {
current += step;
dimmer.setPower(current);
delayMicroseconds(20000); // 20ms
}
fadeHandle = NULL;
free(fp);
vTaskDelete(NULL);
}
void startFade(int targetPower) {
if (fadeHandle != NULL) {
vTaskDelete(fadeHandle);
fadeHandle = NULL;
}
FadeParams* fp = (FadeParams*)malloc(sizeof(FadeParams));
fp->target = targetPower;
xTaskCreate(fadeTask, "fade", 2048, fp, 1, &fadeHandle);
}
Now loop() is free during the fade — WiFi stays connected, MQTT messages are processed. The fade runs on a separate task.
But I am not sure about delayMicroseconds(20000) inside a FreeRTOS task. Is this the correct way to wait? It seems to work but I read somewhere that blocking delays inside tasks are not ideal.
Good instinct to move the fade to a FreeRTOS task — that’s the right approach.
But yes, delayMicroseconds() inside a FreeRTOS task is problematic. It’s a busy-wait loop — the CPU spins doing nothing for 20ms, which means no other task at the same priority can run during that time. It works but wastes CPU cycles and can cause priority inversion issues.
The RTOS-friendly alternative is vTaskDelay():
vTaskDelay(pdMS_TO_TICKS(20)); // yields CPU for 20ms
This tells the FreeRTOS scheduler: \I don’t need the CPU for 20ms, let other tasks run." When the delay expires, the scheduler gives your task back the CPU.
The difference:
delayMicroseconds(20000) — busy-wait, 100% CPU usage during delay, blocks same-priority tasks
delay(20) — calls vTaskDelay() internally in Arduino-ESP32 core, but behaviour depends on the core version
vTaskDelay(pdMS_TO_TICKS(20)) — explicit RTOS yield, 0% CPU during delay, other tasks run freely
Always use vTaskDelay() inside FreeRTOS tasks. Save delayMicroseconds() for sub-millisecond timing where you actually need busy-waiting (like bit-banging protocols).
One more suggestion: add a check for current bounds to prevent overshoot if getPower() returns an unexpected value:
current = max(0, min(100, current + step));
```"
Thank you Hank — I replaced delayMicroseconds(20000) with vTaskDelay(pdMS_TO_TICKS(20)) and added the bounds check. Final working version:
void fadeTask(void* params) {
FadeParams* fp = (FadeParams*)params;
int current = dimmer.getPower();
int step = (fp->target > current) ? 1 : -1;
while (current != fp->target) {
current = max(0, min(100, current + step));
dimmer.setPower(current);
vTaskDelay(pdMS_TO_TICKS(20));
}
fadeHandle = NULL;
free(fp);
vTaskDelete(NULL);
}
I tested this for 3 hours with continuous fade up/down cycles while running WiFi + MQTT + OTA simultaneously. Zero disconnections, zero watchdog resets. The fade is perfectly smooth and the ESP32 stays fully responsive.
The CPU usage difference is significant — with delayMicroseconds the second core was showing 15% usage from the fade task alone. With vTaskDelay it drops to nearly 0% because the task yields properly.
For anyone building soft on/off for their smart lighting — FreeRTOS task with vTaskDelay is definitely the way to go on ESP32.
[SOLVED]