Best approach to run code in a task exactly every millisecond
Posted: Tue May 20, 2025 8:59 pm
Hello,
My application consists of mainly two parts: UART communication and LED control. The UART communication might run any time, depending on what's sent on the external bus. When the LED control is used, it needs to perform accurate fading control where the brightness (PWM duty) is updated continuously towards the target value.
The observed issue is that the fading jitters whenever there's some external bus activity, it's not smooth anymore in that moment and jumps a bit. UART reading and message processing is done in two tasks with priority 1 and 2 (will change that to values around 10). The LED fading control is performed in a timer that runs every 10 ms.
As far as I could read now, timers always run at priority 1 with no core affinity (could be changed, but I also use timers for the communication, so I can't separate those domains with timers).
I'm going to change the LED control timers to a dedicated task that runs the regular code in a loop. The task will have a priority of 30 and be pinned to core 1, whereas I'm pinning comm tasks to core 0 together with the timer. Communication is not time-critical, but LED control is because any delays during fading are well visible to the user.
So I'll have a separate task that needs to update the LED controller every 10 ms. Or even better, every 1 ms, now that I can do that. (Timer resolution is limited to 10 ms; again, if not reconfiguring the tick frequency.) How would I do this? It's also important that the tick frequency in my task is somewhat stable and doesn't drift depending of how long each LED controller update takes. So an external time-keeper is preferred over a constant delay in the loop.
Can I use esp_timer_get_time() and taskYIELD() here? I could set a time baseline, add the desired next run time and just wait for esp_timer_get_time() to return something greater than the baseline. It would run much more often than every millisecond and only yield to allow the idle task to run, too. Is there a more efficient approach? Also, the regular updates are only needed during fadings. As soon as the target brightness has been reached, the task has nothing to do until the next brightness target is set through external communication. Would I use some kind of semaphore here? (The task would self-suspend on core 1 and be resumed from core 0; it must be prevented that race conditions dead-lock the task.) Energy efficiency is not a big concern here, but if it can save something and stay cooler, I'll take that.
I couldn't find any references to this problem online. So nothing to learn from yet.
On the smaller AVR platform (8-bit single-core MCU) I'd just keep the CPU busy, but ESP32/FreeRTOS seems to be treated more like a "big" multi-tasking OS.
My application consists of mainly two parts: UART communication and LED control. The UART communication might run any time, depending on what's sent on the external bus. When the LED control is used, it needs to perform accurate fading control where the brightness (PWM duty) is updated continuously towards the target value.
The observed issue is that the fading jitters whenever there's some external bus activity, it's not smooth anymore in that moment and jumps a bit. UART reading and message processing is done in two tasks with priority 1 and 2 (will change that to values around 10). The LED fading control is performed in a timer that runs every 10 ms.
As far as I could read now, timers always run at priority 1 with no core affinity (could be changed, but I also use timers for the communication, so I can't separate those domains with timers).
I'm going to change the LED control timers to a dedicated task that runs the regular code in a loop. The task will have a priority of 30 and be pinned to core 1, whereas I'm pinning comm tasks to core 0 together with the timer. Communication is not time-critical, but LED control is because any delays during fading are well visible to the user.
So I'll have a separate task that needs to update the LED controller every 10 ms. Or even better, every 1 ms, now that I can do that. (Timer resolution is limited to 10 ms; again, if not reconfiguring the tick frequency.) How would I do this? It's also important that the tick frequency in my task is somewhat stable and doesn't drift depending of how long each LED controller update takes. So an external time-keeper is preferred over a constant delay in the loop.
Can I use esp_timer_get_time() and taskYIELD() here? I could set a time baseline, add the desired next run time and just wait for esp_timer_get_time() to return something greater than the baseline. It would run much more often than every millisecond and only yield to allow the idle task to run, too. Is there a more efficient approach? Also, the regular updates are only needed during fadings. As soon as the target brightness has been reached, the task has nothing to do until the next brightness target is set through external communication. Would I use some kind of semaphore here? (The task would self-suspend on core 1 and be resumed from core 0; it must be prevented that race conditions dead-lock the task.) Energy efficiency is not a big concern here, but if it can save something and stay cooler, I'll take that.
I couldn't find any references to this problem online. So nothing to learn from yet.
On the smaller AVR platform (8-bit single-core MCU) I'd just keep the CPU busy, but ESP32/FreeRTOS seems to be treated more like a "big" multi-tasking OS.