【UCOSii源码解析】任务间通讯与同步

系列文章

  1. UCOSii启动流程以及平台相关文件分析
  2. 优先级算法及内核源码分析
  3. 任务管理
  4. 时间管理
  5. 事件控制块
  6. 内存管理
  7. 任务间通讯与同步

在这里插入图片描述

(一)信号量

µ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,并把该信号量作为一个可计数的信号量使用。

µC/OS-II提供了5个对信号量进行操作的函数。它们是:OSSemCreate(),OSSemPend(),OSSemPost(),OSSemAccept()和OSSemQuery()函数。
在这里插入图片描述

1、建立一个信号量

62 *******************************************************************************************
建立一个信号量
63 *
64 * 描述: 建立并初始化一个信号量。信号量的作用为:
65 * 1、允许一个任务与其它任务或中断同步;
66 * 2、取得共享资源的使用权;
67 * 3、标志事件的发生
68 *
69 * 参数: cnt 建立信号量的初始值,可以为0 ~ 65 535的任何值
70 *
71 * 注意:必须先建立信号量,然后才能使用
72 *
73 * 返回: != (void *)0 返回指向分配给所建立的消息邮箱的事件控制块指针;
74 * == (void *)0 如果没有可用的事件控制块,返回空指针
75 ********************************************************************************************

76 */
77
78 OS_EVENT *OSSemCreate (INT16U cnt) //建立并初始化一个信号量(输入一个信号量值)
79 {
80 #if OS_CRITICAL_METHOD == 3 //中断函数被设定为模式3
81 OS_CPU_SR cpu_sr;
82 #endif
83 OS_EVENT *pevent; //建立信号量的初始值,可以在0至65535之间
84
86 if (OSIntNesting > 0) { //中断嵌套数>0时,表示还有中断任务在运行
87 return ((OS_EVENT *)0); //返回0;
88 }
89 OS_ENTER_CRITICAL(); //关闭中断
90 pevent = OSEventFreeList; //pevent=空余事件管理列表
91 if (OSEventFreeList != (OS_EVENT *)0) { //如果有空余事件管理块
92 OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
93 } //空余事件控制链表指向下一个空余事件控制块
94 OS_EXIT_CRITICAL(); //打开中断
95 if (pevent != (OS_EVENT *)0) { //如果有事件控制块ECB可用
96 pevent->OSEventType = OS_EVENT_TYPE_SEM; //事件类型=信号类型
97 pevent->OSEventCnt = cnt; //将信号量值存入事件管理块中(信号量的计数器)
98 OS_EventWaitListInit(pevent); //初始化一个事件控制块
99 }
100 return (pevent); //返回指针
101 }

2、等待一个信号量

