M0AGX / LB9MG

Amateur radio and embedded systems

FreeRTOS on Kinetis E Cortex M0+ : easy porting tutorial

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).

FRDM KE06 PCB

Creating a new KDS project

Let's begin by creating blank project targeted for Kinetis KE06Z. Don't enable Processor Expert.

Default project structure

empty 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

cleaned up project structure

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: project structure with FreeRTOS

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 

#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
    }
}

blinky led animation

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
    }
}

two blinky leds animation

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).

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.