A comprehensive FreeRTOS demonstration project showcasing real-time task management, inter-task communication, and hardware abstraction on the STM32G071RB microcontroller
- Overview
- Features
- Hardware Requirements
- Software Prerequisites
- Project Structure
- System Architecture
- Getting Started
- Task Description
- RTOS Concepts Demonstrated
- Hardware Configuration
- Operation Flow
- Code Examples
- UART Debug Output
- Configuration
- Troubleshooting
- Future Enhancements
- License
This project demonstrates professional-grade embedded RTOS design patterns using FreeRTOS on an STM32G071RB microcontroller. It bridges the gap between basic "blink" examples and production-ready firmware by implementing:
- Multi-task coordination with proper priority management
- ISR-to-task communication using task notifications
- Inter-task messaging via FreeRTOS queues
- Hardware abstraction for LEDs, buttons, and PWM control
- Deterministic timing using
vTaskDelayUntil() - Pattern-based control with task suspension/resumption
The system orchestrates three LED controllers (blinking and PWM fading) while responding to button press events that trigger predefined LED patterns, all managed by FreeRTOS task scheduler.
- β 5 Concurrent FreeRTOS Tasks with different priorities
- β ISR-Safe Communication using task notifications
- β Queue-Based Messaging for pattern coordination
- β PWM Control with smooth LED fading (TIM1 Channel 4)
- β External Interrupt handling (EXTI13) with debouncing
- β UART Debug Interface (115200 baud) for runtime diagnostics
- β Modular Code Structure with hardware abstraction layers
- β Heap 4 Memory Management (6KB heap for dynamic allocation)
- β Precise Timing Control using FreeRTOS delay functions
| Component | Specification |
|---|---|
| Microcontroller | STM32G071RB (Cortex-M0+, 64KB Flash, 36KB RAM) |
| Development Board | STM32 Nucleo-G071RB or compatible |
| LEDs | 3x LEDs (or use onboard LEDs) |
| Button | 1x Push button (or use USER button) |
| Debug Interface | ST-Link V2/V3 (integrated on Nucleo board) |
| UART Adapter | USB-to-UART (optional, for debug output) |
| Peripheral | Pin | Function | Configuration |
|---|---|---|---|
| Green LED | PC10 | Digital Output | Periodic blink (1Hz) |
| Blue LED | PC11 | PWM Output (TIM1_CH4) | Smooth fading effect |
| Red LED | PC12 | Digital Output | Blink pattern control |
| User Button | PC13 | Digital Input (EXTI13) | Interrupt-driven events |
| UART2 TX | PA2 | Serial Output | Debug logging (115200 baud) |
| UART2 RX | PA3 | Serial Input | Reserved for future use |
-
STM32CubeIDE (v1.10.0 or later)
- Download from STMicroelectronics
- Includes integrated toolchain (arm-none-eabi-gcc)
-
STM32CubeMX (optional, for hardware reconfiguration)
- Project file:
03_FreeRTOSProject.ioc
- Project file:
-
Serial Terminal (for UART debugging)
- Examples: PuTTY, Tera Term, CoolTerm, or
screen
- Examples: PuTTY, Tera Term, CoolTerm, or
- STM32G0xx HAL Driver (v1.4.x)
- CMSIS Core (v5.x)
- FreeRTOS Kernel (v10.3.1)
- Memory management:
heap_4.c - Port: ARM Cortex-M0
- Memory management:
FreeRTOSProject/
β
βββ Core/
β βββ Inc/ # Header files
β β βββ main.h # Main application header
β β βββ FreeRTOSConfig.h # FreeRTOS configuration
β β βββ led.h # LED control interface
β β βββ button.h # Button/interrupt interface
β β βββ pwm.h # PWM control interface
β β βββ stm32g0xx_*.h # HAL/peripheral headers
β β
β βββ Src/ # Source files
β β βββ main.c # Application entry & task definitions
β β βββ app_freertos.c # FreeRTOS application code
β β βββ led.c # LED hardware abstraction
β β βββ button.c # Button & EXTI interrupt handlers
β β βββ pwm.c # TIM1 PWM configuration
β β βββ stm32g0xx_it.c # Interrupt handlers
β β βββ system_stm32g0xx.c # System initialization
β β
β βββ Startup/
β βββ startup_stm32g071r8tx.s # MCU startup assembly
β
βββ Drivers/ # STM32 HAL & CMSIS drivers
β βββ STM32G0xx_HAL_Driver/
β βββ CMSIS/
β
βββ Middlewares/ # Third-party middleware
β βββ Third_Party/
β βββ FreeRTOS/
β βββ Source/ # FreeRTOS kernel source
β
βββ STM32G071R8TX_FLASH.ld # Linker script
βββ 03_FreeRTOSProject.ioc # STM32CubeMX project file
βββ README.md # This file
βββ LICENSE # MIT License
Priority 3 (Highest): [Button Controller Task]
|
| Task Notification
β
Priority 2: [Green LED] [Blue LED PWM] [Red LED]
β
| Queue Message
|
Priority 1 (Lowest): [Pattern Generator Task]
βββββββββββββββββββββββ
β Button Press (ISR) β β External Interrupt (EXTI13)
ββββββββββββ¬βββββββββββ
β vTaskNotifyGiveFromISR()
β
βββββββββββββββββββββββ
β Button Task β β Cycles pattern number (0β1β2β0)
ββββββββββββ¬βββββββββββ
β xQueueSend()
β
βββββββββββββββββββββββ
β Pattern Generator β β Receives pattern from queue
ββββββββββββ¬βββββββββββ
β vTaskSuspend() β Execute Pattern β vTaskResume()
β
βββββββββββββββββββββββββββββββββββββββ
β Green LED β Blue PWM β Red LED β β Suspended during pattern
βββββββββββββββββββββββββββββββββββββββ
git clone https://github.com/kgsinh/FreeRTOSProject.git
cd FreeRTOSProject- Launch STM32CubeIDE
- Select File > Open Projects from File System
- Navigate to the cloned directory
- Click Finish
# Using STM32CubeIDE GUI:
Project > Build Project
# Or using command line:
# (Assuming STM32CubeIDE is installed at default location)
cd Debug
make clean
make all- Connect STM32 Nucleo board via USB
- Select Run > Debug (F11) or Run > Run (Ctrl+F11)
- STM32CubeIDE will automatically flash and start debugging
# Linux/Mac
screen /dev/ttyACM0 115200
# Windows (using PuTTY)
# Configure: COM port, 115200 baud, 8N1Priority: 2 | Stack: 128 words
void vGreenLedControllerTask(void *pvParameters)- Function: Toggles Green LED at 1 Hz (500ms ON, 500ms OFF)
- Timing Method:
vTaskDelayUntil()for precise periodic execution - Hardware: PC10 (GPIO output)
Priority: 2 | Stack: (128 words)
void vBlueLedControllerTask(void *pvParameters)- Function: Smooth PWM fading effect (0% β 100% β 0%)
- Timing Method:
vTaskDelayUntil()at 100ms intervals - Hardware: PC11 (TIM1_CH4, PWM output @ 1kHz)
- Fade Steps: 10% increments/decrements
Priority: 2 | Stack: 128 words
void vRedLedControllerTask(void *pvParameters)- Function: Toggles Red LED at 1 Hz (500ms ON, 500ms OFF)
- Timing Method:
vTaskDelayUntil()for consistent timing - Hardware: PC12 (GPIO output)
Priority: 3 (Highest) | Stack: 256 words
void vButtonControllerTask(void *pvParameters)- Function: Waits for button press notification from ISR
- Trigger: External interrupt (EXTI13) on falling edge
- Action: Cycles through patterns (0 β 1 β 2 β 0) and sends to queue
- Timeout: 5 second notification timeout for status monitoring
Priority: 1 (Lowest) | Stack: 256 words
void vPatternGeneratorTask(void *pvParameters)- Function: Executes LED patterns based on queue messages
- Blocking: Waits indefinitely on queue (
portMAX_DELAY) - Coordination: Suspends all LED tasks during pattern execution
| Pattern | Description | LED(s) Used | Duration |
|---|---|---|---|
| 0 | Blink Green 3 times | Green (PC10) | ~1.8s |
| 1 | Fade Blue LED in/out | Blue (PC11) | ~2.0s |
| 2 | Blink Red 5 times | Red (PC12) | ~2.0s |
This project serves as a comprehensive learning resource for embedded RTOS development:
- Creation:
xTaskCreate()with configurable priorities and stack sizes - Scheduling: Preemptive priority-based scheduler
- Suspension:
vTaskSuspend()/vTaskResume()for coordinated control - Handles: Task handles for inter-task references
- Periodic Tasks:
vTaskDelayUntil()for drift-free periodic execution - Delay:
vTaskDelay()for relative delays - Tick Configuration: 1ms tick rate (
configTICK_RATE_HZ = 1000)
- Task Notifications: Lightweight, ISR-safe signaling
vTaskNotifyGiveFromISR()from interrupt contextulTaskNotifyTake()in task context
- Queues: Type-safe message passing
xQueueCreate()for queue creationxQueueSend()/xQueueReceive()for messaging
- NVIC Priority: Interrupt priority 5 (compatible with FreeRTOS)
- ISR Safety: Using
FromISR()API variants - Context Switch:
portYIELD_FROM_ISR()for immediate task switching
- Heap Scheme:
heap_4.c(coalescence algorithm) - Total Heap: 6144 bytes (6 KB)
- Allocation: Dynamic task and queue creation
// PC10, PC11, PC12 - Output mode, high speed
GPIOC->MODER |= (1U << (2*10)); // PC10: Green LED
GPIOC->MODER |= (2U << (2*11)); // PC11: Blue LED (AF mode)
GPIOC->MODER |= (1U << (2*12)); // PC12: Red LED// PC13 - Input mode with pull-up
GPIOC->MODER &= ~(3U << (2*13)); // Input mode
GPIOC->PUPDR |= (1U << (2*13)); // Pull-up enabled| Parameter | Value | Description |
|---|---|---|
| Timer Clock | 16 MHz | System clock (HSI) |
| Prescaler (PSC) | 79 | Timer clock = 200 kHz |
| Auto-Reload (ARR) | 999 | PWM frequency = 200 Hz |
| Channel | 4 (PC11) | TIM1_CH4 |
| Mode | PWM Mode 1 | Active high |
| Output | Main output enabled | BDTR_MOE set |
PWM Frequency Calculation:
f_PWM = f_timer / (ARR + 1)
= 200 kHz / 1000
= 200 Hz
// EXTI13 - Falling edge trigger
EXTI->EXTICR[3] |= (2U << 8); // Connect to Port C
EXTI->IMR1 |= (1U << 13); // Unmask interrupt
EXTI->FTSR1 |= (1U << 13); // Falling edge trigger
NVIC_SetPriority(EXTI4_15_IRQn, 5);
NVIC_EnableIRQ(EXTI4_15_IRQn);// 115200 baud, 8N1, no flow control
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;1. System starts β FreeRTOS scheduler begins
2. All 5 tasks enter their while(1) loops
3. LED controller tasks run independently:
- Green LED: Toggle every 500ms
- Blue LED: Fade PWM duty cycle (0-100-0%)
- Red LED: Toggle every 500ms
4. Button task waits for notification (5s timeout)
5. Pattern generator waits on queue (indefinite block)
1. User presses button (PC13 pulled low)
β
2. EXTI13 interrupt fires β EXTI4_15_IRQHandler()
β
3. ISR sends notification β vTaskNotifyGiveFromISR()
β
4. Button task wakes up β ulTaskNotifyTake() returns
β
5. Button task cycles pattern: (N + 1) % 3
β
6. Pattern sent to queue β xQueueSend()
β
7. Pattern generator wakes up β xQueueReceive() returns
β
8. Suspend all LED tasks β vTaskSuspend() Γ 3
β
9. Turn off all LEDs (clean slate)
β
10. Execute selected pattern (0, 1, or 2)
β
11. Resume all LED tasks β vTaskResume() Γ 3
β
12. Return to normal operation
// In ISR context (button.c)
void EXTI4_15_IRQHandler(void)
{
if(EXTI->FPR1 & (1U << 13))
{
EXTI->FPR1 |= (1U << 13); // Clear flag
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(xButtonTaskHandle, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
// In task context (main.c)
void vButtonControllerTask(void *pvParameters)
{
while(1)
{
uint32_t notification = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(5000));
if(notification > 0)
{
pattern = (pattern + 1) % 3;
xQueueSend(xPatternQueue, &pattern, portMAX_DELAY);
}
}
}// Create queue in main()
xPatternQueue = xQueueCreate(5, sizeof(uint8_t));
// Producer (Button Task)
uint8_t pattern = 2;
xQueueSend(xPatternQueue, &pattern, portMAX_DELAY);
// Consumer (Pattern Generator Task)
uint8_t receivedPattern;
if(xQueueReceive(xPatternQueue, &receivedPattern, portMAX_DELAY) == pdPASS)
{
// Process pattern
}void vGreenLedControllerTask(void *pvParameters)
{
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(500);
while(1)
{
led_on(10);
vTaskDelayUntil(&xLastWakeTime, xFrequency);
led_off(10);
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}void set_pwm_duty_cycle(uint8_t duty_percent)
{
if(duty_percent > 100)
duty_percent = 100;
TIM1->CCR4 = (TIM1->ARR + 1) * duty_percent / 100;
}
// Smooth fading (in Blue LED task)
void pwm_fade(void)
{
static uint16_t brightness = 0;
static uint8_t direction = 1;
if(direction) {
brightness += 10;
if(brightness >= 999) direction = 0;
} else {
brightness -= 10;
if(brightness <= 0) direction = 1;
}
TIM1->CCR4 = brightness;
}The system provides real-time debug information via UART2 (115200 baud):
=== BUTTON TASK STARTED ===
Pattern 0 sent to Pattern Generator Task
Pattern Generator Task received pattern: 0
Executing Pattern 0: Blink Green LED 3 times
Resumed LED controller tasks after pattern execution
Pattern 1 sent to Pattern Generator Task
Pattern Generator Task received pattern: 1
Executing Pattern 1: Fade Blue LED in and out
Resumed LED controller tasks after pattern execution
Pattern 2 sent to Pattern Generator Task
Pattern Generator Task received pattern: 2
Executing Pattern 2: Blink Red LED 5 times
Resumed LED controller tasks after pattern execution
int __io_putchar(int ch)
{
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}All printf() calls are automatically redirected to UART2.
File: Core/Inc/FreeRTOSConfig.h
| Parameter | Value | Description |
|---|---|---|
configUSE_PREEMPTION |
1 | Preemptive scheduling enabled |
configCPU_CLOCK_HZ |
16000000 | 16 MHz (HSI oscillator) |
configTICK_RATE_HZ |
1000 | 1 ms tick resolution |
configMAX_PRIORITIES |
56 | Maximum priority levels |
configMINIMAL_STACK_SIZE |
128 | Minimum stack (words) |
configTOTAL_HEAP_SIZE |
6144 | Total heap size (bytes) |
configUSE_TASK_NOTIFICATIONS |
1 | Task notifications enabled |
configUSE_MUTEXES |
1 | Mutex support enabled |
configUSE_TIMERS |
1 | Software timers enabled |
- System Clock: 16 MHz (HSI - High-Speed Internal oscillator)
- AHB Prescaler: 1 (HCLK = 16 MHz)
- APB Prescaler: 1 (PCLK = 16 MHz)
- Timer Clock: 16 MHz (no prescaler on APB)
Possible Causes:
- β Check power supply and LED connections
- β Verify GPIO initialization (
led_gpio_init()called) - β Confirm FreeRTOS scheduler started (
vTaskStartScheduler()) - β Check task creation was successful (handle != NULL)
Debug Steps:
// Add to main.c before while(1)
if(xGreenTaskHandle == NULL) {
printf("ERROR: Green task creation failed!\n\r");
}Possible Causes:
- β Interrupt not enabled (
button_enable_interrupt()called) - β Task handle not set (
xButtonTaskHandleis NULL) - β EXTI configuration error (check pin mapping)
Debug Steps:
// Add to EXTI interrupt handler
printf("Button ISR triggered!\n\r");Possible Causes:
- β TIM1 not initialized (
pwm_init()called) - β GPIO alternate function not set (AF2 for PC11)
- β Main output not enabled (
BDTR_MOEbit)
Verification:
// Check CCR4 register value
printf("TIM1->CCR4 = %lu\n\r", TIM1->CCR4);Possible Causes:
- β Stack overflow (increase
configMINIMAL_STACK_SIZE) - β Heap exhausted (increase
configTOTAL_HEAP_SIZE) - β ISR priority too high (use 5 or higher for FreeRTOS compatibility)
- β
configASSERT()triggered
Debug Steps:
- Enable FreeRTOS stack overflow checking:
#define configCHECK_FOR_STACK_OVERFLOW 2- Monitor heap usage:
size_t freeHeap = xPortGetFreeHeapSize();
printf("Free heap: %u bytes\n\r", freeHeap);Possible Causes:
- β Incorrect baud rate (ensure 115200)
- β Wrong COM port selected
- β UART not initialized (
MX_USART2_UART_Init()called)
Verification:
- Check UART settings: 115200 baud, 8 data bits, no parity, 1 stop bit (8N1)
- Verify TX pin (PA2) is properly connected
-
Event Groups for Synchronization
- Replace
vTaskSuspend()/vTaskResume()with Event Groups - More scalable and cleaner synchronization mechanism
- Replace
-
UART Command Interface
- Implement pattern selection via serial commands
- Add runtime configuration (LED brightness, timing)
-
Software Timers
- Button debouncing using FreeRTOS timers
- Automatic pattern timeout/reset
-
Runtime Statistics
- Enable
configGENERATE_RUN_TIME_STATS - Monitor task execution time and CPU usage
- Enable
-
Low Power Modes
- Implement tickless idle mode
- Use STM32 low-power modes during idle
-
Enhanced Patterns
- Music-like rhythm sequencing
- User-configurable pattern library
- Pattern chaining and loops
-
Error Handling
- Add comprehensive error detection
- Implement recovery mechanisms
- LED error indication codes
-
Unit Testing
- Add unit tests for peripheral drivers
- Mock HAL for host-based testing
- Follow existing code style and indentation
- Add comments for complex logic
- Update documentation for API changes
- Test on hardware before submitting PR
This project is licensed under the MIT License - see the LICENSE file for details.
- FreeRTOS Kernel: MIT License (Amazon FreeRTOS)
- STM32 HAL Driver: BSD-3-Clause (STMicroelectronics)
- CMSIS: Apache-2.0 (ARM Limited)
- FreeRTOS Book (Mastering the FreeRTOS Real Time Kernel)
- STM32 MOOC
- Cortex-M0+ Programming
kgsinh
- GitHub: [@kgsinh](https://github.com/kgsinh