201 ************************************************************************************************
202 * 等待一个信号量
203 * 描述: 等待一个信号量。
204 * 任务试图取得共享资源使用权、任务需要与其它任务或中断同步及任务需要等待特定事件的发生的场合。
205 * 若任务调用该函数,且信号量的值>0,那么OSSemPend()递减该值并返回该值;
206 * 若任务调用该函数,且信号量的值=0,那么OSSemPend()函数将任务加入该信号量的等待列表中。
207 *
208 * 参数: pevent 指向信号量指针。该指针的值在建立该信号量时得到。(参见OSSemCreate ()函数)
209 *
210 * timeout 允许任务在经过指定数目的时钟节拍后还没有得到需要的信号量时;恢复运行状态。如果
211 * 该值为0。则表示任务将持续地等待信号量。最长等待时间为65 535个时钟节拍。这个时
212 * 间长度并不是严格的,可能存在一个时间节拍的误差,因为自由一个时钟节拍结束后,才
213 * 会给定义的等待超时时钟节拍减1214 * err 指向包含错误码的变量的指针。返回的错误码可能为以下几种;
215 *
216 * OS_NO_ERR 成功,信号量是可用的;
217 * OS_TIMEOUT 信号量没有在指定的周期数内置位;
218 * OS_ERR_EVENT_TYPE pevent不是指向信号量的指针;
219 * OS_ERR_PEND_ISR 在中断中调用该函数。虽然规定了不允许在中断中调用该函数,但
220 * ucos仍然包含了检测这种情况的功能;
221 * OS_ERR_PEVENT_NULL pevent是空指针。
222 * 返回:223 * 注意:必须先建立信号量,然后才能使用。
224 ************************************************************************************************
225 */
226 //等待一个信号量函数(信号量指针、允许等待的时钟节拍、代码错误指针)
227 void OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
228 {
229 #if OS_CRITICAL_METHOD == 3 //中断函数被设定为模式3
230 OS_CPU_SR cpu_sr;
231 #endif
232
233
234 if (OSIntNesting > 0) { //中断嵌套数>0时,表示还有中断任务在运行
235 *err = OS_ERR_PEND_ISR; //错误等于(试图在中断程序中等待一个信号量事件)
236 return; //返回
237 }
238 #if OS_ARG_CHK_EN > 0 //所有参数在指定的范围之内
239 if (pevent == (OS_EVENT *)0) { //当信号量指针为NULL,即0(空)
240 *err = OS_ERR_PEVENT_NULL; //pevent是空指针
241 return; //返回
242 }
243 if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { //当事件类型不否是信号量类型
244 *err = OS_ERR_EVENT_TYPE; //pevent指针不是指向信号量
245 return; //返回
246 }
247 #endif
248 OS_ENTER_CRITICAL(); //关闭中断
249 if (pevent->OSEventCnt > 0) { //当信号量计数器>0时,
250 pevent->OSEventCnt--; //信号量计数器减1
251 OS_EXIT_CRITICAL(); //打开中断
252 *err = OS_NO_ERR; //错误等于(成功,信号量是可用的)
253 return; //返回
254 }
255
256 OSTCBCur->OSTCBStat |= OS_STAT_SEM; //将任务状态置1,进入睡眠状态,只能通过信号量唤醒
257 OSTCBCur->OSTCBDly = timeout; //最长等待时间=timeout,递减式
258 OS_EventTaskWait(pevent); //使任务进入等待时间唤醒状态
259 OS_EXIT_CRITICAL(); //打开中断
260 OS_Sched(); //进入调度任务,使就绪态优先级最高任务运行
261 OS_ENTER_CRITICAL(); //关闭中断
262 if (OSTCBCur->OSTCBStat & OS_STAT_SEM) { //检查任务状态是否还是在睡眠状态,即信号量没有唤醒
263 OS_EventTO(pevent); //如果没有等到信号量,由等待事件返回
264 OS_EXIT_CRITICAL(); //打开中断
265 *err = OS_TIMEOUT; //错误等于(信号量没有在指定的周期数内置位)
266 return; //返回
267 }
268 OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; //将信号量ECB的指针从该任务控制块中删除
269 OS_EXIT_CRITICAL(); //打开中断
270 *err = OS_NO_ERR; //错误等于(成功,信号量是可用的)
271 }
272 /*$PAGE*/

这是信号量的核心函数,体现了PV操作

当信号量的数值大于0的时候,信号量的数值直接减去1,否则,任务挂起。

3、发出一个信号量

274 *************************************************************************************************
275 * 发出一个信号量
276 *
277 * 描述: 置位指定的信号量。如果指定的信号量是0或大于0,该函数则递增该信号量并返回;
278 如果有任何任务在等待信号量,那么最高优先级任务将得到该信号量并进入就绪态;
279 如果被唤醒的任务就是最高优先级的就绪态任务,则任务调度函数将进入任务调度。
280 *
281 * 参数: pevent 指向信号量指针。该指针的值在建立该信号量时得到。(参见OSSemCreate ()函数)
282 *
283 * 返回: OS_NO_ERR 信号量成功的置位;
284 * OS_SEM_OVF 信号量的值溢出;
285 * OS_ERR_EVENT_TYPE pevent不是指向信号量的指针;
286 * OS_ERR_PEVENT_NULL pevent是空指针。
287 *************************************************************************************************
288 */
288 */
289
290 INT8U OSSemPost (OS_EVENT *pevent) //发出一个信号量函数(信号量指针)
291 {
292 #if OS_CRITICAL_METHOD == 3 //中断函数被设定为模式3
293 OS_CPU_SR cpu_sr;
294 #endif
295
296
297 #if OS_ARG_CHK_EN > 0 //所有参数在指定的范围之内
298 if (pevent == (OS_EVENT *)0) { //当信号量指针为NULL,即0(空)
299 return (OS_ERR_PEVENT_NULL); //pevent是空指针
300 }
301 if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { //当事件类型不否是信号量类型
302 return (OS_ERR_EVENT_TYPE); //pevent指针不是指向信号量
303 }
304 #endif
305 OS_ENTER_CRITICAL(); //关闭中断
306 if (pevent->OSEventGrp != 0x00) { //有任务在等待信号量,等待事件的任务组=0
307 OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM); //使任务进入就绪态
308 OS_EXIT_CRITICAL(); //打开中断
309 OS_Sched(); //进入调度任务,使就绪态优先级最高任务运行
310 return (OS_NO_ERR); //返回(信号量成功的置位)
311 }
312 if (pevent->OSEventCnt < 65535) { //当信号量值< 65535时,
313 pevent->OSEventCnt++; //信号量计数加1
314 OS_EXIT_CRITICAL(); //打开中断
315 return (OS_NO_ERR); //返回(信号量成功的置位)
316 }
317 OS_EXIT_CRITICAL(); //打开中断
318 return (OS_SEM_OVF); //返回(信号量的值溢出)
319 }

