STM32 Software Timers
Introduction
Software timers are essential components in embedded systems that allow you to execute specific functions at predetermined intervals without blocking the main program execution. Unlike hardware timers that are physically implemented in the microcontroller's silicon, software timers are managed by the operating system and provide a more flexible approach to timing operations.
In RTOS-based STM32 applications, software timers offer several advantages:
- They don't consume hardware timer resources
- They execute in the context of the timer service task
- They can be created, modified, and deleted at runtime
- They provide a clean way to implement periodic tasks
This guide will walk you through understanding, implementing, and using software timers in STM32 microcontrollers with FreeRTOS.
Understanding Software Timers
Software timers are virtual timing mechanisms managed by the RTOS. When you create a software timer, you're essentially asking the RTOS to call a specific function (callback) after a defined period.
Types of Software Timers
There are two main types of software timers:
- One-shot timers: Execute only once after the specified period
- Auto-reload timers: Execute repeatedly at the specified interval
Timer Service Task
All timer callbacks execute within a special RTOS task called the Timer Service Task (or Daemon Task). This is important to understand because:
- Timer callbacks don't interrupt other tasks
- Timer callbacks share the same priority (the priority of the timer service task)
- Long-running callbacks can delay other timers
Implementing Software Timers in STM32 with FreeRTOS
Let's see how to implement and use software timers in an STM32 project using FreeRTOS.
Configuration
Before using software timers, make sure they're enabled in your FreeRTOS configuration:
/* in FreeRTOSConfig.h */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( 2 ) /* Medium priority */
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH 256
Creating a Software Timer
Here's how to create a software timer:
/* Timer handle */
TimerHandle_t myTimer;
/* Timer callback function */
void myTimerCallback(TimerHandle_t xTimer) {
/* This code will execute when the timer expires */
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // Toggle LED
}
/* In your initialization code */
void initializeTimer(void) {
/* Create a one-shot timer that expires after 1000 ticks */
myTimer = xTimerCreate(
"MyTimer", /* Name for debugging */
pdMS_TO_TICKS(1000), /* Period in ticks */
pdFALSE, /* Auto-reload (pdFALSE = one-shot, pdTRUE = periodic) */
0, /* Timer ID (used to identify timer in callback) */
myTimerCallback /* Callback function */
);
if (myTimer == NULL) {
/* Timer creation failed, handle error */
} else {
/* Start the timer */
if (xTimerStart(myTimer, 0) != pdPASS) {
/* Failed to start timer, handle error */
}
}
}
Common Timer Operations
Here are the most common operations you can perform with software timers:
Starting a Timer
/* Start timer with 10 ticks of timeout if needed */
xTimerStart(myTimer, pdMS_TO_TICKS(10));
Stopping a Timer
/* Stop the timer with 10 ticks timeout if needed */
xTimerStop(myTimer, pdMS_TO_TICKS(10));
Changing Timer Period
/* Change timer period to 2000 ms */
xTimerChangePeriod(myTimer, pdMS_TO_TICKS(2000), pdMS_TO_TICKS(10));
Resetting a Timer
/* Reset the timer (restart from beginning) with 10 ticks timeout */
xTimerReset(myTimer, pdMS_TO_TICKS(10));
Deleting a Timer
/* Delete the timer when no longer needed */
xTimerDelete(myTimer, pdMS_TO_TICKS(100));
Practical Examples
Let's explore some practical examples of using software timers in real-world STM32 applications.
Example 1: LED Blinker with Different Patterns
This example uses a software timer to implement different LED blinking patterns that can be changed at runtime:
/* Global variables */
TimerHandle_t ledTimer;
uint8_t currentPattern = 0;
const uint32_t patterns[] = {500, 1000, 100}; // Different blink intervals in ms
/* LED Timer callback */
void ledTimerCallback(TimerHandle_t xTimer) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // Toggle LED
}
/* Function to change blink pattern */
void changeBlinkPattern(uint8_t patternId) {
if (patternId < sizeof(patterns)/sizeof(patterns[0])) {
currentPattern = patternId;
/* Change the timer period */
xTimerChangePeriod(
ledTimer,
pdMS_TO_TICKS(patterns[currentPattern]),
pdMS_TO_TICKS(100)
);
}
}
/* Initialization */
void initLedTimer(void) {
/* Create an auto-reload timer (periodic) */
ledTimer = xTimerCreate(
"LedTimer",
pdMS_TO_TICKS(patterns[currentPattern]),
pdTRUE, /* Auto-reload */
0,
ledTimerCallback
);
if (ledTimer != NULL) {
xTimerStart(ledTimer, pdMS_TO_TICKS(100));
}
}
Example 2: Periodic Sensor Reading
This example uses a timer to periodically read from a sensor without blocking other tasks:
/* Global variables */
TimerHandle_t sensorTimer;
float latestTemperature = 0.0f;
/* Sensor reading callback */
void sensorTimerCallback(TimerHandle_t xTimer) {
/* Read temperature from sensor (example using I2C) */
uint8_t data[2];
HAL_StatusTypeDef status;
status = HAL_I2C_Mem_Read(
&hi2c1,
SENSOR_ADDR,
TEMP_REG,
I2C_MEMADD_SIZE_8BIT,
data,
2,
100
);
if (status == HAL_OK) {
/* Process the data (example conversion) */
int16_t rawTemp = (data[0] << 8) | data[1];
latestTemperature = rawTemp * 0.0625f;
/* Optional: Notify other tasks that new data is available */
xTaskNotify(dataProcessingTaskHandle, 0x01, eSetBits);
}
}
/* Initialization */
void initSensorTimer(void) {
/* Create a periodic timer that reads sensor every 5 seconds */
sensorTimer = xTimerCreate(
"SensorTimer",
pdMS_TO_TICKS(5000),
pdTRUE, /* Auto-reload */
0,
sensorTimerCallback
);
if (sensorTimer != NULL) {
xTimerStart(sensorTimer, pdMS_TO_TICKS(100));
}
}
Example 3: Timeout Management
This example implements a communication timeout using a one-shot timer:
/* Global variables */
TimerHandle_t timeoutTimer;
SemaphoreHandle_t responseSemaphore;
bool responseReceived = false;
/* Timeout callback */
void timeoutCallback(TimerHandle_t xTimer) {
/* This executes if no response was received */
responseReceived = false;
/* Signal the waiting task */
xSemaphoreGive(responseSemaphore);
}
/* Function to send command and wait for response with timeout */
bool sendCommandWithTimeout(uint8_t* command, uint8_t length, uint32_t timeoutMs) {
/* Reset state */
responseReceived = false;
/* Send the command (example using UART) */
HAL_UART_Transmit(&huart1, command, length, 100);
/* Start the timeout timer */
xTimerChangePeriod(timeoutTimer, pdMS_TO_TICKS(timeoutMs), pdMS_TO_TICKS(10));
xTimerStart(timeoutTimer, pdMS_TO_TICKS(10));
/* Wait for response or timeout */
if (xSemaphoreTake(responseSemaphore, portMAX_DELAY) == pdTRUE) {
/* Check if we got a response or timed out */
return responseReceived;
}
return false;
}
/* Call this when a response is received */
void onResponseReceived(void) {
/* Stop the timeout timer */
xTimerStop(timeoutTimer, pdMS_TO_TICKS(10));
responseReceived = true;
/* Signal the waiting task */
xSemaphoreGive(responseSemaphore);
}
/* Initialization */
void initTimeoutHandler(void) {
/* Create a one-shot timer */
timeoutTimer = xTimerCreate(
"TimeoutTimer",
pdMS_TO_TICKS(1000), /* Default timeout, will be changed when used */
pdFALSE, /* One-shot */
0,
timeoutCallback
);
/* Create binary semaphore for synchronization */
responseSemaphore = xSemaphoreCreateBinary();
}
Best Practices and Considerations
When working with software timers in STM32 RTOS applications, keep these important considerations in mind:
Callback Execution Context
Remember that timer callbacks execute in the context of the Timer Service Task:
- Keep callbacks short and efficient
- Don't block or delay within a timer callback
- Avoid using blocking API calls
- Consider using the callback to signal another task to do heavy work
Timer Accuracy
Software timers are not as precise as hardware timers:
- They depend on the RTOS tick frequency
- The actual callback execution may be delayed if higher priority tasks are running
- If timing accuracy is critical (sub-millisecond), consider using hardware timers instead
Memory Management
Software timers consume RAM:
- Each timer requires memory for its control structure
- The timer service task requires its own stack
- Consider the memory impact when creating many timers
Concurrency and Thread Safety
Be careful when accessing shared resources from timer callbacks:
- Timer callbacks can execute while other tasks are suspended
- Use proper synchronization (mutexes, semaphores) when accessing shared data
- Remember the priority of the timer service task when considering priority inheritance
Advanced Usage
Timer IDs and User Data
You can associate user data with a timer using the timer ID:
/* Multiple LEDs example */
typedef struct {
GPIO_TypeDef* port;
uint16_t pin;
} LED_Config_t;
/* LED configurations */
LED_Config_t led1 = {GPIOA, GPIO_PIN_5};
LED_Config_t led2 = {GPIOC, GPIO_PIN_13};
/* Timer callback that uses the ID */
void ledTimerCallback(TimerHandle_t xTimer) {
/* Get the LED configuration from the timer ID */
LED_Config_t* led = (LED_Config_t*)pvTimerGetTimerID(xTimer);
/* Toggle the specific LED */
HAL_GPIO_TogglePin(led->port, led->pin);
}
/* Creating timers with different IDs */
TimerHandle_t createLedTimer(LED_Config_t* led, uint32_t period) {
TimerHandle_t timer = xTimerCreate(
"LedTimer",
pdMS_TO_TICKS(period),
pdTRUE,
(void*)led, /* Pass LED config as the timer ID */
ledTimerCallback
);
return timer;
}
/* Usage */
void initializeMultipleLedTimers(void) {
TimerHandle_t timer1 = createLedTimer(&led1, 500);
TimerHandle_t timer2 = createLedTimer(&led2, 1000);
xTimerStart(timer1, 0);
xTimerStart(timer2, 0);
}
Dynamic Timer Creation and Deletion
You can create and delete timers at runtime:
/* Create a timer dynamically in response to an event */
void handleEvent(uint32_t eventType) {
if (eventType == EVENT_START_MONITORING) {
/* Create a timer for monitoring */
TimerHandle_t monitorTimer = xTimerCreate(
"MonitorTimer",
pdMS_TO_TICKS(1000),
pdTRUE,
0,
monitorCallback
);
if (monitorTimer != NULL) {
xTimerStart(monitorTimer, pdMS_TO_TICKS(10));
/* Store the handle somewhere for later reference */
saveTimerHandle(monitorTimer);
}
} else if (eventType == EVENT_STOP_MONITORING) {
/* Get the previously stored timer handle */
TimerHandle_t monitorTimer = getStoredTimerHandle();
if (monitorTimer != NULL) {
/* Delete the timer when no longer needed */
xTimerDelete(monitorTimer, pdMS_TO_TICKS(10));
}
}
}
Summary
Software timers in STM32 RTOS applications provide a flexible way to implement time-based functionality without blocking the main program execution:
- They allow non-blocking periodic or one-shot timed events
- They're managed by the RTOS timer service task
- They can be created, modified, and deleted at runtime
- They're ideal for non-critical timing operations where hardware timers aren't necessary
By leveraging software timers, you can create more responsive embedded systems that efficiently handle multiple timing-related operations simultaneously.
Additional Resources and Exercises
Exercises
-
Basic Timer Practice: Create a simple project that uses a software timer to toggle an LED at a 1-second interval.
-
Multi-Timer System: Implement a system with three different timers controlling different outputs (e.g., LEDs, buzzer) at different intervals.
-
Dynamic Period Adjustment: Create a timer whose period changes based on input (e.g., from a potentiometer or button presses).
-
Timeout Implementation: Implement a communication protocol that waits for responses with a configurable timeout.
-
Timer Callback Optimization: Profile the execution time of your timer callbacks and optimize them for minimal execution time.
Further Reading
- FreeRTOS Software Timer API Documentation
- STM32 HAL Timer Documentation
- Real-Time Operating Systems for the ARM Cortex-M (books by various authors)
- "Mastering STM32" by Carmine Noviello
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)