1. First FreeRTOS project


 

FreeRTOS tutorials have been tested and illustrated using Atollic TrueStudio® for STM32 IDE. You may refer to above tutorials for more details on installing and using Atollic IDE.

 

st_atollic_logo.png       freertos_logo.png

 

1. The foundation : A clean bare-metal working project

 
Start a new empty C Project and copy a minimal set of source files in order to have a basic (e.g. 'blink') application. Basically you may want a minimal set of BSP functions for working with LED, Push-button, and debug console (printf). Let us start with no interrupts, no DMA, ... You may refer to STM32 tutorials above to setup such a starting project.

image_001.png
 
Review the bluid configuration build_config_btn.png  to make sure that everything is set correctly. Go to C/C++ Build → Settings section:
 
image_002.png
 
Then check:
 
  • The Target Settings:
     
    image_003.png

    The above operation may ask you to create a new linker script file. You can accept this, and save the linker script in the project directory. Then make sure that the linker script is correctly set the linker settings.
     

  • Tool Settings → C Linker → General
     
    image_004.png
     
  • You need to define the correct pre-processor symbol in order to include the rigth STM32 header. In Tool Settings → C Compiler → Symbols
     
    image_005.png
    Use the above buttons to add /remove or editinclude symbols.
     
  • You also need to check that all include paths are defined in Tool Settings → C Compiler → Directories
     
    image_006.png

    Use the above buttons to add /remove or editinclude paths.

 

Apply all this settings, and then return to the main window.

 
Now, edit main.c with a very minimal code:
/*
 * main.c
 *
 *  Created on: 24/02/2018
 *      Author: Laurent
 */

#include "stm32f0xx.h"
#include "bsp.h"
#include "main.h"

// Static functions
static void SystemClock_Config	(void);


// Main program
int main()
{
	uint32_t i;

	// Configure System Clock
	SystemClock_Config();

	// Initialize LED pin
	BSP_LED_Init();

	// Main loop
	while(1)
	{
		BSP_LED_Toggle();
		for(i=0; i<1000000; i++);
	}
}


/*
 * 	Clock configuration for the Nucleo STM32F072RB board
 * 	HSE input Bypass Mode	-> 8MHz
 * 	SYSCLK, AHB, APB1 	-> 48MHz
 */

static void SystemClock_Config()
{
	uint32_t	HSE_Status;
	uint32_t	PLL_Status;
	uint32_t	SW_Status;
	uint32_t	timeout = 0;

	timeout = 1000000;

	// Start HSE in Bypass Mode
	RCC->CR |= RCC_CR_HSEBYP;
	RCC->CR |= RCC_CR_HSEON;

	// Wait until HSE is ready
	do
	{
		HSE_Status = RCC->CR & RCC_CR_HSERDY_Msk;
		timeout--;
	} while ((HSE_Status == 0) && (timeout > 0));

	// Select HSE as PLL input source
	RCC->CFGR &= ~RCC_CFGR_PLLSRC_Msk;
	RCC->CFGR |= (0x02 <<RCC_CFGR_PLLSRC_Pos);

	// Set PLL PREDIV to /1
	RCC->CFGR2 = 0x00000000;

	// Set PLL MUL to x6
	RCC->CFGR &= ~RCC_CFGR_PLLMUL_Msk;
	RCC->CFGR |= (0x04 <<RCC_CFGR_PLLMUL_Pos);

	// Enable the main PLL
	RCC-> CR |= RCC_CR_PLLON;

	// Wait until PLL is ready
	do
	{
		PLL_Status = RCC->CR & RCC_CR_PLLRDY_Msk;
		timeout--;
	} while ((PLL_Status == 0) && (timeout > 0));

	// Set AHB prescaler to /1
	RCC->CFGR &= ~RCC_CFGR_HPRE_Msk;
	RCC->CFGR |= RCC_CFGR_HPRE_DIV1;

	//Set APB1 prescaler to /1
	RCC->CFGR &= ~RCC_CFGR_PPRE_Msk;
	RCC->CFGR |= RCC_CFGR_PPRE_DIV1;

	// Enable FLASH Prefetch Buffer and set Flash Latency (required for high speed)
	FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;

	/* --- Until this point, MCU was still clocked by HSI at 8MHz ---*/
	/* --- Switching to PLL at 48MHz Now!  Fasten your seat belt! ---*/

	// Select the main PLL as system clock source
	RCC->CFGR &= ~RCC_CFGR_SW;
	RCC->CFGR |= RCC_CFGR_SW_PLL;

	// Wait until PLL becomes main switch input
	do
	{
		SW_Status = (RCC->CFGR & RCC_CFGR_SWS_Msk);
		timeout--;
	} while ((SW_Status != RCC_CFGR_SWS_PLL) && (timeout > 0));

	/* --- Here we go ! ---*/

	/*--- Use PA8 as MCO output at 48/16 = 3MHz ---*/

	// Set MCO source as SYSCLK (48MHz)
	RCC->CFGR &= ~RCC_CFGR_MCO_Msk;
	RCC->CFGR |=  RCC_CFGR_MCOSEL_SYSCLK;

	// Set MCO prescaler to /16 -> 3MHz
	RCC->CFGR &= ~RCC_CFGR_MCOPRE_Msk;
	RCC->CFGR |=  RCC_CFGR_MCOPRE_DIV16;

	// Enable GPIOA clock
	RCC->AHBENR |= RCC_AHBENR_GPIOAEN;

	// Configure PA8 as Alternate function
	GPIOA->MODER &= ~GPIO_MODER_MODER8_Msk;
	GPIOA->MODER |= (0x02 <<GPIO_MODER_MODER8_Pos);

	// Set to AF0 (MCO output)
	GPIOA->AFR[1] &= ~(0x0000000F);
	GPIOA->AFR[1] |=  (0x00000000);

	// Update SystemCoreClock global variable
	SystemCoreClockUpdate();
}


 