4、使用示例

我们做一个这样的实验:有一块公用的内存区mutex_data,每个任务先打印自己的签名,同时向内存区拷贝签名,然后延时,让出CPU,回来后继续打印。

没有使用信号量:

void usb_task(void *pdatac)
{
	 u8 usbstatus=0;
	 u16 times=0;  
	 u16 len,t;
	 u8 tbuf[20];
	 u8 usb_str[]="usb_task";
	 INT8U   err;
	  usbstatus=bDeviceState;

	while(1){
		if(USB_USART_RX_STA&0x8000)
		{					   
			len=USB_USART_RX_STA&0x3FFF;//得到此次接收到的数据长度
			for(t=0;t<len;t++)
			{
				USB_USART_SendData(USB_USART_RX_BUF[t]);//以字节方式,发送给USB 
			}
			usb_printf("\r\n\r\n");//插入换行
			USB_USART_RX_STA=0;
		}else
		{
			times++;
			usb_printf("usb:");
			memcpy(mutex_data,usb_str,8);
			delay_ms(100);
			usb_printf("%s\r\n",mutex_data);
			delay_ms(1000);
		}
	}
}

void led_task()
{
	u8 led_str[]="led_task";
	INT8U   err;
	while(1)
	{

		LED0=!LED0;	
		usb_printf("led:");		
			memcpy(mutex_data,led_str,8);
			delay_ms(100);
			usb_printf("%s\r\n",mutex_data);
			delay_ms(1000);
	}
}

在这里插入图片描述
可以看到没有达到预期的效果。乱掉了。

使用信号量

void usb_task(void *pdatac)
{
	 u8 usbstatus=0;
	 u16 times=0;  
	 u16 len,t;
	 u8 tbuf[20];
	 u8 usb_str[]="usb_task";
	 INT8U   err;
	  usbstatus=bDeviceState;

	while(1){
		if(USB_USART_RX_STA&0x8000)
		{					   
			len=USB_USART_RX_STA&0x3FFF;//得到此次接收到的数据长度
			for(t=0;t<len;t++)
			{
				USB_USART_SendData(USB_USART_RX_BUF[t]);//以字节方式,发送给USB 
			}
			usb_printf("\r\n\r\n");//插入换行
			USB_USART_RX_STA=0;
		}else
		{
			times++;
			OSSemPend(MY_SEM,0,&err);
			usb_printf("usb:");
			memcpy(shared_resource,usb_str,8);
			delay_ms(200);
			usb_printf("%s\r\n",shared_resource);
			OSSemPost(MY_SEM);
			delay_ms(1000);
		}
	}
}

void led_task()
{
	u8 led_str[]="led_task";
	INT8U   err;
	while(1)
	{

		LED0=!LED0;
		OSSemPend(MY_SEM,0,&err);	
		usb_printf("led:");		
		memcpy(shared_resource,led_str,8);
		delay_ms(200);//会从这里跳转到116行,但是因为信号量不能继续运行
		usb_printf("%s\r\n",shared_resource);
		OSSemPost(MY_SEM);
		delay_ms(1000);
	}
}

在这里插入图片描述

关于优先级反转

在这里插入图片描述
我们可以看到因为最高优先级的任务需要被低优先级任务独占了,所以中优先级的任务看上去比高优先级的优先级要高,也就是优先级反转了。
在这里插入图片描述

(二)消息队列

在这里插入图片描述

消息队列控制块

