详细ZIGBEE学习介绍博客

我使用的协议栈版本及例子信息:

ZigBee2006\Texas Instruments\ZStack-1.4.3-1.2.1\Projects\zstack\Samples\SampleApp

   OSAL作为操作系统抽象层,是整个Z-Stack运行的基础,用户自己建立的任务和应用程序都必须在此基础上运行,那我们知道整个Z-Stack协议就是用C语言编写的,既然使用C语言编写的,那程序的入口点就是main()函数,而且整个Z-Stack都只有一个main()函数入口,那我们的入口点也是main()函数,我们首先找到main()函数,在SampleApp这个工程文件列表中,我们可以看到ZMain文件,在展开该文件后,就可以看到有一个ZMain.c文件,通过文件名我们也可以看出来,里面应该包括main()函数,

那我们首先看看自己添加的应用任务程序唉Z-Stack中的调用过程是怎样的:

(1).main() 执行(在ZMain.c中)

main() ---> osal_init_system()

(2). osal_init_system()调用osalInitTasks(), (在OSAL.c中)

osal_init_system() ---> osalInitTasks()

(3). osalInitTasks()调用SampleApp_Init() , (在OSAL_SampleApp.c中)

osalInitTasks() ---> SampleApp_Init()

    在osalInitTasks()中实现了从MAC层到ZigBee设备应用层任务处理函数,而用户自己的初始化函数在最后,也就就优先级是最低的,taskID依次增加,taskID越高优先级也就越低。

 

 

 

这在下面会看到具体的分析。

1. 首先,我们来看一下main()函数,在上篇文章中,我已经用流程图的形式画出了在main()函数中,各个初始化函数的执行流程,这里简单的用代码演示一下,

ZSEG int main( void )//主函数的功能就是完成初始化任务,然后进入OSAL

{

  // Turn off interrupts  关闭所有的中断

  osal_int_disable( INTS_ALL );

 

  // Initialize HAL 初始化硬件抽象层

  HAL_BOARD_INIT();

 

  // Make sure supply voltage is high enough to run

  //检测电压,以确保提供足够的电压,支持运行

  zmain_vdd_check();

 

  // Initialize stack memory  初始化内存中的堆

  zmain_ram_init();

 

  // Initialize board I/O 初始化板子上的I/O口

  InitBoard( OB_COLD );

 

  // Initialze HAL drivers 初始化硬件抽象层的驱动

  HalDriverInit();

 

  // Initialize NV System 初始化非易失性系统

  osal_nv_init( NULL );

 

  // Determine the extended address 确定扩展地址 也就是64位的IEEE地址

  zmain_ext_addr();

 

  // Initialize basic NV items 初始化基本的NV条目

  zgInit();

 

  // Initialize the MAC 初始化化MAC层

  ZMacInit();

 

#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 */ 在LCD屏上显示设备信息

#ifdef LCD_SUPPORTED

  zmain_lcd_init();

#endif

 

  osal_start_system(); // No Return from here 没有返回值,启动操作系统

} // main()

   注意: 整个main()函数按照一定的顺序的对整个系统进行初始化工作,其中比较重要的,也是和应用开发比较重要的有两个函数,一个是osal_init_system();函数,注释中写的是初始化操作系统,其中和我们相关的是它里面的osalInitTasks();,另一个是osal_start_system(); ,所有的初始化后开始真正的启动操作系统。

 

下面我们就重点看一下osal_init_system();函数,

2. 操作系统初始化流程

2.1.osal_init_system()

byte osal_init_system( void )

{

  // Initialize the Memory Allocation System

  //初始化内存分配系统

  osal_mem_init();

 

  // Initialize the message queue

 //初始化消息队列 任务之间的通信就是靠任务队列

  osal_qHead = NULL;

 

#if defined( OSAL_TOTAL_MEM )

  osal_msg_cnt = 0;

#endif

 

  // 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 ( ZSUCCESS );

}

   注意:在这个函数中初始化了操作系统用到的重要信息,比如:内存,堆栈等,但我们重点关注的是初始化系统任务函数osalInitTasks();在这个函数中初始化了Z-Stack自己定义的各层的,和用户自己定义的任务。

2.2 任务初始化函数-------osalInitTasks();

void osalInitTasks( void )

{

  uint8 taskID = 0; //任务ID

//osal_mem_alloc()该函数是OSAL中的内存管理函数,是一个存储分配函数,返回指向一个缓存的指针,参数是被分配缓存的大小,其tasksCnt的定义如下const uint8 tasksCnt = sizeof( tasksArr ) / sizeof( tasksArr[0] );tasksEvents指向被分配的内存空间,这里注意tasksArr[]函数指针数组的联系是一一对应的。tasksEvents就是指向第一个被分配的任务的内存空间。

  tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);

  //把申请的内存空间全部设置为0,tasksCnt任务数 * 单个任务占的内存空间(4byte)

  osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));

