Zstack之例程GenericApp分析笔记1

来源:互联网 发布:网易博客软件 编辑:程序博客网 时间:2024/06/14 18:04

这两天我左看看TI的官方文档,右看看网上的各种Zigbee教程,再看下TI的Zstack文件夹中提供的几个例程,头都大了都没太明白例程代码的作用和意义。后来在网上一个教程中有一句话算是把我给点明白了。“我这个人就是喜欢把代码分析个透彻”,一般觉得这句话也是不错的,至少说明爱钻研,有那股劲儿。但是仔细一想,我个人觉得这种思想还是不太适合我。

TI的Zstack文件夹的Sample子文件夹中有TI提供的三个例程,最近主要琢磨第一个例程:GenericApp。这个例程是用于介绍OSAL任务初始化流程和消息处理流程的,是最基本的应用任务范例。首先,芯片上电首先从Zmain.c中的main.c函数中执行的,所有的例程的Zmain.c都是共用的,不管打开哪个例程都是这个Zmain.c文件,因为一般App部分需要修改的文件时app_name.h,app_name.c和OSAL_app_name.c三个文件。main函数一进行执行就首先开始不断初始化各种外设和数据结构,这些都不用我们操心,我们只需要关心任务初始化中的应用任务初始化,这也是我们自己使用Zstack需要自己码代码的部分。对于前面的各种初始化函数,和后面的开始运行系统函数网上都有一大堆的教程和资料,就不再赘述了。

TI的官方文档:Z-Stack Sample Applications:SWRA201中对三个例程的详细介绍,而其中又专门对GenericApp初始化过程进行了详细的介绍。

任务初始化流程代码如图1所示:


图1

在任务初始化里面,前面先对各个任务进行了初始化,包括初始化mac,网络层,hal硬件抽象层之类的,注意初始化的时候除了初始化各抽象层对应的数据和模块之外,还有一个非常重要的作用,为各个层对应的任务分配一个task_ID。这个ID是非常重要的,每个ID对应一个不同的任务,用于调用或获取系统资源和信息的,比如调用用定时器,获取消息等。学过操作系统的估计一看就明白了,这是个类似于PID(process id)的东西,这也是每个OSAL任务与其他任务的区别所在。同时要注意到最优先的任务号为0,且用户任务的优先级是最低的了,一般情况这个优先级顺序是不能变动的。下面来看void GenericApp_Init( byte task_id )这个函数是如何进行的。如图2所示:


图2

注意该函数有一个传入参数task_id,这就是应用任务的任务ID了。在该函数里将以之用于设置任务对应的数据,并对需要使用的资源进行注册。

GenericApp_TaskID = task_id;   //将task_id传入参数赋值给本地变量,并在之后被使用,在整个OSAL后续的运行中,这个值应该是不能被改变的,因为在后续的事件处理中就只能通过这个变量才能指定该任务了。

GenericApp_NwkState = DEV_INIT;//指定该设备的联网状态,通过查找该变量的定义,可以发现该变量被定义如下:

devStates_t GenericApp_NwkState;

而devStates_t又是一个什么数据类型呢,继续深入查找,发现其是一个枚举类型:

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;

所以GenericApp_NwkState = DEV_INIT;这句即是初始化设备联网状态,可以看出DEV_INIT意味着不进行任何连接。作为该例程,也确实不在于联网介绍上。根据TI官方手册上对此的介绍,DEV_INIT即device not connected是设备上电的默认状态,在该状态下是不会接收到ZDO_STATE_CHANGE这个消息,这个消息根据后面的介绍是用于指示联网状态改变用的,所以系统上电一定需要直接或间接地对其联网状态进行设置。至于其他的联网状态设置,在后面的例程分析中逐渐分析。不过可以从注释看出这些devState的意义。

接下来的三句代码:

  GenericApp_DstAddr.addrMode = (afAddrMode_t)AddrNotPresent;
  GenericApp_DstAddr.endPoint = 0;
  GenericApp_DstAddr.addr.shortAddr = 0;

通过查看该变量的定义发现:afAddrType_t GenericApp_DstAddr;

那afAddrType_t又是一个什么数据类型呢?

typedef struct
{
  union
  {
    uint16      shortAddr;
    ZLongAddr_t extAddr;
  } addr;
  afAddrMode_t addrMode;
  byte endPoint;
  uint16 panId;  // used for the INTER_PAN feature
} afAddrType_t;

