在嵌入式系统中,任务之间的通信是实现并发、同步和资源共享的关键。FreeRTOS 提供了多种通信机制,以满足不同的应用场景和性能要求。本文将重点介绍三种常用的通信方式:消息队列(Queue)、邮箱(Semaphore)和任务通知(Task Notification),并通过代码示例分析其适用场景和使用方法。
一、消息队列(Queue)
概述
消息队列用于在任务之间传递消息或数据。它可以存储多个数据项,常用于任务与任务、中断与任务之间的通信,适合需要“带数据”传递的情况。
使用场景
- 按钮中断向任务传递事件编号
- 任务间传输传感器值、指令等
示例代码
// 定义一个整型队列
QueueHandle_t my_queue;
// 初始化队列,容量为10,每个元素是int类型
my_queue = xQueueCreate(10, sizeof(int));
// 消费者任务:从队列接收数据
void consumer_task(void *arg) {
int received_data;
while (1) {
if (xQueueReceive(my_queue, &received_data, portMAX_DELAY)) {
printf("Received value: %d\n", received_data);
// 在此处理数据
}
}
}
// 中断服务程序:向队列发送数据
void EXTI_IRQHandler(void) {
int data = 42;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(my_queue, &data, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
特点
- ✅ 可传递任意数据类型
- ✅ 可用于任务与中断间通信
- ❌ 内存开销略大
- ❌ 比任务通知慢一些
二、邮箱(Semaphore)
概述
邮箱本质上是一种二值信号量,主要用于任务同步,不承载数据。它常被用作“任务启动信号”或“事件通知”,而非传输数据。
使用场景
- 通知任务“有事情要做了”,但不传递数据
- 中断触发任务启动
示例代码
// 创建一个二值信号量
SemaphoreHandle_t binary_semaphore;
// 初始化
binary_semaphore = xSemaphoreCreateBinary();
// 任务:等待信号量
void led_task(void *arg) {
while (1) {
if (xSemaphoreTake(binary_semaphore, portMAX_DELAY)) {
printf("Button pressed, toggle LED.\n");
// 翻转 LED 状态
}
}
}
// 中断服务程序:发送信号量
void button_isr(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(binary_semaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
特点
- ✅ 简单高效
- ✅ 适合事件同步(不含数据)
- ❌ 无法携带数据
- ✅ 支持中断触发
三、任务通知(Task Notification)
概述
任务通知是 FreeRTOS 中一种轻量级的任务间通信机制,每个任务有一个 32 位的“通知值”,可以在任务之间或中断中更新和读取。它支持传递一个整数值,效率高,是性能最优的通信方式。
使用场景
- 单一任务之间快速通信
- 按钮等中断触发简单事件通知
- 替代低开销信号量和队列
示例代码
// 假设我们已经获得任务句柄 task_handle
// 任务:等待通知
void notify_task(void *arg) {
uint32_t value;
while (1) {
// 阻塞等待通知,并获取通知值
if (xTaskNotifyWait(0, 0, &value, portMAX_DELAY)) {
printf("Received notify value: %lu\n", value);
// 响应事件
}
}
}
// 中断服务程序:发送通知
void notify_isr(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTaskNotifyFromISR(task_handle, 1, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
特点
- ✅ 传输单个整数值
- ✅ 效率最高、开销最小
- ❌ 每个任务只能有一个通知槽位
- ✅ 适合中断触发事件通知
四、总结对比
特性 | 消息队列 | 邮箱(信号量) | 任务通知 |
---|---|---|---|
是否传递数据 | ✅ 是(任意类型) | ❌ 否(仅通知) | ✅ 是(单个整数) |
是否支持中断调用 | ✅ 是 | ✅ 是 | ✅ 是 |
通信效率 | 中 | 高 | 最高 |
资源开销 | 较大(占用内存) | 小 | 最小(无堆内存) |
多对一通信支持 | ✅ 支持(多个任务写入) | ✅ 支持 | ❌ 单槽位,需注意覆盖 |
典型使用场景 | 传输数据、任务调度 | 同步任务、事件触发 | 快速事件触发、ISR通信 |