Hi all,
Apologies in advance for the verbosity, but I have been stumbling into this problem for a while, which I hope I can get some assistance with - and I think I have finally made a minimal reproducible example at https://gist.github.com/sdbbs/b1410cd45106e0c0ee599f7fcdbb8f90/48e79e193537a9346682b014ffd50d34c940cc1b.
The gist contains the project files CMakeLists.txt, FreeRTOSConfig.h and main.c (the only other files in my project are copy-pasted pico_sdk_import.cmake
and FreeRTOS_Kernel_import.cmake
).
The code (built against the smp branch of FreeRTOS) is very simple: I simply try to start a single FreeRTOS task, led_task
, which I want to run from CPU core 1 - which, when it starts, enables a hardware timer interrupt service routine, and from that point on, simply toggles pins with a semiperiod of 500 ms. There is otherwise no other FreeRTOS interaction (queues and such); and the only thing the timer ISR does is generate some random data, and toggles a pin at the ISR entry and exit.
Therefore, I do not expect anything else from this code, but to see the led_task pin toggled with a semiperiod of 500 ms - and the timer ISR pin toggled with a period close to 250 µs - together/in parallel, continuously.
However, I have had some trouble getting this behavior from the code; in fact, I get the following behaviors (note that the first, behavior A, is the state of the main.c
as is in the gist; the others would require changes in the code - commenting or uncommenting accordingly, - and recompilation; click on thumbnails for full image might be ignored so try right-click/Open Image in New Tab, top trace is led pin, bottom trace isr pin):
So, essentially, I can get the code to work as I expect it to - but in that case, I cannot specify the led_task to run on core 1.
So in general, my main question is: is it possible to both specify the led_task to run on core 1, and have the code (both led_task and timer ISR) run continuously? And if so, how?
However, there were some other subquestions that arose from this ordeal, so I hope I’ll be able to get some assistance/hints with them as well; and for that purpose, I’ll include some more details below.
For most of my development, I had stumbled into behavior A, and it puzzles me to no end. As the screenshot image shows:
… the led_task manages to start the timer ISR, but then stops toggling; I tried stepping in gdb
in this case, and basically, after you step over the first vTaskDelay(500);
in led_task, execution goes back to the program, and gdb
never breaks in led_task
again.
But then, the timer ISR runs for some 70-90 ms (82 ms on the image), then there is a “gap” where there are no ISRs (on the image 1.48 ms, but I have seen 2.x, 3.x, 6.x, 8x ms for that “gap”), then there are a couple of more runs of the timer ISR (2 on the screenshot, but I’ve seen anything from 1 to 4), and then the timer ISR… stops?!
And I’ve noticed, that when this happens, then FreeRTOS usually keeps on running (if I break in gdb
at a random time afterwards, I can see scheduling functions change in the backtrace) …
I simply cannot wrap my head around why would the timer ISR simply stop: since after each timer ISR, corresponding pin toggle goes back to zero, to me that means that restart_timer_alarm()
command has ran, and the next “run” of the timer ISR should therefore be “scheduled” - what on earth could prevent that?
The “gap” however, does imply that something could have disabled all interrupts, and then enabled them again - could this be some FreeRTOS initialization? But then, why does this “gap” consistently happen 70-90 ms after the ISR had started running (so, quite a bit later from where I’d expect FreeRTOS initialization to happen)?
…
Note that I encountered behavior A, by simply setting the led_task affinity to core 1, and then setting up the timer ISR as in the RP2040 pico-examples
code in timer_lowlevel.c.
I thought - well, it’s an official example, why shouldn’t it work?
Then I tried “fixing” this timer ISR “stop” by:
- Changed exit from timer ISR from
portYIELD_FROM_ISR(xHigherPriorityTaskWoken)
toportEND_SWITCHING_ISR(xHigherPriorityTaskWoken)
- that did not help - Having realized that RP2040 has only one hardware timer with four “alarm” ISRs (beyond the SysTick timer, which FreeRTOS already uses for its own systick), I tried changing the original Alarm 0 to 1, and then 2 (to avoid possible conflicts with FreeRTOS using these) - that did not help
- While I still had more tasks in my code, having realized that FreeRTOS might create “Tmr Svc”, “IDLE0” and “IDLE1” tasks in the background (which all run on both cores), and having realized “Tmr Svc” likely has task priority of
configMAX_PRIORITIES-1
, I tried to reduce all of my task priorities to less than that - that did not help (and ultimately I removed all tasks I had, but the led_task) - I realized interrupts have different priorities from the FreeRTOS task priorities; and the RP2040/Cortex M0+ has only four: 0b11000000 = 192 = 0xc0; 0b10000000 = 128 = 0x80; 0b01000000 = 64 = 0x40; 0b00000000 = 0 = 0x00; and noting that
configMAX_SYSCALL_INTERRUPT_PRIORITY
has been commented in all FreeRTOSConfig.h I had seen online so far, I tried to set it (so I could give the timer ISR higher interrupt priority, by giving it a lower priority number) - that did not help either, possibly because:
$ grep -r configMAX_SYSCALL_INTERRUPT_PRIORITY FreeRTOS-Kernel-SMP/portable/GCC/ARM_CM0 FreeRTOS-Kernel-SMP/portable/ThirdParty/GCC/{RP2040,rpi_pico}
$
… configMAX_SYSCALL_INTERRUPT_PRIORITY
is not found anywhere in (what I think are) the port specific files of RP2040/Cortex M0+.
Finally, I somehow managed to end up revisiting IntQueueTimer.c from the FreeRTOS-SMP-Demos for RP2040, and realized it also sets up a hardware timer alarm - but using a different “API” if you will:
-
timer_lowlevel.c uses
irq_set_exclusive_handler(ALARM_IRQ, timer_alarm_isr)
, and to restarttimer_hw->alarm[ALARM_NUM] = (uint32_t) timer_hw->timerawl + delay_us
-
IntQueueTimer.c uses
hardware_alarm_claim(ALARM_NUM); hardware_alarm_set_callback(ALARM_NUM, timer_alarm_isr)
, and to restarthardware_alarm_set_target(ALARM_NUM, make_timeout_time_us( delay_us ) );
So, changing to this hardware_alarm_*
API, which is handled by the #define USE_HARDWARE_ALARM_API
in the code, finally made the timer ISR run continuosly; however the led_task did not run - this is behavior B.
And after some experimentation and commenting random stuff, I finally realized that by not using the hardware_alarm_*
API, and not setting the CPU core affinity of led_task to core 1 - I can get both the led_task and the timer ISR to run continuously in parallel (behavior D), as intended/expected … except, I don’t understand why?
So, to sumarize; my main question is:
- Is it possible to both specify the led_task to run on core 1, and have the code (both led_task and timer ISR) run continuously? And if so, how?
… and my subquestions:
- For RP2040, should I use
portYIELD_FROM_ISR(xHigherPriorityTaskWoken)
, orportEND_SWITCHING_ISR(xHigherPriorityTaskWoken)
, if I want to exit an ISR which otherwise communicates with FreeRTOS?- For RP2040, must I use these macros as the very last command in the ISR function, or is there leeway (e.g. can I toggle the pin down after having called these macros)?
- For RP2040/Cortex M0+, does
configMAX_SYSCALL_INTERRUPT_PRIORITY
have any effect, or not? - What on earth could cause the timer ISR to just stop in behavior A, after some tens of milliseconds? By that I mean, could someone provide a plausible sequence of events as an example (even if it is not the actual sequence of events that causes the problem here), that would cause an already queued hardware timer alarm to not fire?
- Why do I have seeminly two methods ("API"s) to start and run a hardware timer alarm on the RP2040, with different behavior in respect to multicore/SMP operation? How do I know which is the “right” one to use?
- Why does the
hardware_alarm_*
API make the timer ISR run, when led_task has affinity to core 1 but otherwise does not run - and yet, if I want both led_task and timer ISR to run continuously as intended, I must use neither thehardware_alarm_*
API, nor affinity setting of led_task to core 1?
- Why does the
Thanks in advance for any answers!