FreeRTOS 二值信号量的使用与示例
FreeRTOS 二值信号量的使用与示例
一、FreeRTOS 二值信号量的核心概念
二值信号量是 FreeRTOS 中最简单的信号量类型,仅包含0(空) 和1(满) 两个状态:
-
当信号量为「满」时,
xSemaphoreTake()可获取信号量,信号量变为「空」; -
当信号量为「空」时,
xSemaphoreGive()可释放信号量,信号量变为「满」; -
常用于同步场景(任务间 / 中断与任务),也可用于简单的互斥(但推荐用互斥信号量做互斥,因二值信号量无优先级继承)。
二、二值信号量的代码实现(基于 STM32+FreeRTOS)
以下示例包含两个核心场景:中断与任务同步(按键中断通知任务处理)、任务间同步。
1. 头文件与全局句柄定义
#include "stm32f10x.h"
#include "FreeRTOS.h"
#include "semphr.h"
// 二值信号量句柄(按键中断同步用)
SemaphoreHandle_t xBinarySem_Key;
// 二值信号量句柄(任务间同步用)
SemaphoreHandle_t xBinarySem_Task;
// 按键中断初始化(PA0 外部中断0)
void Key_EXTI_Init(void);
// 任务函数声明
void vTask_Key_Process(void *pvParameters);
void vTask_Producer(void *pvParameters);
2. 信号量创建与任务启动(main 函数)
int main(void)
{
// 1. 硬件初始化(串口、按键中断等)
USART_Init();
Key_EXTI_Init();
// 2. 创建二值信号量(动态创建,初始为空)
xBinarySem_Key = xSemaphoreCreateBinary();
xBinarySem_Task = xSemaphoreCreateBinary();
// 检查信号量创建是否成功
if((xBinarySem_Key != NULL) && (xBinarySem_Task != NULL))
{
// 3. 创建任务
// 按键处理任务(优先级2)
xTaskCreate(vTask_Key_Process, "KeyProcess", 128, NULL, 2, NULL);
// 生产者任务(优先级1)
xTaskCreate(vTask_Producer, "Producer", 128, NULL, 1, NULL);
// 4. 启动FreeRTOS调度器
vTaskStartScheduler();
}
// 若调度器启动失败(如内存不足),进入死循环
while(1);
}
3. 按键中断服务函数(发送信号量)
中断中必须使用xSemaphoreGiveFromISR()(而非xSemaphoreGive()),避免中断上下文冲突:
void EXTI0_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE; // 标记是否需要切换任务
// 确认中断触发(消抖简化版)
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// 向二值信号量发送信号(中断中)
xSemaphoreGiveFromISR(xBinarySem_Key, &xHigherPriorityTaskWoken);
// 清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line0);
// 若高优先级任务就绪,触发上下文切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
4. 任务实现
(1)按键处理任务(接收信号量)
void vTask_Key_Process(void *pvParameters)
{
for(;;)
{
// 等待信号量:阻塞时间portMAX_DELAY(永久等待)
if(xSemaphoreTake(xBinarySem_Key, portMAX_DELAY) == pdTRUE)
{
// 信号量获取成功,处理按键逻辑
printf("按键中断触发,任务开始处理...rn");
// 模拟按键处理(如控制LED、读取数据等)
for(int i=0; i<3; i++)
{
printf("按键处理中...%drn", i+1);
vTaskDelay(pdMS_TO_TICKS(200)); // 延时200ms
}
printf("按键处理完成rn");
}
}
}
(2)生产者任务(任务间同步,发送信号量)
void vTask_Producer(void *pvParameters)
{
for(;;)
{
// 模拟生产数据(每3秒生产一次)
vTaskDelay(pdMS_TO_TICKS(3000));
printf("生产者任务:数据生产完成,通知消费任务rn");
// 向任务间同步的二值信号量发送信号(任务中用xSemaphoreGive)
xSemaphoreGive(xBinarySem_Task);
// 可选:消费任务可单独创建,此处简化为在当前任务演示接收
if(xSemaphoreTake(xBinarySem_Task, pdMS_TO_TICKS(100)) == pdTRUE)
{
printf("消费任务(简化):接收数据,开始处理rn");
}
}
}
三、二值信号量的核心用途与举例
二值信号量的核心价值是 「同步」(让一个执行流等待另一个执行流的触发),而非复杂的资源管理,以下是典型场景:
1. 中断与任务同步(最常用)
-
场景:中断处理应尽量简短(避免抢占 CPU),仅做「信号通知」,复杂逻辑交给任务处理。
-
例子:
-
按键中断:中断仅标记 “按键被按下”,任务负责消抖、执行按键逻辑(如控制电机、打印日志);
-
串口接收中断:中断仅缓存 1 字节数据,任务负责拼接数据、解析协议;
-
定时器中断:中断仅触发 “定时到”,任务负责执行周期性任务(如传感器采集)。
-
-
优势:减少中断占用时间,避免嵌套中断风险,任务级逻辑更易维护。
2. 任务间同步
-
场景:一个任务等待另一个任务完成某操作后再执行。
-
例子:
-
生产消费模型(简化版):生产者任务完成数据采集后,发送信号量通知消费者任务处理数据;
-
初始化同步:系统启动时,任务 A 完成硬件初始化后发送信号量,任务 B(依赖硬件)才开始运行;
-
步骤化任务:任务 1 完成 “数据采集”→发信号量→任务 2 执行 “数据处理”→发信号量→任务 3 执行 “数据上传”。
-
3. 简单的资源互斥(不推荐,仅临时使用)
-
场景:两个任务共享一个独占资源(如单串口、单传感器)。
-
例子:任务 A 和任务 B 都需要打印日志到串口,通过二值信号量保证同一时间只有一个任务访问串口。
-
缺点:无优先级继承机制,可能导致 “优先级反转”(低优先级任务占用资源,高优先级任务等待);推荐用互斥信号量(
xSemaphoreCreateMutex())做互斥。
四、关键注意事项
- API 使用场景:
-
任务中:
xSemaphoreTake()/xSemaphoreGive(); -
中断中:
xSemaphoreTakeFromISR()(极少用) /xSemaphoreGiveFromISR()。
-
阻塞时间:
xSemaphoreTake()的第二个参数为阻塞时间(tick 数),portMAX_DELAY表示永久等待,0表示不等待。 -
信号量创建:
xSemaphoreCreateBinary()创建的信号量初始为空,需先Give才能Take;新版 FreeRTOS 也可使用xSemaphoreCreateBinaryStatic()(静态创建,避免内存分配失败)。
五、运行效果
-
按键按下时,中断触发→发送信号量→按键处理任务执行,打印 “按键处理中”;
-
每 3 秒,生产者任务发送信号量→模拟消费任务处理数据,打印 “接收数据,开始处理”;
-
任务间无冲突,中断处理耗时极短,符合嵌入式实时性要求。

浙公网安备 33010602011771号