FreeRTOS 详述及按键呼吸灯

 一、FreeRTOS 概述
FreeRTOS 是一款轻量级、开源的 实时操作系统(RTOS),专为嵌入式系统设计,广泛应用于物联网、工业控制、消费电子等领域。其核心特点包括:
轻量高效:内核最小可裁剪至 4KB 代码 + 几百字节 RAM,适合资源受限的微控制器(如 STM32、ESP32 等)。
可扩展性:支持任务管理、消息队列、信号量、定时器等功能,且可通过插件扩展(如文件系统、网络协议栈)。
开源免费:遵循 MIT 许可协议,允许商业使用,社区活跃且文档丰富。
跨平台兼容:支持 ARM Cortex-M、RISC-V、x86 等多种架构,提供 Keil、IAR、GCC 等开发环境的适配。​
二、核心组件与机制
1. 任务管理(Task Management)
任务定义:每个任务是一个无限循环的函数,拥有独立的栈空间和优先级。
void vTaskFunction(void *pvParameters) {
    for (;;) {
        // 任务逻辑
        vTaskDelay(pdMS_TO_TICKS(100)); // 延时调度
    }
}
任务状态:
运行(Running):当前正在执行的任务。
就绪(Ready):具备运行条件,等待调度器分配 CPU 资源。
阻塞(Blocked):因等待事件(如延时、消息队列)暂停执行。
挂起(Suspended):被显式挂起,不参与调度。
创建与删除任务:
TaskHandle_t xTaskHandle;
// 创建任务(动态分配内存)
xTaskCreate(
    vTaskFunction,  // 任务函数
    "TaskName",     // 任务名称(调试用)
    128,            // 栈大小(字节)
    NULL,           // 参数
    1,              // 优先级(数值越大优先级越高)
    &xTaskHandle    // 任务句柄
);
// 删除任务(通常在任务内部自我删除)
vTaskDelete(xTaskHandle); // 或 vTaskDelete(NULL); // 删除自身
2. 优先级抢占机制(Preemption)
调度策略:
抢占式调度(默认):高优先级任务可中断低优先级任务的执行,确保实时性。
协作式调度:任务主动释放 CPU(如调用 vTaskDelay),适合对实时性要求不高的场景。
优先级数量:可配置为 1~32 级(通过 configMAX_PRIORITIES 定义),数值越大优先级越高。
临界区保护:通过 taskENTER_CRITICAL() 和 taskEXIT_CRITICAL() 关闭 / 开启中断,防止任务调度被打断(如操作共享资源时)。
3. 任务间通信(Inter-Task Communication, IPC)
消息队列(Queues):用于任务间传递数据(如字节数组、结构体),支持先进先出(FIFO)或优先级队列。
QueueHandle_t xQueue;
xQueue = xQueueCreate(5, sizeof(int)); // 创建队列(5个元素,每个元素4字节)
// 发送数据(阻塞模式,超时时间100ms)
int data = 100;
xQueueSend(xQueue, &data, pdMS_TO_TICKS(100));
// 接收数据(阻塞模式,永久等待)
int receive_data;
xQueueReceive(xQueue, &receive_data, portMAX_DELAY);
信号量(Semaphores):
          二进制信号量:用于任务同步或互斥访问资源(如互斥锁)。
          计数信号量:用于限制访问共享资源的任务数量(如限制最多 3 个任务同时使用打印机)。
