FreeRTOS is a popular, open-source operating system that can run on a variety of microcontrollers. This post shows how to make a minimal working setup with two tasks on a new MCU without starting from a complete demo code or code generators (like Processor Expert) on an inexpensive development board FRDM-KE06Z from NXP. All examples use static memory allocation. Most of the procedures and tips mentioned here apply equally well to all Cortex-M microcontrollers.
I will assume that you have good understanding of C, installed Kinetis Design Studio, some experience with Cortex-M microcontrollers and that you know why you want an RTOS. π
From programmer’s point of view FreeRTOS makes several C function appear as they would be executed all at once. It does it by switching between them many times a second. Each of those functions is called a task. Switching happens within a timer interrupt (in case of Cortex-M it is the systick).
Creating a new KDS project
Let’s begin by creating blank project targeted for Kinetis KE06Z. Don’t enable Processor Expert.
Default project structure
The project contains MCU headers and CMSIS headers. CMSIS headers and provided mostly by ARM – they provide a common programming interface to the CPU core. FreeRTOS uses them to access systick and do some interrupt control. As the KE06Z is a Cortex-M0+ the headers for M4 and M7 can be removed.
Project after removing unnecessary files
Adding FreeRTOS files
The number of directories and files in FreeRTOS release package can be frightening, but only a very small part is necessary.
Let’s have a look at FreeRTOS v9.0.0 release directory structure:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
. βββ FreeRTOS βΒ Β βββ Demo βΒ Β βΒ Β βββ ARM7_AT91FR40008_GCC βΒ Β βΒ Β βββ ARM7_AT91SAM7S64_IAR βΒ Β βΒ Β βββ ARM7_AT91SAM7X256_Eclipse βΒ Β βΒ Β βββ ARM7_LPC2106_GCC βΒ Β βΒ Β βββ ARM7_LPC2129_IAR βΒ Β βΒ Β βββ ARM7_LPC2129_Keil_RVDS βΒ Β βΒ Β βββ ARM7_LPC2138_Rowley βΒ Β βΒ Β βββ ARM7_LPC2368_Eclipse βΒ Β βΒ Β βββ ARM7_LPC2368_Rowley βΒ Β βΒ Β βββ ARM7_STR71x_IAR βΒ Β βΒ Β βββ ARM7_STR75x_GCC βΒ Β βΒ Β βββ ARM7_STR75x_IAR βΒ Β βΒ Β βββ ARM9_AT91SAM9XE_IAR βΒ Β βΒ Β βββ ARM9_STR91X_IAR βΒ Β βΒ Β βββ AVR32_UC3 βΒ Β βΒ Β βββ AVR_ATMega323_IAR βΒ Β βΒ Β βββ AVR_ATMega323_WinAVR βΒ Β βΒ Β βββ ColdFire_MCF51CN128_CodeWarrior βΒ Β βΒ Β βββ ColdFire_MCF52221_CodeWarrior βΒ Β βΒ Β βββ ColdFire_MCF52233_Eclipse βΒ Β βΒ Β βββ ColdFire_MCF52259_CodeWarrior βΒ Β βΒ Β βββ ColdFire_MCF5282_Eclipse βΒ Β βΒ Β βββ Common βΒ Β βΒ Β βββ CORTEX_A2F200_IAR_and_Keil βΒ Β βΒ Β βββ CORTEX_A2F200_SoftConsole βΒ Β βΒ Β βββ CORTEX_A53_64-bit_UltraScale_MPSoC βΒ Β βΒ Β βββ CORTEX_A5_SAMA5D2x_Xplained_IAR βΒ Β βΒ Β βββ CORTEX_A5_SAMA5D3x_Xplained_IAR βΒ Β βΒ Β βββ CORTEX_A5_SAMA5D4x_EK_IAR βΒ Β βΒ Β βββ CORTEX_A9_Cyclone_V_SoC_DK βΒ Β βΒ Β βββ CORTEX_A9_RZ_R7S72100_IAR_DS-5 βΒ Β βΒ Β βββ CORTEX_A9_Zynq_ZC702 βΒ Β βΒ Β βββ CORTEX_AT91SAM3U256_IAR βΒ Β βΒ Β βββ CORTEX_ATSAM3S-EK2_Atmel_Studio βΒ Β βΒ Β βββ CORTEX_ATSAM3X_Atmel_Studio βΒ Β βΒ Β βββ CORTEX_CY8C5588_PSoC_Creator_GCC βΒ Β βΒ Β βββ CORTEX_CY8C5588_PSoC_Creator_Keil βΒ Β βΒ Β βββ CORTEX_CY8C5588_PSoC_Creator_RVDS βΒ Β βΒ Β βββ CORTEX_EFM32_Giant_Gecko_Simplicity_Studio βΒ Β βΒ Β βββ CORTEX_EFM32_Pearl_Gecko_Simplicity_Studio βΒ Β βΒ Β βββ CORTEX_EFMG890F128_IAR βΒ Β βΒ Β βββ CORTEX_Kinetis_K60_Tower_IAR βΒ Β βΒ Β βββ CORTEX_LM3S102_GCC βΒ Β βΒ Β βββ CORTEX_LM3S102_Rowley βΒ Β βΒ Β βββ CORTEX_LM3S316_IAR βΒ Β βΒ Β βββ CORTEX_LM3S811_GCC βΒ Β βΒ Β βββ CORTEX_LM3S811_IAR βΒ Β βΒ Β βββ CORTEX_LM3S811_KEIL βΒ Β βΒ Β βββ CORTEX_LM3Sxxxx_Eclipse βΒ Β βΒ Β βββ CORTEX_LM3Sxxxx_IAR_Keil βΒ Β βΒ Β βββ CORTEX_LM3Sxxxx_Rowley βΒ Β βΒ Β βββ CORTEX_LPC1768_GCC_RedSuite βΒ Β βΒ Β βββ CORTEX_LPC1768_GCC_Rowley βΒ Β βΒ Β βββ CORTEX_LPC1768_IAR βΒ Β βΒ Β βββ CORTEX_M0+_Atmel_SAMD20_XPlained βΒ Β βΒ Β βββ CORTEX_M0_Infineon_XMC1000_IAR_Keil_GCC βΒ Β βΒ Β βββ CORTEX_M0_LPC1114_LPCXpresso βΒ Β βΒ Β βββ CORTEX_M0_STM32F0518_IAR βΒ Β βΒ Β βββ CORTEX_M4_ATSAM4L_Atmel_Studio βΒ Β βΒ Β βββ CORTEX_M4_ATSAM4S_Atmel_Studio βΒ Β βΒ Β βββ CORTEX_M4F_ATSAM4E_Atmel_Studio βΒ Β βΒ Β βββ CORTEX_M4F_CEC1302_Keil_GCC βΒ Β βΒ Β βββ CORTEX_M4F_CEC1302_MikroC βΒ Β βΒ Β βββ CORTEX_M4F_Infineon_XMC4000_GCC_Dave βΒ Β βΒ Β βββ CORTEX_M4F_Infineon_XMC4000_IAR βΒ Β βΒ Β βββ CORTEX_M4F_Infineon_XMC4000_Keil βΒ Β βΒ Β βββ CORTEX_M4F_Infineon_XMC4000_Tasking βΒ Β βΒ Β βββ CORTEX_M4F_Infineon_XMC4500_GCC_Atollic βΒ Β βΒ Β βββ CORTEX_M4F_M0_LPC43xx_Keil βΒ Β βΒ Β βββ CORTEX_M4F_MSP432_LaunchPad_IAR_CCS_Keil βΒ Β βΒ Β βββ CORTEX_M4F_STM32F407ZG-SK βΒ Β βΒ Β βββ CORTEX_M7_SAME70_Xplained_AtmelStudio βΒ Β βΒ Β βββ CORTEX_M7_SAMV71_Xplained_AtmelStudio βΒ Β βΒ Β βββ CORTEX_M7_SAMV71_Xplained_IAR_Keil βΒ Β βΒ Β βββ CORTEX_M7_STM32F7_STM32756G-EVAL_IAR_Keil βΒ Β βΒ Β βββ CORTEX_MB9A310_IAR_Keil βΒ Β βΒ Β βββ CORTEX_MB9B500_IAR_Keil βΒ Β βΒ Β βββ CORTEX_MPU_LM3Sxxxx_Rowley βΒ Β βΒ Β βββ CORTEX_MPU_LPC1768_GCC_RedSuite βΒ Β βΒ Β βββ CORTEX_MPU_Simulator_Keil_GCC βΒ Β βΒ Β βββ CORTEX_R4F_RZ_T_GCC_IAR βΒ Β βΒ Β βββ CORTEX_R4_RM48_TMS570_CCS5 βΒ Β βΒ Β βββ CORTEX_R5_UltraScale_MPSoC βΒ Β βΒ Β βββ CORTEX_SmartFusion2_M2S050_SoftConsole βΒ Β βΒ Β βββ CORTEX_STM32F100_Atollic βΒ Β βΒ Β βββ CORTEX_STM32F103_GCC_Rowley βΒ Β βΒ Β βββ CORTEX_STM32F103_IAR βΒ Β βΒ Β βββ CORTEX_STM32F103_Keil βΒ Β βΒ Β βββ CORTEX_STM32F103_Primer_GCC βΒ Β βΒ Β βββ CORTEX_STM32F107_GCC_Rowley βΒ Β βΒ Β βββ CORTEX_STM32L152_Discovery_IAR βΒ Β βΒ Β βββ CORTEX_STM32L152_IAR βΒ Β βΒ Β βββ CORTUS_APS3_GCC βΒ Β βΒ Β βββ Cygnal βΒ Β βΒ Β βββ dsPIC_MPLAB βΒ Β βΒ Β βββ Flshlite βΒ Β βΒ Β βββ H8S βΒ Β βΒ Β βββ HCS12_CodeWarrior_banked βΒ Β βΒ Β βββ HCS12_CodeWarrior_small βΒ Β βΒ Β βββ HCS12_GCC_banked βΒ Β βΒ Β βββ IA32_flat_GCC_Galileo_Gen_2 βΒ Β βΒ Β βββ links_to_doc_pages_for_these_demos.url βΒ Β βΒ Β βββ lwIP_AVR32_UC3 βΒ Β βΒ Β βββ lwIP_Demo_Rowley_ARM7 βΒ Β βΒ Β βββ lwIP_MCF5235_GCC βΒ Β βΒ Β βββ MB91460_Softune βΒ Β βΒ Β βββ MB96340_Softune βΒ Β βΒ Β βββ MB96350_Softune_Dice_Kit βΒ Β βΒ Β βββ MCF5235_GCC βΒ Β βΒ Β βββ MicroBlaze_Kintex7_EthernetLite βΒ Β βΒ Β βββ MicroBlaze_Spartan-6_EthernetLite βΒ Β βΒ Β βββ msp430_CrossWorks βΒ Β βΒ Β βββ msp430_GCC βΒ Β βΒ Β βββ msp430_IAR βΒ Β βΒ Β βββ MSP430X_MSP430F5438_CCS βΒ Β βΒ Β βββ MSP430X_MSP430F5438_IAR βΒ Β βΒ Β βββ MSP430X_MSP430FR5969_LaunchPad_IAR_CCS βΒ Β βΒ Β βββ NEC_78K0R_IAR βΒ Β βΒ Β βββ NEC_V850ES_IAR βΒ Β βΒ Β βββ NiosII_CycloneIII_DBC3C40_GCC βΒ Β βΒ Β βββ PC βΒ Β βΒ Β βββ PIC18_MPLAB βΒ Β βΒ Β βββ PIC18_WizC βΒ Β βΒ Β βββ PIC24_MPLAB βΒ Β βΒ Β βββ PIC32MEC14xx_MPLAB βΒ Β βΒ Β βββ PIC32MX_MPLAB βΒ Β βΒ Β βββ PIC32MZ_MPLAB βΒ Β βΒ Β βββ PPC405_FPU_Xilinx_Virtex4_GCC βΒ Β βΒ Β βββ PPC405_Xilinx_Virtex4_GCC βΒ Β βΒ Β βββ PPC440_DP_FPU_Xilinx_Virtex5_GCC βΒ Β βΒ Β βββ PPC440_SP_FPU_Xilinx_Virtex5_GCC βΒ Β βΒ Β βββ PPC440_Xilinx_Virtex5_GCC βΒ Β βΒ Β βββ readme.txt βΒ Β βΒ Β βββ RL78_multiple_IAR βΒ Β βΒ Β βββ RL78_RL78G13_Promo_Board_IAR βΒ Β βΒ Β βββ RX100-RSK_GCC_e2studio βΒ Β βΒ Β βββ RX100-RSK_IAR βΒ Β βΒ Β βββ RX100-RSK_Renesas_e2studio βΒ Β βΒ Β βββ RX100_RX113-RSK_GCC_e2studio_IAR βΒ Β βΒ Β βββ RX100_RX113-RSK_Renesas_e2studio βΒ Β βΒ Β βββ RX200_RX210-RSK_Renesas βΒ Β βΒ Β βββ RX200_RX231-RSK_GCC_e2studio_IAR βΒ Β βΒ Β βββ RX200_RX231-RSK_Renesas_e2studio βΒ Β βΒ Β βββ RX600_RX62N-RDK_GNURX βΒ Β βΒ Β βββ RX600_RX62N-RDK_IAR βΒ Β βΒ Β βββ RX600_RX62N-RDK_Renesas βΒ Β βΒ Β βββ RX600_RX62N-RSK_GNURX βΒ Β βΒ Β βββ RX600_RX62N-RSK_IAR βΒ Β βΒ Β βββ RX600_RX62N-RSK_Renesas βΒ Β βΒ Β βββ RX600_RX630-RSK_Renesas βΒ Β βΒ Β βββ RX600_RX63N-RDK_Renesas βΒ Β βΒ Β βββ RX600_RX64M_RSK_GCC_e2studio βΒ Β βΒ Β βββ RX600_RX64M_RSK_Renesas_e2studio βΒ Β βΒ Β βββ RX700_RX71M_RSK_GCC_e2studio_IAR βΒ Β βΒ Β βββ RX700_RX71M_RSK_Renesas_e2studio βΒ Β βΒ Β βββ SuperH_SH7216_Renesas βΒ Β βΒ Β βββ TriCore_TC1782_TriBoard_GCC βΒ Β βΒ Β βββ uIP_Demo_IAR_ARM7 βΒ Β βΒ Β βββ uIP_Demo_Rowley_ARM7 βΒ Β βΒ Β βββ Unsupported_Demos βΒ Β βΒ Β βββ WIN32-MingW βΒ Β βΒ Β βββ WIN32-MSVC βΒ Β βΒ Β βββ WIN32-MSVC-Static-Allocation-Only βΒ Β βΒ Β βββ WizNET_DEMO_GCC_ARM7 βΒ Β βΒ Β βββ WizNET_DEMO_TERN_186 βΒ Β βΒ Β βββ Xilinx_FreeRTOS_BSP βΒ Β βββ License βΒ Β βΒ Β βββ license.txt βΒ Β βββ links_to_doc_pages_for_the_demo_projects.url βΒ Β βββ readme.txt βΒ Β βββ Source βΒ Β βββ croutine.c βΒ Β βββ event_groups.c βΒ Β βββ include βΒ Β βββ list.c βΒ Β βββ portable βΒ Β βββ queue.c βΒ Β βββ readme.txt βΒ Β βββ tasks.c βΒ Β βββ timers.c βββ FreeRTOS-Plus βΒ Β βββ Demo βΒ Β βΒ Β βββ Also_See_FreeRTOS+TCP_and_FreeRTOS_FAT_in_the_lab.url βΒ Β βΒ Β βββ Common βΒ Β βΒ Β βββ FreeRTOS_Plus_CLI_with_Trace_Windows_Simulator βΒ Β βΒ Β βββ FreeRTOS_Plus_FAT_SL_and_CLI_Windows_Simulator βΒ Β βΒ Β βββ FreeRTOS_Plus_Reliance_Edge_and_CLI_Windows_Simulator βΒ Β βΒ Β βββ FreeRTOS_Plus_UDP_and_CLI_LPC1830_GCC βΒ Β βΒ Β βββ FreeRTOS_Plus_UDP_and_CLI_Windows_Simulator βΒ Β βΒ Β βββ FreeRTOS_Plus_UDP_CLI_FAT_SL_SAM4E_Atmel_Studio βΒ Β βΒ Β βββ FreeRTOS_Plus_WolfSSL_Windows_Simulator βΒ Β βΒ Β βββ readme.txt βΒ Β βββ readme.txt βΒ Β βββ Source βΒ Β βββ FreeRTOS-Plus-CLI βΒ Β βββ FreeRTOS-Plus-FAT-SL βΒ Β βββ FreeRTOS-Plus-IO βΒ Β βββ FreeRTOS-Plus-Nabto βΒ Β βββ FreeRTOS-Plus-Trace βΒ Β βββ FreeRTOS-Plus-Trace(streaming) βΒ Β βββ FreeRTOS-Plus-UDP βΒ Β βββ readme.txt βΒ Β βββ Reliance-Edge βΒ Β βββ WebDocs.url βΒ Β βββ WolfSSL βββ New - Direct to Task Notifications.url βββ New - FreeRTOS+TCP.url βββ Quick_Start_Guide.url βββ readme.txt βββ Upgrading-to-FreeRTOS-9.url |
FreeRTOS-Plus directory and demos can be totally left out of scope (it is handy to look at the demos – to check if the particulr Cortex type is supported). All important parts are in FreeRTOS directory.
FreeRTOS uses basically two kinds of source files:
- portable – common to all chips and architectures
- port files – specific to the target chip (ideally just two files)
All portable .c and .h files from FreeRTOS/Source have to be added to the project (don’t add anything from the “portable” directory yet – naming is a little confusing as the “portable” directory contains files that are CPU-specific).
Finding the right port for your CPU
While the portable files are written in plain C and are easy to follow, the port itself is written in assembly and essentially does low-level black magic (from C programmer’s point of view). Fortunately the port is dependent only on the CPU core and not on the whole microcontroller, so for example if MCU with Cortex M0+ from Atmel has a port, then it can also be used with an MCU from ST or Silicon Labs that has the same core. Port files should match the core (example: Cortex M4F is different than Cortex M4, but Cortex M0 and M0+ use the same code – YMMV).
In this particular case: NXP Kinetis KE06 has a Cortex M0+ and the port files are in FreeRTOS/Source/portable/GCC/ARM_CM0.
Portable and port files have to be added to Kinetis project.
That is how the project may look like:
FreeRTOSConfig.h
This is the only file that has to be customized for your project. I have taken one from the CORTEX_M0+_Atmel_SAMD20_XPlained demo (as it matches the core type of my MCU).
First thing to specify is the CPU clock speed: #include <asf.h> has to be removed (it is an Atmel library) and instead MKE06Z4.h has to be included. The configCPU_CLOCK_HZ macro has to be defined simply as DEFAULT_SYSTEM_CLOCK.
Second, a bunch of options (in my case) have to be tweaked. My example uses only static memory allocation.
- configUSE_TICK_HOOK is zero
- configGENERATE_RUN_TIME_STATS is zero (and accompanying vMainConfigureTimerForRunTimeStats, ulMainGetRunTimeCounterValue, portCONFIGURE_TIMER_FOR_RUN_TIME_STATS, portGET_RUN_TIME_COUNTER_VALUE next to it can be removed)
- configUSE_TIMERS is 0
- configCHECK_FOR_STACK_OVERFLOW is zero (we will get back to it later)
- remote configTOTAL_HEAP_SIZE (we will be using only static memory allocation)
- add #define configSUPPORT_STATIC_ALLOCATION 1
- add #define configSUPPORT_DYNAMIC_ALLOCATION 0
- change configMINIMAL_STACK_SIZE to 128 (it is CPU-native words, in case of this ARM: uint32_t)
At this point the project will not build yet.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
#ifndef FREERTOS_CONFIG_H #ifndef FREERTOS_CONFIG_H #define FREERTOS_CONFIG_H /*----------------------------------------------------------- * Application specific definitions. * * These definitions should be adjusted for your particular hardware and * application requirements. * * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE. * * See http://www.freertos.org/a00110.html. *----------------------------------------------------------*/ #include <MKE06Z4.h> #define configUSE_PREEMPTION 1 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 #define configCPU_CLOCK_HZ ( DEFAULT_SYSTEM_CLOCK ) #define configTICK_RATE_HZ ( ( TickType_t ) 500 ) #define configMAX_PRIORITIES ( 5 ) #define configMINIMAL_STACK_SIZE ( 128 ) #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 8192 ) ) #define configMAX_TASK_NAME_LEN ( 5 ) #define configUSE_TRACE_FACILITY 1 #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 1 #define configUSE_MUTEXES 1 #define configQUEUE_REGISTRY_SIZE 8 #define configCHECK_FOR_STACK_OVERFLOW 0 #define configUSE_RECURSIVE_MUTEXES 1 #define configUSE_MALLOC_FAILED_HOOK 1 #define configUSE_APPLICATION_TASK_TAG 0 #define configUSE_COUNTING_SEMAPHORES 1 #define configUSE_QUEUE_SETS 1 #define configSUPPORT_STATIC_ALLOCATION 1 #define configSUPPORT_DYNAMIC_ALLOCATION 0 /* Run time stats related definitions. */ #define configGENERATE_RUN_TIME_STATS 0 /* Co-routine definitions. */ #define configUSE_CO_ROUTINES 0 #define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) /* Software timer definitions. */ #define configUSE_TIMERS 0 #define configTIMER_TASK_PRIORITY ( 2 ) #define configTIMER_QUEUE_LENGTH 5 #define configTIMER_TASK_STACK_DEPTH ( 80 ) /* Set the following definitions to 1 to include the API function, or zero to exclude the API function. */ #define INCLUDE_vTaskPrioritySet 1 #define INCLUDE_uxTaskPriorityGet 1 #define INCLUDE_vTaskDelete 1 #define INCLUDE_vTaskCleanUpResources 1 #define INCLUDE_vTaskSuspend 1 #define INCLUDE_vTaskDelayUntil 1 #define INCLUDE_vTaskDelay 1 #define INCLUDE_eTaskGetState 1 /* This demo makes use of one or more example stats formatting functions. These format the raw data provided by the uxTaskGetSystemState() function in to human readable ASCII form. See the notes in the implementation of vTaskList() within FreeRTOS/Source/tasks.c for limitations. */ #define configUSE_STATS_FORMATTING_FUNCTIONS 1 /* Normal assert() semantics without relying on the provision of an assert.h header file. */ #define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); } /* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS standard names - or at least those used in the unmodified vector table. */ #define xPortPendSVHandler PendSV_Handler #define xPortSysTickHandler SysTick_Handler /* The size of the global output buffer that is available for use when there are multiple command interpreters running at once (for example, one on a UART and one on TCP/IP). This is done to prevent an output buffer being defined by each implementation - which would waste RAM. In this case, there is only one command interpreter running. */ #define configCOMMAND_INT_MAX_OUTPUT_SIZE 2048 #endif /* FREERTOS_CONFIG_H */ |
main.c
FreeRTOS requires one task to be always ready to execute (not blocked) – this is the idle task. Idle task (as every task) needs its own stack. Because we are using static memory allocation, vApplicationGetIdleTaskMemory has to be implemented. Static stacks for tasks are simply regular arrays. Very basic main.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include "MKE06Z4.h" #include "FreeRTOS/FreeRTOS.h" #include "FreeRTOS/task.h" static StaticTask_t xIdleTaskTCBBuffer; static StackType_t xIdleStack[configMINIMAL_STACK_SIZE]; void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize); void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize) { *ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer; *ppxIdleTaskStackBuffer = xIdleStack; *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; } int main(void) { vTaskStartScheduler(); //this never returns return 0; } |
At this point the project should build cleanly and can be flashed to the microcontroller. It does totally nothing (not even blink a LED!), but runs and should not crash (inspect by pausing and restarting with the debugger). Impressive! π
Blinking a LED the RTOS way
To get anything done at least one task has to be specified. It must have an endless loop and can’t return (however it can terminate itself via FreeRTOS API call). Of course a separate stack has to be allocated for the blink task. Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
#include "MKE06Z4.h" #include "FreeRTOS/FreeRTOS.h" #include "FreeRTOS/task.h" /* global variables */ static StaticTask_t xIdleTaskTCBBuffer; static StackType_t xIdleStack[configMINIMAL_STACK_SIZE]; static StackType_t blink_red_task_stack[configMINIMAL_STACK_SIZE]; static StaticTask_t blink_red_task_handle; /* prototypes */ void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize); void blink_red_task(void *params); /* implementation */ void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize) { *ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer; *ppxIdleTaskStackBuffer = xIdleStack; *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; } int main(void){ /* Initialize GPIO for LEDs */ GPIOB_PDDR |= PORT_PUE1_PTGPE5_MASK | PORT_PUE1_PTGPE6_MASK | PORT_PUE1_PTGPE7_MASK;/*set as outputs*/\ GPIOB_PSOR = PORT_PUE1_PTGPE5_MASK | PORT_PUE1_PTGPE6_MASK | PORT_PUE1_PTGPE7_MASK;/*all leds off*/\ xTaskCreateStatic(blink_red_task, //task function "blink red task", //task logical name configMINIMAL_STACK_SIZE, //size of stack NULL, //no extra parameters 1, //priority (0 is lowest - idle task) blink_red_task_stack, //stack pointer &blink_red_task_handle); //pointer to StaticTask_t task handle vTaskStartScheduler(); //this never returns return 0; } void blink_red_task(void *params){ while (1){ GPIOB_PCOR = PORT_PUE1_PTGPE5_MASK; //red LED on vTaskDelay(pdMS_TO_TICKS(500)); //sleep 500ms GPIOB_PSOR = PORT_PUE1_PTGPE5_MASK; //red LED off vTaskDelay(pdMS_TO_TICKS(500)); //sleep 500ms } } |
Blinking two LEDs
What is better than a blinking LED? Two blinking LEDs!
This example has two tasks that blink separate LEDs with different timings, so it is easy to observe that both loops are executed “at the same time” (in reality – chopped and executed alternatively), which is the main point of an operating system. π
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
#include "MKE06Z4.h" #include "FreeRTOS/FreeRTOS.h" #include "FreeRTOS/task.h" /* global variables */ static StaticTask_t xIdleTaskTCBBuffer; static StackType_t xIdleStack[configMINIMAL_STACK_SIZE]; static StackType_t blink_red_task_stack[configMINIMAL_STACK_SIZE]; static StaticTask_t blink_red_task_handle; static StackType_t blink_blue_task_stack[configMINIMAL_STACK_SIZE]; static StaticTask_t blink_blue_task_handle; /* prototypes */ void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize); void blink_red_task(void *params); void blink_blue_task(void *params); /* implementation */ void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize) { *ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer; *ppxIdleTaskStackBuffer = xIdleStack; *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; } int main(void) { /* Initialize GPIO for LEDs */ GPIOB_PDDR |= PORT_PUE1_PTGPE5_MASK | PORT_PUE1_PTGPE6_MASK | PORT_PUE1_PTGPE7_MASK;/*set as outputs*/\ GPIOB_PSOR = PORT_PUE1_PTGPE5_MASK | PORT_PUE1_PTGPE6_MASK | PORT_PUE1_PTGPE7_MASK;/*all leds off*/\ xTaskCreateStatic(blink_red_task, //task function "blink red task", //task logical name configMINIMAL_STACK_SIZE, //size of stack NULL, //no extra parameters 1, //priority (0 is lowest - idle task) blink_red_task_stack, //stack pointer &blink_red_task_handle); //pointer to StaticTask_t task handle xTaskCreateStatic(blink_blue_task, //task function "blink blue task", //task logical name configMINIMAL_STACK_SIZE, //size of stack NULL, //no extra parameters 1, //priority (0 is lowest - idle task) blink_blue_task_stack, //stack pointer &blink_blue_task_handle); //pointer to StaticTask_t task handle vTaskStartScheduler(); //this never returns return 0; } void blink_red_task(void *params){ while (1){ GPIOB_PCOR = PORT_PUE1_PTGPE5_MASK; //red LED on vTaskDelay(pdMS_TO_TICKS(500)); //sleep 500ms GPIOB_PSOR = PORT_PUE1_PTGPE5_MASK; //red LED off vTaskDelay(pdMS_TO_TICKS(500)); //sleep 500ms } } void blink_blue_task(void *params){ while (1){ GPIOB_PCOR = PORT_PUE1_PTGPE7_MASK; //blue LED on vTaskDelay(pdMS_TO_TICKS(100)); //sleep 100ms GPIOB_PSOR = PORT_PUE1_PTGPE7_MASK; //blue LED off vTaskDelay(pdMS_TO_TICKS(150)); //sleep 150ms } } |
KinetisE FreeRTOS KDS project files
Common issues and what to do next
Stack size unit
Remember that the stack size everywhere in FreeRTOS is specified in words, for a 32-bit ARM that is 32 bits (so stack size 1 = 4 bytes).
Preemptivness
Even though FreeRTOS is advertized as supporting preemptive multitasking, the meaning of “preemptive” is very different to what it means in “full” operating systems (Linux/Windows/etc.). Preemptive multitasking on a PC means that each task will get its “fair share” of time to execute – task switch no matter what. On FreeRTOS it means that a task will run until another task with a higher priority is ready. In practice if two tasks with identical priority have endless loops – only one of them will effectively run, the other will starve. Tasks must regularly call vTaskDelay to give CPU time to each other.
Stack overflow handling
Memory management (especially with multiple stacks) is an issue in all resource-constrained operating systems. Stack overflows can manifest themselves as “random data corruption somewhere” (stack has “hit” other variables above and changed their values). Fortunately FreeRTOS supports stack instrumentation. configCHECK_FOR_STACK_OVERFLOW has to be defined (to 1 or 2) in FreeRTOSConfig.h to enable vApplicationStackOverflowHook. This function has to be defined somewhere and it is up to the developer to do something about the problem.
Simple example that I use during development:
1 2 3 |
void vApplicationStackOverflowHook(TaskHandle_t xTask __attribute__((unused)), signed char *pcTaskName __attribute__((unused))){ asm("bkpt #1"); } |
When a stack overflow happens this function simply calls a breakpoint. With a debugger attached the CPU will stop, without a debugger connected the breakpoint instruction will trigger system reset. In production grade code I would add storage of some diagnostic information, maybe a controlled system shutdown/cleanup and restart. When any of the stacks overflows and the microcontroller has no MPU or MMU – global data and program state can not be trusted anymore (global data could be corrupted and hold literally anything), so a system reset is mandatory.
Hardware sharing between tasks
is difficult. Whenever accessing global data (hardware registers can be regarded as such) you have to assume that an interrupt (or task switch) can happen at any time. All read-modify-write operations can be interrupted. For example GPIO initilization in sample code would not be safe to carry out in two separate tasks. Simplest solution is a critical section – disabling interrupts for a short while. Another approach is to use mutexes or design a bigger driver that can handle multiple tasks, like an SPI bus driver that supports two devices (eg. flash and accelerometer) on a single bus that can be used by two tasks – the driver must take care to properly multiplex access to the shared bus.
What to do when there is nothing to do – idle hook
What does FreeRTOS do when all tasks are blocked and are waiting for something? It calls the idle hook!
Idle hook is enabled by defining configUSE_IDLE_HOOK in the configuration file. It is a regular function that will be called whenever there is nothing better to do (so it can also be never called, depending on the circumstances). Typical usage: put the CPU into sleep mode and/or kick the watchdog (to guarantee that CPU utilization is below 100%):
1 2 3 4 5 |
void vApplicationIdleHook(void){ watchdog_kick(); //do-it-yourself __WFI(); //CPU core sleep } |
CPU will sleep until next interrupt happens (either systick or another peripheral). This will reduce total power consumption.
Crashes and hard faults
Most of the faults will be caused by stack management or too small stack sizes. It is also important to give proper (~1KB) stack to the idle task (it also does some FreeRTOS housekeeping). When something bad happens it may be hard to figure out which task caused the problem – my way of debugging is to disable/enable tasks or pieces of code until I can find the tipping point that leads to a crash.