喝水不忘挖井人
转: http://rf.eefocus.com/module/forum/thread-555910-1-1.html
Zstack协议栈是由TI公司在2007年4月推出的Zigbee无线通讯协议,是一种半开源式的协议栈,历经多年发展,功能不断完善,当前最新版本为2.5.1-a。网上资料有的说zstack协议栈是开源的,但是实际上zstack中的很多关键代码都是以库文件的形式给出的,我们并不能知道这些代码的真实内容,这也给学习这个协议栈带来了一定的困难。现在也有几个真正开源的Zigbee协议栈,如msstatePAN协议栈、freakz协议栈,但是同这些协议栈相比zstack的真正优势则是其搭载的硬件平台:TI的Zigbee无线通讯芯片:CC2520、CC2530等。
笔者最早接触Zigbee也是从学习TI的开发套件开始的,经过几年的沉积,对于CC2530和ZStack协议栈的掌握也可以算是细致入微了,现在打算把自己对协议栈的理解和认识拿出来和大家一起分享。
首先我们从Zigbee的特性说起。在网上搜索zigbee,紧跟在它后面的多半都是近距离、低功耗、自组织这些名词,这也是Zigbee最重要的特点。说它近距离是硬件模块的载波频率是2.4GHz,波长短,穿透性差,另外它和蓝牙、wifi是工作在同一频段的,容易受到干扰,因此传输距离不远。室内的话笔者做过的模块一般只能传输40到50米,网上那些不加PA的透传模块声称室内能传百米的,笔者认识都是在扯淡。低功耗是Zigbee非常重要的特点,CC2530的发射电流为29mA,在低功耗休眠模式下,只消耗不到1uA的电流,这个还是非常给力的。当然,随着蓝牙4.0的推出,Zigbee的这一优势将大打折扣。自组织以及与之相关的自愈合、自动路由等等技术则是Zigbee的真正核心竞争力所在,笔者也是在学习和使用Zigbee的过程中逐渐体会到这些功能的强大之处,也是接下来笔者所要阐述的主要内容。
当你第一次打开Zstack协议栈,查看程序代码,第一感觉也许会是:我靠,这该不是唐僧取来的梵文真经吧。然后转念一想,飞盘扔的再远,最后总会回到手中;风筝飞的再高,总是逃脱不了牵引它的线and so on(此处省略1万字)。眼前的一切都是虚妄,只有main才是你永远的家。搜索main,果然,熟悉的面孔让当年的笔者有了一丝的宽慰。
int main( void ) { // Turn off interrupts osal_int_disable( INTS_ALL ); // Initialization for board related stuff such as LEDs HAL_BOARD_INIT(); disable_2401(); // Initialze HAL drivers HalDriverInit(); // Initialize NV System osal_nv_init( NULL ); // Initialize the MAC ZMacInit(); // Determine the extended address zmain_ext_addr(); // Initialize basic NV items zgInit(); // Initialize the operating system osal_init_system(); // Allow interrupts osal_int_enable( INTS_ALL ); #ifdef WDT_IN_PM1 /* If WDT is used, this is a good place to enable it. */ WatchDogEnable( WDTIMX ); #endif osal_start_system(); // No Return from here return 0; }
笔者就是从这个入口函数开始,一步步的走入了Zstack的那缤纷复杂的世界不能自拔。方法只有2个:看文档和单步跟踪调试检验,废话少说,这里笔者就对这些函数来进行粗略的解释。
上次我们粗略描述了ZStack协议栈main()函数调用的一些函数,在main()函数的最后,调用的是osal_start_system(),从字面意思我们不难理解,这个函数是要启动“系统”的意思。启动系统??也许你要问了,这个系统难不成是操作系统的意思么?好吧,其实,你也可以理解成就是一个“操作系统”了。这时候可能你可能就来兴致了:ZStack运行的是哪门子操作系统?它是如何工作的?
查看osal_start_system()如下:
void osal_start_system( void ) { for(;;) { osal_run_system(); } } void osal_run_system( void ) { do { if (tasksEvents[idx]) // Task is highest priority that is ready. { break; } }while (++idx < tasksCnt); if (idx < tasksCnt) { uint16 events; halIntState_t intState; HAL_ENTER_CRITICAL_SECTION(intState); events = tasksEvents[idx]; tasksEvents[idx] = 0; // Clear the Events for this task. HAL_EXIT_CRITICAL_SECTION(intState); activeTaskID = idx; events = (tasksArr[idx])( idx, events ); activeTaskID = TASK_NO_TASK; HAL_ENTER_CRITICAL_SECTION(intState); tasksEvents[idx] |= events; // Add back unprocessed events to the current task. HAL_EXIT_CRITICAL_SECTION(intState); } }
查看osal_init_system()如下:
uint8 osal_init_system( void ) { // Initialize the message queue osal_qHead = NULL; // Initialize the timers osalTimerInit(); // Initialize the Power Management System osal_pwrmgr_init(); // Initialize the system tasks. osalInitTasks(); return ( SUCCESS ); } void osalInitTasks( void ) { uint8 taskID = 0; tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt); osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt)); macTaskInit( taskID++ ); nwk_init( taskID++ ); Hal_Init( taskID++ ); APS_Init( taskID++ ); ZDNwkMgr_Init( taskID++ ); zcl_Init( taskID++ ); zclZHome_Init( taskID++ ); #if defined (USE_SMART_GATEWAY_MODE) utpTaskInit(taskID++); //1/ 1. register zigbee process func. 2. process uart:(1, check status.2 let deice leave.3, check_pan_resp) ArpComm_Init(taskID++); //1.annuce: update arp table(send (mac,ip) to uart), 2.check_pan:farward to uart. Gateway_Init(taskID); //reset assoc table, send startup to uart. #else zhomeDevMgr_Init(taskID++); DevNwkMgr_Init(taskID++); #endif } const pTaskEventHandlerFn tasksArr[] = { macEventLoop, nwk_event_loop, Hal_ProcessEvent, APS_event_loop, ZDApp_event_loop, zcl_event_loop, zclZHome_ProcessEvent, #if defined (USE_SMART_GATEWAY_MODE) utpProcessEvent, ArpComm_ProcessEvent, Gateway_ProcessEvent, #else zhomeDevMgr_ProcessEvent, DevNwkMgr_ProcessEvent, #endif };
聪明的朋友也许已经看出来了,任务数组各成员的名称和刚才初始化过程中各个初始化函数的名称有某些契合。呵呵,不契合就怪了(好吧,这里有点废话了)。
上面的图示列出的是某一时刻tasksEvents各成员的数值。可以看到tasksEvents成员值可以有多个非0值,即有多个任务需要进行处理。优先级较高的nwk_event_loop将首先被执行,然后是级别较低的Hal_ProcessEvent。
协调器的组网过程详解
这一讲我要详细说一下协调器的组网过程。在Zstack中,网络组网是从ZDApp_Init函数开始的。具体的执行流程为:Main()->osal_init_system()->osalInitTasks()->ZDApp_Init()。进入到ZDApp_Init中:
{
...
}
我们在以前的文章里曾提到过,设备在复位之后,之前的网络信息就会丢失,那么设备将以全新的状态组建或者加入到网络中,这在实际使用过程中非常不方便。很多的应用场景中都要求设备能够在复位后仍旧按照上次入网的状态重新连接到网络中,Zstack也提供了实现该机制的方法。在工程的Options->C/C++ Complier->reprocessor的Defined symbols中我们填入:NV_RESTORE。再回到刚才说到的代码中,程序会调用HalKeyRead(),来判断SW5的状态。如果SW5没有被按下,程序会调用ZDApp_RestoreNetworkState,取出上一次保存在flash中的网络状态信息,设备将根据这些信息重新连接到网络中;如果SW5被按下,那么即便我们使用了NV_RESTORE,程序将调用NLME_InitNV()和NLME_SetDefaultNV(),清除之前保存的网络信息,然后以全新的状态加入网络中。在完成以上操作之后,程序会调用ZDApp_NetworkInit函数,触发ZDO_NETWORK_INIT事件。
ZStack协调器网络组建过程怎样实现?
ZDApp_NetworkInit函数触发了ZDO_NETWORK_INIT任务,这个任务是由ZDApp_event_loop来处理的:
{
...
}
可以看到,这个函数的主要作用是调用了ZDO_StartDevice函数,参数ZDO_Config_Node_Descriptor.LogicalType在之前已经被设置为NODETYPE_COORDINATOR,表明这个设备的类型为协调器。
ZDO_StartDevice函数的内容非常多,也特别的重要,第一次看的话有点吃力,但是其中最为重要的部分是调用了NLME_NetworkFormationRequest函数,发出网络创建请求。NLME_NetworkFormationRequest函数的详细部分是放在库文件中的,这里我们没有办法列出它的具体代码。
在ZStack中大量使用了回调函数来处理各种任务请求,网络创建请求的回调函数为:
void ZDO_NetworkFormationConfirmCB( ZStatus_t Status )
{
...
}