Clean project from any previous build then rebuild all . Repair any error or warning, what we want is a perfectly clean base for further FreeRTOS integration:

image_007.png
 
 
Next, open the debug configuration window. Then create a new debug profile into the Embedded C/C++ Application category:
 
image_008.png
 
 
Review settings into the Main tab:
 
image_009.png
 
 
Into the Debugger tab, make sure that Debug probe is set to ST-Link, with SWD interface.
 
image_010.png
 
Now you should be ready to launch the Debug session. You can either directly click the Debug button, or close the debug configuration window and click in the main toolbar.
 
If the debugger starts correctly, IDE should switch to the debug perspective and the debugger is waiting at the beginning of main() breakpoint:
 
image_011.png
 
From there, you can play with debugger commands as usually (, , , , , ...). Using the Expressions view Make sure that SystemCoreClock global variable switches from 8MHz to 48MHz when stepping over the SystemClock_Config() function, and then run   the program.
 
You should get the famous blinking LED... Well done !
 
image_013.png
 
 

2. Getting FreeRTOS source files into the project

At the time of writing this tutorial, latest FreeRTOS release is version 10.2.1. Get the FreeRTOS source archive from the web (https://www.freertos.org), or school repository and copy it somewhere safe on your computer.

Take some time to browse the FreeRTOS archive and locate the \Source folder:

image_014.png

Copy\Paste the \Source folder into your project directory, beside \app, \bsp and \cmis folders. Then rename it '\FreeRTOS'.

image_015.png
 
Then delete both readme.txt  and stdint.readme files from \FreeRTOS and \FreeRTOS\include folders respeclively.
 
In the \FreeRTOS\portable directory delete all the folders, exept :
  • \GCC
  • \MemMang
 
In the \FreeRTOS\portable\GCC directory, delete all the folders exept:
  • ARM_CM0

In the \FreeRTOS\portable\MemMang directory, delete all files exept:

  • heap_1.c

 

Finally, browse the FreeRTOS archive, and try to find in the \Demo folder something that merely corresponds to your target device (STM32F072) and toolchain (Atollic). Then Copy/Paste the FreeRTOSConfig.h file from there into your app\inc folder.

For now, a good candidate is for instance in \FreeRTOSv10.2.1\FreeRTOS\Demo\CORTEX_STM32F100_Atollic\Simple_Demo_Source.

 

Once you're done with all this cleaning, you project explorer should look like this:

image_016.png
 
Add the two new header include paths into your build settings and apply changes.
image_017.png

Then open the FreeRTOSConfig.h file and edit as follows :

First, delete (or comment) these lines:

/* The following #error directive is to remind users that a batch file must be
 * executed prior to this project being built.  The batch file *cannot* be 
 * executed from within CCS4!  Once it has been executed, re-open or refresh 
 * the CCS4 project and remove the #error line below.
 */
#error Ensure CreateProjectDirectoryStructure.bat has been executed before building.  See comment immediately above.
 
Second, tune the FreeRTOS configuration as follows:
/*-----------------------------------------------------------
 * 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.
 *----------------------------------------------------------*/

#define configUSE_PREEMPTION			1
#define configUSE_IDLE_HOOK			0
#define configUSE_TICK_HOOK			0
#define configCPU_CLOCK_HZ			( 48000000UL )	
#define configTICK_RATE_HZ			( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES			( 5 )
#define configMINIMAL_STACK_SIZE		( ( unsigned short ) 70 )
#define configTOTAL_HEAP_SIZE			( ( size_t ) ( 7 * 1024 ) )
#define configMAX_TASK_NAME_LEN			( 10 )
#define configUSE_TRACE_FACILITY		0
#define configUSE_16_BIT_TICKS			0
#define configIDLE_SHOULD_YIELD			1
#define configUSE_MUTEXES			1
#define configQUEUE_REGISTRY_SIZE		0
#define configGENERATE_RUN_TIME_STATS	        0
#define configCHECK_FOR_STACK_OVERFLOW	        0
#define configUSE_RECURSIVE_MUTEXES		0
#define configUSE_MALLOC_FAILED_HOOK	        0
#define configUSE_APPLICATION_TASK_TAG	        0
#define configUSE_COUNTING_SEMAPHORES	        0

 

Finally, open stm32f0xx_it.c and comment the 3 interrupt handlers implementations, as FreeRTOS needs its own definition of these:

  • SVC_Handler()
  • PendSV_Handler()
  • SysTick_Handler()
/**
  * @brief  This function handles SVCall exception.
  * @param  None
  * @retval None
  */
//void SVC_Handler(void)
//{
//}

/**
  * @brief  This function handles PendSVC exception.
  * @param  None
  * @retval None
  */
//void PendSV_Handler(void)
//{
//}

/**
  * @brief  This function handles SysTick Handler.
  * @param  None
  * @retval None
  */
//void SysTick_Handler(void)
//{
//  // HAL_IncTick();
//}

 

Save all the project files, clean the project and rebuild all. Hopefully, you'll get a clean build report with no error or warnings:

Generate build reports...
Print size information
   text	   data	    bss	    dec	    hex	filename
  20628	     20	   7652	  28300	   6e8c	nucleo_64_F072_FreeRTOS.elf
Print size information done
Generate listing file
Output sent to: nucleo_64_F072_FreeRTOS.list
Generate listing file done
Generate build reports done

12:49:45 Build Finished (took 3s.503ms)

 

3. Your first FreeRTOS application

Let-us first include FreeRTOS headers into the project. We will take this opportunity to regroup all the headers in main.h as follows:

/*
 * main.h
 *
 *  Created on: 24/02/2018
 *      Author: Laurent
 */

#ifndef APP_INC_MAIN_H_
#define APP_INC_MAIN_H_

// Device header
#include "stm32f0xx.h"

// BSP functions
#include "bsp.h"

// FreeRTOS headers
#include "FreeRTOSConfig.h"
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include "semphr.h"
#include "queue.h"
#include "event_groups.h"
#include "stream_buffer.h"

// Global functions
int my_printf	(const char *format, ...);
int my_sprintf	(char *out, const char *format, ...);

#endif /* APP_INC_MAIN_H_ */

Doing so, you just have to include main.h in main.c and all the required functions will be available:

/*
 * main.c
 *
 *  Created on: 24/02/2018
 *      Author: Laurent
 */

#include "main.h"

// Static functions
static void SystemClock_Config	(void);

Next, still in main.c, let us prototype two global functions that we will use for the implementation of two so-called 'tasks' in the FreeRTOS vocabulary:

// FreeRTOS tasks
void vTask1 	(void *pvParameters);
void vTask2 	(void *pvParameters);

And now, let us write the main() function wich basically:

  • Performs MCU init, including clock settings and some low-level peripheral configurations
  • Create tasks as OS kernel objects, providing a name, a given amount of RAM and a priotity level
  • Start the OS scheduler
// Main program
int main()
{
	// Configure System Clock
	SystemClock_Config();

	// Initialize LED pin
	BSP_LED_Init();

	// Initialize Debug Console
	BSP_Console_Init();

	// Create Tasks
	xTaskCreate(vTask1, "Task_1", 256, NULL, 1, NULL);
	xTaskCreate(vTask2, "Task_2", 256, NULL, 2, NULL);

	// Start the Scheduler
	vTaskStartScheduler();

	while(1)
	{
		// The program should never be here...
	}
}

In a common FreeRTOS application, the vTaskStartScheduler() function never returns. It starts the OS scheduler that manages tasks execution. Any code below this function call will never execute if everything goes well. The while(1) loop is placed here to catch abnormal behavior in the debugger. If you get there, there's something wrong...

 

And finally, the implementation of each task. In this very first example, vTask1() simply toggles the LED every 300ms, while vTask2() outputs a console message every 1s. For simplicity, you can put the following implementation right below the main() function within main.c:

/*
 *	Task1 toggles LED every 300ms
 */
void vTask1 (void *pvParameters)
{
	while(1)
	{
		BSP_LED_Toggle();
		vTaskDelay(300);
	}
}


/*
 *	Task2 sends a message to console every 1s
 */
void vTask2 (void *pvParameters)
{
	uint16_t count;

	count = 0;

	while(1)
	{
		my_printf("Hello %2d from task2\r\n", count);
		count++;
		vTaskDelay(1000);
	}
}

 

Save all and rebuild the project. You should have no error or warning. Open a Putty serial session using correct COM number and baudrate. Then start the debugger and run the program.

You should see both the LED blinking, and the console displaying:

vTask1() vTask2()
image_013.png
image_018.png
 
Congratulations, you've got your first FreeRTOS multi-tasking application up and running!
 
Note that vTask1() and vTask2() have two things in common, that you may consider as primary rules, working in most cases, for ANY task placed under RTOS control:
  1. Both are implemtended just like a small main() function with :
  • local variables declaration
  • variable and peripheral initializations
  • a while(1) never ending loop, that actually performs the desired task operations
     
  1. Inside of the never ending loop, there MUST BE a waiting mechanism. This is something telling the sheduler that the task is not busy for a while. If it's not there, other tasks of lower priority level will never run. That's the purpose of the vTaskDelay() function. We will see later that many other waiting mechanisms may be involved.

 

4. Summary

In this tutorial, you have learned how to create a FreeRTOS template project, and wrote two very simple tasks to verify that everything is actually working as expected.