在这里插入图片描述队列控制块是一个用于维护消息队列信息的数据结构,它包含了以下的一些域。这里,仍然在各个变量前加入一个[.]来表示它们是数据结构中的一个域。
.OSQPtr在空闲队列控制块中链接所有的队列控制块。一旦建立了消息队列,该域就不再有用了。
.OSQStart是指向消息队列的指针数组的起始地址的指针。用户应用程序在使用消息队列之前必须先定义该数组。
.OSQEnd是指向消息队列结束单元的下一个地址的指针。该指针使得消息队列构成一个循环的缓冲区。
.OSQIn是指向消息队列中插入下一条消息的位置的指针。当.OSQIn和.OSQEnd相等时,.OSQIn被调整指向消息队列的起始单元。
.OSQOut是指向消息队列中下一个取出消息的位置的指针。当.OSQOut和.OSQEnd相等时,.OSQOut被调整指向消息队列的起始单元。
.OSQSize是消息队列中总的单元数。该值是在建立消息队列时由用户应用程序决定的。在µC/OS-II中,该值最大可以是65,535。
.OSQEntries是消息队列中当前的消息数量。当消息队列是空的时,该值为0。当消息队列满了以后,该值和.OSQSize值一样。 在消息队列刚刚建立时,该值为0。

消息队列最根本的部分是一个循环缓冲区
在这里插入图片描述

1、建立一个消息队列

71 *************************************************************************************************
72 * 建立一个消息队列(CREATE A MESSAGE QUEUE)
73 *
74 * 描述: 建立一个消息队列。任务或中断可以通过消息队列向其他一个或多个任务发送消息.消息的含义是
75 * 和具体的应用密切相关的.
77 * 参数: start 是消息内存区的基地址,消息内存区是一个指针数组。
78 * size 是消息内存区的大小。
79 *
80 * 返回: OSQCreate()函数返回一个指向消息队列事件控制块的指针;
81 * 如果没有空余的事件空闲块,OSQCreate()函数返回空指针。
82 *
83 * 注意 必须先建立消息队列,然后使用
84 *************************************************************************************************
85 */
86 //建立一个消息队列(消息内存区的基地址(指针数组)、消息内存区的大小)
87 OS_EVENT *OSQCreate (void **start, INT16U size)
88 {
89 #if OS_CRITICAL_METHOD == 3 //中断函数被设定为模式3
90 OS_CPU_SR cpu_sr;
91 #endif
92 OS_EVENT *pevent; //定义一个指向事件控制快的指针
93 OS_Q *pq; //定义一个队列控制模块指针(事件)
94
95
96 if (OSIntNesting > 0) { //中断嵌套数>0时,表示还有中断任务在运行
97 return ((OS_EVENT *)0); //返回0;
98 }
99 OS_ENTER_CRITICAL(); //关闭中断
100 pevent = OSEventFreeList; //pevent=空余事件管理列表
101 if (OSEventFreeList != (OS_EVENT *)0) { //如果有空余事件管理块
102 OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
103 } //空余事件控制链表指向下一个空余事件控制块
104 OS_EXIT_CRITICAL(); //打开中断
105 if (pevent != (OS_EVENT *)0) { //如果有事件控制块ECB可用
106 OS_ENTER_CRITICAL(); //关闭中断
107 pq = OSQFreeList; //队列指针指向空余队列控制链表的队列控制块
108 if (OSQFreeList != (OS_Q *)0) { //当是空余列表时(即链接表中还有空余)
109 OSQFreeList = OSQFreeList->OSQPtr; //控制链接表指向下一个空余控制块
110 }
111 OS_EXIT_CRITICAL(); //打开中断
112 if (pq != (OS_Q *)0) { //消息队列初始化
113 pq->OSQStart = start; //指向消息队列的起始指针
114 pq->OSQEnd = &start[size]; //指向消息队列结束下一个单元地址
115 pq->OSQIn = start; //指向消息队列下一个要插入的消息指针
116 pq->OSQOut = start; //指向消息队列下一个取出消息指针
117 pq->OSQSize = size; //消息队列可容纳的最多消息数
118 pq->OSQEntries = 0; //消息队列中的消息数
119 pevent->OSEventType = OS_EVENT_TYPE_Q; //事件类型为消息队列
120 pevent->OSEventPtr = pq; //OSEventcnt只用于信号量,不用置0
121 OS_EventWaitListInit(pevent); //初始化一个事件控制块
122 } else { //否则,
123 OS_ENTER_CRITICAL(); //关闭中断
124 pevent->OSEventPtr = (void *)OSEventFreeList; //事件控制块ECB返回
125 OSEventFreeList = pevent; //空余块链接表=当前事件指针
126 OS_EXIT_CRITICAL(); //打开中断
127 pevent = (OS_EVENT *)0; //事件指针=0
128 }
129 }
130 return (pevent); //消息队列建立成功,返回一个消息队列得指针,并成为该消息句柄
131 }

