1. 搭建最小实时系统框架

搭建最小实时系统框架 学习卡片

任务定义

本案例基于STM32F103系列MCU与FreeRTOS,搭建一个最小实时系统:创建两个独立任务——LED闪烁任务与按键扫描任务。LED任务以固定周期翻转GPIO电平,按键任务轮询按键输入并在按下时翻转另一路LED状态。通过观察两路LED的独立运行节奏,验证FreeRTOS的任务调度与延时机制。

硬件连接

STM32F103 Cortex-M3 @72MHz PA0 PA1 PC13 PB0 PB1 3.3V GND R1 330Ω LED1 → GND R2 330Ω LED2 → GND KEY → GND 内部上拉 VDD VSS 最小实时系统硬件连接图 LED1: PB0 (任务1控制) | LED2: PB1 (按键任务控制) | KEY: PA0 (按下接地)

软件实现

使用STM32标准外设库与FreeRTOS V10.4.6。系统时钟配置为72MHz,创建两个任务:vLEDTask每500ms翻转PB0电平;vKeyTask每20ms轮询PA0,检测到下降沿(按键按下)时翻转PB1电平。按键采用软件消抖(连续两次采样均为低电平才判定有效)。

/* FreeRTOS 头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "stm32f10x.h"

/* 任务函数声明 */
void vLEDTask(void *pvParameters);
void vKeyTask(void *pvParameters);

/* 按键消抖计数 */
static uint8_t key_debounce = 0;

/* GPIO 初始化 */
static void GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    /* 使能 GPIOB 和 GPIOA 时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOA, ENABLE);
    
    /* PB0, PB1 推挽输出,驱动LED */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    /* PA0 上拉输入,接按键到GND */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    /* 初始状态:LED1灭,LED2灭 */
    GPIO_ResetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1);
}

/* LED闪烁任务:每500ms翻转一次PB0 */
void vLEDTask(void *pvParameters)
{
    (void)pvParameters;
    
    for (;;)
    {
        GPIO_WriteBit(GPIOB, GPIO_Pin_0, 
                      (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_0)));
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

/* 按键扫描任务:每20ms轮询PA0,下降沿翻转PB1 */
void vKeyTask(void *pvParameters)
{
    (void)pvParameters;
    uint8_t key_state = 0;      /* 当前按键状态:0=未按下,1=按下 */
    uint8_t last_key = 1;       /* 上一次采样值:1=高(未按下) */
    
    for (;;)
    {
        uint8_t sample = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
        
        /* 软件消抖:连续两次采样一致才更新状态 */
        if (sample == last_key)
        {
            key_debounce++;
            if (key_debounce >= 2)
            {
                key_state = sample;
                key_debounce = 0;
            }
        }
        else
        {
            key_debounce = 0;
            last_key = sample;
        }
        
        /* 检测下降沿:之前为高,现在为低 → 按键按下 */
        if (key_state == 0 && last_key == 0 && sample == 0)
        {
            GPIO_WriteBit(GPIOB, GPIO_Pin_1,
                          (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_1)));
            /* 等待释放,避免重复触发 */
            while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0)
            {
                vTaskDelay(pdMS_TO_TICKS(10));
            }
            last_key = 1;
            key_state = 1;
        }
        
        vTaskDelay(pdMS_TO_TICKS(20));
    }
}

int main(void)
{
    /* 系统时钟配置为72MHz */
    SystemInit();
    
    /* GPIO初始化 */
    GPIO_Config();
    
    /* 创建两个任务 */
    xTaskCreate(vLEDTask, "LED", 128, NULL, 1, NULL);
    xTaskCreate(vKeyTask, "KEY", 128, NULL, 1, NULL);
    
    /* 启动调度器 */
    vTaskStartScheduler();
    
    /* 正常不会执行到这里 */
    for (;;);
}

工程配置说明

将上述代码保存为main.c,替换STM32标准工程模板中的同名文件。需在工程中添加FreeRTOS内核源码(tasks.c、list.c、queue.c、port.c、heap_4.c),并在FreeRTOSConfig.h中配置:configTICK_RATE_HZ设为1000,configMINIMAL_STACK_SIZE设为128。编译下载至STM32F103最小系统板。

验证方法

将编译生成的hex文件烧录至开发板,观察以下现象:

  • LED1(PB0):以约1Hz频率(亮500ms灭500ms)持续闪烁,不受按键操作影响。
  • LED2(PB1):每次按下KEY按键(PA0接地)时翻转一次状态,松开后不会重复翻转。
  • 两路LED工作相互独立,LED1的闪烁节奏稳定,证明FreeRTOS调度器正常分配CPU时间片,vTaskDelay延时功能准确。

判定标准:LED1以稳定周期闪烁且LED2仅在每次按键按下时翻转一次,即视为任务调度与延时功能验证通过。若LED1闪烁频率明显异常或按键响应失灵,检查系统时钟配置与FreeRTOSConfig.h中的时钟节拍设置。