FreeRTOS: How to End and Restart the Scheduler
Embedded systems are not meant to 'run forever.'
Join the DZone community and get the full member experience.
Join For FreeMost host or desktop systems (say Linux, Mac, or Windows) have a normal use case where you start the operating system, say, in the morning, shut it down in the evening, and then you leave the machine. Embedded Systems are different; they are not attended, and they are supposed to run ‘forever.' Not every embedded system needs to run an OS (or in that world: Real-Time Operating System or RTOS), but the same applies here: after the RTOS is started, it is not intended that it will shut down and restart. To that extent, they do not support the ‘shutdown’ and ‘restart’ functionality at all. In the case of gathering coverage information, this would be really useful:
coverage information from FreeRTOS application
In the case of FreeRTOS, what if I really need to shut down the RTOS and restart it again, as, by default, this is not supported. This is what we will be addressing in this article.
Introduction
Embedded Systems are fundamentally different from desktop systems; while it is kind of normal to shut down and restart a desktop or laptop system from time to time, this is not planned or intended for an embedded system: by its nature, it should run ‘always.' This is further visible from the ‘main’ of an embedded system: typically, the main never returns and stays running, something like this:
void main(void) {
InitClocks();
InitPins();
InitDrivers();
for(;;) {
AppRun();
}
/* never leave main */
}
A similar thing applies to embedded systems if running with an RTOS, where this looks similar like this:
void main(void) {
InitClocks();
InitPins();
InitDrivers();
CreateInitialTasks();
StartScheduler();
/* should never get here as scheduler never terminates */
for(;;) { }
/* never leave main */
}
Screenshots in this post are using the unlimited and free-of-charge Eclipse IDE NXP MCUXpresso IDE V10.3.0 with FreeRTOS V10.1.1
Why Shutdown and Restart?
Obviously, the need for an RTOS shutdown or restart is probably not something needed the most for an embedded RTOS. Still, I have found it very useful.
It makes sense to run some software *after* the RTOS has been shut down. For example, if I collect coverage information (see “Adding GNU Coverage Tools to Eclipse” and “GNU Code Coverage on Embedded Target with Eclipse Neon and ARM gcc 5“), I don’t want to interfere that last process to dump the data with the RTOS. Additionally, I need exclusive access to the system resources including lots of stack space: shutting down the RTOS gives me back all the RAM and stack space I need for the file I/O operations.
writing gcov coverage information after scheduler shutdown
It makes sense to run the RTOS while listening for updates. Running the RTOS while doing the update is certainly possible in some cases, but at some stages, I have to stop it and restart it. This can be done with resetting and restarting the system (e.g. see “How to Reset an ARM Cortex-M with Software“). I have found that it is a better way to shut down the RTOS rather than doing the update outside of the RTOS and restarting the system.
ending the FreeRTOS scheduler from a task
Another use case is for low power applications. While FreeRTOS is great in low power modes too (see “Low Power with FreeRTOS: Tickless Idle Mode“), the application can go even into lower power modes with more restricted functionality if I can turn off more of the active system. So, having the ability to run the RTOS only in the phases of the lifetime of my application where it is making sense and not in other parts gives me greater flexibility and the opportunity for even lower power.
vTaskStartScheduler() followed by low power mode
So, I hope you see why a shutdown and restart of the RTOS can make sense. Most RTOS, including FreeRTOS, have the ability to ‘silence’ the scheduler ( vTaskSuspendAll()
for example), but still, the RTOS is there and uses system resources. But the ability to completely ‘remove’ it and restart it if needed would be a cool thing.
FreeRTOS
FreeRTOS does have a scheduler start function ( vTaskStartScheduler()
) and even have a vTaskEndScheduler()
function in its API:
FreeRTOS vTaskEndScheduler() API function
But as noted in the forums and in the API description, it is only supported for the PC port (see API description):
“NOTE: This has only been implemented for the x86 Real Mode PC port.“
That’s true for the original FreeRTOS port. The port I have extended does support this, and I’m using it in the ARM Cortex-M and HCS08 application.
vTaskEndScheduler() and vTaskStartScheduler()
While the RTOS has the API call prepared to shut it down, FreeRTOS does not have the infrastructure in place to restart the RTOS after a vTaskEndScheduler()
call. But this is exactly what I want — to restart the RTOS after it has been ended.
To have the ability to end the scheduler, the following macro has to be set to 1 in FreeRTOSConfig.h:
#define INCLUDE_vTaskEndScheduler
The challenge is that after the scheduler has been started, it is not easy to return to the code right after the vTaskStartScheduler()
call. Because the tasks and their own stacks, plus the standard port for FreeRTOS and M3, M4, and M7, even reset the MSP stack pointer (see this forum discussion). So if I want to return to the place where the scheduler has been started, I need to prevent resetting the MSP stack pointer on ARM Cortex. This is why I have added a setting to FreeRTOS to configure this:
reset MSP setting
This sets the following configuration define:
#ifndef configRESET_MSP
#define configRESET_MSP (0)
/*!< 1: reset MSP at scheduler start (Cortex M3/M4/M7 only); 0: do not reset MSP */
#endif
With this setting set to 0, my port does *not* reset the MSP at the scheduler starting point.
port code to reset msp stack pointer
This means that not the full MSP stack will be available for interrupts (see “ARM Cortex-M Interrupts and FreeRTOS: Part 3“), but that’s usually ok, too.
To be able to jump back to the starting point of the scheduler, I use a cool feature of the C library: setjmp/longjmp (see http://web.eecs.utk.edu/~huangj/cs360/360/notes/Setjmp/lecture.html).
First, I need a jump buffer variable:
#if INCLUDE_vTaskEndScheduler
#include <setjmp.h>
static jmp_buf xJumpBuf; /* Used to restore the original context when the scheduler is ended. */
#endif
Inside xPortStartScheduler()
, I set up the jump buffer:
#if INCLUDE_vTaskEndScheduler
if(setjmp(xJumpBuf) != 0 ) {
/* here we will get in case of a call to vTaskEndScheduler() */
__asm volatile(
" movs r0, #0 \n" /* Reset CONTROL register and switch back to the MSP stack. */
" msr CONTROL, r0 \n"
" dsb \n"
" isb \n"
);
return pdFALSE;
}
#endif
vPortStartFirstTask(); /* Start the first task. */
/* Should not get here! */
return pdFALSE;
setjmp()
returns 0 if establishing the jump buffer, and it shall return !=0 in the case that setjmp()
is called, which will get called during vTaskEndScheduler()
.
void vTaskEndScheduler( void )
{
/* Stop the scheduler interrupts and call the portable scheduler end
routine so the original ISRs can be restored if necessary. The port
layer must ensure interrupts enable bit is left in the correct state. */
portDISABLE_INTERRUPTS();
xSchedulerRunning = pdFALSE;
vPortEndScheduler();
}
The vPortEndscheduler()
then does all the cleanup and reset of the RTOS. The reset is needed not to confuse any RTOS awareness in the debugger:
void vPortEndScheduler(void) {
vPortStopTickTimer();
vPortInitializeHeap();
uxCriticalNesting = 0xaaaaaaaa;
/* Jump back to the processor state prior to starting the
scheduler. This means we are not going to be using a
task stack frame so the task can be deleted. */
#if INCLUDE_vTaskEndScheduler
longjmp(xJumpBuf, 1);
#else
for(;;){} /* wait here */
#endif
}
With this, the scheduler is terminated gracefully and I’m back on the main stack again:
on msp stack after calling vTaskEndScheduler()
I find that very cool.
Summary
By default, for embedded targets, FreeRTOS does not support ending the scheduler or restarting the scheduler after ending it. This article describes a port for ARM Cortex-M, which implements vTaskEndScheduler()
and the ability to call vTaskStartScheduler()
again without a power-on reset. Ending and starting the scheduler is useful for low-power applications and use cases where an RTOS is not needed during the life cycle of an application or for bootloader applications. The concept is used on different ARM Cortex-M cores from NXP including on the 8/16bit S08 microcontroller but can be easily used for any other microcontroller architectures.
Happy Scheduling!
Links
- McuOnEclipse FreeRTOS port discussed in this article.
- Example project with gcov on GitHub with NXP MCUXpresso IDE.
- FreeRTOS web page
- FreeRTOS vTaskEndScheduler API
- NXP community discussion about FreeRTOS and MSP
- Wikipedia about setjmp/longjmp
- Tutorial Series about ARM Cortex-M Interrupts: ARM Cortex-M, Interrupts and FreeRTOS: Part 1
- MCUXpresso IDE v10.3.0: New NXP MCUXpresso IDE V10.3.0 Release
Published at DZone with permission of Erich Styger, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments