FreeRTOS on STM32

A high-density line of STM32 microcontrollers has quite a bunch of 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 an only main loop and interrupts becomes a time-consuming task to manage. If you don’t want to struggle in tuning things up manually, you can use one of much 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 a 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 let’s stick with FreeRTOS. Directly speaking FreeRTOS is quite simple and easy to use. It has practically most of the features you’d look for an RTOS. Some of the key features would include a preemptive, cooperative and hybrid scheduler, task and co-routine support, queues, semaphores and mutexes for task synchronization and communication. There are many demos, many ports to get started.

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

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

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

Then we have to add FreeRTOSConfig.h file to project where all RTOS configurations are set. The next thing is to match interrupts handlers for systick, SVC and PendSV interrupts. They are used by the scheduler and have a bit different nomenclature. If you look at the 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 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 the 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 a minimal stack size for a task. Also, set a 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 too. There are a lot more settings you’ll find in the 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 a significant increase of ram or CPU usage.

Let us proceed to some code. If you are familiar with RTOS, then you know that program written for RTOS is organized as a set of independent tasks. So each task shouldn’t rely on other tasks and run within its context. Practically speaking task is a function with its 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, let us start with LED flasher. This is a 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 the vTaskDelayUntil function. FreeRTOS counts ticks every time scheduler is called (every 1ms). By setting frequency value to 1000 we are getting a 1s delay. This is simple with LEDs.

Another task would be checking button state. To pass button state, we don’t want to use any global variables here. FreeRTOS has unique means for this – binary semaphores. They are convenient when we need to send 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 the main loop created semaphores to be used:

vSemaphoreCreateBinary(xButtonWakeupSemaphore);

vSemaphoreCreateBinary(xButtonTamperSemaphore);

vSemaphoreCreateBinary(xButtonUser1Semaphore);

vSemaphoreCreateBinary(xButtonUser2Semaphore);

 

Once semaphores are created successfully, 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 the boolean value to ‘1’:

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

 

Semaphores stay set until they are taken. Other functions have to take semaphores to reset. For this I created another task which toggles a particular LED when the 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 I also plan 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 a technique because you can’t predict that LCD task will be called after LED task. It can happen that first will be LCD task and therefore LED won’t be toggled as semaphore will be taken. Probably it would be better to create two semaphores for each task.

The last task I am using here is the USART task which sends and receives via 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 the task where scheduler runs every 1m would undoubtedly lead to loos of data. Another issue is to ensure buffering so that data could be retained until the 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 messages. To start using queues we declare two queue handlers:

xQueueHandle RxQueue, TxQueue;

 

One for receiving and another for transmitting. Within a task, we then create both queues with xQueueCreate function where we put queue length as first parameter and size of the 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);
  }
}

 

The usart1getchar function 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 the 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 is necessary to know that within interrupt handler special functions has to be used xQueueReceiveFromISR and xQueueSentoBackFromISR.

After having all tasks prepare,d we can add to the 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 has their priority level and stack size. Probably hardest part is to define proper stack size it’s too small it may crash your program, it is too large, then we wasting resources. To detect stack overflow, you can use a particular function called

voidvApplicationStackOverflowHook( xTaskHandle *pxTask, signed portCHAR *pcTaskName )

 

Where you can set your indicators like the LED flash whenever stack overflow occurred, then you can increase task stack and start again.

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

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

5 Comments:

  1. پروژه الکترونیک

    Really amazing!

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

    Thank you

  2. thank you for your good work
    i am working on stm32rbt6 and i am using freertos.there were many concepts in defining and using freertos on a project.for example when you define a task and you expect that it works regularly , you must set scheduler setting for type of doing this task.
    i have got some questions:
    1.how can i set the timing for doing my tasks?

    in freertos , interrupts and semaphores are coupled together and everytime i want to use ISR , i must set semaphores to handle it.

    2.can you explain the way we set sempahores to achieve good performance of ISR?

    i want to launch an alphanumerical LCD to freertos . i have designed a lcd.c and added it to freertos stm32 based project in lcd task but it doesnt play the defined tasks.

    3.how can we define the scheduler for doing tasks that we define it ourself?

  3. Thank you very much, excelent work!

  4. Can u please help me to write a task in RTOS for blinking LED. I am having STM32F microcontroller and am using STM324*G-Eval folder from free rtos

  5. Hi ,
    Good work !!
    But i still have few questions :

    1- Why to use 2 queues instead of only one queue because we can send data from isr to rxqueue then we can get it back from this same rxqueue to transmit it via uart ?

    2- Which priority have you chosen for the uart ? The lowest one meaning 15 ??

    3- I get a problem with this function : if(xQueueReceive( RxQueue, ch, 0 ) == pdPASS) . I could never get the pdPASS as result . What could be the problem ?

    Thanks again,

Leave a Reply to tesy Cancel reply