08. µCOS-Ⅲ的内嵌消息队列和信号量
一、任务内嵌消息队列
1.1、什么是任务内嵌消息队列
µC/OS-Ⅲ 为每一个任务都分配了一个任务内嵌消息队列,这意味着,任务内嵌消息队列是每一个任务独自拥有的。任务内嵌消息队列本质上就是一个消息队列,但是任务内嵌消息队列并不需要消息队列这么一个中间的内核对象,任务内嵌消息队列是分配于每一个任务的任务控制块结构体中的,因此每一个任务都有独自的任务内嵌消息队列,任务内嵌消息队列只能被该任务接收,但是可以由其他任务或中断发送。
如上图所示,当任务或中断需要往指定任务的内嵌消息队列发送消息时,只需要调用相应的 API 函数即可,每个任务的内嵌消息队列是在任务创建的时候就已经被创建好的了,并且发出的消息能够直接到达指定任务的任务内嵌消息队列中因此使用任务内嵌消息队列的效率会比使用内核对象的消息队列高得多。
任务内嵌消息队列只能被该任务获取,但是可以由其他任务或者中断释放。
1.2、任务内嵌消息队列相关API函数
1.2.1、获取任务内嵌消息队列中的消息
// 指向消息的指针
void *OSTaskQPend (OS_TICK timeout, // 任务挂起等待任务内嵌消息队列的最大允许时间
OS_OPT opt, // 函数操作选项
OS_MSG_SIZE *p_msg_size, // 指向用于接收消息大小变量的指针
CPU_TS *p_ts, // 指向接收消息队列接收时的时间戳的变量的指针
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSTaskQPend() 的函数操作选项描述,如下所示:
OS_OPT_PEND_BLOCKING // 如果任务队列中没有消息的话就阻塞任务
OS_OPT_PEND_NON_BLOCKING // 如果任务队列中没有消息的话就直接返回
函数 OSTaskQPend() 的错误代码描述,如下所示:
OS_ERR_NONE // 消息接收成功
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 // 任务挂起等待消息队列超时
1.2.2、发送消息到任务内嵌消息队列
void OSTaskQPost (OS_TCB *p_tcb, // 指向任务控制块的指针
void *p_void, // 指向消息的指针
OS_MSG_SIZE msg_size, // 消息的大小,单位:字节
OS_OPT opt, // 函数操作选项
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSTaskQPost() 的函数操作选项描述,如下所示:
OS_OPT_POST_FIFO // 将发送的消息保存在任务队列的末尾
OS_OPT_POST_LIFO // 将发送的消息保存在任务队列的开头
OS_OPT_POST_NO_SCHED // 禁止在本函数内执行任务调度
函数 OSTaskQPost() 的错误代码描述,如下所示:
OS_ERR_NONE // 成功发送消息到任务内嵌消息队列
OS_ERR_MSG_POLL_EMPTY // 任务内嵌消息队列满
OS_ERR_OPT_INVALID // 无效的函数操作选项
OS_ERR_OS_NOT_RUNING // µC/OS-Ⅲ 内核还未运行
OS_ERR_Q_MAX // 任务内嵌消息队列满
OS_ERR_STATE_INVALID // 任务处于无效状态
1.2.3、清空任务内嵌消息队列中的所有消息
// 返回值类型:被释放的消息数量
OS_MSG_QTY OSTaskQFlush (OS_TCB *p_tcb, // 指向任务控制块的指针
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSTaskQFlush() 的错误代码描述,如下所示:
OS_ERR_NONE // 任务内嵌消息队列清空成功
OS_ERR_FLUSH_ISR // 在中断中非法调用该函数
OS_ERR_OS_NOT_RUNING // µC/OS-Ⅲ 内核还未运行
1.2.4、终止任务挂起等待任务内嵌消息队列
CPU_BOOLEAN OSTaskQPendAbort (OS_TCB *p_tcb, // 指向任务控制块的指针
OS_OPT opt, // 函数操作选项
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSTaskQPendAbort() 的错误代码描述,如下所示:
OS_ERR_NONE // 终止任务挂起等待任务内嵌消息队列成功
OS_ERR_OPT_INVALID // 无效的函数操作选项
OS_ERR_OS_NOT_RUNING // µC/OS-Ⅲ 内核还未运行
OS_ERR_PEND_ABORT_ISR // 在中断中非法调用该函数
OS_ERR_PEND_ABORT_NONE // 没有任务挂起等待任务内嵌消息队列
OS_ERR_PEND_ABORT_SELF // 任务终止自己挂起等待任务内嵌消息队列
1.3、实验例程
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-Ⅲ 例程入口函数:
/**
* @brief µC/OS-Ⅲ例程入口函数
*
*/
void UC_OS3_Demo(void)
{
OS_ERR error = {0};
OSInit(&error); // 初始化µC/OS-Ⅲ
// 创建开始任务
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 1
#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 2
#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};
char message[] = "Hello Sakura!";
while (1)
{
switch (Key_Scan(0))
{
case KEY1_PRESS:
printf("发送任务消息队列\r\n");
OSTaskQPost(&task2_tcb, message, sizeof(message), OS_OPT_POST_FIFO, &error);
break;
default:
break;
}
OSTimeDly(10, OS_OPT_TIME_DLY, &error);
}
}
TASK2 任务配置:
/**
* TASK2 任务配置
* 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK2_PRIORITY 2
#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 *message;
OS_MSG_SIZE length = 0;
while (1)
{
message = OSTaskQPend(0, OS_OPT_PEND_BLOCKING, &length, 0, &error);
printf("接收到的数据长度:%d\r\n", length);
printf("接收任务消息队列:%s\r\n", message);
}
}
二、任务内嵌信号量
2.1、什么是任务内嵌信号量
任务内嵌信号量本质上就是一个信号量,但是任务内嵌信号量并不需要信号量这么一个中间的内核对象,任务内嵌信号量是分配于每一个任务的任务控制块结构体中的,因此每一个任务都有独自的任务内嵌信号量,任务内嵌信号量只能被该任务获取,但是可以由其他任务或者中断释放。
如上如所示,当任务或中断需要往指定任务的内嵌信号量发出信号时,是需要调用相应的 API 函数即可,每个任务的内嵌信号量在创建的时候都已经被创建好了,并且发出的信号能够直接到达指定的任务中,因此使用内嵌信号量的效率比使用内核对象的信号量高得多。
任务内嵌信号量只能被该任务获取,但是可以由其他任务或者中断释放。
2.2、任务内嵌信号量相关API函数
2.2.1、获取任务内嵌信号量
// 返回值:任务内嵌信号量更新后的资源数
OS_SEM_CTR OSTaskSemPend (OS_TICK timeout, // 任务挂起等待任务内嵌信号量的最大允许时间
OS_OPT opt, // 函数操作选项
CPU_TS *p_ts, // 指向接收任务内嵌信号量接收时的时间戳的变量的指针
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSTaskSemPend() 的函数操作选项描述,如下所示:
OS_OPT_PEND_BLOCKING // 如果信号量没有资源的话就阻塞任务
OS_OPT_PEND_NON_BLOCKING // 如果信号量没有资源任务就直接返回
函数 OSTaskSemPend() 的错误代码描述,如下所示:
OS_ERR_NONE // 成功获取任务内嵌信号量
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_SCHED_LOCKED // 任务调度器被锁定
OS_ERR_STATUS_INVALID // 无效的任务挂起结果
OS_ERR_TIMEOUT // 获取信号量超时
2.2.2、释放指定任务的任务内嵌信号量
// 返回值:任务内嵌信号量更新后的资源数
OS_SEM_CTR OSTaskSemPost (OS_TCB *p_tcb, // 指向任务控制块的指针
OS_OPT opt, // 函数操作选项
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSTaskSemPost() 的函数操作选项描述,如下所示:
OS_OPT_POST_NONE // 不指定特定的选项
OS_OPT_POST_NO_SCHED // 禁止在本函数内执行任务调度
函数 OSTaskSemPost() 的错误代码描述,如下所示:
OS_ERR_NONE // 成功释放指定任务的任务内嵌信号量
OS_ERR_OPT_INVALID // 无效的函数操作选项
OS_ERR_OS_NOT_RUNING // µC/OS-Ⅲ 内核还未运行
OS_ERR_SEM_OVF // 信号量资源数溢出
OS_ERR_STATE_INVALID // 任务处于无效状态
2.2.3、终止任务挂起等待任务内嵌信号量
// 返回值:终止任务挂起是否成功
CPU_BOOLEAN OSTaskSemPendAbort (OS_TCB *p_tcb, // 指向任务控制块的指针
OS_OPT opt, // 函数操作选项
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSTaskSemPendAbort() 的错误代码描述,如下所示:
OS_ERR_NONE // 成功终止任务挂起等待任务内嵌信号量
OS_ERR_OPT_INVALID // 无效的函数操作选项
OS_ERR_OS_NOT_RUNING // µC/OS-Ⅲ 内核还未运行
OS_ERR_PEND_ABORT_ISR // 在中断中非法调用该函数
OS_ERR_PEND_ABORT_NONE // 任务没有挂起等待任务内嵌信号量
OS_ERR_PEND_ABORT_SELF // 任务终止自己挂起等待任务内嵌信号量
2.2.4、强制设置指定的任务内嵌信号量为指定值
// 返回值:任务内嵌信号量设置前的资源数
OS_SEM_CTR OSTaskSemSet (OS_TCB *p_tcb, // 指向任务控制块的指针
OS_SEM_CTR cnt, // 指定的信号量资源数
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSTaskSemSet() 的错误代码描述,如下所示:
OS_ERR_NONE // 成功设置任务内嵌信号量资源数
OS_ERR_SET_ISR // 在中断中非法调用该函数
OS_ERR_TASK_WAITING // 任务正在等待任务内嵌信号量,资源数设置失败
2.3、实验例程
修改 TASK1 任务配置:
/**
* TASK1 任务配置
* 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK1_PRIORITY 2
#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:
printf("释放任务信号量\r\n");
OSTaskSemPost(&task2_tcb, OS_OPT_POST_NONE, &error); // 往TASK2释放任务信号量
break;
default:
break;
}
OSTimeDly(10, OS_OPT_TIME_DLY, &error);
}
}
修改 TASK2 任务配置:
/**
* TASK2 任务配置
* 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK2_PRIORITY 2
#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};
OS_SEM_CTR count = 0;
while (1)
{
count = OSTaskSemPend(0, OS_OPT_PEND_BLOCKING, 0, &error);
printf("获取任务信号量成功,计数值:%d\r\n", count);
OSTimeDly(1000, OS_OPT_TIME_DLY, &error);
}
}