FreeRTOS on STM32

High density line of STM32 microcontrollers have quite a bunch on features that can be used in user programs. The more features you add to source the more complicated program becomes and this way it starts to be difficult to keep up with all things. Using only main loop and interrupts becomes time consuming task to manage. If you don’t want to struggle in tuning things up manually you can use one of many real time operating systems (RTOS). They are great when you need lots of separate functions to run in parallel so no task would be missed. RTOS scheduler takes care of giving each task a decent time to perform. There are lots of great RTOS systems around. Many of them are free and opensource.

It happens so that I love using FreeRTOS which has quite long history and is flexible enough to fit multiple types of hardware. You can check out my recent demo on Atmega128. I encourage you to give a try to other RTOS systems like ChibiOS, BeRTOS, and many more. But lets stick with FreeRTOS. Simply speaking FreeRTOS is quite simple and easy to use. It has practically most of features you’d look for in RTOS. Some of key features would include preemptive, cooperative and hybrid scheduler, task and co-routine support, queues, semaphores and mutexes for task synchronisation and communication. Many demos, many ports to to get started with.

We’ve done some demos for our STM32F103ZET6 board that include LEDs, Buttons, USART, and LCD. All we did was wrote some code that main loop and generated interrupts for some events. This is efficient and OK since program isn’t complicated. But if you plan your program to be big, start building it with RTOS to keep it smooth til the end.

Simply we are going to do these previous tasks but with scheduler.

First of all we need to build our template that include FreeRTOS source files. We just import FreeRTOS folder to our project tree.

Then we have to add FreeRTOSConfig.h file to project where all RTOS configurations are set. Next thing is to match interrupts handlers for systick, SVC and PendSV interrupts. They are used by scheduler and have a bit different nomenclature. If you look at port.c file you’ll see that it uses exception handlers:

voidxPortPendSVHandler( void ) __attribute__ (( naked ));

voidxPortSysTickHandler( void );

voidvPortSVCHandler( void ) __attribute__ (( naked ));


All we have to place them instead of

void SVC_Handler (void) __attribute__((weak));

void PendSV_Handler (void) __attribute__((weak));

void SysTick_Handler (void) __attribute__((weak));


In our vector table where start-up code residues.

Before we start writing code lets configure RTOS. To do so we need to edit FreeRTOSConfig.h file contents. There are lots of them but most important are following:

#define configUSE_PREEMPTION 1

#define configCPU_CLOCK_HZ ( ( unsignedlong ) 72000000 )

#define configTICK_RATE_HZ ( ( portTickType ) 1000 )

#define configMAX_PRIORITIES ( ( unsigned portBASE_TYPE ) 5 )

#define configMINIMAL_STACK_SIZE ( ( unsignedshort ) 120 )

#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 18 * 1024 ) )

#define configMAX_TASK_NAME_LEN ( 16 )

#define configUSE_TRACE_FACILITY 1

#define configIDLE_SHOULD_YIELD 1

#define configUSE_MUTEXES 1

#define configUSE_COUNTING_SEMAPHORES 1

#define INCLUDE_vTaskPrioritySet 1

#define INCLUDE_vTaskDelayUntil 1

#define INCLUDE_vTaskDelay 1

just a quick overview of these. We are going to use preemption so we set it to 1, then we select cpu clock rate which is 72MHz, also we configure tick timer what means that scheduler will run every 1ms.

Then we select minimal stack size for a task. Also set total heap size.

We are going to use task priorities so we set vTaskPrioritySet to 1. Also we are going to use vTaskDelay utilities that help with task timing. So we select them to. There are lot more settings youll find in Config file. Many of them are self explanatory but be sure to check their meaning before using as setting one or another may lead to significant increase of ram or CPU usage.

Lets proceed to some code. If you are familiar with RTOS, then you know that program writen for RTOS is organized as set of independent tasks. So each tasks simply shouldn’t rely on other tasks and run withing its own context. Practically speaking task is a function with its own stack. If multiple tasks are created, scheduler switches between these tasks according to priorities set. Task itself is a function with endless loop and function should never return from it:

    void vATaskFunction( void *pvParameters )
    {
        for( ;; )
        {
            -- Task application code here. --
        }
    }

I put all my tasks in separate source file mytasks.c (and mytasks.h). Ok lets start with LED flasher. This is simple routine which flashes LED every 1s. This is how tasks look like:

void vLEDFlashTask( void *pvParameters )
{
  portTickType xLastWakeTime;
  const portTickType xFrequency = 1000;
  xLastWakeTime=xTaskGetTickCount();
    for( ;; )
    {
	  LEDToggle(5);
	  vTaskDelayUntil(&xLastWakeTime,xFrequency);
    }
}


To set timing we are using vTaskDelayUntil function. FreeRTOS counts ticks everytime scheduler is called (every 1ms). By setting frequency value to 1000 we are getting 1s delay. This is simple with LEDs.

