FreeRTOS Tutorials #8 -> Software Timers
A software timer allows a function to be executed at a set time in the future. The function executed by the timer is called the timer’s callback function. The time between a timer being started, and its callback function being executed, is called the timer’s period. Put simply, the timer’s callback function is executed when the timer’s period expires.
There are two types of timers, one-shot timers, and auto-reload timers.
A one-shot timer can execute its callback function only once. It can be manually re-started, but will not automatically re-start itself.
Conversely, an auto-reload timer will automatically re-start itself after each execution of its callback function, resulting in periodic callback execution.
In this tutorial, we will cover how to implement both software timers in Free RTOS, using the CMSIS functions and using the RTOS functions directly
Using CMSIS
As the heading says, this part will cover the implementation of timers using the CMSIS functions. let’s see the setup part first
Setup
Let’s setup the Free RTOS first
As shown in the figure above
- Enable the Use of TIMERS
- Set the priority for the timers. I have set it to the maximum value i.e 6
Now we will setup the tasks.
As shown in the figure above, I have set up 2 tasks i.e uart task and the led task. Both of them have the same priorities so that they don’t preempt each other.
Next is the setup for the timers
I have already mentioned in the beginning that there are 2 different types of timers in freeRTOS. Periodic Timer is the one which, once expired, restarts itself after executing the callback. On the other hand, One Shot Timer only runs once, and we have to manually start it if needed.
As you can see in the figure above, I have created 2 timers
- periodic Timer is the Periodic type (osTimerPeriodic), and it’s callback is declared as PTCallback
- once Timer is the One Shot Timer (osTimerOnce), and it’s callback function is OTCallback
Next we will setup the uart, since we will be sending some data to the computer
and at last I am setting PA5 as output (for on board LED) and PC13 as input (for on board user button)
This completes the setup process. Let’s see some code now
some insight into the CODE
let’s take a look at the pre-generated functions first
/* Create the timer(s) */
/* definition and creation of periodicTimer */
osTimerDef(periodicTimer, PTCallback);
periodicTimerHandle = osTimerCreate(osTimer(periodicTimer), osTimerPeriodic, NULL);
/* definition and creation of onceTimer */
osTimerDef(onceTimer, OTCallback);
onceTimerHandle = osTimerCreate(osTimer(onceTimer), osTimerOnce, NULL);
/* Create the thread(s) */
/* definition and creation of uartTask */
osThreadDef(uartTask, UartTask, osPriorityNormal, 0, 128);
uartTaskHandle = osThreadCreate(osThread(uartTask), NULL);
/* definition and creation of ledTask */
osThreadDef(ledTask, LedTask, osPriorityNormal, 0, 128);
ledTaskHandle = osThreadCreate(osThread(ledTask), NULL);
- In the code above, first the timer is defined using the osTImerDef function, and then it is created using osTimerCreate function
- you can see the timer name, it’s callback, and the type is as we set in the cubeMX
Similarly 2 tasks are created with normal priorities. UartTask and LedTask are the functions for the 2 tasks and we will write the rest of the code in these functions
void UartTask(void const * argument)
{
/* USER CODE BEGIN 5 */
osTimerStart(periodicTimerHandle, 1000);
/* Infinite loop */
for(;;)
{
HAL_UART_Transmit(&huart2, "Sending from UART TASK\n", 23, 100);
osDelay(2000);
}
/* USER CODE END 5 */
}
As you might have noticed that the pregenerated functions only create the timers, they don’t start them. So we need to start the timers on our own, when we feel necessary
- When the control enters the Uart Task, we will start the periodic timer.
- osTImerStart will take the parameters as the timer handle that you want to start, and the period for the timer before it expires
- as shown above, the periodic timer will start here, and it’s period will be 1 second. That means the timer will expire in 1 second, it’s callback will be executed, and the timer will start again on it’s own.
- I wrote the timer start code outside the while loop because the periodic timer only needs to run once, and once expired it will restart on it’s own
- inside the while loop, we will send the data to the uart and the task will keep running every 2 seconds
now we will write another function for the LED TASK
void LedTask(void const * argument)
{
/* USER CODE BEGIN LedTask */
/* Infinite loop */
for(;;)
{
if (!(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13))) // if the button is pressed
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, 1); // set the LED
osTimerStart(onceTimerHandle, 4000);
}
osDelay(20);
}
/* USER CODE END LedTask */
}
- as you can see above, first we will check if the button is pressed
- if it is, then we will turn in the LED
- now start the one shot timer with a period of 4 seconds
- and this task will run every 20 ms. I am choosing a very small suspension time because we don’t want to miss any button input
once the periodic timer expires, it will execute the callback i.e PTCallback function
void PTCallback(void const * argument)
{
/* USER CODE BEGIN PTCallback */
HAL_UART_Transmit(&huart2, "Sending from PERIODIC TIMER\n", 28, 100);
/* USER CODE END PTCallback */
}
here we will just send the data to the uart indicating that the control entered the callback function
similarly when the one shot timer expires, it also calls the OTCallback function
void OTCallback(void const * argument)
{
/* USER CODE BEGIN OTCallback */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, 0); // Reset the LED
/* USER CODE END OTCallback */
}
here we will just turn off the LED.
so basically when the button is pressed, the LED turns on. And if we don’t press the button again in another 4 seconds (we don’t reset the timer), the LED will turn off. meanwhile the uart task, and the timer callback will keep sending the data to the uart
The video is at the end of the Post.
Using Free RTOS Functions
Here we will implement the software timers using the pure RTOS functions. let’s start with the setup required in the cubeMX
Setup
Let’s setup the FreeRTOS first
As shown in the figure above
- Enable the Use of TIMERS
- Set the priority for the timers. I have set it to the maximum value i.e 6
That’s all the setup we need for the freeRTOS. Next we will setup the uart, since we will be sending some data to the computer
and at last I am setting PA5 as output (for on board LED) and PC13 as input (for on board user button)
This is it for the setup. Now we will see the code
Some insight into the CODE
First of all we will define the timer handles and the task handles
// Create timer handles
xTimerHandle PTHandle;
xTimerHandle OTHandle;
// create task handles
xTaskHandle uartHandle;
xTaskHandle ledHandle;
Next in the main function, we will create the timers
// create timers
PTHandle = xTimerCreate("timer1", pdMS_TO_TICKS(1000), pdTRUE, (void *) 1, TimerCallback);
OTHandle = xTimerCreate("timer2", pdMS_TO_TICKS(4000), pdFALSE, (void *) 2, TimerCallback);
xTimerCreate takes the following parameters
- Name of the timer -> you can give any name here, since it won’t be used anywhere.
- Time Period for the timer -> this is the period for which the timer will run in the background, before it expires
- Timer Type -> Either the timer is Auto Reload (TRUE), or One Shot (FALSE)
- Timer ID -> You can give any ID here to identify the timer
- Timer Callback -> The callback function, which will be called once the timer is expired
I am using the same callback for both the timers. In the callback function, we will use the condition to identify the timer responsible for the callback.
On success, xTImerCreate returns the Timer handle, and we will store that in the respective timer handles.
After creating timers, we will create the tasks
// create tasks
xTaskCreate(uartTask, "uart", 128, NULL, 1, &uartHandle);
xTaskCreate(ledTask, "led", 128, NULL, 1, &ledHandle);
As you can see above, 2 tasks are being created. The arguments for the xTaskCreate are as follows
- Task Function -> Here we will write the code related to the respective task
- Task name -> you can give any name here, since it won’t be used anywhere
- Stack Size -> I am leaving this to 128
- Parameters to the task -> We are not passing any parameters, so keep it NULL
- Task Priority -> I am keeping the task priority to normal (1)
- Task Handle -> The pointer to the handle for the task
The timers and the tasks are created successfully in the main function. Now we will write their functions and the callbacks
Let’s start by writing the UART TASK
void uartTask (void *argument)
{
xTimerStart(PTHandle, 0);
while (1)
{
HAL_UART_Transmit(&huart2, (uint8_t *) "sending from UART TASK\n", 23, 100);
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
As you might have noticed we have only create the timers, we haven’t started them. We will start them, when we feel necessary
When the control enters the Uart Task, we will start the periodic timer.
xTimerStart Takes the following arguments
- The Handle to the timer
- The Time to wait before starting the timer. I am keeping this as 0, since I want the timer to start immediately
This timer will expire in 1 second, it’s callback will be executed, and the timer will start again on it’s own.
I wrote the timer start code outside the while loop because the periodic timer only needs to run once, and once expired it will restart on it’s own
Inside the while loop, we will send the data to the uart and the task will keep running every 2 seconds
now we will write another function for the LED TASK
void ledTask (void *argument)
{
while (1)
{
if (!(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13))) // if the button is pressed
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, 1); // Turn On the LED
xTimerStart(OTHandle, 0);
}
vTaskDelay(pdMS_TO_TICKS(20));
}
}
- as you can see above, first we will check if the button is pressed
- if it is, then we will turn in the LED
- now start the one shot timer immediately
- and this task will run every 20 ms. I am choosing a very small suspension time because we don’t want to miss any button input
once the timers expires, they will execute the callback i.e TimerCallback function
void TimerCallback (xTimerHandle xTimer)
{
if (xTimer == PTHandle)
{
HAL_UART_Transmit(&huart2, (uint8_t *) "sending from TIMER CALLBACK\n", 28, 100);
}
else if (xTimer == OTHandle)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, 0);
}
}
Timer Callback function takes Timer handle as the argument, based on which we would be able to identify which timer made a call to the callback
- if the callback is made by the periodic timer, we will send the string the uart, indicating that the callback has been executed
- and if the callback is called by the one shot timer, we will turn off the LED
so basically when the button is pressed, the LED turns on. And if we don’t press the button again in another 4 seconds (we don’t reset the timer), the LED will turn off. Meanwhile the uart task, and the timer callback will keep sending the data to the uart
Some IMPORTANT Points
- xTImerStart can also be used to reset the timer if the timer is running
- xTimerChangePeriod can be used to change the period of the timer during runtime
- xTimerStartFromISR should be used, if the timer is being started from an interrupt callback
- xTimerStop is used to stop the timer
Result
As you can see above, the timer runs every 1 second and sends the data to the uart. On the other hand, the uart task runs every 2 seconds
When we press the button, the LED turns on for 4 seconds, and if we don’t press the button in another 4 seconds, it will timeout and turn OFF