2、等待消息

283 /*
284 *************************************************************************************************
285 * 任务等待消息队列中的消息(PEND ON A QUEUE FOR A MESSAGE)
286 *
287 * 描述: 用于任务等待消息。消息通过中断或另外的任务发送给需要的任务。
288 * 消息是一个以指针定义的变量,在不同的程序中消息的使用也可能不同。如果调用OSQPend()函数时
289 * 队列中已经存在需要的消息,那么该消息被返回给OSQPend()函数的调用者,队列中清除该消息.如果
290 * 调用OSQPend()函数时队列中没有需要的消息,OSQPend()函数挂起当前任务直到得到需要的消息或
291 * 超出定义的超时时间.如果同时有多个任务等待同一个消息,uC/OS-ii默认最高优先级的任务取得消
292 * 息并且任务恢复执行.一个由OSTaskSuspend()函数挂起的任务也可以接受消息,但这个任务将一直
293 * 保持挂起状态直到通过调用OSTaskResume()函数恢复任务的运行。
294 *
295 * 参数: pevent 是指向即将接受消息的队列的指针。
296 * 该指针的值在建立该队列时可以得到。(参考OSMboxCreate()函数)
297 *
298 * timeout 允许一个任务在经过了指定数目的时钟节拍后还没有得到需要的消息时恢复运行状态。
299 * 如果该值为零表示任务将持续的等待消息.最大的等待时间为65535个时钟节拍.这个时
300 * 间长度并不是非常严格的,可能存在一个时钟节拍的误差,因为只有在一个时钟节拍结
301 * 束后才会减少定义的等待超时时钟节拍。
302 *
303 * err 是指向包含错误码的变量的指针。OSQPend()函数返回的错误码可能为下述几种:
304 * OS_NO_ERR 消息被正确的接受;
305 * OS_TIMEOUT 消息没有在指定的周期数内送到;
306 * OS_ERR_EVENT_TYPE 'pevent'不是指向消息队列的指针;
307 * OS_ERR_PEVENT_NULL 'pevent'是空指针;
308 * OS_ERR_PEND_ISR 从中断调用该函数。虽然规定了不允许从中断调用该函数,但
309 * uC/OS-ii仍然包含了检测这种情况的功能
310 *
311 * 返回: OSQPend()函数返回接受的消息并将 *err置为OS_NO_ERR。
312 * 如果没有在指定数目的时钟节拍内接受到需要的消息, OSQPend()函数返回空指针并且将 *err
313 * 设置为OS_TIMEOUT.
314 *
315 * 注意: 1、必须先建立消息邮箱,然后使用;
316 * 2、不允许从中断调用该函数.
317 *************************************************************************************************
318 */
319 //任务等待消息队列中的消息(消息队列指针、允许等待的时钟节拍、代码错误指针)
320 void *OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
321 {
322 #if OS_CRITICAL_METHOD == 3 //中断函数被设定为模式3
323 OS_CPU_SR cpu_sr;
324 #endif
325 void *msg; //定义消息队列的指针、取出的暂存指针
326 OS_Q *pq; //定义一个队列事件
327
328
329 if (OSIntNesting > 0) { //中断嵌套数>0时,表示还有中断任务在运行
330 *err = OS_ERR_PEND_ISR; //试图从中断调用该函数
331 return ((void *)0); //返回空(0)
332 }
333 #if OS_ARG_CHK_EN > 0 //所有参数在指定的范围之内
334 if (pevent == (OS_EVENT *)0) { //当信号量指针为NULL,即0(空)
335 *err = OS_ERR_PEVENT_NULL; //pevent是空指针
336 return ((void *)0); //返回
337 }
338 if (pevent->OSEventType != OS_EVENT_TYPE_Q) { //当事件类型不否是消息队列类型
339 *err = OS_ERR_EVENT_TYPE; //'pevent'不是指向消息队列的指针
340 return ((void *)0); //返回空(0)
341 }
342 #endif
343 OS_ENTER_CRITICAL(); //关闭中断
344 pq = (OS_Q *)pevent->OSEventPtr; //队列指针=当前事件指针
345 if (pq->OSQEntries != 0) { //当前消息队列中消息数 > 0,有消息
346 msg = *pq->OSQOut++; //OSQOut将对应的地址的消息复制到msg
347 pq->OSQEntries--; //当前队列消息数减1
348 if (pq->OSQOut == pq->OSQEnd) { //当取出指针=最高消息队列单元时
349 pq->OSQOut = pq->OSQStart; //取出指针跳转到起始单元
350 }
351 OS_EXIT_CRITICAL(); //打开中断
352 *err = OS_NO_ERR; //消息被正确的接受
353 return (msg); //返回消息暂存(数据)指针
354 } // 无消息
355 OSTCBCur->OSTCBStat |= OS_STAT_Q; //将事件进入睡眠状态,由消息队列唤醒
356 OSTCBCur->OSTCBDly = timeout; //等待时间置入任务控制中
357 OS_EventTaskWait(pevent); //使任务进入等待消息队列状态
358 OS_EXIT_CRITICAL(); //打开中断
359 OS_Sched(); //任务调度函数,调用一个就绪的高优先级任务运行
360 OS_ENTER_CRITICAL(); //关闭中断
361 msg = OSTCBCur->OSTCBMsg; //接收消息=指向当前任务的消息指针
362 if (msg != (void *)0) { //检查消息是否为空
363 OSTCBCur->OSTCBMsg = (void *)0; //传递给消息的指针为空
364 OSTCBCur->OSTCBStat = OS_STAT_RDY; //表示任务处于就绪状态
365 OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; //指向事件控制块的指针=0
366 OS_EXIT_CRITICAL(); //打开中断
367 *err = OS_NO_ERR; //成功等待消息队列
368 return (msg); //返回消息暂存(数据)指针
369 }
370 OS_EventTO(pevent); //如果没有获得消息,由于等待起始时间
371 OS_EXIT_CRITICAL(); //打开中断
372 *err = OS_TIMEOUT; //消息没有在指定的时间送到
373 return ((void *)0); //返回0
374 }
375 /*$PAGE*/?

