1,消息队列

1.1 消息队列的概念及其作用

消息队列就是通过 RTOS 内核提供的服务,任务或中断服务子程序可以将一个消息放入到队列。

(注意,FreeRTOS 消息队列传递的是实际数据,并不是数据地址,RTX,uCOS-II 和 uCOS-III 是传递的地址)

一个或者多个任务可以通过 RTOS 内核服务从队列中得到消息。

一般先进入消息队列的消息先传给任务,任务先得到的是最先进入到消息队列的消息,即先进先出的原则(FIFO)

FreeRTOS 的消息队列支持 FIFO 和 LIFO 两种数据存取方式。

相比消息队列,使用全局数组 主要有如下四个问题:

  1. 使用消息队列可以让 RTOS 内核有效地管理任务,而全局数组是无法做到的,任务的超时等机制需要 用户自己去实现。
  2. 使用了全局数组就要防止多任务的访问冲突,而使用消息队列则处理好了这个问题,用户无需担心。
  3. 使用消息队列可以有效地解决中断服务程序与任务之间消息传递的问题。
  4. FIFO 机制更有利于数据的处理

1.2FreeRTOS 任务间消息队列的实现

1.3FreeRTOS 中断方式消息队列的实现

中断方式的消息机制要注意以下四个问题:

  • 中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
  • 建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在 任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优 先级,以便退出中断函数后任务可以得到及时执行。
  • 中断服务程序中一定要调用专用于中断的消息队列函数,即以 FromISR 结尾的函数。
  • 在操作系统中实现中断服务程序与裸机编程的区别。
    • 如果 FreeRTOS 工程的中断函数中没有调用 FreeRTOS 的消息队列 API 函数,与裸机编程是一 样的。
    • 如果 FreeRTOS 工程的中断函数中调用了 FreeRTOS 的消息队列的 API 函数,退出的时候要检 测是否有高优先级任务就绪,如果有就绪的,需要在退出中断后进行任务切换,这点与裸机编程 稍有区别
    • NVIC 优先级分组设置为 4
    • 用户要在 FreeRTOS 多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。

2,消息队列 API 函数

2.1Modules

2.2我们重点的说以下 4 个函数:

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, /* 消息个数 */
UBaseType_t uxItemSize ); /* 每个消息大小,单位字节 */

函数 xQueueCreate 用于创建消息队列:

  • 第 1 个参数是消息队列支持的消息个数。
  • 第 2 个参数是每个消息的大小,单位字节。
  • 返回值,如果创建成功会返回消息队列的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不足, 无法为此消息队列提供所需的空间会返回 NULL。
  • FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址
static QueueHandle_t xQueue1 = NULL;
static QueueHandle_t xQueue2 = NULL;

