大家好,又见面了,我是你们的朋友全栈君。
typedef unsigned char uint8 只占一个字节,即二进制的8位,0b00000000,16进制的两位0x00;
typedef unsigned short uint16 只占两个字节,即二进制的16位,0b0000000000000000,16进制的四位 0x0000
协议栈中有三个变量至关重要:
l tasksCnt保存了任务的总个数
uint8 tasksCnt
l tasksEvents这是一个指针(可以看做数组),作为事件表,数组的索引是任务ID号,每一元素对应了该任务下的所有事件,这个事件可以拆分为小事件;
uint16 *tasksEvents
l tasksArr——这是一个数组,数组中的每一项都是一个函数指针,指向了任务事件的处理函数。数组的索引是任务的ID号,该ID号下的元素就是对应任务的事件处理函数,事件处理函数利用switch将该任务下的所有事件处理;
pTaskEventHandlerFntasksArr[] ,pTaskEventHandlerFn 是函数指针
typedef unsignedshort (*pTaskEventHandlerFn) (unsigned char task_id, unsigned short event)
表明,pTaskEventHandlerFn是一个指向返回值为(unsigned short)形参为(unsigned char task_id ,unsigned short event)的函数的指针
tasksEvents中的元素为一个16位二进制数,zigbee协议栈用一位二进制来定义事件,为1表示有事件,为0表示无事件,如任务ZDAppTaskID下的事件为
事件 |
十六进制 |
二进制 |
ZDO_NETWORK_INIT |
0x0001 |
0b0000000000000001 |
ZDO_NETWORK_START |
0x0002 |
0b0000000000000010 |
ZDO_DEVIEC_RESET |
0x0004 |
0b0000000000000100 |
ZDO_COMMAND_CNF |
0x0008 |
0b0000000000001000 |
ZDO_START_CHANGE_EVT |
0x0010 |
0b0000000000010000 |
ZDO_ROUTER_START |
0x0020 |
0b0000000000100000 |
ZDO_NEW_DEVICE |
0x0040 |
0b0000000001000000 |
ZDO_DEVICE_AUTH |
0x0080 |
0b0000000010000000 |
ZDO_SCEMGR_EVENT |
0x0100 |
0b0000000100000000 |
ZDO_NWK_UPDATE_NV |
0x0200 |
0b0000001000000000 |
ZDO_FRAMECOUNTER_CHANGE |
0x0400 |
0b0000010000000000 |
如上图,这样二进制的每一位的1可以定义为一个事件,理论上一个任务下可以定义16个事件。这样的好处是,事件与事件之间可以用二进制加法处理即异或算法相加。
比如:tasksEvents[ZDAppTaskID]=0x0003 ,由于 0x0003=0x0001^0x0002,所以可以看做ZDO_NETWORK_INT+ZDO_NETWORK_START,所以此时该任务下的事件有两个,即ZDO_NETWORK_INT和ZDO_NETWORK_START。提取的时候可以利用与运算来提取。 “与”运算能用来判断二进制数的某一位是否为1。由于二进制的减法运算与加法运算相同,所以也可以通过加法异或运算来清零某一已经处理过的事件。
全zigbee协议栈最重要的就是 void osal_start_system( void )函数,整个轮询机制也在这个函数中被完成
第5行,定义了一个变量idx,用来标识任务,(如任务0),用来在事件表和函数表中索引;
第6、7行,更新系统时钟,同时查看硬件方面是否有事件发生,如串口是否接收到数据、是否有按键按下等信息,这部分内容在此暂不考虑;
第9~15行,idx从零增大到tasksCnt,依次遍历事件表,查看哪个任务idx对应的事件不为空,当找到任务idx的事件不为空时停止遍历,转而去调用该任务的事件处理函数,不妨设此时的idx = 8;
第19~22行,将该任务8的事件取出来放到events变量中,由于事件值被取出来了,所以对应事件表中的元素值要清零,即tasksEvents[8] = 0;
第25行,由于任务事件处理函数表的元素索引与任务事件表的索引一一对应,所以直接将8作为索引带入任务事件处理函数表就可以调用任务8的事件处理函数(tasksArr[8])(8,events),而且由于函数表中的事件处理函数是以函数指针的形式给出的,所以还需要填写形参,分别是任务id号8,和从任务8的事件events。形参中有events天经地义,因为需要分辨events中含有哪些具体事件,形参中为什么有任务id号呢?是因为,在处理任务的时候可能需要调用其他与任务绑定的函数。同时,任务处理函数的返回值也是任务值,UINT16,返回的是未被处理的具体事件。
第32行,将任务8未被处理的具体事件放回事件表中任务8对应的事件元素中。
然而,以上的机制可以解释Zigbee是怎样处理一个任务下面的事件的,但处理后的事件表是被清零的,那是谁来给任务的这些事件来置一呢?
osal_set_event(uint8 task_id , uint16 event_flag ) ;由它来给事件表中的元素赋值。
函数原型为:
uint8osal_set_event( uint8 task_id , uint16 event_flag )
{
if( task_id < tasksCnt)
{
halIntState_t intState ;
HAL_ENTER_CRITICALL_SECTION( inState ) ; //关中断
tasksEvents[task_id] |= event_falg ; //给任务task_id设置事件event_flag
HAL_EXTI_CRITICALL_SECTION(inState ) ; //开中断
return( SUCCESS )
}
else
{
return( INVALID_TASK )
}
}
如,原来的tasksEvents[ZDAppTaskID] = 0x0000,即此任务下无事件可以处理,当我调用
osal_set_event(uint8 ZDAppTaskID , ZDO_NETWORK_INIT )后 tasksEvents[ZDAppTaskID] = 0x0001,这样当进入轮询时就会调用事件处理函数处理该事件。
知道了任务下的事件是如何被设置、处理的,我们知道任务ID是将事件表和事件处理函数联系起来的关键,那么任务本身是怎样被设置的呢?
添加任务的关键是
l 新任务的初始化函数
l 新任务的事件处理函数
如要添加任务GenericApp则其初始化函数GenericApp_Init( taskID ),事件处理函数GenericApp_ProcessEvent应该分别放在osalInitTasks函数、事件处理函数表tasksArr[]的最后,而tasksEvent[]的大小会随着tasksArr[]的大小一起变化,即当在tasksArr[]最后一位加入GenericApp_ProcessEvent时,tasksEvent[]也随着增加一个元素用来安放对应的事件。
为了方便看出原理,将taskArr[]、osalInitTasks()简化处理得到
值得一提的是,taskArr[]是先于tasksEvents[]声明定义的,所以可以根据taskArr[]的长度来设置tasksEvents[]的长度与任务相匹配。
由osalInitTask(),任务ID号从0开始,按先后顺序分别分配给各个任务,所以这个ID号与数组的索引也是相匹配的,设备必须的任务优先,从上图看到,用户自定义的任务ID应该是8。正是借助这个ID号将任务事件表中的事件元素和事件处理函数表中的事件处理函数对应了起来。
2018年1月22日星期一
武汉大学青楼
本文参考自:《ZigBee无线传感器网络设计与实现》 王小强等人编著化学工业出版社
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/140472.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...