事件组(Event Groups):用于任务等待多个事件的组合(如 “事件 1 发生且事件 3 发生”)。
4. 定时器(Software Timers)
周期性定时器:创建后按固定周期触发回调函数,用于定时任务(如传感器数据采集)。
TimerHandle_t xTimer;
// 创建定时器(周期1s,自动重装,回调函数)
xTimer = xTimerCreate(
    "Timer",         // 名称
    pdMS_TO_TICKS(1000), // 周期
    pdTRUE,          // pdTRUE=自动重装,pdFALSE=单次
    NULL,            // 用户参数
    vTimerCallback   // 回调函数
);
xTimerStart(xTimer, 0); // 启动定时器
单次定时器:触发一次后停止,用于延迟任务执行。
三、内存管理与裁剪配置
1. 内存管理策略
FreeRTOS 提供 5 种内存分配方案(位于 heap_1.c~heap_5.c):
heap_1:简单的静态分配,适合只创建不删除任务的场景(无内存碎片)。
heap_2:基于块链表的动态分配,使用 pvPortMalloc/vPortFree,适合频繁创建 / 删除任务(存在轻微碎片)。
heap_3:封装标准库的 malloc/free,通过互斥锁实现线程安全。
heap_4:高效的动态分配(类似 glibc 的 ptmalloc),适合大多数场景(需设置堆起始地址和大小)。
heap_5:支持跨多个非连续内存块分配,适合内存不连续的系统(如包含外部 RAM)。
2. 配置文件(FreeRTOSConfig.h)
通过修改该文件裁剪功能,典型配置项:
#define configUSE_PREEMPTION        1        // 使能抢占式调度
#define configUSE_TICKLESS_IDLE     1        // 使能低功耗tickless模式
#define configMAX_PRIORITIES        5        // 最大优先级数
#define configTICK_RATE_HZ          1000     // 系统滴答频率(1ms中断一次)
#define configMINIMAL_STACK_SIZE    128      // 任务最小栈大小(字节)
#define INCLUDE_vTaskDelete         1        // 使能vTaskDelete函数
四、实时性与性能优化
减少中断延迟:
缩短中断服务程序(ISR)执行时间,关键操作可通过消息队列传递给任务处理。
使用 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 设置可安全调用 RTOS API 的最高中断优先级。
任务优先级分配:
高实时性任务(如电机控制)设为高优先级,低优先级任务处理非关键业务(如日志打印)。
避免优先级反转(高优先级任务等待低优先级任务释放资源),可通过优先级继承机制(需开启 configUSE_MUTEXES)缓解。
栈溢出检测:
开启 configCHECK_FOR_STACK_OVERFLOW,通过回调函数检测任务栈溢出(调试阶段常用)。
五、典型应用场景
多任务并发控制:
案例:同时运行 LED 控制任务、按键检测任务、串口通信任务,通过消息队列传递按键状态给 LED 任务。
实时传感器处理:
高优先级任务周期性采集传感器数据(如 ADC 采样),低优先级任务处理数据并通过 WiFi 发送。
低功耗系统:
利用 tickless idle 模式,在无任务运行时关闭系统滴答定时器,降低 MCU 功耗。

一、任务创建与删除(FreeRTOS API 详解)

不管是静态创建还是动态创建任务,都是来自SRAM区。静态创建任务来自SRAM区的堆栈,动态创建任务来自SRAM区的总堆。 

1. 任务创建(xTaskCreate)
BaseType_t xTaskCreate(
    TaskFunction_t pxTaskCode,      // 任务函数指针(入口函数)
    const char * const pcName,      // 任务名称(用于调试,最大 configMAX_TASK_NAME_LEN 字符)
    configSTACK_DEPTH_TYPE usStackDepth,  // 任务栈大小(单位:字,非字节)
    void *pvParameters,             // 传递给任务函数的参数(void* 类型)
    UBaseType_t uxPriority,         // 任务优先级(0 为最低优先级)
    TaskHandle_t *pxCreatedTask     // 任务句柄(用于后续操作,如删除任务)
);
void vTaskFunction(void *pvParameters) {
    // 任务代码
    for (;;) {
        // 任务主体逻辑
        vTaskDelay(pdMS_TO_TICKS(100));  // 延时100ms
    }
}

