FreeRTOS software timers run callbacks without creating full tasks. Today you'll use them, then profile your system's timing behavior.
FreeRTOS software timers call a callback function after a period (one-shot) or repeatedly (auto-reload). They run in the timer service task (priority configTIMER_TASK_PRIORITY). xTimerCreate(name, period, autoReload, timerID, callback). xTimerStart(timer, timeout). Callbacks must be short and non-blocking — they run in the timer task, blocking it affects all other timers. For longer work, post to a queue from the callback and process in a dedicated task.
FreeRTOS+Trace (Percepio Tracealyzer) records every task switch, ISR, queue operation, and semaphore event. The recorder runs in RAM (circular buffer, configurable size). On demand, dump the buffer via UART, USB, or Segger RTT. Visualize in Tracealyzer: see exactly which tasks ran, for how long, what blocked them. Free version available with limited features. Alternative: SystemView (Segger, free) with similar capabilities and real-time streaming.
Configure configCHECK_FOR_STACK_OVERFLOW=2 and implement vApplicationStackOverflowHook(). Configure configUSE_MALLOC_FAILED_HOOK=1 and implement vApplicationMallocFailedHook(). In production: log the faulting task name to flash or send over UART before resetting. Use vTaskList() to dump a table of all tasks, their state, priority, and stack HWM — invaluable for debugging.
// Software timers + system profiling
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
TimerHandle_t xHeartbeatTimer;
TimerHandle_t xWatchdogTimer;
volatile uint32_t watchdog_fed = 0;
// Heartbeat: toggle LED every 500ms (auto-reload)
void vHeartbeatCb(TimerHandle_t xTimer) {
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
// Watchdog: if not fed within 3 seconds, reset
void vWatchdogCb(TimerHandle_t xTimer) {
if (!watchdog_fed) {
printf("WATCHDOG: system not responding, resetting!\n");
NVIC_SystemReset();
}
watchdog_fed = 0;
// Restart the timer
xTimerReset(xTimer, 0);
}
// Print task list and heap stats every 10 seconds
void vProfileTask(void *pv) {
static char taskListBuf[512];
while (1) {
vTaskDelay(pdMS_TO_TICKS(10000));
// Dump task table: name, state, priority, HWM, task number
vTaskList(taskListBuf);
printf("\n=== Task List ===\n%s\n", taskListBuf);
printf("Free heap: %u / %u bytes\n",
(unsigned)xPortGetFreeHeapSize(),
(unsigned)configTOTAL_HEAP_SIZE);
}
}
// Main application task — feeds watchdog
void vAppTask(void *pv) {
while (1) {
// Do application work...
watchdog_fed = 1; // feed the watchdog
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
int main(void) {
// One-shot watchdog (restarted in callback)
xWatchdogTimer = xTimerCreate("WDG", pdMS_TO_TICKS(3000),
pdFALSE, 0, vWatchdogCb);
// Auto-reload heartbeat
xHeartbeatTimer = xTimerCreate("HB", pdMS_TO_TICKS(500),
pdTRUE, 0, vHeartbeatCb);
xTaskCreate(vAppTask, "APP", 256, NULL, 2, NULL);
xTaskCreate(vProfileTask, "PROF", 512, NULL, 1, NULL);
xTimerStart(xHeartbeatTimer, 0);
xTimerStart(xWatchdogTimer, 0);
vTaskStartScheduler();
while (1);
}
watchdog_fed) should only be set in the task that represents the system's heartbeat — not by every task independently.Implement a FreeRTOS task notification system as a lightweight alternative to binary semaphores. Use xTaskNotifyGive() from an ISR and ulTaskNotifyTake() in the task. Benchmark: signal 100,000 times and compare latency to binary semaphore. Task notifications skip the semaphore overhead (no handle lookup, direct to task TCB) — on Cortex-M, they're typically 30–50% faster than semaphores.