1. 创建第一个RTOS任务
任务定义
在STM32F103裸机工程基础上集成FreeRTOS,创建两个优先级相同的任务,分别控制板载LED的亮灭,通过任务调度实现LED交替闪烁。本案例聚焦于任务创建API的使用、调度器启动流程以及任务函数的基本结构,不涉及中断管理、信号量等高级特性。
硬件准备
以STM32F103C8T6最小系统板为例,板载LED接在PC13引脚(高电平点亮)。若使用其他型号,只需修改GPIO初始化中的引脚定义即可。需要准备:STM32开发板、ST-Link调试器、USB转TTL串口模块(可选,用于调试输出)。
工程结构
在标准STM32裸机工程(包含标准外设库或HAL库均可)基础上,添加FreeRTOS源码。所需文件如下:
- FreeRTOS核心源码:tasks.c、queue.c、list.c、timers.c、event_groups.c、stream_buffer.c、croutine.c(后三个可裁剪,本案例仅需前三个)
- FreeRTOS移植层:port.c、portmacro.h(对应ARM_CM3架构)
- FreeRTOS配置头文件:FreeRTOSConfig.h
- 应用代码:main.c、stm32f10x_it.c(需修改)
FreeRTOSConfig.h 关键配置
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ 72000000
#define configTICK_RATE_HZ 1000
#define configMAX_PRIORITIES 5
#define configMINIMAL_STACK_SIZE 128
#define configTOTAL_HEAP_SIZE 10240
#define configMAX_TASK_NAME_LEN 16
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 0
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_COUNTING_SEMAPHORES 0
#define configUSE_TRACE_FACILITY 0
任务实现代码
/* main.c 核心代码 */
#include "FreeRTOS.h"
#include "task.h"
#include "stm32f10x.h"
/* 任务函数声明 */
void vLED1_Task(void *pvParameters);
void vLED2_Task(void *pvParameters);
/* 任务句柄(本案例未使用,但保留便于调试) */
TaskHandle_t xLED1_Handle = NULL;
TaskHandle_t xLED2_Handle = NULL;
/* GPIO初始化:PC13为推挽输出 */
static void GPIO_Init_LED(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_SetBits(GPIOC, GPIO_Pin_13); /* 初始熄灭 */
}
/* 任务1:LED亮100ms,灭100ms */
void vLED1_Task(void *pvParameters)
{
(void)pvParameters;
for (;;)
{
GPIO_SetBits(GPIOC, GPIO_Pin_13); /* LED灭 */
vTaskDelay(pdMS_TO_TICKS(100));
GPIO_ResetBits(GPIOC, GPIO_Pin_13); /* LED亮 */
vTaskDelay(pdMS_TO_TICKS(100));
}
}
/* 任务2:LED亮300ms,灭300ms */
void vLED2_Task(void *pvParameters)
{
(void)pvParameters;
for (;;)
{
GPIO_ResetBits(GPIOC, GPIO_Pin_13); /* LED亮 */
vTaskDelay(pdMS_TO_TICKS(300));
GPIO_SetBits(GPIOC, GPIO_Pin_13); /* LED灭 */
vTaskDelay(pdMS_TO_TICKS(300));
}
}
int main(void)
{
/* 系统时钟已由SystemInit()配置为72MHz */
GPIO_Init_LED();
/* 创建两个任务,优先级均为1 */
xTaskCreate(vLED1_Task, "LED1", 128, NULL, 1, &xLED1_Handle);
xTaskCreate(vLED2_Task, "LED2", 128, NULL, 1, &xLED2_Handle);
/* 启动调度器(不会返回) */
vTaskStartScheduler();
/* 正常情况下不会执行到这里 */
for (;;);
}
/* 必须实现:FreeRTOS堆内存管理 */
void vApplicationMallocFailedHook(void)
{
taskDISABLE_INTERRUPTS();
for (;;);
}
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
(void)xTask;
(void)pcTaskName;
taskDISABLE_INTERRUPTS();
for (;;);
}
中断处理修改
在stm32f10x_it.c中,需要将SysTick_Handler替换为FreeRTOS的调度心跳:
/* stm32f10x_it.c 中修改 */
extern void xPortSysTickHandler(void);
void SysTick_Handler(void)
{
xPortSysTickHandler();
}
同时,在stm32f10x_it.c中确保PendSV_Handler和SVC_Handler为空实现(FreeRTOS会使用自己的实现),若原工程已有定义需删除或注释掉。
调度时序说明
由于两个任务优先级相同(均为1),FreeRTOS使用时间片轮转调度。每个任务运行一个时间片(1ms)后切换到另一个就绪任务。LED1任务每100ms翻转一次电平,LED2任务每300ms翻转一次。实际观察到的现象是:LED以约200ms为周期快速闪烁(由LED1主导),同时叠加一个约600ms的慢速亮度变化(由LED2主导)。若希望看到更明显的交替效果,可将两个任务的延时改为不同且互质的数值,例如LED1延时100ms、LED2延时150ms。
编译与烧录
- 将FreeRTOS源码添加到工程,包含路径指向
FreeRTOS/Source/include、FreeRTOS/Source/portable/GCC/ARM_CM3(或对应编译器移植目录)以及FreeRTOS/Source/portable/MemMang(选择heap_4.c)。 - 在工程设置中定义
USE_STDPERIPH_DRIVER和STM32F10X_MD(根据具体芯片选择)。 - 编译工程,通过ST-Link烧录到开发板。
验证方法与判定标准
静态验证:编译无错误、无警告(或仅有与未使用变量相关的可忽略警告)。
动态验证:
- 上电后观察PC13引脚所接LED是否开始闪烁;
- 用示波器或逻辑分析仪测量PC13引脚波形,应观察到高低电平交替,且高/低电平持续时间符合任务延时设定(100ms或300ms的整数倍,实际因调度切换会有微小抖动);
- 若使用调试器连接,可在
vLED1_Task和vLED2_Task入口处设置断点,全速运行后应能轮流触发两个断点,证明两个任务均被调度执行。
判定通过标准:LED持续闪烁且无死机、无硬件异常;两个任务均能进入执行(通过断点或波形确认);系统长时间运行(至少10分钟)不出现任务卡死。