// 创建任务
TaskHandle_t xTaskHandle = NULL;
xTaskCreate(vTaskFunction, "TaskName", 256, NULL, 1, &xTaskHandle);
2. 任务删除(vTaskDelete)
void vTaskDelete(TaskHandle_t xTaskToDelete);
  • 参数xTaskToDelete 为任务句柄,传入 NULL 时删除当前正在执行的任务。
  • 注意:被删除任务的栈空间和 TCB(任务控制块)会被 FreeRTOS 自动回收,但需手动释放任务中动态分配的资源(如 malloc 的内存)。
  • // 在其他任务中删除指定任务
    vTaskDelete(xTaskHandle);
    
    // 在任务内部自我删除
    vTaskDelete(NULL);

    二、优先级抢占机制(核心调度规则)

    1. 优先级定义与范围
  • 数值范围
    • 优先级从 0(最低)到 configMAX_PRIORITIES-1(最高),通过 uxPriority 参数设置。
    • 建议使用 tskIDLE_PRIORITY(空闲任务优先级,值为 0)作为基准,例如:
      #define TASK_PRIORITY (tskIDLE_PRIORITY + 1)  // 比空闲任务高一级
      
  • 抢占规则
    • 高优先级任务 可立即抢占 低优先级任务 的 CPU 使用权(若内核配置为可抢占模式,configUSE_PREEMPTION 为 1)。
    • 相同优先级任务 按时间片轮转(时间片长度由 configTICK_RATE_HZ 决定,默认 10ms)。
2. 调度器行为示例

假设有三个任务:

  • TaskA(优先级 2,周期 100ms)
  • TaskB(优先级 1,周期 200ms)
  • TaskC(优先级 3,周期 500ms)

调度时序

  1. TaskC(最高优先级)运行,直到主动阻塞(如调用 vTaskDelay())。
  2. TaskA(次高优先级)抢占 TaskB,运行 100ms 后阻塞。
  3. TaskB(最低优先级)获得 CPU,运行 200ms 后阻塞。
  4. 若所有任务均阻塞,空闲任务(优先级 0)运行,直到有任务就绪。

三、关键配置参数(FreeRTOSConfig.h)

1. 抢占式调度 vs 合作式调度
#define configUSE_PREEMPTION        1  // 1=抢占式调度(默认),0=合作式调度
  • 抢占式:高优先级任务可立即中断低优先级任务。
  • 合作式:任务仅在主动放弃 CPU(如调用 taskYIELD())时切换。
2. 优先级数量限制
#define configMAX_PRIORITIES        5  // 允许的最大优先级数量
3. 时间片调度
#define configUSE_TIME_SLICING      1  // 1=启用时间片(默认),0=禁用
  • 仅对 相同优先级任务 有效,每个任务运行一个时间片(由 configTICK_RATE_HZ 决定)后切换。

四、实战技巧与注意事项

1. 任务优先级设计原则
  • 关键任务(如传感器数据采集):设为高优先级(如 3)。
  • 用户交互任务(如按键处理):设为中等优先级(如 2)。
  • 后台任务(如数据处理):设为低优先级(如 1)。
  • 空闲任务(系统自动创建):优先级最低(0)。
2. 避免优先级反转问题
  • 场景:低优先级任务持有互斥锁时,被中优先级任务抢占,导致高优先级任务无法获取锁而阻塞。
  • 解决方案
    • 使用 互斥量(Mutex)代替二值信号量,支持 优先级继承configUSE_MUTEXES 需为 1)。
SemaphoreHandle_t xMutex;
xMutex = xSemaphoreCreateMutex();  // 创建互斥量

// 获取锁(在任务中)
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
    // 临界区代码
    xSemaphoreGive(xMutex);  // 释放锁
}
3. 任务创建与删除的线程安全
  • 创建任务:可在调度器启动前或运行中调用,但需注意:
    • 调度器启动前创建的任务会在 vTaskStartScheduler() 后立即执行。
    • 调度器运行中创建任务需确保堆内存充足(FreeRTOS 使用动态内存分配 TCB 和栈)。
  • 删除任务:避免在中断服务函数(ISR)中直接调用 vTaskDelete(),应使用 xTimerPendFunctionCallFromISR() 替代。

