【转】协议栈的初始化

时间:2021-08-20 11:01:41
      由于协议栈跑在平台硬件上,所以,在协议栈初始化之前,一般先完成的是平台初始化,比如时钟、中断、定时器、串口设置,还有RF模块、led、按键什么。另外,几乎所有的Zigbee协议栈都需要使用操作系统,来支撑各个任务间的来回调用,Z-stack中OSAL机制,Freakz的Contiki,都是类似的思想。所以,在完成平台硬件初始化之后、协议栈初始化之前,还需要完成OS相关的初始化。应用于嵌入式系统的OS,一般都很"mini",对多线程的实现并不复杂,比如ucosii,只需要按照固定的格式填充调用接口就可以了,嵌入式系统的OS的初始化也不复杂,下面是Freakz中contiki的初始化。

  • /*第一步*/
  • void process_init(void)
  • {
  •   lastevent = PROCESS_EVENT_MAX;

  •   nevents = fevent = 0;

  •   process_current = process_list = NULL;
  • }
  • /*第二步*/
  • void autostart_start(struct process * const processes[])
  • {
  •   int i;

  •   for(i = 0; processes[i] != NULL; ++i) {
  •     process_start(processes[i], NULL);
  •   }
  • }
  • /*第三步*/
  • void ctimer_init(void)   /*ctimer是contiki里很重要的概念,具体说来就是一种定时器模型:Active timer,calls a function when it expires.*/
  • {
  •   initialized = 0;
  •   list_init(ctimer_list);
  •   process_start(&ctimer_process, NULL);
  • }

     Z-stack的OSAL初始化过程也差不多,由于好久没搞cc2530,就不贴代码了。
     完成平台和OS的初始化后,就是协议栈的初始化了,Zigbee协议栈的初始化是件挺复杂的工作。协议栈的初始化就是各个Layer的初始化,aps、af、zdo、nwk、mac。实际顺序如下:
  •     aps_init();
  •     nwk_init();
  •     mac_init();
  •     af_init();
  •     zdo_init();
     在这些函数之前还有一些内存管理的工作要做。
     
     /* 对nv_save_tbl[]的初始化。zigbee协议栈中的长地址、短地址、节点属性等重要数据需要保存下来, nv_save_tbl 用来保存这些数据。*/
  •      memset((void*)nv_save_tbl,0,sizeof(nv_save_tbl));
  •      nv_save_tbl[0].start_addr = (uint32_t)save_flag; /* static const uint8_t save_flag[] = "saved"; */
  •      nv_save_tbl[0].len = sizeof(save_flag);
  •      nv_save_tbl[item].start_addr = (uint32_t)&aib;
  •      nv_save_tbl[item].len = (uint16_t)(sizeof(aps_aib_t));
      
     /* The memory pointer pool  contains an array of all the memory pointers that 
        are available to the stack. Each memory pointer can contain one block
        of memory allocated from the heap. Needless to say, if we run out of

        memory pointers, then that is just as bad as running out of memory. */
  •      memset(mem_ptr_pool, 0, MAX_MEM_PTR_POOL * sizeof(mem_ptr_t));

     /* Init  the frame buffer pool . buf_pool[] is the frame buffer data structure that is used for TX and RX.
      */
  •      for (i=0; i<MAX_BUF_POOL_SIZE; i++)
  •     {
  •         memset(&buf_pool[i], 0, sizeof(buffer_t));
  •     }

     现在我们接着来看aps_init()。APS(应用支持层)是应用层的核心,APS子层是负责上层应用程序对象与下层网络层的协调。其工作有:
          维护Binding表,这是用来配对两网络节点间所需服务的对应表;
          转发已配对设备间的信息;
          处理64位IEEE地址与16位NWK地址间的对应。
     APS层使用一个结构体来记录该层的信息:
  • typedef struct
  • {
  •     bool    desig_coord;        ///< Device is the designated coordinator for this network
  •     U16     desig_parent;       ///< Address of parent we're forcing the device to join
  •     U32     chan_mask;          ///< Channel mask for scans
  •     U64     use_ext_pan_id;     ///< Use this extended PAN ID (0 means use extended address
  •     
  •     U8      dev_type; 
  •     bool    sleep;              ///< If is end device this will use 
  •     bool    use_desig_pan;      ///< pan id of net we're forcing the device to join
  •     U16     pan_id;             ///< pan id
  •     bool    use_desig_ch;       ///< pan id of net we're forcing the device to join
  •     U8      channel;            ///< pan id
  •     U64     ieee_addr;          ///< IEEE address
  • } aps_aib_t;

     因此,APS层的初始化也就是对 aps_aib_t 结构体成员的初始化:
  • void aps_init()
  • {
  •     // init the aib
  •     memset(&aib, 0, sizeof(aps_aib_t));
  •     aib.aps_ctr         =(U8) drvr_get_rand();
  •     aib.chan_mask      = PhyChannelsSupported;
  •   
  •     aib.use_insec_join  = true;
  •     
  •     aib.use_desig_parent = false;
  •  
  •     aib.desig_coord    = false; 
  •     aib.ieee_addr      = 0xFFFFFFFFFFFFFFFF;
  •     aib.pan_id         = 0xFFFF;  
  •     aib.use_desig_ch   = false;
  •     aib.channel        = MAC_PHY_CHANNEL_OFFSET;
  •     aib.sleep          = false;
  •     aib.dev_type       = NWK_ROUTER;
  •     
  •     aib.traxn_persist_time = ZIGBEE_POLL_INTERVAL+2;

  •     aps_retry_init();  // Init the retry list.
  •     aps_dupe_init();  // Init the dupe table.
  • }
      What is the retry list ?
      Implement a retry queue to handle the APS retries on outgoing frames that have their ACK request flag set. The retry queue is just for reliable transfers that require an APS level ACK. The frame is buffered in the retry queue and if an ACK does not come back within the specified time, the frame will be re-sent from the retry queue.    The APS retry queue will be removed later and the application will perform the retry. Since we are at such a high level, there is not much savings to buffer the frame here, versus rebuilding it. It would be more efficient to save the RAM that the APS retry queue takes up and just have the endpoint profiles handle the retry. It is actually easier since most application implementations won't increment the app's data pointer until a SUCCESS confirmation is received. Hence if no SUCCESS confirmation is received, that would be the signal to re-send the data .

     And what is the dupe table ?
     Implement the APS duplicate rejection table. The dupe table checks for duplicate data received by the ACK layer. It may be possible that an APS ACK wasn't sent out in time from a received frame, so the frame is retried by the remote node. Hence, we will get two identical frames. This table is used to check if we receive a duplicate, and if so, discard the dupe frame.


      nwk_init() 和 aps_init()类似,也是对一些结构体成员初始化。由于 nwk layer较复杂,需要存储NKW相关的许多配置信息,如节点类型、网络深度、最大孩子数、最大路由数等  ;还需要使用控制块(Protocol Block,类似于Linux的进程控制块的概念)来处理NWK层的广播、网络发现、网络管理、加入网络等,此外还要初始化NWK Layer中的邻居表(neighbor table)、路由表(route table)、路由发现表(route discovery table)、待定表(pending list)。 所以会涉及到很多变量。

  • /*******************************************************************
  •     NWK Information Base. Holds all the configuration information for the NWK layer.
  • *******************************************************************/
  • typedef struct _nwk_nib_t
  • {
  •     U8                  seq_num;                ///< Current sequence number
  •     U8                  max_children;           ///< Max children this device will support (used to determine tree addr allocation)
  •     U8                  max_depth;              ///< Max network depth this device will support (used to determine tree addr allocation)
  •     U8                  max_routers;            ///< Max routers this device will support (used to determine tree addr allocation)
  •     bool                report_const_cost;      ///< Use constant path cost for all links
  •     bool                sym_link;               ///< Enable symmetric links
  •     U8                  capability_info;        ///< Capability info for this device
  •     U8                  traxn_persist_time;     ///< Length of time before an indirect transaction is expired
  •     U16                 short_addr;             ///< Network address for this device
  •     U8                  stack_profile;          ///< Stack profile (Zigbee or Zigbee pro)
  •     U64                 ext_pan_ID;             ///< Extended PAN ID
  •     U8                  depth;                  ///< Device depth
  •     U8                  rtr_cap;                ///< Remaining available addresses for routers to join
  •     U8                  ed_cap;                 ///< Remaining available addreses for end devices to join
  •     U16                 cskip;                  ///< CSkip value for tree addressing
  •     U8                  dev_type;               ///< Device type (coordinator, router, end device)
  •     U8                  rreq_id;                ///< Current route request ID number (used to select the rreq ID)
  •     bool                joined;                 ///< Joined to the network
  •     U8                  nwk_scan_attempts;      ///< Number of scan attempts
  •     U16                 time_betw_scans;        ///< Time between scan attempts
  •     U16                 nwk_mngr;               ///< Address of this network's network manager
  •     //Added byLiutian min
  •     U8                  nbor_min_lqi;           ///< Node can be added in nbor table with min lqi
  •     U32                 child_cap_mask;          ///< 0-unused 1-used,0~19 bit for end device 20~31 for router
  •     //Added byLiutian min
  • } nwk_nib_t;


  • /*******************************************************************
  •       Network Protocol Block. This holds miscellaneous variables needed by the NWK layer. 
  • *******************************************************************/
  • typedef struct _nwk_pcb_t
  • {
  •     // these are to handle broadcasts
  •     ...

  •     // nwk discovery
  •     U32             channel_mask;       ///< Channel mask for network scan
  •     U8              duration;           ///< Duration for network scan

  •     // these are to handle network management
  •     U8              nlme_state;         ///< Network management state
  •     U8              *energy_list;       ///< Pointer to MAC layer energy list

  •     // nwk join
  •     mem_ptr_t       *curr_join_target;  ///< Current parent we are trying to join
  •     bool            join_as_rtr;        ///< Joining as router or end device

  • } nwk_pcb_t;


  • /*******************************************************************
  •      结构体 nwk_capab_info_t 存储 节点的 Capability information,当请求加入父节点时会被用到 。 
  • *******************************************************************/
  • typedef struct
  • {
  •     bool dev_type;      ///< Device type
  •     bool pwr_src;       ///< Power source (Mains, battery)
  •     bool rx_on_idle;    ///< Receive always on
  •     bool alloc_addr;    ///< Allocate address
  • } nwk_capab_info_t;

     nwk_init()的具体实现如下:

          /*1:声明pcb、pcb、info*/
  •    static nwk_pcb_t  pcb;  
  •    static nwk_nib_t pcb; 
  •    nwk_capab_info_t  info;
          /*2: IS_RFD 是有用户自行定义的标志位,用来设置 NWK Information Base Struct 中节点类型  */
  •    if(IS_RFD == true)
  •    {
  •          nib.dev_type            = NWK_END_DEVICE;
  •    }
  •    else
  •    {
  •          nib.dev_type            = aib->dev_type;
  •    }
          /*3:根据节点类型来配置 Network Protocol Block Struct 中的sleep成员,因为在zigbee中只有 end device 能够休眠 */
  •    if(nib.dev_type != NWK_END_DEVICE)
  •    {
  •          aib->sleep          = false;
  •     }
  •    info.rx_on_idle         = aib->sleep ? false : true;
  •    

          /*4:根据节点类型和是否sleep 来设置 Capability information  Struct 中 Power source ,这里是节能配置 。*/ 
  •    if(nib.dev_type == NWK_END_DEVICE && aib->sleep)
  •    {
  •          info.pwr_src = false;
  •    }
  •    else
  •    {
  •          info.pwr_src = true;
  •    }
  •     
 
        /*5:对 NWK Information Base Struct 其它成员配置 。*/ 
  •     nib.capability_info         = nwk_gen_capab_info(&info);
  •     nib.seq_num                 = (U8)drvr_get_rand();
  •     nib.rreq_id                 = (U8)drvr_get_rand();
  •     nib.report_const_cost       = true;
  •     nib.traxn_persist_time      = aib->traxn_persist_time;
  •     nib.short_addr              = 0xFFFF;
  •     nib.stack_profile           = ZIGBEE_STACK_PROFILE;
  •     nib.max_routers             = ZIGBEE_MAX_ROUTERS;
  •     nib.max_children            = ZIGBEE_MAX_CHILDREN;
  •     nib.max_depth               = ZIGBEE_MAX_DEPTH;
  •     nib.rtr_cap                 = nib.max_routers;
  •     nib.ed_cap                  = nib.max_children - nib.max_routers;
  •     nib.joined                  = false;  
  •     nib.nbor_min_lqi            = ZIGBEE_NBOR_MIN_LQI;
  •     nib.child_cap_mask          = 0;
  •   
     前面曾提到 NWK Layer中包含各种表( xxx table ... ),现在就来看下这些table是怎样完成初始化的。首先介绍contiki的链表操作。
     在Contiki中,采用一种宏的机制来声明一个链表:
  • #define LIST(name) \
  •          static void *LIST_CONCAT(name,_list) = NULL; \
  •          static list_t name = (list_t)&LIST_CONCAT(name,_list)
     声明之后,又使用特定的接口函数对这个链表进行初始化:
  • void list_init(list_t list)
  • {
  •   *list = NULL;
  • }
     所以,当你想声明并初始化某个链表时,可以这样写:
  •      LIST(my_list);
  •      list_init(my_list);
     然后你就可以调用contiki提供的各种链表操作接口对my_list进行各种操作了,如list_insert()、list_remove()、list_add().

     知道了这,NWK Layer中邻居表(neighbor table)、路由表(route table)、路由发现表(route discovery table)、待定表(pending list)、广播表(broadcast table)的初始化就简单多了。

          /*6: 邻居表的初始化*/
  • LIST(nbor_tbl);
  • list_init(nbor_tbl);

           /*7: 路由表的初始化*/
  • LIST(rte_tbl);
  • list_init(rte_tbl);

           /*8: 路由发现表的初始化*/
  • LIST(disc_tbl);
  • list_init(disc_tbl);

           /*9: 待定表的初始化*/
  • LIST(pend_list);
  • list_init(pend_list);

           /*10: 广播表的初始化*/
  • LIST(brc_list);
  • list_init(brc_list);

     到这里,nwk_init()的内容大致就讲完了。除了APS和NWK的之外,协议栈初始化还包括MAC、AF、ZDO的初始化,这些内容这里不再赘述。