I have just started with both the RP2040, and with FreeRTOS, so I’m hardly an expert in either.
However, I noticed something which I’d like to confirm - I guess you could call it a theoretical question.
Let’s say I have a UART receive interrupt service routine, which runs byte by byte; I would like to protect the UART receive buffer filling as a critical section, and would also like to protect the reading of the same buffer in “normal” code.
In RP2040, there is raspberrypi. github. io/pico-sdk-doxygen/group__critical__section.html) API; and I’ve written tests for RP2040, that in general look like:
// ...
critical_section_t myLock;
uint8_t myRxBuffer[];
// ...
void on_uart_rx_ISR() {
// ...
uint8_t ch = uart_getc(UART_ID);
critical_section_enter_blocking(&myLock);
myRxBuffer[buf_write_ptr++] = ch;
critical_section_exit(&myLock);
// ...
}
int main() {
// ...
critical_section_init(&myLock);
// ...
irq_set_exclusive_handler(UART_IRQ, on_uart_rx_ISR);
irq_set_enabled(UART_IRQ, true);
// ...
while(true) {
// ...
// time to copy buffer
critical_section_enter_blocking(&myLock);
memcpy(final_buffer, myRxBuffer, buffer_size);
critical_section_exit(&myLock);
// ...
}
}
I have done some very simple tests with the above structure, and I did not notice anything out of the ordinary - so I guess it works as intended.
What I like about this api, is that I can define different critical_section_t
“lock” variables, and protect different sections of the code: for instance, I could have one lock for UART0 receive buffer, and another lock for UART1 receive buffer, and they shouldn’t interfere with one another (e.g. if main has locked UART0 for copying, and UART1 RX ISR fires, UART1 will proceed without issue).
Now I would like to do something similar, again on RP2040, but with FreeRTOS (if relevant, I’ve tried the smp
branch of FreeRTOS). That would also mean, that “normal” code is not main()
anymore as in above example, but a FreeRTOS task.
I assume that nothing will change in terms of defining the interrupts, because it is stated in forums. freertos. org/t/attaching-interrupt-to-pin-platform-specific-code/15107 :
Am I right in assuming that there are no FreeRTOS wrapper routines which handle such device-specific hardware-to-ISR mappings, and consequently any such code will likely be non-portable from device to device?
Yes, FreeRTOS doesn’t try to wrap all the possible hardware configurations for things like I/O to make a portable application, just a mostly portable task structure.
However, the only thing I’ve found comparable to the critical_section
API of RP2040, is the www. freertos. org/taskENTER_CRITICAL_taskEXIT_CRITICAL.html , and www. freertos. org/taskENTER_CRITICAL_FROM_ISR_taskEXIT_CRITICAL_FROM_ISR.html
So, I imagine, the above example in FreeRTOS would look like:
// ...
uint8_t myRxBuffer[];
// ...
void on_uart_rx_ISR() {
// ...
uint8_t ch = uart_getc(UART_ID);
UBaseType_t saved_intr_status = taskENTER_CRITICAL_FROM_ISR();
myRxBuffer[buf_write_ptr++] = ch;
taskEXIT_CRITICAL_FROM_ISR(saved_intr_status);
// ...
}
void my_task( void* pvParameters ) {
while(true) {
// ...
// time to copy buffer
taskENTER_CRITICAL();
memcpy(final_buffer, myRxBuffer, buffer_size);
taskEXIT_CRITICAL();
// ...
}
}
int main() {
// ...
irq_set_exclusive_handler(UART_IRQ, on_uart_rx_ISR);
irq_set_enabled(UART_IRQ, true);
// ...
xTaskCreate(my_task, "MY_Task", 256, NULL, 1, NULL);
// ...
vTaskStartScheduler();
// should never reach here (FreeRTOS-SMP-Demos/FreeRTOS/Demo/CORTEX_M0+_RP2040/OnEitherCore/main.c)
panic_unsupported();
}
Now, here are my doubts/problems:
- www. freertos. org/taskENTER_CRITICAL_taskEXIT_CRITICAL.html notes:
If the FreeRTOS port being used does not make use of the
configMAX_SYSCALL_INTERRUPT_PRIORITY
… then calling taskENTER_CRITICAL() will leave interrupts globally disabled. If the FreeRTOS port being used does make use of theconfigMAX_SYSCALL_INTERRUPT_PRIORITY
…, then calling taskENTER_CRITICAL() will leave interrupts at and below the interrupt priority set by configMAX_SYSCALL_INTERRUPT_PRIORITY disabled, and all higher priority interrupt enabled.
So, if taskENTER_CRITICAL disables interrupts, it might lead to missed bytes (bytes not copied) in the UART RX ISR (which I have observed).
- taskENTER_CRITICAL/_FROM_ISR does not take any arguments.
That means, I cannot really use these functions to protect, say, two UARTs as I described above - because if I lock UART0 for copying, then that will also lock UART1 operation (interrupt disabling notwithstanding).
- As far as I can tell,
critical_section_t
from the RP2040/Pico SDK is basically a “spinlock”; and that would imply “busy wait” - as per stackoverflow. com/questions/38124337/spinlock-vs-busy-wait :
So a spin-lock is implemented using busy-waiting. Busy-waiting is useful in any situation where a very low latency response is more important than wasting CPU cycles (like in some types of embedded programming).
Which, I guess, means that I shouldn’t use critical_section_t
API from RP2040/Pico SDK within FreeRTOS, as it would sort of defeat the purpose of FreeRTOS (primarily managing the timing of tasks).
So, in summary, here are my questions:
- Is there a FreeRTOS “native” “critical section” handling, that does not work globally as a single lock - but instead can allow for “multiple” locks (like the RP2040/Pico API allows: by using a spinlock/
critical_section_t
variable as argument to the relevant functions, which effectively provides multiple locks)? - (even if I already arrived at the opposite conclusion above, maybe best to double-check:) Would it be OK to mix RP2040/Pico SDK “critical section” API, with ISRs and FreeRTOS tasks?