1. Introduction
This project implements a simple real-time operating system (RTOS) application on the RT-Thread RT‑Spark development board using FreeRTOS and STM32CubeIDE.
Four concurrent tasks (threads) run on the board:
- Display the temperature on the LCD using the AHT21 sensor
- Adjust RGB LED brightness using a potentiometer
- Display an incrementing counter on the LCD
- Blink a status LED
The LCD is shared between tasks, so a mutex is used to protect exclusive access. All tasks run in infinite loops and cooperate by delaying themselves after each update so that other tasks can run.
2. Hardware and software requirements
2.1 Hardware
- RT-Thread RT‑Spark development board (STM32-based)
- Onboard 1.3" 240×240 LCD (ST7789, FSMC/8080 interface)
- 1Onboard AHT2 temperature and humidity sensor (I²C)
- External RGB LED
- Externalpotentiometer (connected to ADC pin)
- Onboard user LED
- Micro USB cable for power and programming
- STM32CubeIDE (used for the.ioc and C code)
- FreeRTOS (integrated through STM32CubeMX / CubeIDE)
3. Project overview and task design
The application uses four FreeRTOS tasks:
1.Temperature display task
- Reads temperature from the AHT21 sensor via I²C
- Converts the value to human-readable format
- Uses a LCD mutex to safely write the temperature to the LCD
2.RGB brightness control task
- Reads the potentiometer using ADC
- Maps the ADC value to a PWM duty cycle
- Updates one or more channels of the RGB LED to change brightness
3.Counter display task
- Maintains an incrementing integer counter
- Periodically locks the LCD mutex, updates the counter on the LCD, and unlocks the mutex
4.LED blink task
- Toggles a simple status LED at a fixed interval
- Used as a visual heartbeat to show the RTOS is running
The main function:
- Initializes the hardware and peripherals
- Clears the LCD
- Creates all four tasks
- Starts the scheduler
- Optionally configures low-power/sleep mode
4. Step‑by‑step guide
4.1 Create the STM32CubeIDE project
1.Create a new STM32 project in STM32CubeIDE.
- File → New → STM32 Project
- Select the MCU used by the RT‑Spark board (or the RT‑Spark board itself if it appears in the board database).
- Give it a name such as
FreeRTOS_RT_Spark_Project.
2.Generate the base project with HAL drivers enabled.
- Accept default clock settings initially.
- Finish the wizard to open the
.iocconfiguration.
4.2.ioc pinout and peripheral configuration
4.2.1 Configure I²C for AHT20 sensor
1.Open the Pinout & Configuration tab in the.ioc.
2. Enable I2C peripheral (e.g., I2C1 or I2C2, depending on RT‑Spark schematic and your docs).
3. Set the pins to match the RT‑Spark AHT21 connection:
- SCL pin:
GPIO AHT SCL(from schematic Fig. 1.3) - SDA pin:
GPIO AHT SDA
4. In I2C settings:
- Set mode to I2C
- Configure speed (e.g., standard mode 100 kHz unless you changed it)
Add: screenshot of the I²C configuration and the pins used.
4.2.2 Configure the LCD interface (FSMC/8080)
1. Enable the FSMC/FMC (Flexible Static Memory Controller) used to drive the ST7789 LCD via 8080 parallel interface.
2. Configure the data linesDB0–DB7, control lines CS, DC, WR, RD, and backlight pin (LCD_BL) as per RT‑Spark schematic (Fig. 1.1).
3. Set the corresponding pins as required by the existing LCD library you used (this should match the repo code).
4. Make sure the GPIO speed and output type match the requirements of the LCD driver code.
4.2.3 Configure the RGB LED and potentiometer
1.RGB LED (PWM):
- Choose a TIMx timer and enable PWM mode on channels connected to the RGB LED pins (as defined by the RT‑Spark board).
- Configure each color channel as PWM output with suitable frequency (e.g., 1–10 kHz).
- Map the pins to the RGB LED pads.
2.Potentiometer (ADC):
- Enable an ADC peripheral (e.g.,
ADC1). - Select the channel connected to the potentiometer (refer to your docs / board manual).
- Set continuous or single conversion mode depending on how your code reads it.
4.2.4 Configure the status LED and other GPIOs
1. Identify the LED pin used for blinking (onboard user LED).
2. Set it as GPIO Output in the Pinout.
3. Configure any other GPIOs required by your code (e.g., reset pins, backlight enable, etc.).
4.3 Enable FreeRTOS in STM32CubeMX
1. In the .iocMiddleware section, enable FreeRTOS.
2. Select a basic heap scheme (e.g., heap_4) that matches your project settings.
3. Configure:
- Minimum stack size per task
- Total heap size (enough for 4 tasks + other allocations)
1. Click Project → Generate Code (or press the gear icon) to let STM32CubeIDE regenerate source files based on.ioc.
2. Add / import your LCD driver and AHT21 sensor driver into the project:
- Copy the LCD driver
.cand.hfiles intoCore/SrcandCore/Inc(or appropriate folders). - Copy the AHT21 driver
.cand.hfiles as well. - Ensure the include paths in Project Properties → C/C++ General → Paths and Symbols are correct.
3. Include these drivers in your application code (e.g., main.c or a dedicated app.c):
#include "lcd_driver.h"
#include "aht20.h"4.5 Implement the mutex and tasks
Now we tie everything together using FreeRTOS.
4.5.1 Create the LCD mutex
In your application code (e.g., near the top of main.c):
SemaphoreHandle_t xLcdMutex;In main() before starting the scheduler:
xLcdMutex = xSemaphoreCreateMutex();
if (xLcdMutex == NULL) {
// Handle error (e.g., trap here)
}Any time you access the LCD in a task:
if (xSemaphoreTake(xLcdMutex, portMAX_DELAY) == pdTRUE) {
// Safe LCD operations here
// e.g., LCD_ShowString(...);
xSemaphoreGive(xLcdMutex);
}4.5.2 Temperature display task
Pseudo-structure (adapt it to your existing code):
void vTempTask(void *pvParameters)
{
float temperature;
for (;;)
{
// Read temperature from AHT20
temperature = AHT20_ReadTemperature();
// Display on LCD with mutex
if (xSemaphoreTake(xLcdMutex, portMAX_DELAY) == pdTRUE) {
LCD_ShowTemperature(temperature); // your function
xSemaphoreGive(xLcdMutex);
}
vTaskDelay(pdMS_TO_TICKS(1000)); // Update every 1 second
}
}4.5.3 RGB brightness task (pot-controlled)
void vRgbTask(void *pvParameters)
{
uint16_t adcValue;
uint16_t dutyCycle;
for (;;)
{
// Read potentiometer
adcValue = Read_Potentiometer_ADC(); // your function
// Map ADC value to PWM duty cycle
dutyCycle = MapToPWM(adcValue); // e.g., 0–4095 to 0–100%
// Update RGB LED brightness
Set_RGB_Brightness(dutyCycle); // your function(s)
vTaskDelay(pdMS_TO_TICKS(100)); // Adjust update rate as needed
}
}4.5.4 Counter display task
void vCounterTask(void *pvParameters)
{
uint32_t counter = 0;
for (;;)
{
counter++;
if (xSemaphoreTake(xLcdMutex, portMAX_DELAY) == pdTRUE) {
LCD_ShowCounter(counter); // show on LCD, e.g., at fixed position
xSemaphoreGive(xLcdMutex);
}
vTaskDelay(pdMS_TO_TICKS(500)); // 0.5 second increment
}
}4.5.5 LED blink task
void vBlinkTask(void *pvParameters)
{
for (;;)
{
Toggle_Status_LED(); // HAL_GPIO_TogglePin(...)
vTaskDelay(pdMS_TO_TICKS(250)); // Blink every 250 ms
}
}4.6 Create tasks and start the scheduler
In main() after hardware initialization and before vTaskStartScheduler():
int main(void)
{
// HAL initialization
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init(); // or chosen I2C
MX_ADC1_Init(); // or chosen ADC
MX_TIMx_Init(); // timers for PWM
MX_FMC_Init(); // LCD via FSMC
MX_FREERTOS_Init(); // if using generated code, or manual config
// Initialize LCD and AHT20
LCD_Init();
AHT20_Init();
// Clear LCD
LCD_Clear();
// Create mutex
xLcdMutex = xSemaphoreCreateMutex();
// Create tasks
xTaskCreate(vTempTask, "TempTask", 256, NULL, 2, NULL);
xTaskCreate(vRgbTask, "RgbTask", 256, NULL, 2, NULL);
xTaskCreate(vCounterTask, "CounterTask", 256, NULL, 1, NULL);
xTaskCreate(vBlinkTask, "BlinkTask", 128, NULL, 1, NULL);
// Start scheduler
vTaskStartScheduler();
// Should never reach here
while (1) {}
}4.7 Build, flash, and test1.Build the project in STM32CubeIDE.
- Fix any missing includes or type errors, using your repo as reference.
2.Connect the RT‑Spark board via USB.
3. Select the correct debug configuration and flash the firmware.
4. Reset the board and observe:
- LCD shows temperature and counter (in different areas or styles).
- RGB LED brightness changes as you turn the potentiometer.
- Status LED blinks at a regular rate.
5. How it works (summary)
- FreeRTOS manages four tasks, each with an infinite loop and its own responsibility.
- Tasks cooperate by calling
vTaskDelay(), which yields the CPU so other tasks can run. - The LCD mutex ensures only one task uses the LCD at a time, preventing overlapping writes.
- The AHT21 sensor provides real-time temperature data over I²C.
- The potentiometer input is read via ADC and mapped to PWM duty cycles to control RGB brightness.
- The LED blink task provides a simple visual heartbeat for the RTOS scheduler.
6. Possible improvements
You can expand this lab into a more advanced RTOS project:
- Add humidity display from AHT21 and show both Temp/Humidity on the LCD.
- Use queues to send data between tasks (e.g., sensor task → display task).
- Add button inputs to change display modes or RGB patterns.
- Implement low-power modes more aggressively and wake on timers or interrupts.
7. Results and Output













Comments