06. µCOS-Ⅲ的消息队列
一、消息队列简介
任务与其它任务或任务与中断间的通讯一般可以通过全局变量或消息队列来完成。
如果是使用全局变量的话,那么这个全局变量将被作为任务与任务之间或任务与中断之间的共享资源,因此开发者在设计时还需要考虑该共享资源的互斥访问问题,并且当全局变量被一个任务或中断访问更新后,通讯中全局变量的接收任务无法知道该全局变量是否已经被更新,从而无法实时地获取该全局变量的最新数据。
假设有一个全局变量 a = 0,现有两个任务都在写这个变量 a。
全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损。
正是由于使用全局变量进行任务与任务或任务与中断之间的通讯存在诸多的弊端,因此诞生了消息队列这一机制。消息队列中包含了消息,这些消息可以通过消息队列进行传输,也可以以直接送到指定的任务中,因为每个任务都可以有独自的内嵌消息队列。通过消息队列传输的消息,可以发送给多个任务。
µC/OS-Ⅲ 队列特点:
- 数据入队出队方式:队列通常采用 “先进先出” (FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取,µC/OS-Ⅲ 中也可以配置为 “后进先出” LIFO 方式。
- 数据传输方式:µC/OS-Ⅲ 的队列数据是一个 “万能指针”,可以指向任何数据,甚至是函数,所以发送方和接收方必须按照约定好的方式去发送和接收消息,这样才能正常解析接收到的消息。
- 多任务访问:队列不属于某个任务,任何任务和中断都可以向队列发送消息,但是读取消息只能在任务中,不支持中断读取消息。
- 出队阻塞:当任务向一个队列读取消息时,可以指定一个阻塞时间,假设此时当队列没有数据时无法读取:
- 若阻塞时间 = 0:死等,一直等到可以队列有数据可以出队为止。
- 若阻塞时间 > 0:等待设定的阻塞时间,若在该时间内还未接收到数据,超时后直接返回不再等待。
读写队列做好了保护,防止多任务同时访问冲突。
中断不可以调用队列接收函数,但是可以调用队列发送函数。
入队(发送消息队列)不会阻塞。
二、消息队列相关API函数
2.1、消息的数据类型
typedef struct os_msg OS_MSG;
struct os_msg {
OS_MSG *NextPtr; // 指向下一条消息的指针
void *MsgPtr; // 指向消息内容的指针
OS_MSG_SIZE MsgSize; // 消息内容的大小,单位:字节
#if (OS_CFG_TS_EN > 0u)
CPU_TS MsgTS; // 消息发送时的时间戳
#endif
};
消息的结构中包含了一个指向消息内容的指针 MsgPtr,其数据类型为 void*,这可以理解为是一个 “万能指针”,MsgPtr 可以指向任何的数据,甚至可以指向一个函数,因此消息的发送方和接收方必须按照实现约定好的方式去发送和接收消息,只有这样,消息的接收方才能够正确地解析接收到的消息。
2.2、创建一个消息队列
void OSQCreate (OS_Q *p_q, // 指向消息队列结构体的指针
CPU_CHAR *p_name, // 指向作为消息队列名的 ASCII 字符串的指针
OS_MSG_QTY max_qty, // 消息队列的大小
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSQCreate() 的错误代码描述,如下所示:
OS_ERR_NONE // 消息队列创建成功
OS_ERR_CREATE_ISR // 在中断中非法调用该函数
OS_ERR_ILLEGAL_CREATE_RUN_TIME // 在系统运行过程中非法创建内核对象
OS_ERR_OBJ_PTR_NULL // 指向消息队列结构体的指针为空
OS_ERR_Q_SIZE // 消息队列的大小为非法值0
2.3、删除一个消息队列
// 返回值:删除消息队列时,被终止挂起任务的数量
OS_OBJ_QTY OSQDel (OS_Q *p_q, // 指向消息队列结构体的指针
OS_OPT opt, // 函数操作选项
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSQDel() 的错误代码描述,如下所示:
OS_ERR_NONE // 消息队列删除成功
OS_ERR_DEL_ISR // 在中断中非法调用该函数
OS_ERR_ILLEGAL_DEL_RUN_TIME // 在系统运行过程中非法创建内核对象
OS_ERR_OBJ_PTR_NULL // 指向消息队列结构体的指针为空
OS_ERR_OBJ_TYPE // 操作的内核对象的类型不是消息队列
OS_ERR_OPT_INVALID // 无效的函数操作选项
OS_ERR_OS_NOT_RUNING // µC/OS-Ⅲ 内核还未运行
OS_ERR_TASK_WAITING // 有任务挂起等待消息队列,不能删除消息队列
2.4、获取消息队列中的消息
// 返回值:指向消息的指针
void *OSQPend (OS_Q *p_q, // 指向消息队列结构体的指针
OS_TICK timeout, // 任务挂起等待消息队列的最大允许时间,当为0时,表示将一直等待,直到接收到消息
OS_OPT opt, // 函数操作选项
OS_MSG_SIZE *p_msg_size, // 指向用于接收消息大小变量的指针
CPU_TS *p_ts, // 指向接收消息队列接收时的时间戳的变量的指针,为NULL,说明用户没有要求时间戳
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSQPend() 的函数选项描述,如下所示:
OS_OPT_PEND_BLOCKING // 如果没有任何消息存在的话就阻塞任务
OS_OPT_PEND_NON_BLOCKING // 如果消息队列没有任何消息的话任务就直接返回
函数 OSQPend() 的错误代码描述,如下所示:
OS_ERR_NONE // 消息队列清空成功
OS_ERR_OBJ_DEL // 指定的消息队列已经被删除
OS_ERR_OBJ_PTR_NULL // 指向消息队列结构体的指针为空
OS_ERR_OBJ_TYPE // 操作的内核对象的类型不是消息队列
OS_ERR_OPT_INVALID // 无效的函数操作选项
OS_ERR_OS_NOT_RUNING // µC/OS-Ⅲ 内核还未运行
OS_ERR_PEND_ABORT // 任务挂起等待消息队列时被终止(还未超时)
OS_ERR_PEND_ISR // 在中断中非法调用该函数
OS_ERR_PEND_WOULD_BLOCK // 获取消息队列失败,并且不挂起任务等待互斥信号量
OS_ERR_PTR_INVALID // 参数 p_msg_size 指针为空
OS_ERR_SCHED_LOCKED // 任务调度器已锁定
OS_ERR_TIMEOUT // 任务挂起等待消息队列超时
2.5、发送消息到消息队列
void OSQPost (OS_Q *p_q, // 指向消息队列结构体的指针
void *p_void, // 指向消息的指针
OS_MSG_SIZE msg_size, // 消息的大小,单位:字节
OS_OPT opt, // 函数操作选项
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSQPost() 的函数选项描述,如下所示:
OS_OPT_POST_FIFO // 将发送的消息保存在队列的末尾
S_OPT_POST_LIFO // 将发送的消息保存在队列的开头
OS_OPT_POST_ALL // 将消息发送给所有等待该消息的任务
OS_OPT_POST_NO_SCHED // 禁止在本函数内执行任务调度
函数 OSQPost() 的错误代码描述,如下所示:
OS_ERR_NONE // 成功发送消息到消息队列
OS_ERR_MSG_POLL_EMPTY // 消息队列满
OS_ERR_OBJ_PTR_NULL // 指向消息队列结构体的指针为空
OS_ERR_OBJ_TYPE // 操作的内核对象的类型不是消息队列
OS_ERR_OPT_INVALID // 无效的函数操作选项
OS_ERR_OS_NOT_RUNING // µC/OS-Ⅲ 内核还未运行
OS_ERR_Q_MAX // 消息队列满
2.6、清空消息队列中所有消息
// 返回值:被释放的消息数量
OS_MSG_QTY OSQFlush (OS_Q *p_q, // 指向消息队列结构体的指针
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSQFlush() 的错误代码描述,如下所示:
OS_ERR_NONE // 消息队列清空成功
OS_ERR_FLUSH_ISR // 在中断中非法调用该函数
OS_ERR_OBJ_PTR_NULL // 指向消息队列结构体的指针为空
OS_ERR_OBJ_TYPE // 操作的内核对象的类型不是消息队列
OS_ERR_OS_NOT_RUNING // µC/OS-Ⅲ 内核还未运行
2.7、终止任务挂起等待消息队列
// 返回值:被终止挂起任务的数量
OS_OBJ_QTY OSQPendAbort (OS_Q *p_q, // 指向消息队列结构体的指针
OS_OPT opt, // 函数操作选项
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSQPendAbort() 的错误代码描述,如下所示:
OS_ERR_NONE // 终止任务挂起等待消息队列成功
OS_ERR_OBJ_PTR_NULL // 指向消息队列结构体的指针为空
OS_ERR_OBJ_TYPE // 操作的内核对象的类型不是消息队列
OS_ERR_OPT_INVALID // 无效的函数操作选项
OS_ERR_OS_NOT_RUNING // µC/OS-Ⅲ 内核还未运行
OS_ERR_PEND_ABORT_ISR // 在中断中非法调用该函数
OS_ERR_PEND_ABORT_NONE // 没有任务挂起等待消息队列
三、实验例程
main() 函数内容如下:
int main(void)
{
HAL_Init();
System_Clock_Init(8, 336, 2, 7);
Delay_Init(168);
UART_Init(&g_usart1_handle, USART1, 115200);
Key_Init();
UC_OS3_Demo();
return 0;
}
µC/OS-Ⅲ 例程入口函数:
OS_Q Q1;
char message[] = "Sakura";
/**
* @brief µC/OS-Ⅲ例程入口函数
*
*/
void UC_OS3_Demo(void)
{
OS_ERR error = {0};
OSInit(&error); // 初始化µC/OS-Ⅲ
OSQCreate(&Q1, "Q1", 1, &error); // 创建队列
// 创建开始任务
OSTaskCreate((OS_TCB * ) &start_task_tcb, // 任务控制块
(CPU_CHAR * ) "start_task", // 任务名
(OS_TASK_PTR ) Start_Task, // 任务函数
(void * ) 0, // 任务参数
(OS_PRIO ) START_TASK_PRIORITY, // 任务优先级
(CPU_STK * ) start_task_stack, // 任务堆栈
(CPU_STK_SIZE) START_TASK_STACK_SIZE / 10, // 任务栈的使用警戒线
(CPU_STK_SIZE) START_TASK_STACK_SIZE, // 任务栈大小
(OS_MSG_QTY ) 0, // 消息队列长度
(OS_TICK ) 0, // 时间片长度
(void * ) 0, // 扩展内存
(OS_OPT ) (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), // 任务选项
(OS_ERR * ) &error); // 错误码
OSStart(&error); // 开始任务调度
}
START_TASK 任务配置:
/**
* START_TASK 任务配置
* 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define START_TASK_PRIORITY 5
#define START_TASK_STACK_SIZE 256
OS_TCB start_task_tcb;
CPU_STK start_task_stack[START_TASK_STACK_SIZE];
void Start_Task(void *p_arg);
/**
* @brief 开始任务的任务函数
*
* @param p_arg 任务参数
*/
void Start_Task(void *p_arg)
{
OS_ERR error = {0};
CPU_INT32U cnts = 0;
CPU_Init(); // 初始化CPU库
CPU_SR_ALLOC(); // 临界区保护
cnts = HAL_RCC_GetSysClockFreq() / OS_CFG_TICK_RATE_HZ;
OS_CPU_SysTickInit(cnts); // 根据配置的节拍频率配置SysTick中断及优先级
CPU_CRITICAL_ENTER(); // 进入临界区
// 创建任务1
OSTaskCreate((OS_TCB * ) &task1_tcb, // 任务控制块
(CPU_CHAR * ) "task1", // 任务名
(OS_TASK_PTR ) Task1, // 任务函数
(void * ) 0, // 任务参数
(OS_PRIO ) TASK1_PRIORITY, // 任务优先级
(CPU_STK * ) task1_stack, // 任务堆栈
(CPU_STK_SIZE) TASK1_STACK_SIZE / 10, // 任务栈的使用警戒线
(CPU_STK_SIZE) TASK1_STACK_SIZE, // 任务栈大小
(OS_MSG_QTY ) 0, // 消息队列长度
(OS_TICK ) 0, // 时间片长度,设置为0,则默认时间片长度
(void * ) 0, // 扩展内存
(OS_OPT ) (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), // 任务选项
(OS_ERR * ) &error); // 错误码
// 创建任务2
OSTaskCreate((OS_TCB * ) &task2_tcb, // 任务控制块
(CPU_CHAR * ) "task2", // 任务名
(OS_TASK_PTR ) Task2, // 任务函数
(void * ) 0, // 任务参数
(OS_PRIO ) TASK2_PRIORITY, // 任务优先级
(CPU_STK * ) task2_stack, // 任务堆栈
(CPU_STK_SIZE) TASK2_STACK_SIZE / 10, // 任务栈的使用警戒线
(CPU_STK_SIZE) TASK2_STACK_SIZE, // 任务栈大小
(OS_MSG_QTY ) 0, // 消息队列长度
(OS_TICK ) 0, // 时间片长度
(void * ) 0, // 扩展内存
(OS_OPT ) (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), // 任务选项
(OS_ERR * ) &error); // 错误码
CPU_CRITICAL_EXIT(); // 退出临界区
OSTaskDel(NULL, &error); // 删除任务
}
TASK1 任务配置:
/**
* TASK1 任务配置
* 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK1_PRIORITY 4
#define TASK1_STACK_SIZE 256
OS_TCB task1_tcb;
CPU_STK task1_stack[TASK1_STACK_SIZE];
void Task1(void *p_arg);
/**
* @brief 任务1的任务函数
*
* @param p_arg 任务参数
*/
void Task1(void *p_arg)
{
OS_ERR error = {0};
while (1)
{
switch (Key_Scan(0))
{
case KEY1_PRESS:
OSQPost(&Q1, message, sizeof(message), OS_OPT_POST_FIFO, &error);
break;
default:
break;
}
OSTimeDly(10, OS_OPT_TIME_DLY, &error);
}
}
TASK2 任务配置:
/**
* TASK2 任务配置
* 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK2_PRIORITY 4
#define TASK2_STACK_SIZE 256
OS_TCB task2_tcb;
CPU_STK task2_stack[TASK2_STACK_SIZE];
void Task2(void *p_arg);
/**
* @brief 任务2的任务函数
*
* @param p_arg 任务参数
*/
void Task2(void *p_arg)
{
OS_ERR error = {0};
char *data;
OS_MSG_SIZE size = 0;
while (1)
{
data = OSQPend(&Q1, 0, OS_OPT_PEND_BLOCKING, &size, 0, &error);
printf("接收到的数据长度为: %d\n", size);
printf("接收到的数据为: %s\n", data);
}
}