首先我们必须清楚(任务-端口)与(事件-簇)的一个关系,在任务中存在一个或多个事件,也就是任务与事件不一定是等价的关系。明白了这点之后开始分析我们的zigbee最重要的函数 osal_run_system。
void osal_run_system( void )
{
1 uint8 idx = 0;
2
3 osalTimeUpdate(); //初始化系统时钟
4 Hal_ProcessPoll(); //查看硬件方面是否有事件发生 例如 串口 ……
5
6 do {
7 if (tasksEvents[idx]) // Task is highest priority that is ready.
8 {
9 break;
10 }
11 } while (++idx < tasksCnt);
12
13 if (idx < tasksCnt)
14 {
15 uint16 events;
16 halIntState_t intState;
17
18 HAL_ENTER_CRITICAL_SECTION(intState); //进入临界区 -- 关闭中断
19 events = tasksEvents[idx]; //每个事件是用unsigned short表示的 执 行该idx的任务事件,是第idx个任务的事件发生了
20 tasksEvents[idx] = 0; // Clear the Events for this task. 将事件表该项清零,注意有可能几个事件同时发生,
//这里清零是暂时的,后面会将未处理的事件存放在事件表中
21 HAL_EXIT_CRITICAL_SECTION(intState); //退出临界区 -- 恢复中断
22
23 activeTaskID = idx; //保存任务事件ID
24 events = (tasksArr[idx])( idx, events ) //调用第idx个任务的事件处理函数,用events保存未完成的事件
25 activeTaskID = TASK_NO_TASK; //清除保存的任务事件ID
26
27 HAL_ENTER_CRITICAL_SECTION(intState);
28 tasksEvents[idx] |= events; //当没有处理完,把返回的 events 继续放到 tasksEvents[idx]当中
29 HAL_EXIT_CRITICAL_SECTION(intState);
30 }
31 #if defined( POWER_SAVING ) //如果开启低功耗,在没有事件发生的情况下,则会进入该函数
32 else // Complete pass through all task events with no activity?
33 {
34 osal_pwrmgr_powerconserve(); // Put the processor/system into sleep
35 }
36 #endif
6至11行表示对事件通过轮询进行查询,18至21行对事件进行提出,并将此事件注销掉也就是清零,接着23至25行处理任务,这个时候将任务中需要执行的事件传递进去,并返回未执行完的事件通过28行重新将该事件保存在事件表中,以待下一次任务执行的时候继续调用。
这个时候会有一个问题,为什么一个任务不能将其需要执行的一个或多个任务执行完?
答:首先我们zigbee采用的是操作系统的思想进行任务的处理,而zigbee采用的是轮询式的操作系统,为了实现实时的特性,这使得每个任务有固定的执行时间,且执行事件很短。具体的可以百度查找。
接着需要弄明白的问题是:事件表(tasksEvents)中的事件是怎么来的?以及怎么清除的?
首先,我们先看看tasksEvents的初始化:
uint16 *tasksEvents; //指向了事件表首地址 事件为16位 unsigned short,每一位表示一个事件
//为每个任务分配一个事件表
tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
//将每个任务对应的事件表中的事件全部清零,zigbee启动时候便会执行这步
osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
tasksEvents为一个指针,类型为unsigned short长度为16位,那么说明一个任务中最多有16个事件(我是这么理解的,如果有错误还需大牛帮其纠正),原因如下:
我们知道事件的创建有两个API (osal_set_event以及osal_start_timerEx)
第一:我们分析其中一个API(osal_set_event)
uint8 osal_set_event( uint8 task_id, uint16 event_flag )
{
1 if ( task_id < tasksCnt ) {
2 halIntState_t intState;
3 HAL_ENTER_CRITICAL_SECTION(intState); // Hold off interrupts
4 tasksEvents[task_id] |= event_flag; // Stuff the event bit(s)
5 HAL_EXIT_CRITICAL_SECTION(intState); // Release interrupts
6 return ( SUCCESS );
}
7 else {
8 return ( INVALID_TASK );
}
}
我们看到第4行的事件保存方式是通过或运算进行保存,其中的task_id显然是针对当前任务
第二:通过第一张图中第28行的代码(tasksEvents[idx] |= events )也可以看出是没完成的任务也是通过或运算重新保存到当前任务对应的事件表中。
开始回答问题:
1、事件表(tasksEvents)中的事件是怎么来的?
第一:对于zigbee操作系统初始化的时候,各个任务会有自己的一个事件注册(像 MT_ProcessEvent 什么的),这个内核方面自处理的,这方面可以不用考虑。
第二:用户自定义的任务里面事件的创建,通过分析用户自定义的代码
UINT16 GenericApp_ProcessEvent( byte task_id, UINT16 events )
{
afIncomingMSGPacket_t *MSGpkt; //定义一个指向消息结构体的指针 MSGpkt
1 if ( events & SYS_EVENT_MSG ) { //SYS_EVENT_MSG(0x8000) 强制事件
2 MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID ); //osal_msg_receive 从消息队列上接收信息
3 while ( MSGpkt ) {
4 switch ( MSGpkt->hdr.event ) {
5 case ZDO_STATE_CHANGE: //网络状态的变化
6 GenericApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
7 if ( (GenericApp_NwkState == DEV_END_DEVICE) ) { //加入网络成功
8 osal_start_timerEx( GenericApp_TaskID, //当节点加入到网络后使用osal_start_timerEx函数设置GENERICAPP_SEND_MSG_EVT 事件
9 GENERICAPP_SEND_MSG_EVT,
10 GENERICAPP_SEND_MSG_TIMEOUT );
11 osal_set_event( GenericApp_TaskID,GENERICAPP_SEND_MSG_EVT); //当加入网络后 此处设置GENERICAPP_SEND_MSG_EVT事件
12 }
13 break;
14 default:
15 break;
}
16 osal_msg_deallocate( (uint8 *)MSGpkt );
17 MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );
}
18 return (events ^ SYS_EVENT_MSG); //将任务表中的完成的任务进行清零
}
19 if ( events & GENERICAPP_SEND_MSG_EVT ) { //这是对该事件的处理,对需要重复进行执行的事件采用定时器处理, 开始执行GENERICAPP_SEND_MSG_EVT事件{
20 Get_TEMP_SEND_TO_COOD(); //这里我就针对GENERICAPP_SEND_MSG_EVT事件,\
//绑定了一个温度采集函数,当执行该事件时,便会执行该函数。
21 osal_start_timerEx( GenericApp_TaskID,
22 GENERICAPP_SEND_MSG_EVT,
23 GENERICAPP_SEND_MSG_TIMEOUT );
24 return (events ^ GENERICAPP_SEND_MSG_EVT);
}
24 return 0;
}
首先我们看到第一行 if ( events & SYS_EVENT_MSG ) 这个SYS_EVENT_MSG事件是怎么来的,作用是什么?
首先此处的SYS_EVENT_MSG事件定义为0x8000,是由协议栈定义的一个强制事件,且此事件是一个事件的集合,主要还包括以下几个事件(其中前两个最为常用)
1、AF_INCOMING_MSG_CMD
2、ZDO_STATE_CHANGE
3、ZDO_CB_MSG
4、AF_DATA_CONFIRM_CMD
那么它是怎么来的,我们知道zigbee无线信息发送要经历四个步骤:
1、osal_msg_allocate 对发送的信息进行一个打包,并返回其的信息存放地址
2、osal_msg_send 通过调用该地址,将其信息发送出去,此处便会创建事件SYS_EVENT_MSG ,这也保证了我们自己定义的任务在没有设置事件的时候,也能通过osal强制定义的事件,使得协调器和中断之间能够通信,这是协调器或中断接收到的第一个事件(zigbee的osal是一个基于事件驱动的轮询式操作系统)。
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 ); //返回错误码
}
// Check the message header
if ( OSAL_MSG_NEXT( msg_ptr ) != NULL || //此处查看osal_msg_allocate初始化
OSAL_MSG_ID( msg_ptr ) != TASK_NO_TASK )
{
osal_msg_deallocate( msg_ptr );
return ( INVALID_MSG_POINTER );
}
OSAL_MSG_ID( msg_ptr ) = destination_task; //指定目标地址
// queue message
osal_msg_enqueue( &osal_qHead, msg_ptr ); //让信息事件进入队列
// Signal the task that a message is waiting
osal_set_event( destination_task, SYS_EVENT_MSG ); //对该目标任务绑定一个事件SYS_EVENT_MSG,\
//此处也就说明为什么自定义的任务第一步会执行该事件
return ( SUCCESS );
}
3、osal_msg_receive 信息的接收
4、osal_msg_deallocate 信息的删除
第三:在我们创建的任务(GenericApp_ProcessEvent)中我们可以通过(osal_set_event以及osal_start_timerEx)这两个API任意选择进行创建我们自定义的事件(查看此上例子第8至11行),然后处理我们自定义的事件(查看我们此上例子19至22行)
2、事件是怎么清除的?
我们查看此上例子第23行
return (events ^ GENERICAPP_SEND_MSG_EVT);
发现是通过异或的方式进行清零,之前我们知道一个事件长度是16位,每位保存一个事件,通过与当前执行的事件进行异或,刚好可以将该事件进行清除。举个栗子:
假设当前任务有两个事件 火焰事件(0x0001)、光照事件(0x0004),那么该任务中的事件表应该为 0x0005(火焰事件与光照事件相或,具体可以查看osal_set_evnet这个创建事件的API),那么我们如果执行完了火焰事件,那么总事件(0x0005)异或上火焰事件(0x0001),不就刚好还剩光照事件(0x0004),这个时候我们的自定义任务执行的时间到了,这个时候光照时间还未完成,所以就会出现osal_run_system中的。
activeTaskID = idx; //保存任务事件ID
events = (tasksArr[idx])( idx, events ) //调用第idx个任务的事件处理函数,用events保存未完成的事件
activeTaskID = TASK_NO_TASK; //清除保存的任务事件ID
HAL_ENTER_CRITICAL_SECTION(intState);
tasksEvents[idx] |= events; //当没有处理完,把返回的 events 继续放到 tasksEvents[idx]当中
HAL_EXIT_CRITICAL_SECTION(intState);
好了,现在osal最重要的任务及其事件处理情况我们已经大致的知道了,所以我们便可以自定义自己的任务以及相应的事件处理,我在这里写个大概。
创建任务:
1、需要在taskArr这个任务表中保存我们的任务
const pTaskEventHandlerFn tasksArr[] = { //tastsArr每一项指向了一个事件处理函数
macEventLoop,
nwk_event_loop,
Hal_ProcessEvent,
#if defined( MT_TASK )
MT_ProcessEvent,
#endif
APS_event_loop,
#if defined ( ZIGBEE_FRAGMENTATION )
APSF_ProcessEvent,
#endif
ZDApp_event_loop,
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
ZDNwkMgr_event_loop,
#endif
//此下为用户自定义的任务
GenericApp_ProcessEvent, //采集任务
GenericApp_ProcessEvent2 //处理任务
};
2、需要在osalInitTasks中初始化我们的任务
void osalInitTasks( void )
{
uint8 taskID = 0;
// tasksEvents 是一个指向事件表首地址的指针 tasksCnt 保存了任务的总个数
tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
macTaskInit( taskID++ );
nwk_init( taskID++ );
Hal_Init( taskID++ );
#if defined( MT_TASK )
MT_TaskInit( taskID++ );
#endif
APS_Init( taskID++ );
#if defined ( ZIGBEE_FRAGMENTATION )
APSF_Init( taskID++ );
#endif
ZDApp_Init( taskID++ );
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
ZDNwkMgr_Init( taskID++ );
#endif
//与上面两个任务相对应的初始化
GenericApp_Init( taskID++ );
GenericApp_Init2( taskID );
}
3、接着我们便要在协调器或者中断的代码里面写好这两部分的代码即可
4、写好的任务代码中,我们便可以添加自己想要的任务中需要执行的事件了。
还有一点我们知道一个节点中可以使用多个端口,那么我们怎么开启呢(一个任务只能注册一个端口,我在一个任务中注册多个端口会无效,不知道是不是代码问题,还望大牛帮忙解决),首先 我们的端口是保存在节点描述符中,然后通过afRegister进行注册,所以一个任务只能注册一个端口,如果一个协调器和终端想实现多个端口的开启,就要自己在taskArr中创建多个任务,每个任务中对应不同的端口即可,这个时候你的协调器或终端便注册了多个端口。