//下面就是Z-Stack协议栈中,从MAC层到ZDO层的初始化函数,其中的参数都是任务的ID,不过ID号是依次递增的,

  macTaskInit( taskID++ ); //mac_ID = 0

  nwk_init( taskID++ );   //nwk_ID = 1

  Hal_Init( taskID++ );  //Hal_ID = 2

#if defined( MT_TASK )

  MT_TaskInit( taskID++ ); //mt_ID = 3

#endif

  APS_Init( taskID++ ); //APS_ID =4

  ZDApp_Init( taskID++ );//ZDO_ID =5

  SampleApp_Init( taskID ); //sapp_ID = 6 这里是用户创建的自己的任务的id,优先级最低

}

   注意:任务初始化,就是为系统的各个任务分配存储空间,当然,这个空间初始化时为全0(NULL),然后为各任务分配taskID;这里的顺序要注意.系统主循环函数里tasksEvents[ idx]和tasksArr[ idx]的idx与这里taskID是一一对应关系。我们可以在OSAL_SampleApp.c文件中看到有下面的一个数组的定义,数组中的每个成员都是一个函数,这里的函数和上面的初始化顺序是一样的,也是和tasksEvents所指向的任务的内存地址一保持一致。在这个数组中我们仍然重点观注的是最后一个SampleApp_ProcessEvent,这个是用户应用层任务处理函数。

const pTaskEventHandlerFn tasksArr[] = {

  macEventLoop,

  nwk_event_loop,

  Hal_ProcessEvent,

#if defined( MT_TASK )

  MT_ProcessEvent,

#endif

  APS_event_loop,

  ZDApp_event_loop,

  SampleApp_ProcessEvent

};

指针数组tasksEvents[ ]里面最终分别指向的是各任务存储空间

指针数组tasksArr[ ]里面最终分别指向的是各任务事件处理函数

   这两个指针数组里面各元素的顺序要一一对应,因为后面需要相应任务调用相应事件处理函数.

       由于MAC层和NWK层是不开源的,这里面的代码我们看不到,还是重点看一个用户的任何函数吧,

2.3. SampleApp_init()---用户自己定义的任务处理函数

void SampleApp_Init( uint8 task_id )

