这篇文章让我们来移植RTOS到STM32U3C5单片机。众所周知,新一代的STM32U3单片机具有非常大的SRAM和Flash,这非常适合移植操作系统,而我作为一个半吊子水的工程师,这也是我的第一次移植操作系统。让我们开始吧。

目标: 我们的目标是使用RTOS建造几个线程,然后不同的线程实现不同的功能。看一下GPIO的配置:一共配置4个引脚,其中三个是板上的LED;还将板子上的C13按键配置成中断引脚,这样可以探索一下,在操作系统中,如何使用中断。

中断配置: 但是值得注意的是,如果是裸机的中断等级最高应该是0,但是这里由于是采用操作系统,所以它的中断最高等级的中断是从5开始的,你不能配置低于5,否则的话系统会报错。

时钟配置: 然后我们还需要注意时钟。使用RTOS的话,系统时钟的Systick会被RTOS占用,你需要另指定一个定时器作为SYS的时钟,在这里我使用了TIM8的时钟。
以及,时钟树如上,外设96Mhz,内核12Mhz,其实这个都可以的,随便配置一下就好。


RTOS的小齿轮怎么解决? 找到X-Cube-FreeRTOS, 你会发现在前面有个小齿轮。这个小齿轮卡了我一天,我都没解决掉。原来这个小齿轮的意思是你没有在它的配置界面完全配置好,所以说他不会让你去打勾的。这个单片机比较高级,需要进一步配置,但是有些单片机可以直接就用上了,例如F103这种比较老的型号。

然后我们如下配置。左击或右击鼠标,会打开一个配置界面,可以看到这里可以选择RTOS的版本。单击开RTOS的底下,你需要配置好采用什么样的内存方式,以及什么样的内核运行方式,这两个是一定要选择的,不选择的话是没有办法配置的。

由于我们没有使用TrustZone, 所以说这里我们TrustZone内运行选择并不使用。然后内存的这个选择方案的话,其实这些都能用,我这里用了Heap1方案, 然后确定,就可以让你去勾选了,然后打勾CMSIS RTOS2,然后点击并创建线程就可以了。

创建线程: 在这里我们创建了三个线程,分别是LD1的和LD3以及串口线程。

程序内LD1和LD3是以不同的频率闪烁。串口用来打印hello world。我还配置了个中断引脚,在外部中断中翻转LD2。接下来我们看程序吧,将程序烧录进单片机,可以看到信息正确打印,外部中断正确响应,两颗LED灯正常闪烁,由于使用了操作系统,可以看到程序出现的现象就是类似于并发运行,各自忙各自的。这对于复杂项目使用RTOS打下了一个良好的基础。


程序如下:
main.c:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2026 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_os2.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* 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 ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);
/* USER CODE BEGIN PFP */
void EXTI13_IRQHandlerCallBack(void);
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void EXTI13_IRQHandlerCallBack(void){
HAL_GPIO_TogglePin(LD2_GPIO_Port,LD2_Pin);
}
/* 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_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("ST Chinese Forum Evaluation Plan\r\n");
printf("Board Mode:Nucleo-U3C5\r\n");
printf("Demo6:U3C5_RTOS\r\n");
printf("Reviewer:Xiang Zhong Bli:C70E\r\n");
/* USER CODE END 2 */
/* Init scheduler */
osKernelInitialize();
/* Call init function for freertos objects (in app_freertos.c) */
MX_FREERTOS_Init();
/* 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 System Power Supply
*/
if (HAL_PWREx_ConfigSupply(PWR_SMPS_SUPPLY) != HAL_OK)
{
Error_Handler();
}
/** Enable Epod Booster
*/
if (HAL_RCCEx_EpodBoosterClkConfig(RCC_EPODBOOSTER_SOURCE_MSIS, RCC_EPODBOOSTER_DIV1) != HAL_OK)
{
Error_Handler();
}
if (HAL_PWREx_EnableEpodBooster() != HAL_OK)
{
Error_Handler();
}
/** Configure the main internal regulator output voltage
*/
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
{
Error_Handler();
}
/** Set Flash latency before increasing MSIS
*/
__HAL_FLASH_SET_LATENCY(FLASH_LATENCY_3);
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSIS;
RCC_OscInitStruct.MSISState = RCC_MSI_ON;
RCC_OscInitStruct.MSISSource = RCC_MSI_RC0;
RCC_OscInitStruct.MSISDiv = RCC_MSI_DIV1;
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_CLOCKTYPE_PCLK3;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSIS;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB3CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
int fputc(int ch,FILE *f)
{
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xFFFF);
return ch;
}
/* USER CODE END 4 */
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM8 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 == TIM8)
{
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 */
app_freertos.c:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* File Name : app_freertos.c
* Description : FreeRTOS applicative file
******************************************************************************
* @attention
*
* Copyright (c) 2026 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 "app_freertos.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "main.h"
#include "stdio.h"
/* 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 ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
/* USER CODE END Variables */
/* Definitions for LED1Task */
osThreadId_t LED1TaskHandle;
const osThreadAttr_t LED1Task_attributes = {
.name = "LED1Task",
.priority = (osPriority_t) osPriorityNormal,
.stack_size = 128 * 4
};
/* Definitions for SerialTask */
osThreadId_t SerialTaskHandle;
const osThreadAttr_t SerialTask_attributes = {
.name = "SerialTask",
.priority = (osPriority_t) osPriorityLow,
.stack_size = 128 * 4
};
/* Definitions for LED3Task */
osThreadId_t LED3TaskHandle;
const osThreadAttr_t LED3Task_attributes = {
.name = "LED3Task",
.priority = (osPriority_t) osPriorityLow,
.stack_size = 128 * 4
};
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* 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 */
/* creation of LED1Task */
LED1TaskHandle = osThreadNew(StartLED1Task, NULL, &LED1Task_attributes);
/* creation of SerialTask */
SerialTaskHandle = osThreadNew(StartSerialTask, NULL, &SerialTask_attributes);
/* creation of LED3Task */
LED3TaskHandle = osThreadNew(StartLED3Task, NULL, &LED3Task_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 */
}
/* USER CODE BEGIN Header_StartLED1Task */
/**
* @brief Function implementing the LED1Task thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartLED1Task */
void StartLED1Task(void *argument)
{
/* USER CODE BEGIN LED1Task */
/* Infinite loop */
for(;;)
{ HAL_GPIO_TogglePin(LD1_GPIO_Port,LD1_Pin);
osDelay(333);
}
/* USER CODE END LED1Task */
}
/* USER CODE BEGIN Header_StartSerialTask */
/**
* @brief Function implementing the SerialTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartSerialTask */
void StartSerialTask(void *argument)
{
/* USER CODE BEGIN SerialTask */
/* Infinite loop */
for(;;)
{
printf("Hello world!\r\n");
osDelay(5000);
}
/* USER CODE END SerialTask */
}
/* USER CODE BEGIN Header_StartLED3Task */
/**
* @brief Function implementing the LED3Task thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartLED3Task */
void StartLED3Task(void *argument)
{
/* USER CODE BEGIN LED3Task */
/* Infinite loop */
for(;;)
{ HAL_GPIO_TogglePin(LD3_GPIO_Port,LD3_Pin);
osDelay(1000);
}
/* USER CODE END LED3Task */
}
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
/* USER CODE END Application */
注意将中断服务函数放到stm32u3xx_it.c对应位置,不然中断不会起效!