FreeRTOS的任务间通信方式提供了多种机制来帮助任务之间交换信息、同步执行和共享资源。以下是几种常见的通信方式及其典型应用场景:
1. 信号量(Semaphores)
应用场景:
资源互斥访问:例如,假设多个任务需要访问一个共享的ADC(模拟数字转换器),可以使用互斥信号量来确保一次只有一个任务可以访问ADC。
事件通知:一个任务完成某项工作后,可以通过信号量通知其他任务,如数据采集完成。
1.1 创建信号量:
#include "freertos/semphr.h"
SemaphoreHandle_t xSemaphore;
void vSemaphoreCreate()
{
xSemaphore = xSemaphoreCreateBinary();
configASSERT(xSemaphore);
}
1.2 使用信号量:
void vSemaphoreTake()
{
if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE)
{
// 信号量获取成功,执行临界区代码
// ...
xSemaphoreGive(xSemaphore);
}
}
2. 消息队列(Message Queues)
应用场景:
异步数据传输:一个任务采集传感器数据,并将其放入消息队列,另一个任务负责处理这些数据,从而实现异步通信。
命令响应:一个任务通过消息队列发送命令给另一个任务,接收任务处理命令后,再通过队列返回响应。
2.1 创建消息队列
#include "freertos/queue.h"
QueueHandle_t xQueue;
void vQueueCreate()
{
xQueue = xQueueCreate(10, sizeof(uint32_t));
configASSERT(xQueue);
}
2.2 发送消息队里
void vQueueSend()
{
uint32_t ulValue = 123;
xQueueSend(xQueue, &ulValue, portMAX_DELAY);
}
2.3 接收消息队列
void vQueueReceive()
{
uint32_t ulReceivedValue;
if (xQueueReceive(xQueue, &ulReceivedValue, portMAX_DELAY) == pdTRUE)
{
// 消息接收成功
}
}
3. 事件组(Event Groups)
应用场景:
多事件组合:当一个任务需要等待多个事件中的任何一个或几个事件发生时,可以使用事件组。例如,一个任务可能需要等待网络连接建立或外部存储器准备好,可以使用事件组来同时监听这两种事件。
3.1 创建事件组
#include "freertos/event_groups.h"
EventGroupHandle_t xEventGroup;
void vEventGroupCreate()
{
xEventGroup = xEventGroupCreate();
configASSERT(xEventGroup);
}
3.2 设置事件
void vEventGroupSetBits()
{
xEventGroupSetBits(xEventGroup, 1 << 0);
}
3.3 等待事件
void vEventGroupWaitBits()
{
uint32_t ulBits;
ulBits = xEventGroupWaitBits(xEventGroup, 1 << 0, pdFALSE, pdFALSE, portMAX_DELAY);
if (ulBits & (1 << 0))
{
// 事件已发生
}
}
4. 任务通知(Task Notifications)
应用场景:
轻量级通信:当一个任务需要简单地通知另一个任务某些事件已经发生时,可以使用任务通知,如任务完成、定时器到期或外部事件触发。
4.1 发送通知
void taskF(void *pvParameters)
{
while(1)
{
xTaskNotify(taskGHandle, 0, eIncrement);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
4.2 接收通知
void taskG(void *pvParameters)
{
while(1)
{
uint32_t ulNotificationValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if(ulNotificationValue > 0)
{
// Notification received
// ...
}
}
}
5. 互斥量(Mutexes)和递归互斥量(Recursive Mutexes)
应用场景:
保护共享资源:当多个任务需要访问共享内存或硬件资源时,可以使用互斥量来保证资源的独占访问。递归互斥量允许一个任务多次获取同一互斥量,这在任务内部需要多次访问共享资源时非常有用。
5.1 创建互斥量
#include "semphr.h"
SemaphoreHandle_t xMutex = NULL;
void createMutex(void)
{
xMutex = xSemaphoreCreateMutex();
configASSERT(xMutex != NULL);
}
5.2 使用互斥量
void taskA(void *pvParameters)
{
while(1)
{
xSemaphoreTake(xMutex, portMAX_DELAY);
// Critical section
// Access shared resource here
// ...
xSemaphoreGive(xMutex);
}
}
5.3 创建递归互斥量
SemaphoreHandle_t xRecursiveMutex = NULL;
void createRecursiveMutex(void)
{
xRecursiveMutex = xSemaphoreCreateRecursiveMutex();
configASSERT(xRecursiveMutex != NULL);
}
5.4 使用递归互斥量
void taskB(void *pvParameters)
{
while(1)
{
xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
// Critical section 1
// Access shared resource here
// ...
// If this task needs to re-enter the critical section:
xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
// Critical section 2
// Access shared resource again
// ...
// Release lock twice because it was taken twice
xSemaphoreGiveRecursive(xRecursiveMutex);
xSemaphoreGiveRecursive(xRecursiveMutex);
}
}
6. 软件定时器(Software Timers)
虽然软件定时器本身不是直接的通信机制,但它们可以间接用于任务间的通信。
应用场景:
周期性任务执行:一个任务可以配置一个软件定时器,当定时器到期时,它会自动重新加载并执行回调函数,这可以用于定期执行任务,如数据上报、周期性维护等。
每种通信方式都有其优缺点和最适合的场景,选择合适的方法可以提高系统的效率和可靠性。在实际应用中,可能需要根据具体需求组合使用多种通信方式。例如,一个复杂的系统可能同时使用消息队列来传输数据,使用信号量来同步资源访问,以及使用事件组来处理多事件组合的情况。
6.1 定义软件定时器回调函数
static void prvTimerCallback(TimerHandle_t pxTimer)
{
// This function will be called every 1 second (as specified in the timer creation).
// You can put your code here that should run periodically.
// Example: Print something to the console
printf("Timer expired!\n");
// Optionally, you can restart the timer from here if it's a one-shot timer.
// xTimerReset(pxTimer, 0);
}
6.2 创建和配置软件定时器
#include "progtypes.h"
#include "timers.h"
static void prvTimerCallback(TimerHandle_t pxTimer);
void createTimer(void)
{
TimerHandle_t xTimer = xTimerCreate(
"DemoTimer", /* Just a text name, not used by the RTOS kernel. */
pdMS_TO_TICKS(1000), /* The rate at which the timer ticks. */
pdFALSE, /* The timer is not auto-reload (one-shot). */
(void *)0, /* The ID of the timer - not needed. */
prvTimerCallback);/* The function to be called when the timer expires. */
if(xTimer != NULL)
{
// Start the timer.
if(xTimerStart(xTimer, 0) != pdPASS)
{
// Failed to start the timer.
}
}
}
6.3 创建和启动软件定时器
void main_task(void *pvParameters)
{
// Create and start the timer.
createTimer();
// Your other tasks or code...
}