1. LED闪烁

任务目标

用同一颗LED灯实现1Hz闪烁(亮500ms、灭500ms),分别以裸机主循环延时和RTOS任务延时两种方式编写,对比两者在代码组织、CPU占用和实时响应上的差异。本案例以STM32F407 + HAL库为硬件基础,RTOS选用FreeRTOS(CMSIS-OS v2封装)。

环境与依赖

  • 硬件:STM32F407开发板,LED接PD12(或任意GPIO,需在代码中对应修改)
  • 软件:STM32CubeIDE(或Keil MDK),STM32CubeF4固件包,FreeRTOS中间件(CubeMX勾选即可)
  • 时钟:系统时钟168MHz,HAL库已初始化

裸机轮询实现

裸机方式使用主循环调用HAL_Delay()阻塞延时。代码结构为顺序执行,CPU在延时期间被占用,无法处理其他任务。

// main.c 裸机轮询版本(核心代码)
#include "main.h"

void LED_Blink_Polling(void)
{
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);   // LED亮
    HAL_Delay(500);                                        // 阻塞500ms
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET); // LED灭
    HAL_Delay(500);                                        // 阻塞500ms
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();  // 168MHz
    MX_GPIO_Init();        // PD12配置为推挽输出

    while (1)
    {
        LED_Blink_Polling();
    }
}

RTOS任务实现

RTOS方式将LED闪烁封装为独立任务,使用osDelay()主动让出CPU。任务在延时期间进入阻塞态,调度器可运行其他就绪任务,实现并发。

// app_rtos.c RTOS任务版本(核心代码)
#include "cmsis_os.h"

void LED_Blink_Task(void *argument)
{
    for (;;)
    {
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);   // LED亮
        osDelay(500);                                          // 阻塞500ms,让出CPU
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET); // LED灭
        osDelay(500);                                          // 阻塞500ms,让出CPU
    }
}

// 在main.c中创建任务(CubeMX自动生成部分)
// osThreadId_t LEDTaskHandle;
// LEDTaskHandle = osThreadNew(LED_Blink_Task, NULL, &LEDTask_attributes);

代码结构对比

维度裸机轮询RTOS任务
代码组织主循环顺序执行,功能耦合在while(1)中每个功能独立任务,结构清晰
延时方式HAL_Delay()阻塞CPUosDelay()让出CPU,任务进入阻塞态
CPU利用率延时期间CPU空转延时期间CPU可运行其他任务
实时响应需等待当前延时结束高优先级任务可抢占
扩展性添加功能需修改主循环,耦合度高添加功能只需新建任务

实时性差异说明

裸机方式中,若在HAL_Delay(500)期间发生外部中断(如按键),中断服务函数可立即执行,但主循环中的其他非中断逻辑必须等待延时结束才能运行。RTOS方式中,osDelay()使当前任务挂起,调度器立即切换到其他就绪任务;若存在更高优先级任务(如按键处理任务),其响应延迟仅取决于任务切换时间(通常微秒级),而非500ms延时。

运行与验证

  1. 裸机验证:编译烧录后,观察LED以1Hz频率闪烁(亮500ms、灭500ms)。用示波器或逻辑分析仪测量PD12引脚波形,高电平持续500ms±10ms,低电平持续500ms±10ms。
  2. RTOS验证:编译烧录后,同样观察LED闪烁频率。在调试器中暂停程序,查看任务列表,确认LED_Blink_Task状态在Blocked(阻塞)和Running(运行)之间切换。
  3. 对比验证:在裸机版本主循环中添加一个空操作计数器(如volatile uint32_t cnt++),在RTOS版本中创建同优先级空任务执行相同计数。运行10秒后比较两者计数值——RTOS版本中空任务计数应显著高于裸机版本(因为裸机CPU被延时占用,而RTOS空任务在延时期间获得CPU)。

通过标准:两种方式LED均以1Hz±10%频率闪烁;RTOS任务状态切换正常;对比实验中RTOS空任务计数值明显大于裸机版本。