五、调试与验证

1. 查看任务状态

通过 uxTaskGetSystemState() 和 vTaskList() 函数获取任务运行时信息:

char pcTaskList[512];
vTaskList(pcTaskList);  // 获取任务列表(包含优先级、状态、栈使用等)
printf("Task List:\n%s\n", pcTaskList);
2. 优先级验证
  • 在低优先级任务中添加高优先级任务创建代码,观察调度器行为:
    void vLowPriorityTask(void *pvParameters) {
        printf("Low priority task running...\n");
        // 创建高优先级任务
        xTaskCreate(vHighPriorityTask, "HighTask", 256, NULL, 3, NULL);
        printf("Low priority task continues...\n");  // 此行不会立即执行(被高优先级任务抢占)
    }

创建完任务后,需要开启调度器-vTaskStartScheduler()函数。vTaskStartScheduler()函数实现了空闲任务和定时器任务。
FreeRTOS 一旦启动,就必须要保证系统中每时每刻都有一个任务处于运行态(Runing),并且空闲任务不可以被挂起与删除, 空闲任务的优先级是最低的,以便系统中其他任务能随时抢占空闲任务的 CPU 使用权。

 项目 5:多任务 LED 控制
- Task1:呼吸灯(PWM)
- Task2:按键状态检测

 

// FreeRTOS任务配置
#define TASK_BLINK_PRIORITY      (tskIDLE_PRIORITY + 2)  // 呼吸灯任务优先级
#define TASK_BLINK_STACK_SIZE    256                     // 任务栈大小(字)
#define TASK_KEY_PRIORITY        (tskIDLE_PRIORITY + 1)  // 按键检测任务优先级
#define TASK_KEY_STACK_SIZE      128                     // 任务栈大小(字)

// PWM配置
#define PWM_FREQ_HZ              1000                    // PWM频率(Hz)
#define PWM_PERIOD               (1000000 / PWM_FREQ_HZ) // PWM周期(微秒)
#define PWM_MAX_DUTY             100                     // 最大占空比(%)

// 按键配置
#define KEY_DEBOUNCE_TIME        20                      // 消抖时间(ms)



// 初始化PWM输出(假设使用TIM3_CH1,对应GPIOA_PIN6)
void PWM_Init(void) {
    TIM_HandleTypeDef htim3;
    TIM_OC_InitTypeDef sConfigOC;

    // 使能定时器和GPIO时钟
    __HAL_RCC_TIM3_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();

    // 配置GPIO为复用推挽输出
    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 配置定时器
    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 72 - 1;         // 72MHz / 72 = 1MHz
    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim3.Init.Period = PWM_PERIOD - 1;    // 1MHz / 1000 = 1kHz
    htim3.Init.ClockDivision = 0;
    HAL_TIM_PWM_Init(&htim3);

    // 配置PWM输出
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 0;                  // 初始占空比为0%
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);

    // 启动PWM输出
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
}

// 设置PWM占空比
void PWM_SetDutyCycle(uint8_t duty) {
    // 将0-100%的占空比转换为定时器计数值
    uint32_t pulse = (duty * PWM_PERIOD) / 100;
    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, pulse);
}



// 初始化按键输入(假设使用GPIOA_PIN0)
void KEY_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // 使能GPIO时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();

    // 配置GPIO为输入模式,上拉
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

// 读取按键状态(已消抖)
uint8_t KEY_Read(void) {
    static uint8_t key_state = 0;
    static uint32_t debounce_time = 0;
    
    uint8_t current_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
    
    if (current_state != key_state) {
        // 状态变化,开始消抖计时
        debounce_time = HAL_GetTick();
        key_state = current_state;
    }
    
    // 消抖时间后返回稳定状态
    if (HAL_GetTick() - debounce_time > KEY_DEBOUNCE_TIME) {
        return key_state;
    }
    
    return key_state;
}




