2. 多任务并发执行

任务目标

在RTOS中创建两个独立任务:一个控制LED以500ms周期闪烁,另一个控制蜂鸣器以200ms周期鸣响。通过串口打印任务运行状态,观察RTOS调度器如何在不同优先级下并发调度两个任务。

硬件依赖

本案例基于STM32F407开发板,使用标准库。LED接PB0(推挽输出),蜂鸣器接PB1(推挽输出),串口1(PA9/PA10)用于打印调试信息。若使用其他平台,仅需修改GPIO初始化部分。

完整代码


/* FreeRTOS + STM32F407 多任务并发示例 */
#include "FreeRTOS.h"
#include "task.h"
#include "stm32f4xx.h"
#include "stdio.h"

/* 任务句柄 */
TaskHandle_t xLEDTaskHandle = NULL;
TaskHandle_t xBuzzerTaskHandle = NULL;

/* 串口发送字符 */
void USART1_SendChar(char c) {
    while (!(USART1->SR & USART_SR_TXE));
    USART1->DR = c;
}

/* 串口打印字符串 */
void USART1_Print(const char* str) {
    while (*str) USART1_SendChar(*str++);
}

/* GPIO初始化 */
void GPIO_Init(void) {
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

    GPIO_InitTypeDef gpio;
    gpio.GPIO_Mode = GPIO_Mode_OUT;
    gpio.GPIO_OType = GPIO_OType_PP;
    gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    gpio.GPIO_PuPd = GPIO_PuPd_NOPULL;
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &gpio);

    gpio.GPIO_Mode = GPIO_Mode_AF;
    gpio.GPIO_Pin = GPIO_Pin_9;
    gpio.GPIO_AF = GPIO_AF_USART1;
    GPIO_Init(GPIOA, &gpio);

    gpio.GPIO_Pin = GPIO_Pin_10;
    GPIO_Init(GPIOA, &gpio);

    USART_InitTypeDef usart;
    usart.USART_BaudRate = 115200;
    usart.USART_WordLength = USART_WordLength_8b;
    usart.USART_StopBits = USART_StopBits_1;
    usart.USART_Parity = USART_Parity_No;
    usart.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    usart.USART_Mode = USART_Mode_TX;
    USART_Init(USART1, &usart);
    USART_Cmd(USART1, ENABLE);
}

/* LED任务:500ms翻转一次 */
void vLEDTask(void* pvParameters) {
    GPIO_SetBits(GPIOB, GPIO_Pin_0);
    for (;;) {
        GPIO_ToggleBits(GPIOB, GPIO_Pin_0);
        USART1_Print("LED Task Running\r\n");
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

/* 蜂鸣器任务:200ms翻转一次 */
void vBuzzerTask(void* pvParameters) {
    GPIO_SetBits(GPIOB, GPIO_Pin_1);
    for (;;) {
        GPIO_ToggleBits(GPIOB, GPIO_Pin_1);
        USART1_Print("Buzzer Task Running\r\n");
        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

int main(void) {
    GPIO_Init();

    xTaskCreate(vLEDTask, "LED", 128, NULL, 1, &xLEDTaskHandle);
    xTaskCreate(vBuzzerTask, "Buzzer", 128, NULL, 2, &xBuzzerTaskHandle);

    vTaskStartScheduler();
    for (;;);
}

构建与运行

  1. 创建STM32F407裸机工程,加入FreeRTOS源码(heap_4.c、list.c、queue.c、tasks.c、port.c等)。
  2. 将上述代码保存为main.c,替换工程中的主文件。
  3. 编译并烧录到开发板。
  4. 打开串口助手,波特率115200,观察输出。

验证方法

通过条件:

  • LED以约500ms周期闪烁(人眼可观察)。
  • 蜂鸣器以约200ms周期鸣响(声音节奏明显快于LED)。
  • 串口交替打印"LED Task Running"和"Buzzer Task Running",且Buzzer打印频率约为LED的2.5倍。
  • 两个任务互不阻塞,同时运行——证明RTOS时间片调度生效。

调度行为说明

蜂鸣器任务优先级为2,高于LED任务的优先级1。当蜂鸣器任务调用vTaskDelay进入阻塞态时,LED任务获得运行机会;LED任务阻塞后,系统进入空闲态。由于两个任务都主动让出CPU,因此即使优先级不同,两者都能按各自周期执行,实现并发效果。若将蜂鸣器任务改为死循环且不延时,LED任务将因优先级低而永远无法运行——这是观察RTOS优先级抢占的附加实验。

任务调度时序示意(时间轴 →) t LED任务 蜂鸣器任务 运行态 阻塞态(vTaskDelay) 运行态 阻塞态 500ms周期 200ms周期

图中深色块表示任务处于运行态,浅色块表示任务因vTaskDelay进入阻塞态。蜂鸣器任务周期短,因此运行频率更高;LED任务在蜂鸣器阻塞期间获得CPU执行权,两者交替占用处理器,宏观上表现为“同时运行”。