3、发送消息

377 *************************************************************************************************
378 * 向消息队列发送一则消息(POST MESSAGE TO A QUEUE)
379 *
380 * 描述: 通过消息队列向任务发送消息.消息是一个指针长度的变量,在不同的程序中消息的使用也可能不同.
381 * 如果队列中已经存满消息,返回错误码. OSQPost()函数立即返回调用者,消息也没有能够发到队列.
382 * 如果有任何任务在等待队列中的消息,最高优先级的任务将得到这个消息. 如果等待消息的任务优先
383 * 级比发送消息的任务优先级高, 那么高优先级的任务将得到消息而恢复执行,也就是说, 发生了一次
384 * 任务切换. 消息队列是先入先出(FIFO)机制的,先进入队列的消息先被传递给任务.
385 *
386 * 参数: pevent 是指向即将接受消息的消息队列的指针。该指针的值在建立该队列时可以得到。
387 * (参考OSQCreate()函数)
388 *
389 * msg 是即将实际发送给任务的消息. 消息是一个指针长度的变量,在不同的程序中消息的使用也
390 * 可能不同. 不允许传递一个空指针.
391 *
392 * 返回:
393 * OS_NO_ERR 消息成功的放到消息队列中;
394 * OS_Q_FULL 消息队列中已经存满;
395 * OS_ERR_EVENT_TYPE 'pevent'不是指向消息队列的指针;
396 * OS_ERR_PEVENT_NULL 'pevent'是空指针;
397 * OS_ERR_POST_NULL_PTR 用户发出空指针。根据规则,这里不支持空指针.
398 *
399 * 注意: 1、必须先建立消息队列,然后使用;
400 * 2、不允许从中断调用该函数。
401 *************************************************************************************************
402 */
403
404 #if OS_Q_POST_EN > 0 //允许生成 OSQPost()代码
405 INT8U OSQPost (OS_EVENT *pevent, void *msg)
406 { //向消息队列发送一则消息FIFO(消息队列指针、发送的消息)
407 #if OS_CRITICAL_METHOD == 3 //中断函数被设定为模式3
408 OS_CPU_SR cpu_sr;
409 #endif
410 OS_Q *pq; //定义一个队列事件
411
412
413 #if OS_ARG_CHK_EN > 0 //所有参数在指定的范围之内
414 if (pevent == (OS_EVENT *)0) { //当消息队列指针为NULL,即0(空)
415 return (OS_ERR_PEVENT_NULL); //pevent是空指针
416 }
417 if (msg == (void *)0) { //检查消息队列是否为空,用户试发出空消息
418 return (OS_ERR_POST_NULL_PTR); //用户发出空指针。根据规则,这里不支持空指针.
419 }
420 if (pevent->OSEventType != OS_EVENT_TYPE_Q) { //事件类型是否为消息队列
421 return (OS_ERR_EVENT_TYPE); //'pevent'不是指向消息队列的指针
422 }
423 #endif
424 OS_ENTER_CRITICAL(); //关闭中断
425 if (pevent->OSEventGrp != 0x00) { //是否有任务在等待该消息队列,索引值≠0
426 OS_EventTaskRdy(pevent, msg, OS_STAT_Q); //使最高优先级任务进入就绪态
427 OS_EXIT_CRITICAL(); //打开中断
428 OS_Sched(); //任务调度函数,调用一个就绪的高优先级任务运行
429 return (OS_NO_ERR); //消息成功的放到消息队列中
430 }
431 pq = (OS_Q *)pevent->OSEventPtr; //消息队列指针=当前事件指针
432 if (pq->OSQEntries >= pq->OSQSize) { //消息队列当前消息数>=消息中可容纳的消息数
433 OS_EXIT_CRITICAL(); //打开中断
434 return (OS_Q_FULL); //返回消息队列已满
435 }
436 *pq->OSQIn++ = msg; //插入当前的消息(内容),地址为指针加1
437 pq->OSQEntries++; //消息队列数加1
438 if (pq->OSQIn == pq->OSQEnd) { //当插入的消息指针=最后(结束)的指针
439 pq->OSQIn = pq->OSQStart; //插入指针跳到起始处指针
440 }
441 OS_EXIT_CRITICAL(); //打开中断
442 return (OS_NO_ERR); //消息成功的放到消息队列中
443 }
444 #endif
445 /*$PAGE*/?

