欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Zigbee协议简单学习

程序员文章站 2022-07-13 17:21:19
...

不完全记录

本工程以TI公司的CC2530芯片为例,简单记录了ZigBee协议栈下的种种,也是对目前所学课程的二次理解和输出吧。
首先是应用层的App.c文件

cId_t DNUI_SampleApp_ClusterIDS[DNUI_SAMPLEAPP_CLUSTER_NUM] = 
{
  DNUI_SAMPLEAPP_DATATEST_CLUSTER_ID
};
首先在AF.h文件中定义了一个cID_t即unit16的数据类型,在app.c中一开始定义了一个根据簇的编号来存放簇ID数据的一个数组。

```c
SimpleDescriptionFormat_t  DNUI_Sample_SimpleDesc = 
{
  DNUI_SAMPLEAPP_ENDPOINT,
  DNUI_SAMPLEAPP_PROFILE_ID,
  DNUI_SAMPLEAPP_DEVICE_ID,
  DNUI_SAMPLEAPP_DEVICE_VER,
  0,
  DNUI_SAMPLEAPP_CLUSTER_NUM,
  (cId_t*)DNUI_SampleApp_ClusterIDS,
  DNUI_SAMPLEAPP_CLUSTER_NUM,
  (cId_t*)DNUI_SampleApp_ClusterIDS
};

本段代码是根据所定义的简单描述符的格式为其中的参数赋值。
其中包括端点号、协议ID、设备ID、设备版本、“0”为一个保留选项、应用层输入簇ID的数目、指向输入簇ID列表的指针以及相应的输出簇的ID的数目、指向输出簇ID列表的指针。

void DNUI_SampleApp_Init( uint8 task_id )
{
  //--start--给三个重要的变量赋初值---不需要修改
  DNUI_SampleAppTaskID = task_id;
  DNUI_SampleApp_NwkState = DEV_INIT;
  DNUI_SampleApp_TransID = 0;  
  //--end--给三个重要的变量赋初值---不需要修改
  
  //--start--构造端点描述符,并注册端点--需要修改
  DNUI_SampleApp_epDesc.endPoint = DNUI_SAMPLEAPP_ENDPOINT;   
  DNUI_SampleApp_epDesc.task_id = &DNUI_SampleAppTaskID;
  DNUI_SampleApp_epDesc.latencyReq = noLatencyReqs;
  DNUI_SampleApp_epDesc.simpleDesc = (SimpleDescriptionFormat_t*)&DNUI_Sample_SimpleDesc;
  afRegister(&DNUI_SampleApp_epDesc); 
  //--end--构造端点描述符,并注册端点--需要修改
 
  //--start--做应用相关的初始化工作--需要根据应用进行添加
  Coor_Addr.addrMode = (afAddrMode_t) Addr16Bit;
  Coor_Addr.endPoint = DNUI_SAMPLEAPP_ENDPOINT;
  Coor_Addr.addr.shortAddr = 0x0;// 定义协调器的地址   
  UartInit(NULL);
  //--end--做应用相关的初始化工作--需要根据应用进行添加 
}

应用层初始化函数,给相关变量赋值并且构造端点描述符。
以下是端点描述符的具体结构:

typedef struct
{
  uint8 endPoint;
  uint8 *task_id;  // Pointer to location of the Application task ID.
  SimpleDescriptionFormat_t *simpleDesc;
  afNetworkLatencyReq_t latencyReq;
} endPointDesc_t;

成员变量有端点号、指向应用层任务ID的指针、端点描述符的格式在上面已经介绍过,接下来是应用框架层(application frame)的潜藏周期,为枚举类型,可供选择的模式有:没有潜藏周期,信标模式的快慢等。

typedef enum
{
  noLatencyReqs,
  fastBeacons,
  slowBeacons
} afNetworkLatencyReq_t;

枚举类型的afAddrMode_t即地址模式。

typedef enum
{
  afAddrNotPresent = AddrNotPresent,
  afAddr16Bit      = Addr16Bit,
  afAddr64Bit      = Addr64Bit,
  afAddrGroup      = AddrGroup,
  afAddrBroadcast  = AddrBroadcast
} afAddrMode_t;
  • 按绑定地址传输
  • 按16位短地址传输
  • 按64位MAC地址传输
  • 组播
  • 广播

接下来是地址类型afAddrType_t

typedef struct
{
  union
  {
    uint16      shortAddr;
    ZLongAddr_t extAddr;
  } addr;
  afAddrMode_t addrMode;
  uint8 endPoint;
  uint16 panId;  // used for the INTER_PAN feature
} afAddrType_t;
  • 一个联合体存放地址,其中包括16为短地址也叫网络地址,64位长地址即IEEE地址,一般在芯片出厂前就已经定好。
  • 刚才的地址模式
  • 端点
  • 网络标号

接下来就是协议栈中顶顶重要的事件处理函数啦。它有两个重要的参数:任务号和事件类号。函数的主要工作为:

  1. 根据事件类号event来提取并判断该事件是何种类型。
  2. 根据任务号得到消息指针MSGpkt
  3. 再根据MSGpkt结构里的事件号具体处理事件。

首先呢定义了一个收发函数的指针类型,
如下 定义的收发消息的包类型中的具体参数为


typedef struct
{
  osal_event_hdr_t hdr;     /* OSAL Message header */
  uint16 groupId;           /* Message's group ID - 0 if not set */
  uint16 clusterId;         /* Message's cluster ID */
  afAddrType_t srcAddr;     /* Source Address, if endpoint is STUBAPS_INTER_PAN_EP,
                               it's an InterPAN message */
  uint16 macDestAddr;       /* MAC header destination short address */
  uint8 endPoint;           /* destination endpoint */
  uint8 wasBroadcast;       /* TRUE if network destination was a broadcast address */
  uint8 LinkQuality;        /* The link quality of the received data frame */
  uint8 correlation;        /* The raw correlation value of the received data frame */
  int8  rssi;               /* The received RF power in units dBm */
  uint8 SecurityUse;        /* deprecated */
  uint32 timestamp;         /* receipt timestamp from MAC */
  uint8 nwkSeqNum;          /* network header frame sequence number */
  afMSGCommandFormat_t cmd; /* Application Data */
} afIncomingMSGPacket_t;

应用数据包的格式为·:

  • 信息头部
  • 组ID,默认为0
  • 簇ID
  • 源地址
  • 目的地址
  • 端点号
  • 如果是广播为true
  • 接收数据帧链路质量
  • 接收数据帧的相关系数
  • 接收的信号强度
  • 不建议使用**
  • 时间戳 来自MAC地址的时间
  • 网络帧顺序数
  • 最后是应用数据

如果获得的事件类型是系统消息事件SYS_EVENT_MSG,
用switch-case语句判断该事件是哪种具体类型。
当是系统收发事件时,如果网络状态是协调器接收数据便调用串口写入函数,如果节点状态是路由器或者是终端则写入相应的操作便可。

A设备用AF_DataRequest函数发出报文消息
B设备收到报文消息将触发AF_INCOMING_MSG_CMD事件

当是网络状态改变事件时,定义一个变量来放入当前网络状态。

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;
  • 保持状态 即当前没有被授权
  • 初始化状态 但没有连接任何设备
  • 网络发现状态 发现一个PANID准备加入
  • 网络加入状态 已经加入了一个信道
  • 重加入状态 即重新加入了一个信道,只限于终端设备
  • 设备未授权状态
  • 终端状态
  • 路由器状态
  • 协调器正在组建网络状态
  • 协调器状态
  • 孤儿(orphan)状态 即节点失去了它的父节点的信息

如果网络状态是终端的话,启动了一个定时器函数。

 osal_start_timerEx(task_id,DNUI_SAMPLEAPP_ENDDEVICE_PERIODIC_MSG_EVT,1000);

1000ms后触发
”DNUI_SAMPLEAPP_ENDDEVICE_PERIODIC_MSG_EVT“
此事件。
同理,当网络状态是其他两种时做相应的操作。
接着类操作系统回收消息指针所占用的内存,重新判断当前是何种类型的事件。

如果当前事件为用户自定义的事件时,有一个无线收发函数的数据请求参数如下:

afStatus_t AF_DataRequest( afAddrType_t *dstAddr, endPointDesc_t *srcEP,
                           uint16 cID, uint16 len, uint8 *buf, uint8 *transID,
                           uint8 options, uint8 radius )
  • 目的地址即网络地址+端点号
  • 源地址
  • 簇ID
  • 发送数据的长度
  • 存放数据的缓冲区
  • 传输有效位的掩码?
  • 跳数(传输半径)

该函数实参如下(例)

AF_DataRequest( &Coor_Addr, //
                            &DNUI_SampleApp_epDesc,
                            DNUI_SAMPLEAPP_DATATEST_CLUSTER_ID,
                            11, //Lab6
                            "I'm a Node!",
                            &DNUI_SampleApp_TransID,
                            AF_DISCV_ROUTE,
                            AF_DEFAULT_RADIUS );

当前节点发送11个字节的数据 "I’m a Node!"给协调器。
发送完毕又触发一个定时器函数

 osal_start_timerEx( task_id, DNUI_SAMPLEAPP_ENDDEVICE_PERIODIC_MSG_EVT,1000); 

1000ms后再次执行用户自定义事件,在这个函数的不断循环手动调用中就实现了定时器的重载,周期性地执行该事件。

到此为止应用层初始化和事件处理函数就简单地介绍完毕啦。

附事件处理函数的完整代码

uint16 DNUI_SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{
  afIncomingMSGPacket_t *MSGpkt;  
  
  if ( events & SYS_EVENT_MSG )
  {
    MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( task_id );
    while ( MSGpkt )
    {
      switch ( MSGpkt->hdr.event )
      {
      case AF_INCOMING_MSG_CMD:
        if(DNUI_SampleApp_NwkState == DEV_ZB_COORD)
        {//--start--协调器接收数据的处理逻辑  
          HalUARTWrite(0,MSGpkt->cmd.Data,MSGpkt->cmd.DataLength-1);          
          //--end--协调器接收数据的处理逻辑           
        }else if(DNUI_SampleApp_NwkState == DEV_END_DEVICE)
        {//--start--终端设备接收数据的处理逻辑  
                    
          //--end--终端设备接收数据的处理逻辑          
        }else if(DNUI_SampleApp_NwkState == DEV_ROUTER)
        {
          //--start--路由器接收数据的处理逻辑  
          
          //--end--路由器接收数据的处理逻辑          
        }
        break;
      case ZDO_STATE_CHANGE:
        DNUI_SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
        if(DNUI_SampleApp_NwkState == DEV_END_DEVICE)
        { //--start--终端设备接收数据的处理逻辑  
          osal_start_timerEx(task_id,DNUI_SAMPLEAPP_ENDDEVICE_PERIODIC_MSG_EVT,1000);          
          //--end--终端设备接收数据的处理逻辑            
          
        } else if(DNUI_SampleApp_NwkState == DEV_ZB_COORD)
        {//--start--协调器接收数据的处理逻辑  
          
          
          //--end--协调器接收数据的处理逻辑   
          
        }else if(DNUI_SampleApp_NwkState == DEV_ROUTER)
        {
          //--start--路由器接收数据的处理逻辑  
          
          
          //--end--路由器接收数据的处理逻辑          
        }        
        break;
      default:
        break;
      }      
      osal_msg_deallocate( (uint8 *)MSGpkt );      
      MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( DNUI_SampleAppTaskID);
    }   
    return (events ^ SYS_EVENT_MSG);
  } 
  
   if ( events & DNUI_SAMPLEAPP_ENDDEVICE_PERIODIC_MSG_EVT )
    {  
      ret = Temperature_GetVal(buf);
     if(ret == TEMPERATURE_GET_OK)   
        AF_DataRequest( &Coor_Addr, //
                            &DNUI_SampleApp_epDesc,
                            DNUI_SAMPLEAPP_DATATEST_CLUSTER_ID,
                            11, //Lab6
                            "I'm a Node!",
                            &DNUI_SampleApp_TransID,
                            AF_DISCV_ROUTE,
                            AF_DEFAULT_RADIUS );
        osal_start_timerEx( task_id, DNUI_SAMPLEAPP_ENDDEVICE_PERIODIC_MSG_EVT,1000);    
    return (events ^ DNUI_SAMPLEAPP_ENDDEVICE_PERIODIC_MSG_EVT);
   }