这个结构变量的定义大家没晕吧,很简单的一个struct结构,只有4个成员,只是其中的一个成员是联合体,用于指示设备短地址或者扩展地址的。

第一句:  GenericApp_DstAddr.addrMode = (afAddrMode_t)AddrNotPresent;

这里面的afAddrMode_t通过定义查找发现其也是一个枚举类型,如下所示

typedef enum
{
  afAddrNotPresent = AddrNotPresent,//绑定后的点播
  afAddr16Bit      = Addr16Bit,//短地址点播
  afAddr64Bit      = Addr64Bit,//长地址点播
  afAddrGroup      = AddrGroup,//组播
  afAddrBroadcast  = AddrBroadcast//全播
} afAddrMode_t;

用于指定用于发送信息的方式。

后面两句则是指定目标设备端点号为0,短地址为0。这儿关于endpoint端点号的分配还有待进一步查看TI的说明。

接下来的几句是:

  GenericApp_epDesc.endPoint = GENERICAPP_ENDPOINT;
  GenericApp_epDesc.task_id = &GenericApp_TaskID;
  GenericApp_epDesc.simpleDesc
            = (SimpleDescriptionFormat_t *)&GenericApp_SimpleDesc;
  GenericApp_epDesc.latencyReq = noLatencyReqs;

GenericApp_epDesc是个什么类型的变量呢?通过查找定义发现:

endPointDesc_t GenericApp_epDesc;好吧,继续查找endPointDesc_t是什么变量类型,如下所示:

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

里面包含4个成员变量,其中第一个又是endpoint。这里我们首先要能够理解一点,一个zigbee设备,上电之后,不管是作为router,还是enddevice,总得有一个方法来描述自己是个什么东西吧?就像每个人都有档案一样,通过查询一个人的档案就能够知道这个人的具体情况。同样的,该结构体就是zstack中用于设备描述的数据结构,其中endpoint是指定自身的端点号,取值范围根据官方手册貌似是0-240?第二个是指向OSAL应用任务的id变量指针,第三个成员变量又是一个结构体变量SimpleDescriptionFormat_t,根据TI的文档介绍,该变量是用于该zigbee设备的功能描述的,该变量的具体结构如下所示:

typedef struct
{
  byte          EndPoint;
  uint16        AppProfId;
  uint16        AppDeviceId;
  byte          AppDevVer:4;
  byte          Reserved:4;             // AF_V1_SUPPORT uses for AppFlags:4.
  byte          AppNumInClusters;
  cId_t         *pAppInClusterList;
  byte          AppNumOutClusters;
  cId_t         *pAppOutClusterList;
} SimpleDescriptionFormat_t;

这个结构体里面的成员又引申出了两个新概念,profile和cluster,两个让人头大的概念,目前还不是搞得很清楚,不过大体上profile是指一种应用配置,比如智能家居HA对应一个profileID,如果该设备是作为智能家居终端的话,那么AppProfId就应该是0x104(这个是zigbee联盟规定好了的),至于cluster,则类似于设备属性之类的概念。

在该例程中,该设备功能描述结构体是通过一个预定义好的数据结构进行加载的:

  GenericApp_epDesc.simpleDesc
            = (SimpleDescriptionFormat_t *)&GenericApp_SimpleDesc;

通过定义查找GenericApp_SimpleDesc,可以发现如下:

const SimpleDescriptionFormat_t GenericApp_SimpleDesc =
{
  GENERICAPP_ENDPOINT,              //  int Endpoint;
  GENERICAPP_PROFID,                //  uint16 AppProfId[2];
  GENERICAPP_DEVICEID,              //  uint16 AppDeviceId[2];
  GENERICAPP_DEVICE_VERSION,        //  int   AppDevVer:4;
  GENERICAPP_FLAGS,                 //  int   AppFlags:4;
  GENERICAPP_MAX_CLUSTERS,          //  byte  AppNumInClusters;
  (cId_t *)GenericApp_ClusterList,  //  byte *pAppInClusterList;
  GENERICAPP_MAX_CLUSTERS,          //  byte  AppNumInClusters;
  (cId_t *)GenericApp_ClusterList   //  byte *pAppInClusterList;
};

此为一个const即静态变量,且该结构体中的各个成员变量都已经定义好了,而该例程代码就由此完成了对simpleDesc这个成员变量的赋值。

另外要提一点的是,设备描述这个结构体endPointDesc_t,既可以上电后进行自定义初始化该结构体,也可作为一个静态结构体在代码中固定好。这两种方式各有各的好处,在此只是略提一下。