4、使用示例

当按键监听任务发现按键按下向打印任务发送打印消息,并且把数据发过去

void usb_task(void *pdatac)
{
	 u8 usbstatus=0;
	 u16 times=0;  
	 u16 len,t;
	 u8* key;
	 u8 tbuf[20];
	 u8 usb_str[]="usb_task";
	 INT8U   err;
	  usbstatus=bDeviceState;

	while(1){
		if(USB_USART_RX_STA&0x8000)
		{					   
			len=USB_USART_RX_STA&0x3FFF;//得到此次接收到的数据长度
			for(t=0;t<len;t++)
			{
				USB_USART_SendData(USB_USART_RX_BUF[t]);//以字节方式,发送给USB 
			}
			usb_printf("\r\n\r\n");//插入换行
			USB_USART_RX_STA=0;
		}else
		{
			times++;
			key=OSQPend(KEY_Q,0,&err);
			usb_printf("key value:%d\r\n",*key);

			delay_ms(1000);
		}
	}
}

void led_task()
{
	u8 led_str[]="led_task";
	INT8U   err;
	while(1)
	{

		LED0=!LED0;	
		usb_printf("led:");		

		delay_ms(100);

	}
}

void key_task()
{
	u8 key;
	
	while(1)
	{
		key=KEY_Scan();

		if(key==KEY0_PRES){
		usb_printf("post message\r\n");
		OSQPost(KEY_Q,&key);//向USB任务发送一个消息队列
		}
		delay_ms(200);
	}
}

在这里插入图片描述

(三)总结

其他还有几种任务通信与同步的方式。这里列出了两种具有代表性的。

我们发现无论哪一种同步或消息类型最后都是需要通过事件管理器去进行事件管理。

每当发生Pend(申请)资源的时候,会先检查一下资源有没有,如果有的话就直接拿了东西跑路,如果没有的话就会
都会需要先将自己挂起(优先级置0)然后完成一次调度,然后进入事件管理中的wait函数,如果设置的时间为0的话就是代表无限等待。

每当发生Post(发送)资源的时候,也检查一有没有任务在等这个资源如果有的话就调度过去,如果没有就拉倒。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

与光同程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值