这一部分,我们将综合任务、事件和消息,进行一个完整过程的阐述。
消息的发送是通过定义 OSAL.c
文件中的 osal_msg_send()
函数定义的:
uint8 osal_msg_send( uint8 destination_task, uint8 *msg_ptr ) {
if (msg_ptr == NULL) // 指针为空返回错误信息
return (INVALID_MSG_POINTER);
// 如果要发送的任务号大于系统定义的最大任务号
if (destination_task >= tasksCnt) {
osal_msg_deallocate( msg_ptr );
return ( INVALID_TASK );
}
// 检查元数据,是否为错误的消息
if ( OSAL_MSG_NEXT( msg_ptr ) != NULL ||
OSAL_MSG_ID( msg_ptr ) != TASK_NO_TASK ) {
osal_msg_deallocate( msg_ptr );
return ( INVALID_MSG_POINTER );
}
// 设置其元数据的目的地址
OSAL_MSG_ID( msg_ptr ) = destination_task;
// 将该消息进队
osal_msg_enqueue( &osal_qHead, msg_ptr );
// 告知任务有消息需要处理
osal_set_event( destination_task, SYS_EVENT_MSG );
return (SUCCESS)
}
这一过程可用图1 表示:
注意函数 osal_set_event()
的使用,这是一个十分关键的函数,我们首先分析其源代码:
uint8 osal_set_event( uint8 task_id, uint16 event_flag ) {
if (task_id < tasksCnt) {
halIntState_t intState;
HAL_ENTER_CRITICAL_SECTION(intState); // 关中断
tasksEvents[task_id] |= event_flag;
HAL_EXIT_CRITICAL_SECTION(intState);// 开中断
}
else {
return (INVALID_TASK);
}
}
这个函数有两个参数,一个是 task_id
,另一个是 event_flag
,执行了一个特别重要的操作:
tasksEvents[task_id] |= event_flag; // 按位或并赋值
这样就通过了这个函数,改变了 tasksEvents[idx]
的值,从而在下一次扫描的时候,处理该事件。按位或的目的在于不能够影响其他已经存在的未经处理的事件。
其次,在消息发送的函数中第二个参数的值为 SYS_EVENT_MSG
。其值为 0x8000
。从这一点,我们必须认识到消息是系统层次的事件,也就是说,消息是只能够存在在系统事件中的,这是消息发送这个函数所规定的。
消息的接收是通过定义 OSAL.c
文件中的 osal_msg_receive()
函数定义的:
uint8 *osal_msg_receive( uint8 task_id ) {
osal_msg_hdr_t *listHdr; // 定义一个消息结构体指针
osal_msg_hdr_t *prevHdr = NULL;
osal_msg_hdr_t *foundHdr = NULL;
halIntState_t intState;
HAL_ENTER_CRITICAL_SECTION(intState); // 进入临界区,关中断。
listHdr = osal_qHead; // 指向队首指针
// 遍历队列
while ( listHdr != NULL ) {
// 如果某个消息的目的地是 task_id
if ((listHdr - 1)->dest_id == task_id) {
// 第一次找到
if ( foundHdr == NULL ) {
foundHdr = listHdr;
}
else break;
}
if (foundHdr == NULL) {
// 保存找到的消息之前的消息
// 方便进行删除操作
prevHdr = listHdr;
}
listHdr = OSAL_MSG_NEXT( listHdr ); // 遍历
}
// 不为空,证明为遍历完,也就是存在多个消息
if ( listHdr != NULL ) {
// 告知任务还有消息在等待处理
osal_set_event( task_id, SYS_EVENT_MSG );
}
else {
osal_clear_event( task_id, SYS_EVENT_MSG );
}
// 找到了消息
if (foundHdr != NULL) {
// 提取出这个消息
osal_msg_extract( &osal_qHead, foundHdr, prevHdr );
}
HAL_EXIT_CRITICAL_SECTION(intState);// 退出临界区,开中断。
return ( (uint8*) foundHdr ); // 返回消息
}
消息的接收过程可以用图2 表示:
通过上述的过程,我们已经可以总结出事件是如何捕获的了,事件的捕获是通过函数 osal_set_event()
实现的。
在第4 部分中,我们仅仅简单地讲述了任务处理函数,下面我们通过一个具体的例子进行说明:
UINT16 GenericApp_ProcessEvent( byte x, UINT16 events ) {
if ( events & SYS_EVENT_MSG ) {
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );
while (MSGpkt) {
switch(MSGpkt) {
case ZDO_CB_MSG:
GenericApp_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt );
break;
case ZDO_CB_MSG:
GenericApp_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt );
break;
default: break;
}
osal_msg_deallocate( (uint8 *)MSGpkt ); // 释放消息
// 处理该事件的下一个消息
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );
}
return (events ^ SYS_EVENT_MSG) // 返回未处理的事件
}
}
我们从上面的代码可以看出,首先进入了任务处理函数,会去优先处理系统事件,再次强调,消息只能在系统时间中处理,这是OSAL 规定好了的。首先利用 osal_msg_receive()
函数从消息队列中获得有关该层任务的消息,然后根据发送的消息提供的信息,作出相应的操作,然后释放该消息并去处理下一个消息。
至此,我们已经基本讲清了事件驱动机制的一个实现过程,希望对读者了解这个机制有所帮助。