在开始的第一个笔记中我写到了如何安装zstack,其实很多人可能和我一样,开始装了zstack不知道拿来干嘛的,特别是一些刚接触这个行业的人,所以我们先来搞清楚zstack是装来干嘛的!其实很简单,zstack安装了之后得到了不是一大堆文件夹吗?里面包含了很多TI开源的代码和不开源的一些库,我们在编程的时候需要用到这些代码和库,就像我们写c语言程序的时候很多#include这些,很多都是库文件一样,而且Ti给了很多例子程序给我们,我们开始学习就从这些例子程序开始学起,特别重要的就是学习不是只看懂TI的例程就可以了,还要学会去修改TI的例程,把一个实现的简单的功能的程序,改编成一个实现复杂功能的程序。
工欲善其事,必先利其器,在学习zstack之前我们先要安装两个软件辅助我们学习,一个就是大名鼎鼎的source insight,一个同样是大名鼎鼎的IAR(8.0),source insight辅助我们查看代码,IAR作为开发编译工具使用,当然,IAR也能作为看代码的工具,只是没有source insight那么直观。这两个软件的安装使用请询问百度或者谷歌。
我们先来看看我们装完zstack之后得到的目录结构
在图中我们可以看到好几个目录,我在第一篇中都解析过这些目录里面有啥干啥用的,如果你不想在你安装的目录去弄这些东西,你完全可以把这些文件拷贝到别的地方去弄,我就把他铐到了G盘去,这样子做的好处就是,就算你够衰把文件搞不见了,你还有原来的那个,不至于又要重装一次。现在我们先来看看Projects这个文件夹,里面是Ti给我们的一些例子程序之类的东西。,Projects->zstack之后我们可以看到这样子的一堆目录
同样来说说这些目录都是干啥的,第一个就够霸气了,HomeAutomation,会英文的都知道,这是智能家居的例子,里面包含了一个开关程序例子,灯光例子,还有一个空中升级的例子。Libraies我就不说了,这是库的文件夹,OTA是啥来的呢?这是Ti提供的一个空中升级的插件程序,你的程序不一定要用到。Samples这个文件夹放得是一下简单的例子程序,好了,其他的不说了,ZMain放的是main函数的文件,因为协议栈用到了很多种的板子,所以提供了不同类型板子的程序模块。我们一般用到的是TI2530DB里面的那部分程序。
/********************************************************************* * @fn main * @brief First function called after startup. * @return don't care */ int main( void ) { // Turn off interrupts osal_int_disable( INTS_ALL ); // Initialization for board related stuff such as LEDs HAL_BOARD_INIT(); // Make sure supply voltage is high enough to run zmain_vdd_check(); // Initialize board I/O InitBoard( OB_COLD ); // Initialze HAL drivers HalDriverInit(); // Initialize NV System osal_nv_init( NULL ); // Initialize the MAC ZMacInit(); // Determine the extended address zmain_ext_addr(); #if defined ZCL_KEY_ESTABLISH // Initialize the Certicom certificate information. zmain_cert_init(); #endif // Initialize basic NV items zgInit(); #ifndef NONWK // Since the AF isn't a task, call it's initialization routine afInit(); #endif // Initialize the operating system osal_init_system(); // Allow interrupts osal_int_enable( INTS_ALL ); // Final board initialization InitBoard( OB_READY ); // Display information about this device zmain_dev_info(); /* Display the device info on the LCD */ #ifdef LCD_SUPPORTED zmain_lcd_init(); #endif #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; // Shouldn't get here. } // main()
也许有人会问,大循环去哪啦?别着急,其实main做了一大堆东西之后在osal_start_system这里就进入大循环了,我们先来看看osal_start_system做了什么事情
void osal_start_system( void ) { #if !defined ( ZBIT ) && !defined ( UBIT ) for(;;) // Forever Loop #endif { osal_run_system(); } }
这会儿你看到你可爱的大循环了吧,继续看看osal_run_system做了什么东西
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 }
也许现在你看不明白他写了什么,放心总有一天你看得明白的,可是有个疑问会不会冒出来了呢?那我自己的代码写在哪啊!,难道我还得改这个大循环不成?!别急,现在我们来说说这协议栈是怎么工作的。
首先,我们要来说的就是zstack的操作系统---OSAL,我们的协议栈其实是运行在TI的OSAL操作系统之上的,我们的程序(在程序中叫做task)和协议栈的各层都是作为osal的一个个任务在运行,由osal来调度的。我们刚才看到的是如何去启动了osal系统而已,既然我们的程序和协议栈都是osal的一个任务,那么我们如何把自己的任务注册进来呢?这是接下来我们要说的问题,至于osal的基本机制,我们以后再说,现在要做的是先让大家能够下载些程序下去玩玩,满足一下虚荣之心先。
还是按照例子来说比较好说,那么我们挑选的例子就是TI的一个基本的例子,实现了一些最基本的入网和收发数据亮灯程序。我挑选了ZStack-CC2530-2.5.1a\Projects\zstack\Samples\GenericApp这个工程来说明如何建立自己的任务,打开ZStack-CC2530-2.5.1a\Projects\zstack\Samples\GenericApp\CC2530DB里面的IAR工程,我们可以看到这样子的架构
当然你重头到尾建立这样子一个工程是挺费劲的,TI已经提供好了,那就用TI的例子修改就行了,没必要重头到尾建立一次,我建过,还是要看着TI的配置来做,很麻烦,很多配置东西你要清楚才能配置好。我们要建立自己任务,那么这些东西都在App这个Group里面,看看里面有啥东西
可以看出只有三个文件,这三个文件却大有头,这样子的架构几乎成了TI的zstack的程序架构,几乎所有的都是这样子写的,GenericApp.h放的是一些用户的宏定义,GenericApp.c实现我们自己的任务代码,OSAL_GenericApp.c主要是实现osal协议的协议初始化接口的编写,我们的任务添加,就靠这三个文件了!好,我们先不看我们的任务代码是怎么实现的,我们先看OSAL_GenericApp.c是怎么添加我们的任务的和初始化osal的任务的,内容不多,就全放上来吧
#include "ZComDef.h" #include "hal_drivers.h" #include "OSAL.h" #include "OSAL_Tasks.h" #if defined ( MT_TASK ) #include "MT.h" #include "MT_TASK.h" #endif #include "nwk.h" #include "APS.h" #include "ZDApp.h" #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT ) #include "ZDNwkMgr.h" #endif #if defined ( ZIGBEE_FRAGMENTATION ) #include "aps_frag.h" #endif #include "GenericApp.h" /********************************************************************* * GLOBAL VARIABLES */ // The order in this table must be identical to the task initialization calls below in osalInitTask. const pTaskEventHandlerFn tasksArr[] = { 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 }; const uint8 tasksCnt = sizeof( tasksArr ) / sizeof( tasksArr[0] ); uint16 *tasksEvents; /********************************************************************* * FUNCTIONS *********************************************************************/ /********************************************************************* * @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); 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 ); }
其实里面就两样东西是重要的,一个是tasksArr这个数组,一个是osalInitTasks这个函数
tasksArr其实就是osal的一个任务列表,里面包含了osal系统中所有的任务,里面有我们任务处理事件的循环,当有消息是给我们的任务的时候osal就会调用我们的任务处理循环GenericApp_ProcessEvent,当然啦,osal没有win系统那么牛叉,更没有linux系统那么让人感到可爱,他是一个让人感到不恶心,可是不讨人喜欢的小小的实时操作系统。osalInitTasks这个其实就是任务初始化函数,在上面的osal_start_system之前会被调用,而且这个函数会调用很多任务的初始化函数,同样包括我们自己的任务的初始化函数GenericApp_Init,至于GenericApp_ProcessEvent和GenericApp_Init等下再说,我们先来看看到底是啥时候调用了osalInitTasks,以及tasksArr是如何被osal使用的。
我们回到main函数中,可以看到这样子的一个函数被调用了osal_init_system,我们再看看看osal_init_system做了什么
uint8 osal_init_system( void ) { // Initialize the Memory Allocation System osal_mem_init(); // Initialize the message queue osal_qHead = NULL; // Initialize the timers osalTimerInit(); // Initialize the Power Management System osal_pwrmgr_init(); // Initialize the system tasks. osalInitTasks(); // Setup efficient search for the first free block of heap. osal_mem_kick(); return ( SUCCESS ); }
很明显,在这里他不但初始化了osal的内存管理,电源管理,并且调用了我们的osalInitTasks函数来初始化osal的所有task,所以在系统进入大循环之前,这些任务是被初始化了的,而osal是如何知道我们的这些任务的呢,就是通过tasksArr这个数组。我们来看看osal_run_system可以看到这样子一段events = (tasksArr[idx])( idx, events );由此可以看出,我们的任务是这样子被添加进来的,并且任务初始化的时候每个task都带了一个taskID,osal就是根据这个taskID来区分每个task,event是哪个task发起的,应该发给哪个task。
至于GenericApp_ProcessEvent和GenericApp_Init是怎么实现的,下篇再说。