{

  SampleApp_TaskID = task_id;//OSAL分配的任务ID,从前面可知这里是6.

  SampleApp_NwkState = DEV_INIT;//设备状态的初始化,这里的DEV_INIT表示设备初始化并且没有任何的连接,这里在下面具体讲解

  SampleApp_TransID = 0;//这是唯一的消息ID

 

  // Device hardware initialization can be added here or in main() (Zmain.c).

  // If the hardware is application specific - add it here.

  // If the hardware is other parts of the device add it in main().

 

 #if defined ( SOFT_START )

 //编译器选项作为协调器时使用

  // The "Demo" target is setup to have SOFT_START and HOLD_AUTO_START

  // SOFT_START is a compile option that allows the device to start

  //  as a coordinator if one isn't found.

  // We are looking at a jumper (defined in SampleAppHw.c) to be jumpered

  // together - if they are - we will start up a coordinator. Otherwise,

  // the device will start as a router.

// 检查跳线,决定作为协调器还是作为路由器

  if ( readCoordinatorJumper() )

    zgDeviceLogicalType = ZG_DEVICETYPE_COORDINATOR;

  else

    zgDeviceLogicalType = ZG_DEVICETYPE_ROUTER;

#endif // SOFT_START

 

#if defined ( HOLD_AUTO_START )

 //编译器选项如果定义了HOLD_AUTO_STAT,则调用层的ZDOInitDevice,按照默认顺   
    //序网络中的第一个设备作为协调器,其他的设备作为子设备

  // HOLD_AUTO_START is a compile option that will surpress ZDApp

  //  from starting the device and wait for the application to

  //  start the device.

  ZDOInitDevice(0);

#endif

 

  // Setup for the periodic message's destination address

  // Broadcast to everyone

//下面的几行代码都是在设备传送数据的格式

  SampleApp_Periodic_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast;//发送模式(广播)

  SampleApp_Periodic_DstAddr.endPoint = SAMPLEAPP_ENDPOINT; //指定的端点号EP20

  SampleApp_Periodic_DstAddr.addr.shortAddr = 0xFFFF;//指定的网络短地址为广播地址

 

  // Setup for the flash command's destination address - Group 1

 //flash消息发送到组

  SampleApp_Flash_DstAddr.addrMode = (afAddrMode_t)afAddrGroup;//组寻址

  SampleApp_Flash_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;//指定的端点号EP20

  SampleApp_Flash_DstAddr.addr.shortAddr = SAMPLEAPP_FLASH_GROUP;//flash发送的组ID,#define SAMPLEAPP_FLASH_GROUP                  0x0001

 

  // Fill out the endpoint description.

//定义设备用来通信的APS层的端点描述符

  SampleApp_epDesc.endPoint = SAMPLEAPP_ENDPOINT;//EP号,EP=20

  SampleApp_epDesc.task_id = &SampleApp_TaskID;//任务ID号,这里是6

  SampleApp_epDesc.simpleDesc

            = (SimpleDescriptionFormat_t *)&SampleApp_SimpleDesc;//SampleApp EP的简单描述符

  SampleApp_epDesc.latencyReq = noLatencyReqs;

 

  // Register the endpoint description with the AF

  //向AF层注册EP描述符,应用程序中的每一个EP都必须使用该函数进行注册,告诉应用层有一个EP已经可以使用

  afRegister( &SampleApp_epDesc );

 

  // Register for all key events - This app will handle all key events

  //注册所有的按键事件

  RegisterForKeys( SampleApp_TaskID );

 

  // By default, all devices start out in Group 1 

  SampleApp_Group.ID = 0x0001; //组号

  osal_memcpy( SampleApp_Group.name, "Group 1", 7  );//组名

  aps_AddGroup( SAMPLEAPP_ENDPOINT, &SampleApp_Group );//把组添加到APS中

 

#if defined ( LCD_SUPPORTED )//如果定义胃LCD_SUPPORTED,在LCD上显示

  HalLcdWriteString( "SampleApp", HAL_LCD_LINE_1 );

#endif

}

   注意:在上面的程序中有这样一句话,SampleApp_NwkState = DEV_INIT;其中,SampleApp_NwkState是下面这样一个枚举类型,这句话主要是初始化应用设备的网络类型,设备类型的改变都要产生一个事件—ZDO_STATE_CHANGE,说明ZDO状态发生了改变。所以在设备初始化的时候一定要把它初始化为DEV_INIT状态。那么它就要去检测整个环境,看是否能重新建立或者加入存在的网络。但是有一种情况例外,就是当NV_RESTORE被设置的时候(NV_RESTORE是把信息保存在非易失存储器中),那么当设备断电或者某种意外重启时,由于网络状态存储在非易失存储器中,那么此时就只需要恢复其网络状态,而不需要重新建立或者加入网络了*/

typedef enum

{

  DEV_HOLD,               // Initialized - not started automatically

  DEV_INIT,               // Initialized - not connected to anything

  DEV_NWK_DISC,           // Discovering PAN's to join

  DEV_NWK_JOINING,        // Joining a PAN

  DEV_NWK_REJOIN,         // ReJoining a PAN, only for end devices

  DEV_END_DEVICE_UNAUTH,  // Joined but not yet authenticated by trust center

  DEV_END_DEVICE,         // Started as device after authentication

  DEV_ROUTER,             // Device joined, authenticated and is a router

  DEV_COORD_STARTING,     // Started as Zigbee Coordinator

  DEV_ZB_COORD,           // Started as Zigbee Coordinator

  DEV_NWK_ORPHAN          // Device has lost information about its parent..

} devStates_t;

   上面的代码中还有一个很重要的数据结构,SampleApp_Periodic_DstAddr;它其实就是数据发送模式和地址等参数的设置,

typedef struct

{

  union

  {

    uint16  shortAddr;

  } addr;

  afAddrMode_t addrMode;

  byte endPoint;

} afAddrType_t;

    这个结构体在上一篇文章解析AF_DataRequest函数中,有比较详细的介绍,可以参考那篇文章。在SamleAPP的例子中,应用层提供了两中发送数据的方式,一种是周期性发送,一种是Flash发送。 用户应用任务初始化大致是: 在osalInitTasks( void )函数中添加应用初始化函数SampleApp_Init( taskID )。然后在应用初始化函数中,设置本应用发送数据的方式和目的地址寻址模式,登记注册本应用所用到的端点,以及配置相关发送模式所需的参数.

   所有初始化结束以后开始进入osal_start_system(),也就是真正的开始启动操作系统.

   在这里参考了网上的很多文章,有一篇非常不错,

 

网址是:http://wjf88223.blog.163.com/blog/static/35168001201032373919480/

看一下有点不同,有点注释不同

12-18 11:49