I am running FreeRTOS v10.2.1 on a SAMD51 (Cortex M4F) and am attempting to use its deepest sleep mode (hibernate). The difficulty is that waking from hibernate causes a system reset but I want to resume execution from the state before it went to sleep.
My approach so far…
- Configure the power management controller to retain the contents of RAM in hibernate mode.
- Define a flag that the main task sets when the system should sleep.
- Implement
traceTASK_SWITCHED_OUT()
to check that flag and, if set, clear it and enter hibernate mode. Doing it this way instead of inconfigPRE_SLEEP_PROCESSING()
means thatxPortPendSVHandler()
has already stacked the task context (as suggested here). - Insert code in
Reset_Handler()
before all the C startup stuff to check whether the reset was caused by exiting hibernate mode. - If it was, restore the state of some core registers:
- Set BASEPRI to the value it had before sleeping
- Set PendSV and SysTick interrupt priorities
- Enable the FPU
- Call
vPortSetupTimerInterrupt()
to reenable SysTick - Set the sleep mode back to idle instead of hibernate
SVC
- Amend
vPortSVCHandler()
to test LR and pop the floating-point registers if necessary, as happens in the second half ofxPortPendSVHandler()
. This shouldn’t affectprvPortStartFirstTask()
sinceportINITIAL_EXC_RETURN
has bit 15 set.
This almost works. The system goes to sleep, wakes up, and resumes executing the task from before it slept. But at the first PendSV interrupt, taskSELECT_HIGHEST_PRIORITY_TASK()
sets pxCurrentTCB to NULL (in listGET_OWNER_OF_NEXT_ENTRY
), which of course leads to a HardFault.
I’m not sure whether the problem is that there’s some processor state I’m not restoring or whether I’m violating some assumption of FreeRTOS’s internals. I’ve checked that AIRCR.PRIGROUP remains zero after reset, and all the NVIC interrupt priorities seem to retain their values. The SCB and SysTick registers appear to lose their configuration at reset, so I manually re-enable the FPU and reconfigure SysTick (point 5 above).
Things I’ve tried that didn’t help:
- Restoring the MSP to its pre-sleep value. I don’t think there’s anything useful left on the main stack anyway because at reset I get the task pointer from pxCurrentTCB.
- Calling
vTaskStepTick()
after wakeup to indicate that time has passed. - Setting
xYieldPending = true
at wakeup. Grasping at straws by this point.
Is anybody able to shed any light on why taskSELECT_HIGHEST_PRIORITY_TASK()
would be setting pxCurrentTCB to NULL and how to avoid it? I’ve spent many hours poring over assembly and single-stepping gdb to get to this point but now I’m stuck.