// 呼吸灯任务
void vTaskBlink(void *pvParameters) {
    uint8_t duty = 0;       // 初始占空比
    uint8_t direction = 1;  // 1=增加,0=减少
    
    for (;;) {
        // 更新PWM占空比,实现呼吸灯效果
        PWM_SetDutyCycle(duty);
        
        // 调整占空比变化方向
        if (duty >= PWM_MAX_DUTY) {
            direction = 0;
        } else if (duty <= 0) {
            direction = 1;
        }
        
        // 根据方向调整占空比
        duty += (direction ? 1 : -1);
        
        // 延时,控制呼吸灯变化速度
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

// 按键检测任务
void vTaskKey(void *pvParameters) {
    uint8_t key_last_state = 1;  // 初始状态:按键释放
    
    for (;;) {
        uint8_t key_current_state = KEY_Read();
        
        // 检测按键下降沿(按下)
        if (key_last_state == 1 && key_current_state == 0) {
            printf("按键按下!\n");
            // 此处可添加按键按下的处理逻辑
        }
        
        key_last_state = key_current_state;
        
        // 延时,控制按键扫描频率
        vTaskDelay(pdMS_TO_TICKS(20));
    }
}


int main(void) {
    // 硬件初始化
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    PWM_Init();
    KEY_Init();
    
    // 创建任务
    xTaskCreate(vTaskBlink, "Blink", TASK_BLINK_STACK_SIZE, NULL, TASK_BLINK_PRIORITY, NULL);
    xTaskCreate(vTaskKey, "Key", TASK_KEY_STACK_SIZE, NULL, TASK_KEY_PRIORITY, NULL);
    
    // 启动调度器
    vTaskStartScheduler();
    
    // 如果程序执行到这里,说明发生了错误
    for (;;);
}



若需实现按键控制呼吸灯开关,可添加任务间通信机制
// 创建二值信号量(用于控制呼吸灯开关)
SemaphoreHandle_t xLedControl;

// 在main()中初始化信号量
xLedControl = xSemaphoreCreateBinary();
xSemaphoreGive(xLedControl);  // 默认允许呼吸灯运行

// 修改呼吸灯任务
void vTaskBlink(void *pvParameters) {
    uint8_t duty = 0;
    uint8_t direction = 1;
    
    for (;;) {
        // 获取信号量,控制呼吸灯是否运行
        if (xSemaphoreTake(xLedControl, portMAX_DELAY) == pdTRUE) {
            // 更新PWM占空比
            PWM_SetDutyCycle(duty);
            
            // 调整占空比
            if (duty >= PWM_MAX_DUTY) direction = 0;
            if (duty <= 0) direction = 1;
            duty += (direction ? 1 : -1);
            
            // 释放信号量
            xSemaphoreGive(xLedControl);
        }
        
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

// 修改按键任务(检测到按键按下时切换呼吸灯状态)
void vTaskKey(void *pvParameters) {
    uint8_t key_last_state = 1;
    static uint8_t led_enabled = 1;  // 呼吸灯默认开启
    
    for (;;) {
        uint8_t key_current_state = KEY_Read();
        
        if (key_last_state == 1 && key_current_state == 0) {
            // 按键按下,切换呼吸灯状态
            led_enabled = !led_enabled;
            
            if (led_enabled) {
                printf("呼吸灯开启\n");
                xSemaphoreGive(xLedControl);  // 允许呼吸灯运行
            } else {
                printf("呼吸灯关闭\n");
                xSemaphoreTake(xLedControl, 0);  // 禁止呼吸灯运行
                PWM_SetDutyCycle(0);  // 立即熄灭LED
            }
        }
        
        key_last_state = key_current_state;
        vTaskDelay(pdMS_TO_TICKS(20));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值