1. 创建第一个RTOS任务

创建第一个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个Tick=1ms) t 0 100 200 300 400 500ms LED1任务 亮100ms 灭100ms 亮100ms 灭100ms LED2任务 亮300ms 灭300ms 调度器 LED1运行 LED2运行 LED1运行 LED2运行 LED1运行 LED2运行 LED1亮 LED2亮 LED灭

由于两个任务优先级相同(均为1),FreeRTOS使用时间片轮转调度。每个任务运行一个时间片(1ms)后切换到另一个就绪任务。LED1任务每100ms翻转一次电平,LED2任务每300ms翻转一次。实际观察到的现象是:LED以约200ms为周期快速闪烁(由LED1主导),同时叠加一个约600ms的慢速亮度变化(由LED2主导)。若希望看到更明显的交替效果,可将两个任务的延时改为不同且互质的数值,例如LED1延时100ms、LED2延时150ms。

编译与烧录

  1. 将FreeRTOS源码添加到工程,包含路径指向FreeRTOS/Source/include、FreeRTOS/Source/portable/GCC/ARM_CM3(或对应编译器移植目录)以及FreeRTOS/Source/portable/MemMang(选择heap_4.c)。
  2. 在工程设置中定义USE_STDPERIPH_DRIVER和STM32F10X_MD(根据具体芯片选择)。
  3. 编译工程,通过ST-Link烧录到开发板。

验证方法与判定标准

静态验证:编译无错误、无警告(或仅有与未使用变量相关的可忽略警告)。

动态验证:

  1. 上电后观察PC13引脚所接LED是否开始闪烁;
  2. 用示波器或逻辑分析仪测量PC13引脚波形,应观察到高低电平交替,且高/低电平持续时间符合任务延时设定(100ms或300ms的整数倍,实际因调度切换会有微小抖动);
  3. 若使用调试器连接,可在vLED1_Task和vLED2_Task入口处设置断点,全速运行后应能轮流触发两个断点,证明两个任务均被调度执行。

判定通过标准:LED持续闪烁且无死机、无硬件异常;两个任务均能进入执行(通过断点或波形确认);系统长时间运行(至少10分钟)不出现任务卡死。