Another task would be checking button state. In order to pass button state we don’t want to use any global variables here. FreeRTOS have special means for this – binary semaphores. Thay are convenient when we need to sent a binary value between tasks. In my example I declared semaphore handlers for each button:

staticxSemaphoreHandle xButtonWakeupSemaphore = NULL;

staticxSemaphoreHandle xButtonTamperSemaphore = NULL;

staticxSemaphoreHandle xButtonUser1Semaphore = NULL;

staticxSemaphoreHandle xButtonUser2Semaphore = NULL;


then within vButtonCheckTask before main loop created semaphores to be used:

vSemaphoreCreateBinary(xButtonWakeupSemaphore);

vSemaphoreCreateBinary(xButtonTamperSemaphore);

vSemaphoreCreateBinary(xButtonUser1Semaphore);

vSemaphoreCreateBinary(xButtonUser2Semaphore);


Once sempahores are created succesfully we can start using them. There are several functions available for manipulating semaphores. This time we are going to use two of them: Give and take. So when we check button and detect that it was pressed we give semaphore which is something like setting bolean value to ’1′:

 if (ButtonRead(BWAKEUPPORT, BWAKEUP)==pdTRUE)
        {
          count++;
          if(count==DEBOUNCECOUNTS)
            {
              xSemaphoreGive(xButtonWakeupSemaphore);
              count = 0;
            }
        }


Semaphores stay set until they are taken. Other function have to take semaphores in order to reset. For this I created another task which toggles particular LED when button is pressed:

void vButtonLEDsTask( void *pvParameters )
{
  portTickType xLastWakeTime;
  const portTickType xFrequency = 100;
  xLastWakeTime=xTaskGetTickCount();

  for( ;; )
  {
      if((xButtonWakeupSemaphore!=NULL))
      {
         if (xSemaphoreTake(xButtonWakeupSemaphore, (portTickType)10)==pdTRUE)
             {
               LEDToggle(1);
               //give semaphore back
               xSemaphoreGive(xButtonWakeupSemaphore);
             }
      }
    vTaskDelayUntil(&xLastWakeTime,xFrequency);
  }
}


In this shortened version of task you can see how single semaphore is taken. If statement checks if semaphore was given. If condition is met then LED is toggled. XsemaphoreTake function also resets semaphore automatically. As you can see after toggling LED I give semaphore back because Ialso plant to detect button press withing another task. This is vLCDTask:

void vLCDTask( void *pvParameters )
{
  extern uint8_t Image_Table[];
  portTickType xLastWakeTime;
  const portTickType xFrequency = 100;
  xLastWakeTime=xTaskGetTickCount();
  LCD_SetDisplayWindow(00, 00, 239, 319);
  LCD_Clear(Black);
  LCD_DisplayStringLine(Line4,  (char*)pcLCDTaskStartMsg, White, Black);
  for(;;)
    {
      if((xButtonWakeupSemaphore!=NULL)&&(xButtonTamperSemaphore!=NULL)
       &&(xButtonUser1Semaphore!=NULL)&&(xButtonUser2Semaphore!=NULL))
      {
         if (xSemaphoreTake(xButtonWakeupSemaphore, (portTickType)10)==pdTRUE)
             {
             LCD_Clear(Blue);
             LCD_WriteBMP_Dim(30, 30, 210, 210, Image_Table);
             }
      }
    vTaskDelayUntil(&xLastWakeTime,xFrequency);
    }
}


With button presses I trigger different things to display on LCD. You have to be careful by using such technique because you cant predict that LCD task will be called after LED task. It can happen that first will be LCD task and there fore LED won’t be toggled as semaphore will be taken. Probably it would be better to create two semaphores for each task.

Last task I am using here is USART task which sends and receives vie terminal. With USART things start to be more complicated. Because we don’t want to miss any received character and be sure that all messages have been sent. This can be achieved by using interrupt based transferring. Doing this in task where scheduler runs every 1m would definitely lead to loos of data. Another issue is to ensure buffering so that data could be retained until task is called. Who knows there might be 100 characters received before task runs and serves data. So there is another nice FreeRTOS feature – queues. These are like predefined lengths buffers to store any type of messages. To start using queues we simply declare two queue handlers:

xQueueHandle RxQueue, TxQueue;


one for receiving and another for transmitting. Within task we then create both queues with xQueueCreate function where we put queue length as first parameter and size of message which in our case will be char size (8-bit).

