Electronics - FreeRTOS - Timer, task notification and event group on STM32F3Discovery with I2C and UART

Mutexes protect, semaphores signal.

But in some situations you need a bit more: A callback that fires on its own schedule, a direct line between an ISR and a task, and a way to say "do not start until everything is ready."

This tutorial introduces 3 FreeRTOS mechanisms that round out the toolkit: software timers, task notifications, and event groups.

Let's get started.

 

Shopping list

  • Logic Analyzer for I2C and UART bus communication

 

Software timer

A software timer calls a function automatically after a given delay, or repeatedly at a fixed period.

No dedicated task, no vTaskDelay loop.

FreeRTOS handles it internally via a timer daemon task that wakes up, fires the callback, and goes back to sleep.

Two modes are available.

  • One-shot fires once and stops.
  • Auto-reload repeats forever at the configured period.

In this project, a 500ms auto-reload timer toggles PE8.

The blue LED blinks at a steady rhythm without any task knowing it exists.

The callback runs inside the timer daemon, which means two rules apply without exception: It must never block, and it must be short.

A slow callback delays every other timer in the system.

 

Task notification

A task notification is the lightest signalling mechanism in FreeRTOS.

Every task already has a built-in 32-bit notification value in its control block.

No object to create, no heap allocation, no queue involved.

In a previous tutorial, the button ISR gave a binary semaphore to wake button_task.

Here, the ISR calls vTaskNotifyGiveFromISR() directly on the task handle.

button_task blocks on ulTaskNotifyTake() and wakes up the moment the notification arrives.

The difference with a semaphore is minimal in this case, but task notifications are meaningfully faster and use less RAM.

The trade-off is that a notification targets one specific task by handle.

A semaphore or queue can be consumed by any task.

 

Event group

An event group is a 24-bit register where each bit represents an independent event.

Tasks can set bits, clear bits, and wait for combinations of bits to be set before proceeding.

In this tutorial, display_task does not start printing until two bits are both set: one confirming that I2C is ready, one confirming that UART is ready.

The task calls xEventGroupWaitBits() with the AND condition and blocks until both bits are set simultaneously.

The bits are set in main() right after each peripheral initialisation.

Since this happens before the scheduler starts, display_task finds both bits already set the first time it runs and proceeds immediately.

If one of the initialisations had failed and the bit was never set, display_task would block forever, which is a clean way to surface hardware problems.

Two conditions are trivial to demonstrate here, but event groups scale well.

Waiting for five peripherals, three sensor readings, and a network connection to all be ready before launching a state machine is exactly the kind of problem event groups solve cleanly.

 

Reading signals

Connect the following pins to your logic analyzer:

  • PB6: I2C SCL
  • PB7: I2C SDA
  • PA9: UART TX

 

Compiling, flashing, analyzing

$ cd firmware/stm32f3discovery/

$ chmod +x run.sh

$ ./run.sh all

 

What you should see

PE8 blinks at 500ms driven entirely by the software timer.

PE9 through PE15 rotate independently via led_task.

The UART stream starts only after both peripherals are confirmed ready, then runs continuously.

Pressing the USER button inserts a Button pressed! message via task notification.

 

Conclusion

Three mechanisms, three different problems solved cleanly with software timers, task notifications and event groups.

Together with tasks, queues, mutexes, and semaphores, the full FreeRTOS toolkit is now on the table.

Good job, you did it cool

Add new comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.