Two sensors, two buses, six tasks, one watchdog.
This tutorial makes a firmware production-ready: strict periodic scheduling, stack size validation, and hardware-level crash recovery.
Ready? Let's go.
Every sensor task in this project uses vTaskDelayUntil instead of vTaskDelay.
The difference matters in real embedded systems.
vTaskDelay(100) means "sleep for 100ms after I finish executing."
If the task takes 10ms to read a sensor, the actual period is 110ms.
Over time this drifts.
vTaskDelayUntil measures from the moment the task last woke up.
The period is exactly 100ms regardless of execution time.
The gyroscope is read every 50ms.
The accelerometer every 100ms.
Both periods are guaranteed to be constant for as long as the firmware runs.
This matters for signal processing, data logging, and any application where irregular sampling introduces errors.
Every FreeRTOS task has a fixed stack allocated at creation.
FreeRTOS fills it with a known pattern at startup.
As the task runs, function calls consume stack from the top.
The watermark is the lowest point ever reached: The minimum free space observed since the task started.
Every 5 seconds, stats_task calls uxTaskGetStackHighWaterMark() on every task and prints the result on UART:
--- stack watermarks (words free) --- watchdog: 98 gyro : 187 accel : 142 display : 201 stats : 89 leds : 112 ---
A value close to zero means the stack is about to overflow.
A large value means the stack is oversized and RAM is being wasted.
This output tells you exactly how to tune each stack for the real workload.
The Independent Watchdog is a hardware timer clocked by its own oscillator, completely independent of the CPU and the RTOS.
Once started it cannot be stopped.
If the firmware does not write a specific value to a register within the configured timeout, the MCU resets unconditionally.
watchdog_task runs at the highest priority in the system (priority 4) and pets the IWDG every 500ms using vTaskDelayUntil.
The timeout is set to 1000ms, giving a 2x safety margin.
If any task enters an infinite loop, deadlocks on a mutex, or otherwise monopolises the CPU long enough to prevent watchdog_task from running, the IWDG fires and the MCU reboots.
No human intervention required, no silent hang, the system recovers on its own.
This is not a FreeRTOS feature, it is a hardware feature of the STM32.
FreeRTOS simply provides the task structure to use it correctly.
The gyroscope (L3GD20) runs on SPI1.
The accelerometer (LSM303DLHC) runs on I2C1.
Each has its own mutex.
Each has its own task.
Each has its own queue feeding display_task.
The gyroscope reads at 50ms, the accelerometer at 100ms. display_task is driven by the gyroscope queue and picks up accelerometer data whenever it is available.
On cycles where no new accelerometer data has arrived, it prints "---" for those columns.
GX GY GZ | AX AY AZ -12 34 1024 | -8 22 -15 -11 33 1023 | --- --- --- -13 35 1022 | -9 21 -14
The two buses run truly in parallel.
Neither blocks the other.
Neither shares any register or hardware resource.
The mutexes exist as a matter of good practice for when a second device is added to each bus.
Connect the following pins to your logic analyzer:
$ cd firmware/stm32f3discovery/ $ chmod +x run.sh $ ./run.sh all
Six tasks running concurrently, two hardware buses active simultaneously, a watchdog ensuring the system never hangs silently, and stack watermarks giving a clear picture of memory usage.
This is what production embedded firmware looks like.
Good job, you did it ![]()
Add new comment