void vUSARTTask( void *pvParameters )
{

  portTickType xLastWakeTime;
  const portTickType xFrequency = 50;
  xLastWakeTime=xTaskGetTickCount();
  char ch;
  // Create a queue capable of containing 128 characters.
  RxQueue = xQueueCreate( configCOM0_RX_BUFFER_LENGTH, sizeof( portCHAR ) );
  TxQueue = xQueueCreate( configCOM0_TX_BUFFER_LENGTH, sizeof( portCHAR ) );
  USART1PutString(pcUsartTaskStartMsg,strlen( pcUsartTaskStartMsg ));
  for( ;; )
  {
      //Echo back
      if (Usart1GetChar(&ch))
        {
          Usart1PutChar(ch);
        }
      vTaskDelayUntil(&xLastWakeTime,xFrequency);
  }
}


Usart1GetChar function simply pulls out char value out of queue as follows:

uint32_t Usart1GetChar(char *ch){
  if(xQueueReceive( RxQueue, ch, 0 ) == pdPASS)
    {
      return pdTRUE;
    }
  return pdFALSE;
}


While Usart1PutChar sends char to queue:

uint32_t Usart1PutChar(char ch)
{
  if( xQueueSend( TxQueue, &ch, 10 ) == pdPASS )
    {
      USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
      return pdTRUE;
    }else{
     return pdFAIL;
    }
}


The rest is left for interrupt handler which simply responds to interrupt requests and either sends bytes from TxQueue or receives bytes and places them to RxQueue.

void USART1_IRQHandler(void)
{
  long xHigherPriorityTaskWoken = pdFALSE;
  uint8_t ch;
  //if Receive interrupt
  if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
      ch=(uint8_t)USART_ReceiveData(USART1);
      xQueueSendToBackFromISR( RxQueue, &ch, &xHigherPriorityTaskWoken );
    }

  if (USART_GetITStatus(USART1, USART_IT_TXE) != RESET)
        {
      if( xQueueReceiveFromISR( TxQueue, &ch, &xHigherPriorityTaskWoken ) )
        {
          USART_SendData(USART1, ch);
        }else{
           //disable Transmit Data Register empty interrupt
           USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
             }
        }
  portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
}


it important to know that within interrupt handler special functions has to be used xQueueReceiveFromISR and xQueueSentoBackFromISR.

After having all tasks prepared we can add to scheduler queue in our main source file:

//STM32F103ZET6 FreeRTOS Test
#include "stm32f10x.h"
//#include "stm32f10x_it.h"
#include "mytasks.h"
//task priorities
#define mainLED_TASK_PRIORITY			( tskIDLE_PRIORITY )
#define mainButton_TASK_PRIORITY                   ( tskIDLE_PRIORITY )
#define mainButtonLEDs_TASK_PRIORITY                   ( tskIDLE_PRIORITY + 1 )
#define mainLCD_TASK_PRIORITY                   ( tskIDLE_PRIORITY )
#define mainUSART_TASK_PRIORITY                   ( tskIDLE_PRIORITY )
#define mainLCD_TASK_STACK_SIZE configMINIMAL_STACK_SIZE+50
#define mainUSART_TASK_STACK_SIZE configMINIMAL_STACK_SIZE+50
int main(void)
{
  //init hardware
  LEDsInit();
  ButtonsInit();
  LCD_Init();
  Usart1Init();
  xTaskCreate( vLEDFlashTask, ( signed char * ) "LED", configMINIMAL_STACK_SIZE, NULL, mainLED_TASK_PRIORITY, NULL );
  xTaskCreate( vButtonCheckTask, ( signed char * ) "Button", configMINIMAL_STACK_SIZE, NULL, mainButton_TASK_PRIORITY, NULL );
  xTaskCreate( vButtonLEDsTask, ( signed char * ) "ButtonLED", configMINIMAL_STACK_SIZE, NULL, mainButtonLEDs_TASK_PRIORITY, NULL );
  xTaskCreate( vLCDTask, ( signed char * ) "LCD", mainLCD_TASK_STACK_SIZE, NULL, mainLCD_TASK_PRIORITY, NULL );
  xTaskCreate( vUSARTTask, ( signed char * ) "USART", mainUSART_TASK_STACK_SIZE, NULL, mainUSART_TASK_PRIORITY, NULL );
  //start scheduler
  vTaskStartScheduler();
  //you should never get here
  while(1)
	{ }
}


As you can see we have created 5 tasks. Each of them have their own priority level and stack size. Probably hardest part is to define proper stack size it it’s to small it may crash your program, it its too large, then we wasting resources. To detect stack overflow you can use special function called

voidvApplicationStackOverflowHook( xTaskHandle *pxTask, signed portCHAR *pcTaskName )


where you can set your indicators like LED flash when ever stack overflow occurred. Then you can increase task stack and start again.

Hopefully you find this part useful and interesting. As always comments and questions are welcome.

Working source code fro Codesourcery with Eclipse IDE is here: STM32F103ZET6FreeRTOS

Tagged , , , . Bookmark the permalink.

One Response to FreeRTOS on STM32

  1. Really amazing!

    I work with STM32F103RBT6 chip and will try this,
    although its RAM is 20 kB.

    Thank you

Add Comment Register



Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>