ZigBee开发(12)--协议栈工作原理
由于我的学习平台是基于 TI 公司的,所以讲述的当然也是 TI 的 Z-STACK。
协议栈下载链接 https://pan.baidu.com/s/1QCO1-E_UXsad__e3R8fQkw
TI 公司搭建了一个小型的操作系统(本质也是大型的程序),名叫 Z-stack。他们帮你考虑底层和网络层的内容,将复杂部分屏蔽掉。
让用户通过 API 函数就可以轻易用 ZigBee。这样大家使用他们的产品也理所当然了,确实高明。
也就是说,协议栈是一个小操作系统。不要听到是操作系统就感觉到很复杂。回想当初学习 51 单片机时候是不是会用到定时器的功能?
我们会利用定时器计时,令 LED 一秒改变一次状态。好,现在进一步,我们利用同一个定时器计时,令 LED1 一秒闪烁一次, LED2 二秒闪烁一次。这样就有 2个任务了。
再进一步…有 n 个 LED,就有 n 个任务执行了。协议栈的最终工作原理也一样。从它工作开始,定时器周而复始地计时,有发送、接收…等任务要执行时就执行。这个方式称为任务轮询:
打开协议栈文件夹 Texas Instruments\Projects\zstack 。里面包含了 TI 公司的例程和工具。其中的功能我们会在用的的实验里讲解。再打开 Samples 文件夹:
Samples 文件夹里面有三个例子: GenericApp、 SampleApp、 SimpleApp在这里们选择 SampleApp 对协议栈的工作流程进行讲解。
打开\SampleApp\CC2530DB 下工程文件 SampleApp.eww。留意左边的工程目录, 我们暂时只需要关注 Zmain 文件夹和 App 文件夹。
任何程序都在 main 函数开始运行, Z-STACK 也不例外。打开 Zmain.C, 找到 int main( void ) 函数。我们大概浏览一下 main 函数代码:
1 /********************************************************************* 2 * @fn main 3 * @brief First function called after startup. 4 * @return don\'t care 5 */ 6 int main( void ) 7 { 8 // Turn off interrupts 9 osal_int_disable( INTS_ALL ); ////关闭所有中断 10 11 // Initialization for board related stuff such as LEDs 12 HAL_BOARD_INIT();//初始化系统时钟 13 14 // Make sure supply voltage is high enough to run 15 zmain_vdd_check(); //检查芯片电压是否正常 16 17 // Initialize board I/O 18 InitBoard( OB_COLD ); //初始化 I/O , LED 、 Timer 等 19 20 // Initialze HAL drivers 21 HalDriverInit(); //初始化芯片各硬件模块 22 23 // Initialize NV System 24 osal_nv_init( NULL ); // 初始化 Flash 存储器 25 26 // Initialize the MAC 27 ZMacInit(); //初始化 MAC 层 28 29 // Determine the extended address 30 zmain_ext_addr(); //确定 IEEE 64 位地址 31 32 #if defined ZCL_KEY_ESTABLISH 33 // Initialize the Certicom certificate information. 34 zmain_cert_init(); 35 #endif 36 37 // Initialize basic NV items 38 zgInit(); // 初始化非易失变量 39 40 #ifndef NONWK 41 // Since the AF isn\'t a task, call it\'s initialization routine 42 afInit(); 43 #endif 44 45 // Initialize the operating system 46 osal_init_system(); // 初始化操作系统 47 48 // Allow interrupts 49 osal_int_enable( INTS_ALL );// 使能全部中断 50 51 // Final board initialization 52 InitBoard( OB_READY ); // 初始化按键 53 54 // Display information about this device 55 zmain_dev_info(); //显示设备信息 56 57 /* Display the device info on the LCD */ 58 #ifdef LCD_SUPPORTED 59 zmain_lcd_init(); 60 #endif 61 62 #ifdef WDT_IN_PM1 63 /* If WDT is used, this is a good place to enable it. */ 64 WatchDogEnable( WDTIMX ); 65 #endif 66 67 osal_start_system(); // No Return from here 执行操作系统,进去后不会返回 68 69 return 0; // Shouldn\'t get here. 70 } // main()
看了上面的代码后,感觉很多函数不认识。不过,代码很有条理性,开始先执行初始化工作。包括硬件、网络层、任务等的初始化。
然后执行 osal_start_system();操作系统。进去后可不会回来了。在这里, 重点了解 2 个函数:
c) 初始化操作系统
osal_init_system();
d) 运行操作系统
osal_start_system();
/************怎么看?在函数名上单击右键——go to definition of…,(或把光标放到函数上,按F12)便可以进入函数。 ********************/
osal_init_system(); 系统初始化函数,进入函数。发现里面有 6个初始化函数,没事,这里只关心osalInitTasks();任务初始化函数。继续由该函数进入。
看下面代码的注释应该能有一些规律
/********************************************************************* * @fn osalInitTasks * * @brief This function invokes the initialization function for each task. * * @param void * * @return none */ void osalInitTasks( void ) { uint8 taskID = 0; // 分配内存,返回指向缓冲区的指针 tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt); // 设置所分配的内存空间单元值为0 osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt)); // 任务优先级由高向低依次排列,高优先级对应taskID 的值反而小 macTaskInit( taskID++ ); //macTaskInit(0) ,用户不需考虑 nwk_init( taskID++ ); //nwk_init(1),用户不需考虑 Hal_Init( taskID++ ); //Hal_Init(2) ,用户需考虑 #if defined( MT_TASK ) MT_TaskInit( taskID++ ); #endif APS_Init( taskID++ ); //APS_Init(3) ,用户不需考虑 #if defined ( ZIGBEE_FRAGMENTATION ) APSF_Init( taskID++ ); #endif ZDApp_Init( taskID++ ); //ZDApp_Init(4) ,用户需考虑 #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT ) ZDNwkMgr_Init( taskID++ ); #endif //用户创建的任务 SampleApp_Init( taskID ); // SampleApp_Init _Init(5) ,用户需考虑 }
可以这样理解,函数对 taskID 个东西进行初始化,每初始化一个,taskID++。
注释后面有些写着用户需要考虑,有些则写着用户不需考虑。没错,需要考虑的用户可以根据自己的硬件平台或者其他设置,而写着不需考虑的也是不能修改的。
TI 公司出品协议栈已完成的东西。 SampleApp_Init(taskID );很重要,是应用协议栈例程的必需要函数,用户通常在这里初始化自己的东西。
至此, osal_init_system();大概了解完毕。
再来看第二个函数 osal_start_system();运行操作系统。同样用 go todefinition 的方法进入该函数。 /********************************************************************* * @fn osal_start_system *
* @brief * * This function is the main loop function of the task system (if * ZBIT and UBIT are not defined). This Function doesn\'t return.
这个是任务系统轮询的主要函数。他会查找发生的事件然后调用相应的事件执行函数。如果没有事件登记要发生,那么就进入睡眠模式。这个函数是永远不会返回的。 * * @param void * * @return none */ void osal_start_system( void ) { #if !defined ( ZBIT ) && !defined ( UBIT ) for(;;) // Forever Loop #endif { osal_run_system(); } } /********************************************************************* * @fn osal_run_system * * @brief * * This function will make one pass through the OSAL taskEvents table * and call the task_event_processor() function for the first task that * is found with at least one event pending. If there are no pending * events (all tasks), this function puts the processor into Sleep. * * @param void * * @return none */ void osal_run_system( void ) { uint8 idx = 0; osalTimeUpdate(); Hal_ProcessPoll(); 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); } #if defined( POWER_SAVING ) else // Complete pass through all task events with no activity? { osal_pwrmgr_powerconserve(); // Put the processor/system into sleep } #endif /* Yield in case cooperative scheduling is being used. */ #if defined (configUSE_PREEMPTION) && (configUSE_PREEMPTION == 0) { osal_task_yield(); } #endif }
进入 tasksEvents[idx]数组定义,如图 3.4H,发现恰好在刚刚 osalInitTasks( void )函数上面。而且 taskID 一一对应。这就是初始化与调用的关系。 taskID 把任务联系起来了。