最后一个成员变量afNetworkLatencyReq_t latencyReq;一般都定义为noLatencyReqs;,至于原因,仍待求解中。

好了,完成了参数设置之后,接下来进入一个很有操作系统特色的地方:注册

注册组件代码如下:

  // Register the endpoint description with the AF
  afRegister( &GenericApp_epDesc );//
该行代码向AF层注册该终端描述,这样一是能向周围的zigbee设备描述本设备特征,二是应用任务才能够进行数据处理和端点管理。这也是AF层的作用范围所在。

  // Register for all key events - This app will handle all key events
  RegisterForKeys( GenericApp_TaskID );

 同样的,这儿注册对按键的使用,只有这样注册之后,当有按键事件发生(按下或松开),才会通知这个task_id对应的任务处理程序task_event_process进行处理。

这里再详细说下 按键注册函数: RegisterForKeys( GenericApp_TaskID ),其具体代码如下:

uint8 RegisterForKeys( uint8 task_id )
{
  // Allow only the first task
  if ( registeredKeysTaskID == NO_TASK_ID )
  {
    registeredKeysTaskID = task_id;
    return ( true );
  }
  else
    return ( false );
}

在这个函数中,可以看出对按键的注册其实是对registeredKeysTaskID这个全局变量进行注册,当HAL层检测到按键事件时,将会发送消息CHANGE_KEY(前面分析过的SYS_EVENT_MSG子事件之一)到registeredKeysTaskID这个变量所指向的task_id中去。这个我们就不更加详细地去理解了,至少现在不用。值得注意是一点,按键注册是对一个变量进行赋值的,那么很显然同一时间只能对一个task_id对应的任务发送该消息,也就是说OSAL中的注册是只允许一个的。

操作系统中,注册这个概念的作用本质主要在于通知和调用,对于Zstack来说,你这个task登记了你需要关于按键的事件通知,那么有按键的事件我才知道我需要通知你。

任务初始化函数的最后两句代码是:

  ZDO_RegisterForZDOMsg( GenericApp_TaskID, End_Device_Bind_rsp );
  ZDO_RegisterForZDOMsg( GenericApp_TaskID, Match_Desc_rsp );

要了解这两句代码起什么作用,有什么样的意义,我们还是需要首先来了解下

 ZDO_RegisterForZDOMsg这个函数,如图3所示为该函数在ZstackAPI手册上的说明:


图3

首先,该函数属于一个callback,即回调函数。该类函数在Zstack中的意义以及运作方式暂时还是未知,但是根据手册的说明,ZDO message callbacks这类函数的作用是为一个应用任务注册,然后可以接收到指定的zigbee设备之间的传播信息。

函数原型为:ZStatus_t ZDO_RegisterForZDOMsg( uint8 taskID, uint16 clusterID ); 

其中taskID--the application’s task ID.  This will be used to send the OSAL message. 即设定需要接收该信息的应用任务ID

clusterID – the over the air message’s clusterID that you would like to receive (example: NWK_addr_rsp).  These are defined in ZDProfile.h. clusteer这个概念是Zstack中特有的,这个概念类似于一个对象的一个类。这儿引申出来就太多了,待后续进一步深入分析。

在这儿,该例程函数所指定的End_Device_Bind_rsp和Match_Desc_rsp是ZDO层设备绑定命令中的两个,料想应该是用于指定接收终端绑定响应和地址匹配响应的。

所以由上的分析可以知道,这儿使用ZDO_RegisterForZDOmsg函数可以为Generic_App这个应用进程进行注册需要接收的指定cluster(簇)的Zigbee设备之间的传输信息(OTA信息,over the air)。

在手册上,关于使用该函数注册后的效果还有这样的说明:在进使用了该函数进行注册之后,如果zigbee设备有接收到指定的cluster消息,OSAL会向该应用进程添加一个系统消息:ZDO_CB_MSG,通过该消息,在后续的Generic_Process_event中就可以根据消息种类进行对应的处理了。

这里再对上面的分析进行一个大体的回顾:应用任务初始化,首先进行任务ID,网络状态等本地变量初始化,以便之后使用,然后设置信息要传输的目的地址,对设备自身的描述结构变量进行设置,然后进行各种注册,注册AF,注册按键,注册指定消息接收。这就是Generic例程的任务初始化函数流程,同时也是TI建议的参考书写方式。



0 0