实验三、消息队列
本节我们主要介绍的是uC/OS-II的通信方式--消息队列 1、队列介绍
为了使用uC/OS-II的消息队列,需要将OS_CFG.H中的OS_Q_EN及其下面的函数使能宏置位,并设置OS_MAX_QS为应用程序中最多可以设置的消息队列个数。消息队列使用的是先入先出的方式进行异步通信,即对先来的事件先处理,uC/OS-II也可以使用OSQPostFront将消息加入到队列头来实现后入先出。下图是任务、中断服务子程序和消息队列之间的关系。
2、队列操作
uC/OS-II提供了一系列函数对队列进行创建、接收和发送等。 1)创建队列OSQCreate()
OS_EVENT *OSQCreate(void **start, INT16U size)
start 二级指针,类型为void 数组的起始地址
size 队列长度
返回值 NULL表示没有空闲的事件控制块 OS_EVENT类型的指针指向生成的队列
2)将数据发送到队列尾OSQPost()与头OSQPostFront()
INT8U OSQPost (OS_EVENT *pevent, void *msg) INT8U OSQPostFront (OS_EVENT *pevent, void*msg) pevent 创建队列时返回的指针 msg 发送数据的指针。 返回值 有三个可能的返回值:
1.OS_NO_ERR 数据被成功发送到队列中。 2.OS_ERR_EVENT_TYPE 事件类型错误 3.OS_Q_FULL 队列满
3)从消息队列中获取一个消息OSQPend()等待和OSQAccept()立即返回 void *OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) void *OSQAccept (OS_EVENT *pevent)
pevent 创建队列时返回的指针 timeout 阻塞超时时间 err 错误类型指针 返回值 有两个可能的返回值: 1.NULL 没有获取到数据 2.数据指针
4)清空消息队列OSQFlush()
INT8U OSQFlush (OS_EVENT *pevent)
pevent 创建队列时返回的指针 返回值 有两个可能的返回值:
1.OS_NO_ERR 清空成功 2.OS_ERR_EVENT_TYPE 事件类型错误 5)查询队列的当前状态OSQQuery()
INT8U OSQQuery (OS_EVENT *pevent, OS_Q_DATA *pdata) pevent 创建队列时返回的指针 pdata 消息队列的信息 返回值 有两个可能的返回值:
1.OS_NO_ERR 清空成功 2.OS_ERR_EVENT_TYPE 事件类型错误 3、实验分析
实验创建两个任务,并创建一个长度为10的队列,一个任务用于每500ms向队列发送字符串,另一个任务用于每隔1s等待接收字符串。接收到字符串后将其打印出来,由于发送间隔时间比接收间隔时间短,因此实验到后来会出现队列满的现象。其源代码如下: OS_EVENT *CommQ;
void *CommMsg[OS_MAX_QS]; //OS_MAX_QS(在os_cfg.h里定义)个指针的数组 int main (void) {
INT8U err;
SysTick_Configuration(); //系统定时器初始化
USART_Configuration(); //串口初始化
LED_Configuration();
OSInit(); //usos ii初始化
CommQ= OSQCreate(&CommMsg[0], 10); //建立消息队列 长度为10 OSQFlush(CommQ);
AppTaskCreate();//创建任务 OSStart(); //开始任务调度
}
static void AppTaskCreate(void) {
INT8U err;
OSTaskCreateExt(AppTask1,(void*)0,(OS_STK )&AppTask1Stk[APP_TASK1_STK_SIZE-1],APP_TASK1_PRIO,APP_TASK1_PRIO,(OS_STK)&AppTask1Stk[0],APP_TASK1_STK_SIZE,(void )0,OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR); //创建任务1
OSTaskNameSet(APP_TASK1_PRIO, \&err);
OSTaskCreateExt(AppTask2,(void*)0,(OS_STK )&AppTask2Stk[APP_TASK2_STK_SIZE-1],APP_TASK2_PRIO,APP_TASK2_PRIO,(OS_STK )&AppTask2Stk[0],APP_TASK2_STK_SIZE,(void*)0,OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR); //创建任务2
OSTaskNameSet(APP_TASK2_PRIO, \&err); }
//任务1
static void AppTask1 (void *p_arg) {
INT8U err;
void *msg; while(1) {
msg= OSQPend(CommQ, 100, &err);
if (err == OS_NO_ERR){//读取成功,打印消息
printf(\读取队列成功:%s\\r\\n\*)msg); } else{
printf(\读取失败\\r\\n\ //读取失败 }
OSTimeDlyHMSM(0,0,0,500); LED1(1);
OSTimeDlyHMSM(0,0,0,500); LED1(0); } }
INT8U *CommRxBuf=\//任务2
static void AppTask2 (void *p_arg)
{
INT8U err; while(1) {
err= OSQPost(CommQ, (void *)&CommRxBuf[0]); if (err == OS_NO_ERR){
printf(\消息加入队列中 \\r\\n\ //将消息放入消息队列
} else{
printf(\队列已满 \\r\\n\ //消息队列已满
}
OSTimeDlyHMSM(0,0,0,500); } }
由于写入消息队列的速度快于读出队列的速度,因此到实验做了一段时间后会出现消息队列满的现象,等待消息被读出后,才能继续往队列里面写数据,通过串口打印的现象如下:
实验四、信号量
本节我们主要介绍的是uC/OS-II的信号量 1、信号量介绍
信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可用。如果大于0则资源可以请求,等于0则无资源可用,进程会进入睡眠状态直至资源可用。
μC/OS-II中的信号量由两部分组成:一个是信号量的计数值,它是一个16位的无符号整数(0到65,535之间);另一个是由等待该信号量的任务组成的等待任务表。用户要在 OS_CFG.H中将 OS_SEM_EN 开关量常数置成 1,这样μC/OS-II才能支持信号量。在使用一个信号量之前, 首先要建立该信号量, 也即调用 OSSemCreate()函数,对信号量的初始计数值赋值。该初始值为0到 65,535之间的一个数。如果信号量是用来表示一个或者多个事件的发生, 那么该信号量的初始值应设为0。如果信号量是用于对共享资源的访问,那么该信号量的初始值应设为1(例如,把它当作二值信号量使用)。最后,如果该信号量是用来表示允许任务访问n 个相同的资源,那么该初始值显然应该是n,并把该信号量作为一个可计数的信号量使用。如下图是任务、中断服务子程序和信号量之间的关系。
2、信号量操作
μC/OS-II提供了一系列函数对信号量进行创建、获取和给出等。 1)创建信号量OSSemCreate()
OS_EVENT *OSSemCreate (INT16U cnt)
cnt 信号量的初始值 返回值 NULL创建失败 否则返回事件控制块指针
2)信号量获取OSSemPend( )等待和OSSemAccept()立即返回
void OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) INT16U OSSemAccept (OS_EVENT *pevent)
pevent 创建队列时返回的指针 Timeout 阻塞超时时间。 err 错误类型 返回值 信号量计数值减一 3)发出信号量OSSemPost()
INT8U OSSemPost (OS_EVENT *pevent)
pevent 创建队列时返回的指针 返回值 有两个可能的返回值: 1.OS_NO_ERR 清空成功 2.OS_ERR_EVENT_TYPE 事件类型错误 3.OS_SEM_OVF 溢出 3)查询一个信号量的当前状态OSSemQuery()