static void AppObjCreate (void)
{
/* 创建 10 个 uint8_t 型消息队列 */
xQueue1 = xQueueCreate(10, sizeof(uint8_t));
 if( xQueue1 == 0 )
 {
 /* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
 }
/* 创建 10 个存储指针变量的消息队列,由于 CM3/CM4 内核是 32 位机,一个指针变量占用 4 个字节 */
xQueue2 = xQueueCreate(10, sizeof(struct Msg *));
 if( xQueue2 == 0 )
 {
 /* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
 } 
    
}

BaseType_t xQueueSend( QueueHandle_t xQueue, /* 消息队列句柄 */
const void * pvItemToQueue, /* 要传递数据地址 */
TickType_t xTicksToWait /* 等待消息队列有空间的最大等待时间 */ );

函数 xQueueSend 用于任务中消息发送。

  • 第 1 个参数是消息队列句柄。
  • 第 2 个参数要传递数据地址,每次发送都是将消息队列创建函数 xQueueCreate 所指定的单个消息大 小复制到消息队列空间中。
  • 第 3 个参数是当消息队列已经满时,等待消息队列有空间时的最大等待时间,单位系统时钟节拍。
  • 返回值,如果消息成功发送返回 pdTRUE,否则返回 errQUEUE_FULL。

  • 如果消息队列已经满且第三个参数为 0,那么此函数会立即返回。
  • portMAX_DELAY,函数会永久等待
  • 消息队列还有两个函数 xQueueSendToBack 和 xQueueSendToFront,函数 xQueueSendToBack 实现的是 FIFO 方式的存取,函数 xQueueSendToFront 实现的是 LIFO 方式的读写。
typedef struct Msg
{
uint8_t ucMessageID;
uint16_t usData[2];
uint32_t ulData[2];
}MSG_T;

MSG_T g_tMsg; /* 定义一个结构体用于消息队列 */
MSG_T *ptMsg;

/* 初始化结构体指针 */
ptMsg = &g_tMsg;


ptMsg->ucMessageID++;
ptMsg->ulData[0]++;;
ptMsg->usData[0]++;
/* 使用消息队列实现指针变量的传递 */
if(xQueueSend(xQueue2, /* 消息队列句柄 */
(void *) &ptMsg, /* 发送结构体指针变量 ptMsg 的地址 */
(TickType_t)10) != pdPASS )
{
/* 发送失败,即使等待了 10 个时钟节拍 */
printf("K3 键按下,向 xQueue2 发送数据失败,即使等待了 10 个时钟节拍\r\n");
}
else
{
/* 发送成功 */
printf("K3 键按下,向 xQueue2 发送数据成功\r\n");
}

BaseType_t xQueueSendFromISR
(
QueueHandle_t xQueue, /* 消息队列句柄 */
const void *pvItemToQueue, /* 要传递数据地址 */
BaseType_t *pxHigherPriorityTaskWoken /* 高优先级任务是否被唤醒的状态保存 */
);

函数 xQueueSendFromISR 用于中断服务程序中消息发送。

  • 3 个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是 pdTRUE, 说明有高优先级任务要执行,否则没有。
  • 返回值,如果消息成功发送返回 pdTRUE,否则返回 errQUEUE_FULL。
  • FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址。正因为这个原因,用户在创建消息队列时单个消息大小不可太大,因为一定程度上面会增加中断服务程序的执行时间。

static void TIM1_IRQHandler(void)
{
    BaseType_t xResult;
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    g_uiCount++;
    /* 向消息队列发数据 */
    xQueueSendFromISR(xQueue1,
                     (void *)&g_uiCount,
                     &xHigherPriorityTaskWoken);
    /* 如果 xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */
    if(xHigherPriorityTaskWoken==pdTRUE)
    {
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

BaseType_t xQueueReceive( QueueHandle_t xQueue, /* 消息队列句柄 */
void *pvBuffer, /* 接收消息队列数据的缓冲地址 */
TickType_t xTicksToWait /* 等待消息队列有数据的最大等待时间 */ );

函数 xQueueReceive 用于接收消息队列中的数据。

  • 第 1 个参数是消息队列句柄。
  • 第 2 个参数是从消息队列中复制出数据后所储存的缓冲地址,缓冲区空间要大于等于消息队列创建函 数 xQueueCreate 所指定的单个消息大小,否则取出的数据无法全部存储到缓冲区,从而造成内存溢 出。
  • 第 3 个参数是消息队列为空时,等待消息队列有数据的最大等待时间,单位系统时钟节拍。
  • 返回值,如果接到到消息返回 pdTRUE,否则返回 pdFALSE。

注意以下问题:

  • 如果消息队列为空且第三个参数为 0,那么此函数会立即返回。
  • 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第三个参数配 置为 portMAX_DELAY,那么此函数会永久等待直到消息队列有数据。

uint8_t ucQueueMsgValue;

xResult = xQueueReceive(xQueue1,
                        (void *) &ucQueueMsgValue,
                        (TickType_t)xMaxBlockTime
                        );
                        if(xResult == pdPASS)
{
    /* 成功接收,并通过串口将数据打印出来 */
    printf("接收到消息队列数据 ucQueueMsgValue = %d\r\n", ucQueueMsgValue);
}

毫秒转tick函数:pdMS_TO_TICKS(300);