I just poll the RX queue and if there is nothing there (or not enough bytes to form a usable message) then I yield right away. But I can see this increases power consumption.
[...]
But unless you are putting the CPU into a low power mode most of the time, the power benefit must be minimal.
And, once again as often happens to me when peter-h is involved, I'm confused.
Didn't you say that you have a number of CPU bound tasks?
Clarification: to me CPU bound means IO on the task must wait for the CPU to finish. IO bound the opposite, the CPU is always ready to process new IO.
So, if there are CPU bound task, what's the point of trying to switch to low power modes? The CPU bound task should be always ready to run
Also, consider that FreeRTOS has an
Idle Task - running whenever none of the user defined tasks are (as it has the lowest possible priority).
This Idle task will consume the rest of your CPU time, unless you take specific steps to configure FreeRTOS for using low power modes.
For serial RX, you are on the right track, if you just want a task to wake up when at least 5 characters have been received, use the ISR to put them in a queue (in general terms - might or might not be a FreeRTOS queue) and signal the task when five have been received (e.g. using a cheap direct task notification, or a semaphore).
As a general rule for inter-task communication it's better to use what the RTOS provides rather than faffing around with volatile atomic flags or variables etc. - unless you are starved for memory or CPU time, the overhead is quite tolerable and they work correctly.
Remember that in FreeROTS ISRs must use special versions of the primitives (e.g. xSemaphoreGive
FromISR) and explicitly yield with portYIELD_FOM_ISR() (otherwise your task will not wake up until the next time the scheduler invoked).
Other RTOS are simpler in this respect, e.g. Azure RTOS/ThreadX: there's a list of API that are allowed from ISR (most are) and that's all.
For me, in most of my stuff:
user inputs are always polled (encoders, buttons) the input task is woken up periodically and checks if anything has happened.
I2S and SPI use DMA + interrupts, I2C just interrupts. The interrupt wakes the relevant task using the provided RTOS primitives.