This project implements a Real-Time Operating System (RTOS) on the STM32F407ZGT6 microcontroller using FreeRTOS. With the RT-Thread RT-Spark development board, we integrate an LCD display, AHT20 temperature/humidity sensor, potentiometer, and LEDs. The activity demonstrates cooperative multitasking, mutex protection, and embedded C programming for practical IoT applications. With that, let's begin making our first RTOS!
Step 1: Obtain Hardware and Software MaterialsBefore starting this project, it’s important to prepare all the necessary hardware and software. Having these materials ready ensures you can set up the STM32 environment smoothly, connect peripherals without issues, and run FreeRTOS tasks efficiently. With everything on hand, you’ll avoid delays and keep the workflow seamless.
Hardware
- RT-Thread RT-Spark Development Board (STM32F407ZGT6 MCU)
- RGB LED + potentiometer
- Standard LED for blinking task
Software
- STM32CubeIDE
- FreeRTOS libraries and RT-Thread BSP (Board Support Package) [to be found through this link: https://www.st.com/content/st_com/en/support/learning/stm32-education/stm32-moocs/freertos-common-microcontroller-software-interface-standard-osv2.html ]
Since the RT-Spark board already includes the LCD and AHT20 sensor, the only external wiring you need is for the potentiometer and LED. The potentiometer is mounted on the breadboard and connected to the RT-Spark board so its analog output can be read through an ADC pin. The board then drives the LED, which is also wired on the breadboard with a series resistor, allowing you to test both brightness control and blinking tasks.
- Potentiometer → RT-Spark board (ADC input)
- RT-Spark board (GPIO output) → LED + resistor on breadboard
The wiring can be shown in this image:
This simple wiring ensures that your external components integrate seamlessly with the built-in peripherals, keeping the setup clean and easy to debug.
Step 3: IOC ConfigurationAfter wiring the potentiometer and LED to the RT-Spark board, the next step is configuring the microcontroller. Proper IOC setup ensures that each peripheral — ADC for the potentiometer, GPIO for the LED, and built-in modules — is initialized correctly. This configuration lays the foundation for FreeRTOS threads to run seamlessly in the later coding stage.
In STM32CubeIDE, configure the following:
ADC1: Enable one channel to read the potentiometer’s analog input.GPIO: Set the LED pin as a digital output for blinking control.- FreeRTOS Middleware: Enable FreeRTOS and define four threads (temperature display, RGB brightness adjustment, counter display, LED blink).
- System Clock: Verify that the clock tree is stable to support FSMC (for LCD) and I²C (for the
AHT20sensor).
By completing this IOC setup, you ensure that all peripherals are properly mapped and ready for multitasking under FreeRTOS.
Step 4: Coding and DebuggingThread Creation Example
// Mutex for LCD access
osMutexId lcdMutexHandle;
osMutexDef(lcdMutex);
void StartTempTask(void const * argument) {
for(;;) {
osMutexWait(lcdMutexHandle, osWaitForever);
float temp = AHT20_ReadTemperature();
LCD_DisplayFloat(0, 0, temp);
osMutexRelease(lcdMutexHandle);
osDelay(1000);
}
}
void StartCounterTask(void const * argument) {
static int counter = 0;
for(;;) {
osMutexWait(lcdMutexHandle, osWaitForever);
LCD_DisplayInt(0, 20, counter++);
osMutexRelease(lcdMutexHandle);
osDelay(500);
}
}
void StartLEDBlinkTask(void const * argument) {
for(;;) {
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
osDelay(1000);
}
}
void StartRGBTask(void const * argument) {
for(;;) {
uint16_t adcVal = HAL_ADC_GetValue(&hadc1);
SetRGBBrightness(adcVal);
osDelay(200);
}
}Main Initialization
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C2_Init();
MX_FSMC_Init();
MX_ADC1_Init();
MX_FREERTOS_Init();
// Create LCD mutex
lcdMutexHandle = osMutexCreate(osMutex(lcdMutex));
// Start scheduler
osKernelStart();
while (1) {
// MCU enters sleep mode when idle
}
}Here is the main code used for this project:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "drv_lcd.h"
#include "drv_aht21.h"
#include <stdio.h>
#include <stdlib.h> // For abs()
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
TIM_HandleTypeDef htim1;
SRAM_HandleTypeDef hsram1;
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for TempTask */
osThreadId_t TempTaskHandle;
const osThreadAttr_t TempTask_attributes = {
.name = "TempTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for RGBTask */
osThreadId_t RGBTaskHandle;
const osThreadAttr_t RGBTask_attributes = {
.name = "RGBTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for CounterTask */
osThreadId_t CounterTaskHandle;
const osThreadAttr_t CounterTask_attributes = {
.name = "CounterTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for BlinkTask */
osThreadId_t BlinkTaskHandle;
const osThreadAttr_t BlinkTask_attributes = {
.name = "BlinkTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityLow,
};
/* Definitions for LCDMutex */
osMutexId_t LCDMutexHandle;
const osMutexAttr_t LCDMutex_attributes = {
.name = "LCDMutex"
};
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_FSMC_Init(void);
static void MX_ADC1_Init(void);
static void MX_TIM1_Init(void);
void StartDefaultTask(void *argument);
void StartTempTask(void *argument);
void StartRGBTask(void *argument);
void StartCounterTask(void *argument);
void StartBlinkTask(void *argument);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_FSMC_Init();
MX_ADC1_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
LCD_Init();
LCD_Clear(WHITE);
AHT21_Init();
// Static UI
LCD_ShowString(20, 20, "Lab 5 RTOS", BLUE, WHITE);
/* USER CODE END 2 */
/* Init scheduler */
osKernelInitialize();
/* Create the mutex(es) */
/* creation of LCDMutex */
LCDMutexHandle = osMutexNew(&LCDMutex_attributes);
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* creation of defaultTask */
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* creation of TempTask */
TempTaskHandle = osThreadNew(StartTempTask, NULL, &TempTask_attributes);
/* creation of RGBTask */
RGBTaskHandle = osThreadNew(StartRGBTask, NULL, &RGBTask_attributes);
/* creation of CounterTask */
CounterTaskHandle = osThreadNew(StartCounterTask, NULL, &CounterTask_attributes);
/* creation of BlinkTask */
BlinkTaskHandle = osThreadNew(StartBlinkTask, NULL, &BlinkTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
/* Start scheduler */
osKernelStart();
/* We should never get here as control is now taken by the scheduler */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief ADC1 Initialization Function
* @param None
* @retval None
*/
static void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_4;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
/**
* @brief TIM1 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM1_Init(void)
{
/* USER CODE BEGIN TIM1_Init 0 */
/* USER CODE END TIM1_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
/* USER CODE BEGIN TIM1_Init 1 */
/* USER CODE END TIM1_Init 1 */
htim1.Instance = TIM1;
htim1.Init.Prescaler = 84-1;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 1000-1;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM1_Init 2 */
/* USER CODE END TIM1_Init 2 */
HAL_TIM_MspPostInit(&htim1);
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIO_LCD_BL_GPIO_Port, GPIO_LCD_BL_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(EXT_LED_PIN_GPIO_Port, EXT_LED_PIN_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIO_LCD_RST_GPIO_Port, GPIO_LCD_RST_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOE, AHT_SCL_Pin|AHT_SCLE1_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : GPIO_LCD_BL_Pin */
GPIO_InitStruct.Pin = GPIO_LCD_BL_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIO_LCD_BL_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : EXT_LED_PIN_Pin */
GPIO_InitStruct.Pin = EXT_LED_PIN_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(EXT_LED_PIN_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : GPIO_LCD_RST_Pin */
GPIO_InitStruct.Pin = GPIO_LCD_RST_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIO_LCD_RST_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : AHT_SCL_Pin AHT_SCLE1_Pin */
GPIO_InitStruct.Pin = AHT_SCL_Pin|AHT_SCLE1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* FSMC initialization function */
static void MX_FSMC_Init(void)
{
/* USER CODE BEGIN FSMC_Init 0 */
/* USER CODE END FSMC_Init 0 */
FSMC_NORSRAM_TimingTypeDef Timing = {0};
/* USER CODE BEGIN FSMC_Init 1 */
/* USER CODE END FSMC_Init 1 */
/** Perform the SRAM1 memory initialization sequence
*/
hsram1.Instance = FSMC_NORSRAM_DEVICE;
hsram1.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
/* hsram1.Init */
hsram1.Init.NSBank = FSMC_NORSRAM_BANK3;
hsram1.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
hsram1.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;
hsram1.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_8;
hsram1.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
hsram1.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
hsram1.Init.WrapMode = FSMC_WRAP_MODE_DISABLE;
hsram1.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
hsram1.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
hsram1.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
hsram1.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
hsram1.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
hsram1.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
hsram1.Init.PageSize = FSMC_PAGE_SIZE_NONE;
/* Timing */
Timing.AddressSetupTime = 15;
Timing.AddressHoldTime = 15;
Timing.DataSetupTime = 255;
Timing.BusTurnAroundDuration = 15;
Timing.CLKDivision = 16;
Timing.DataLatency = 17;
Timing.AccessMode = FSMC_ACCESS_MODE_A;
/* ExtTiming */
if (HAL_SRAM_Init(&hsram1, &Timing, NULL) != HAL_OK)
{
Error_Handler( );
}
/* USER CODE BEGIN FSMC_Init 2 */
/* USER CODE END FSMC_Init 2 */
}
/* USER CODE BEGIN 4 */
void LCD_ShowNum(uint16_t x, uint16_t y, int num, uint16_t color, uint16_t back_color) {
char buf[12];
int i = 0;
if (num == 0) {
LCD_ShowChar(x, y, '0', color, back_color);
return;
}
// Handle negative numbers
if (num < 0) {
LCD_ShowChar(x, y, '-', color, back_color);
x += 8;
num = -num;
}
// Extract digits into buffer
while (num > 0) {
buf[i++] = (num % 10) + '0';
num /= 10;
}
// Print in reverse order (MSB first)
while (--i >= 0) {
LCD_ShowChar(x, y, buf[i], color, back_color);
x += 8;
}
}
// Manually renders a float value to the screen
void LCD_ShowFloatManual(uint16_t x, uint16_t y, float val, uint16_t color, uint16_t back_color) {
// 1. Extract Integer Part
int intPart = (int)val;
LCD_ShowNum(x, y, intPart, color, back_color);
// Calculate cursor offset based on number length
int offset = 8;
if (intPart < 0) offset += 8;
if (abs(intPart) > 9) offset += 8;
if (abs(intPart) > 99) offset += 8;
// 2. Print Decimal Point
LCD_ShowChar(x + offset, y, '.', color, back_color);
// 3. Extract Decimal Part (2 decimal places)
int decPart = (int)((val - (float)intPart) * 100.0f);
if (decPart < 0) decPart = -decPart;
// Handle leading zeros
if (decPart < 10) {
LCD_ShowChar(x + offset + 8, y, '0', color, back_color);
LCD_ShowNum(x + offset + 16, y, decPart, color, back_color);
} else {
LCD_ShowNum(x + offset + 8, y, decPart, color, back_color);
}
}
/* USER CODE END 4 */
/* USER CODE BEGIN Header_StartDefaultTask */
/**
* @brief Function implementing the defaultTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN 5 */
/* Infinite loop */
for(;;)
{
osDelay(1);
}
/* USER CODE END 5 */
}
/* USER CODE BEGIN Header_StartTempTask */
/**
* @brief Function implementing the TempTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTempTask */
void StartTempTask(void *argument)
{
/* USER CODE BEGIN StartTempTask */
float temp = 0.0f, hum = 0.0f;
/* Infinite loop */
for(;;)
{
// 1. Read Sensor
AHT21_Read(&temp, &hum);
// 2. Lock LCD Mutex
if (osMutexAcquire(LCDMutexHandle, osWaitForever) == osOK)
{
LCD_ShowString(20, 50, "Temp:", RED, WHITE);
LCD_ShowFloatManual(80, 50, temp, RED, WHITE);
osMutexRelease(LCDMutexHandle);
}
osDelay(2000);
}
/* USER CODE END StartTempTask */
}
/* USER CODE BEGIN Header_StartRGBTask */
/**
* @brief Function implementing the RGBTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartRGBTask */
void StartRGBTask(void *argument)
{
/* USER CODE BEGIN StartRGBTask */
uint32_t adc_val = 0;
// Ensure TIM1 CH1 is correct for your wiring (PA8)
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
/* Infinite loop */
for(;;)
{
HAL_ADC_Start(&hadc1);
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) {
adc_val = HAL_ADC_GetValue(&hadc1);
}
// Common Cathode: Higher value = Brighter
// Map 0-4095 (ADC) -> 0-1000 (PWM Period)
uint32_t brightness = adc_val/4;
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, brightness);
osDelay(50);
}
/* USER CODE END StartRGBTask */
}
/* USER CODE BEGIN Header_StartCounterTask */
/**
* @brief Function implementing the CounterTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartCounterTask */
void StartCounterTask(void *argument)
{
/* USER CODE BEGIN StartCounterTask */
static int counter = 0;
/* Infinite loop */
for(;;)
{
counter++;
if (osMutexAcquire(LCDMutexHandle, osWaitForever) == osOK)
{
LCD_ShowString(20, 100, "Count:", BLACK, WHITE);
LCD_ShowNum(80, 100, counter, BLACK, WHITE);
osMutexRelease(LCDMutexHandle);
}
osDelay(1000);
}
/* USER CODE END StartCounterTask */
}
/* USER CODE BEGIN Header_StartBlinkTask */
/**
* @brief Function implementing the BlinkTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartBlinkTask */
void StartBlinkTask(void *argument)
{
/* USER CODE BEGIN StartBlinkTask */
/* Infinite loop */
for(;;)
{
// Toggle Ext LED (PA1)
HAL_GPIO_TogglePin(EXT_LED_PIN_GPIO_Port, EXT_LED_PIN_Pin);
osDelay(500);
}
/* USER CODE END StartBlinkTask */
}
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM6 interrupt took place, inside
* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
* a global variable "uwTick" used as application time base.
* @param htim : TIM handle
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM6)
{
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* USER CODE END Callback 1 */
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT *//* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "drv_lcd.h"
#include "drv_aht21.h"
#include <stdio.h>
#include <stdlib.h> // For abs()
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
TIM_HandleTypeDef htim1;
SRAM_HandleTypeDef hsram1;
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for TempTask */
osThreadId_t TempTaskHandle;
const osThreadAttr_t TempTask_attributes = {
.name = "TempTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for RGBTask */
osThreadId_t RGBTaskHandle;
const osThreadAttr_t RGBTask_attributes = {
.name = "RGBTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for CounterTask */
osThreadId_t CounterTaskHandle;
const osThreadAttr_t CounterTask_attributes = {
.name = "CounterTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for BlinkTask */
osThreadId_t BlinkTaskHandle;
const osThreadAttr_t BlinkTask_attributes = {
.name = "BlinkTask",
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityLow,
};
/* Definitions for LCDMutex */
osMutexId_t LCDMutexHandle;
const osMutexAttr_t LCDMutex_attributes = {
.name = "LCDMutex"
};
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_FSMC_Init(void);
static void MX_ADC1_Init(void);
static void MX_TIM1_Init(void);
void StartDefaultTask(void *argument);
void StartTempTask(void *argument);
void StartRGBTask(void *argument);
void StartCounterTask(void *argument);
void StartBlinkTask(void *argument);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_FSMC_Init();
MX_ADC1_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
LCD_Init();
LCD_Clear(WHITE);
AHT21_Init();
// Static UI
LCD_ShowString(20, 20, "Lab 5 RTOS", BLUE, WHITE);
/* USER CODE END 2 */
/* Init scheduler */
osKernelInitialize();
/* Create the mutex(es) */
/* creation of LCDMutex */
LCDMutexHandle = osMutexNew(&LCDMutex_attributes);
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* creation of defaultTask */
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* creation of TempTask */
TempTaskHandle = osThreadNew(StartTempTask, NULL, &TempTask_attributes);
/* creation of RGBTask */
RGBTaskHandle = osThreadNew(StartRGBTask, NULL, &RGBTask_attributes);
/* creation of CounterTask */
CounterTaskHandle = osThreadNew(StartCounterTask, NULL, &CounterTask_attributes);
/* creation of BlinkTask */
BlinkTaskHandle = osThreadNew(StartBlinkTask, NULL, &BlinkTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
/* Start scheduler */
osKernelStart();
/* We should never get here as control is now taken by the scheduler */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief ADC1 Initialization Function
* @param None
* @retval None
*/
static void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_4;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
/**
* @brief TIM1 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM1_Init(void)
{
/* USER CODE BEGIN TIM1_Init 0 */
/* USER CODE END TIM1_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
/* USER CODE BEGIN TIM1_Init 1 */
/* USER CODE END TIM1_Init 1 */
htim1.Instance = TIM1;
htim1.Init.Prescaler = 84-1;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 1000-1;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM1_Init 2 */
/* USER CODE END TIM1_Init 2 */
HAL_TIM_MspPostInit(&htim1);
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIO_LCD_BL_GPIO_Port, GPIO_LCD_BL_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(EXT_LED_PIN_GPIO_Port, EXT_LED_PIN_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIO_LCD_RST_GPIO_Port, GPIO_LCD_RST_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOE, AHT_SCL_Pin|AHT_SCLE1_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin : GPIO_LCD_BL_Pin */
GPIO_InitStruct.Pin = GPIO_LCD_BL_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIO_LCD_BL_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : EXT_LED_PIN_Pin */
GPIO_InitStruct.Pin = EXT_LED_PIN_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(EXT_LED_PIN_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : GPIO_LCD_RST_Pin */
GPIO_InitStruct.Pin = GPIO_LCD_RST_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIO_LCD_RST_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : AHT_SCL_Pin AHT_SCLE1_Pin */
GPIO_InitStruct.Pin = AHT_SCL_Pin|AHT_SCLE1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* FSMC initialization function */
static void MX_FSMC_Init(void)
{
/* USER CODE BEGIN FSMC_Init 0 */
/* USER CODE END FSMC_Init 0 */
FSMC_NORSRAM_TimingTypeDef Timing = {0};
/* USER CODE BEGIN FSMC_Init 1 */
/* USER CODE END FSMC_Init 1 */
/** Perform the SRAM1 memory initialization sequence
*/
hsram1.Instance = FSMC_NORSRAM_DEVICE;
hsram1.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
/* hsram1.Init */
hsram1.Init.NSBank = FSMC_NORSRAM_BANK3;
hsram1.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
hsram1.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;
hsram1.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_8;
hsram1.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
hsram1.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
hsram1.Init.WrapMode = FSMC_WRAP_MODE_DISABLE;
hsram1.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
hsram1.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
hsram1.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
hsram1.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
hsram1.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
hsram1.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
hsram1.Init.PageSize = FSMC_PAGE_SIZE_NONE;
/* Timing */
Timing.AddressSetupTime = 15;
Timing.AddressHoldTime = 15;
Timing.DataSetupTime = 255;
Timing.BusTurnAroundDuration = 15;
Timing.CLKDivision = 16;
Timing.DataLatency = 17;
Timing.AccessMode = FSMC_ACCESS_MODE_A;
/* ExtTiming */
if (HAL_SRAM_Init(&hsram1, &Timing, NULL) != HAL_OK)
{
Error_Handler( );
}
/* USER CODE BEGIN FSMC_Init 2 */
/* USER CODE END FSMC_Init 2 */
}
/* USER CODE BEGIN 4 */
void LCD_ShowNum(uint16_t x, uint16_t y, int num, uint16_t color, uint16_t back_color) {
char buf[12];
int i = 0;
if (num == 0) {
LCD_ShowChar(x, y, '0', color, back_color);
return;
}
// Handle negative numbers
if (num < 0) {
LCD_ShowChar(x, y, '-', color, back_color);
x += 8;
num = -num;
}
// Extract digits into buffer
while (num > 0) {
buf[i++] = (num % 10) + '0';
num /= 10;
}
// Print in reverse order (MSB first)
while (--i >= 0) {
LCD_ShowChar(x, y, buf[i], color, back_color);
x += 8;
}
}
// Manually renders a float value to the screen
void LCD_ShowFloatManual(uint16_t x, uint16_t y, float val, uint16_t color, uint16_t back_color) {
// 1. Extract Integer Part
int intPart = (int)val;
LCD_ShowNum(x, y, intPart, color, back_color);
// Calculate cursor offset based on number length
int offset = 8;
if (intPart < 0) offset += 8;
if (abs(intPart) > 9) offset += 8;
if (abs(intPart) > 99) offset += 8;
// 2. Print Decimal Point
LCD_ShowChar(x + offset, y, '.', color, back_color);
// 3. Extract Decimal Part (2 decimal places)
int decPart = (int)((val - (float)intPart) * 100.0f);
if (decPart < 0) decPart = -decPart;
// Handle leading zeros
if (decPart < 10) {
LCD_ShowChar(x + offset + 8, y, '0', color, back_color);
LCD_ShowNum(x + offset + 16, y, decPart, color, back_color);
} else {
LCD_ShowNum(x + offset + 8, y, decPart, color, back_color);
}
}
/* USER CODE END 4 */
/* USER CODE BEGIN Header_StartDefaultTask */
/**
* @brief Function implementing the defaultTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN 5 */
/* Infinite loop */
for(;;)
{
osDelay(1);
}
/* USER CODE END 5 */
}
/* USER CODE BEGIN Header_StartTempTask */
/**
* @brief Function implementing the TempTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTempTask */
void StartTempTask(void *argument)
{
/* USER CODE BEGIN StartTempTask */
float temp = 0.0f, hum = 0.0f;
/* Infinite loop */
for(;;)
{
// 1. Read Sensor
AHT21_Read(&temp, &hum);
// 2. Lock LCD Mutex
if (osMutexAcquire(LCDMutexHandle, osWaitForever) == osOK)
{
LCD_ShowString(20, 50, "Temp:", RED, WHITE);
LCD_ShowFloatManual(80, 50, temp, RED, WHITE);
osMutexRelease(LCDMutexHandle);
}
osDelay(2000);
}
/* USER CODE END StartTempTask */
}
/* USER CODE BEGIN Header_StartRGBTask */
/**
* @brief Function implementing the RGBTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartRGBTask */
void StartRGBTask(void *argument)
{
/* USER CODE BEGIN StartRGBTask */
uint32_t adc_val = 0;
// Ensure TIM1 CH1 is correct for your wiring (PA8)
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
/* Infinite loop */
for(;;)
{
HAL_ADC_Start(&hadc1);
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK) {
adc_val = HAL_ADC_GetValue(&hadc1);
}
// Common Cathode: Higher value = Brighter
// Map 0-4095 (ADC) -> 0-1000 (PWM Period)
uint32_t brightness = adc_val/4;
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, brightness);
osDelay(50);
}
/* USER CODE END StartRGBTask */
}
/* USER CODE BEGIN Header_StartCounterTask */
/**
* @brief Function implementing the CounterTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartCounterTask */
void StartCounterTask(void *argument)
{
/* USER CODE BEGIN StartCounterTask */
static int counter = 0;
/* Infinite loop */
for(;;)
{
counter++;
if (osMutexAcquire(LCDMutexHandle, osWaitForever) == osOK)
{
LCD_ShowString(20, 100, "Count:", BLACK, WHITE);
LCD_ShowNum(80, 100, counter, BLACK, WHITE);
osMutexRelease(LCDMutexHandle);
}
osDelay(1000);
}
/* USER CODE END StartCounterTask */
}
/* USER CODE BEGIN Header_StartBlinkTask */
/**
* @brief Function implementing the BlinkTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartBlinkTask */
void StartBlinkTask(void *argument)
{
/* USER CODE BEGIN StartBlinkTask */
/* Infinite loop */
for(;;)
{
// Toggle Ext LED (PA1)
HAL_GPIO_TogglePin(EXT_LED_PIN_GPIO_Port, EXT_LED_PIN_Pin);
osDelay(500);
}
/* USER CODE END StartBlinkTask */
}
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM6 interrupt took place, inside
* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
* a global variable "uwTick" used as application time base.
* @param htim : TIM handle
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM6)
{
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* USER CODE END Callback 1 */
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
Step 5: Testing and Expected OutputAfter completing the coding and debugging stage, the final step is to test the system and observe the results. Running the program allows you to verify that each thread behaves as expected and that the peripherals respond correctly to inputs. This stage confirms the success of your RTOS implementation and demonstrates how cooperative scheduling keeps tasks running smoothly.
During testing, you should see the following outcomes:
- The LCD displays temperature readings from the AHT20 sensor.
- The LCD also shows an incrementing counter that updates continuously.
- The RGB LED brightness changes smoothly as you adjust the potentiometer.
- The standard LED blinks at regular intervals, confirming GPIO control.
- All tasks run concurrently without blocking, showcasing FreeRTOS scheduling and mutex protection for the LCD.
This activity brings together every essential aspect of embedded systems development — from preparing hardware and wiring simple peripherals, to configuring STM32CubeIDE, coding with FreeRTOS, and finally testing the results on the RT-Spark board. By creating four cooperative threads, you’ve seen how an RTOS can elegantly manage multiple tasks: reading sensor data, adjusting LED brightness, updating counters, and blinking outputs, all without conflict thanks to mutex protection.
More than just a lab exercise, this project shows the real power of multitasking in microcontrollers and how FreeRTOS transforms a single-core MCU into a responsive, efficient system. With the LCD and sensor already built in, and only minimal external wiring required, you’ve experienced how quickly ideas can move from concept to working prototype.
The skills you’ve practiced here — task scheduling, peripheral integration, and debugging — are the same building blocks used in professional IoT and embedded applications. Think of this as your launchpad: from here, you can expand to cloud connectivity, more sensors, or advanced user interfaces. By mastering these fundamentals, you’re not just completing a lab — you’re stepping into the world of real-time embedded innovation.














Comments