第四部分 提高篇-第1章 CC2530协议栈剖析

1.1 ZigBee 协议栈简介

本节内容仅仅是对 ZigBee 协议栈的一些大家必须理解清楚的概念进行简单的讲解,并没有对 ZigBee 协议栈的构成及工作原理进行详细的讨论。让刚接触 ZigBee 协议栈的朋友们对它有个初步的感性认识,有助于后面使用 ZigBee 协议栈进行真正的项目开发。

什么是 ZigBee 协议栈呢?它和 ZigBee 协议有什么关系呢协议是一系列的通信标准,通信双方需要共同按照这一标准进行正常的数据发射和接收。协议栈是协议的具体实现形式,通俗点来理解就是协议栈是协议和用户之间的一个接口,开发人员通过使用协议栈来使用这个协议的,进而实现无线数据收发。

图 1展示了 ZigBee无线网络协议层的架构图。ZigBee的协议分为两部分,IEEE 802.15.4 定义了 PHY(物理层)和 MAC(介质访问层)技术规范; ZigBee联盟定义了 NWK(网络层)、 APS(应用程序支持子层)、 APL(应用层)技术规范。 ZigBee 协议栈就是将各个层定义的协议都集合在一直,以函数的形式实现,并给用户提供 API(应用层),用户可以直接调用。

这里写图片描述

图 1-1 ZigBee 无线网络协议层

在开发一个应用时,协议较底下的层与应用是相互独立的,它们可以从第三方来获得,因此我们需要做的就只是在应用层进行相应的改动。

介绍到这里,大家应该清楚协议和协议栈的关系了吧,是不是会想着怎么样才能用协议栈来开发自己的项目呢?技术总是不断地在发展地,我们可以用ZigBee 厂商提供的协议栈软件来方便地使用 ZigBee 协议栈(注意:不同厂商提供的协议栈是有区别的此处介绍 TI 推出的 ZigBee 2007 协议栈也称 Z-Stack)。

Z-stack 是挪威半导体公司 Chipcon(目前已经被 TI 公司收购)推出其CC2430 开发平台时,推出的一款业界领先的商业级协议栈软件,由于这个协议栈软件的出现,用户可以很容易地开发出具体的应用程序来,也就是大家说的掌握 10 个函数就能使用 ZigBee 通讯的原因。它使用瑞典公司 IAR 开发的 IAREmbedded Workbench for MCS-51 作为它的集成开发环境。 Chipcon 公司为自己设计的 Z-Stack 协议栈中提供了一个名为操作系统抽象层 OSAL 的协议栈调度程序。对于用户来说,除了能够看到这个调度程序外,其它任何协议栈操作的具体实现细节都被封装在库代码中。用户在进行具体的应用开发时只能够通过调用API 接口来进行,而无权知道 ZigBee 协议栈实现的具体细节,也没必要去知道。因此在这里提醒各位开发者,在使用 ZigBee 协议栈进行实际项目开发时,不需要关心协议栈是具体怎么实现的,当然有兴趣的也可以深入分析。

TI 公司的基于 ZigBee2007 的协议栈 Z-Stack-CC2530-2.3.0,所有文件目录如红色框所示,我们可以把它看做一个庞大的工程。或者是一个小型的操作系统。采用任务轮询的方法运行来个小总结: ZigBee 协议栈已经实现了 ZigBee 协议,用户可以使用协议栈提供的 API 进行应用程序的开发,在开发过程中完全不必关心 ZigBee 协议的具体实现细节,要关心的问题是:应用层的数据是使用哪些函数通过什么方式把数据发送出去或者把数据接收过来的。所以最重要的是我们要学会使用 ZigBee协议栈。具体的例程讲解在这里就不说先了,在接下来的教程里面会详细地和大家一起讨论 ZigBee 协议栈架构中每个层所包含的内容和功能及 Z-stack 的软件架构。

1.2 ZStack 体系架构

这里写图片描述

图1-2 ZigBee 和 IEEE 802.15.4 的关系图

zigbee 网络协议的规范ZigBee 是建立在 IEEE802.15.4 标准之上,由于IEEE 802.15.4 标准只定义了物理层协议和 MAC 层协议,于是成立了zigbee联盟,ZigBee 联盟对其网络层协议和 API 进行了标准化,还开发了安全层。经过 ZigBee 联盟对IEEE 802.15.4 的改进,这才真正形成了 ZigBee协议栈(Zstack)。

ZStack 的体系结构在ZigBee简介里提到了一些,在该讲中对协议栈进行深度剖析。ZStack 的体系结构由称为层的各模块组成。每一层为其上层提供特定的服务:即由数据服务实体提供数据传输服务;管理实体提供所有的其他管理服务。每个服务实体通过相应的服务接入点(SAP) 为其上层提供一个接口,每个服务接入点通过服务原语来完成所对应的功能。

ZStack 根据 IEEE 802.15.4 和 ZigBee 标准分为以下几层:API(Application Programming Interface),HAL(Hardware Abstract Layer),MAC(Media Access Control),NWK(Zigbee Network Layer),OSAL(Operating System Abstract System),Security,Service,ZDO(Zigbee Device Objects)。ZStack 的体系结构如图1-3所示,下面分别介绍各层。

这里写图片描述

图1-3 ZStack体系结构

 物理层(PHY)
物理层定义了物理无线信道和 MAC 子层之间的接口,提供物理层数据服务和物理层管理服务,物理层内容:
 ZigBee 的激活;
 当前信道的能量检测;
 接收链路服务质量信息;
 ZigBee 信道接入方式;
 信道频率选择;
 数据传输和接收。

 介质接入控制子层(MAC)
MAC 层负责处理所有的物理无线信道访问,并产生网络信号、同步信号;支持 PAN 连接和分离,提供两个对等 MAC 实体之间可靠的链路。MAC 层功能:
 网络协调器产生信标;
 与信标同步;
 支持 PAN (个域网) 链路的建立和断开;
 为设备的安全性提供支持;
 信道接入方式采用免冲突载波检测多址接入(CSMA-CA)机制;
 处理和维护保护时隙(GTS )机制;
 在两个对等的 MAC 实体之间提供一个可靠的通信链路

 网络层(NWK)
ZStack 的核心部分在网络层。网络层主要实现节点加入或离开网络、接收或抛弃其他节点、路由查找及传送数据等功能。网络层功能:
 网络发现;
 网络形成;
 允许设备连接;
 路由器初始化;
 设备同网络连接;
 直接将设备同网络连接;
 断开网络连接;
 重新复位设备;
 接收机同步;
 信息库维护。

 应用层(APL)
ZStack 应用层框架包括应用支持层(APS)、ZigBee 设备对象(ZDO)和制造商所定义的应用对象。

 应用支持层的功能包括:维持绑定表、在绑定的设备之间传送消息。

 ZigBee 设备对象的功能包括:定义设备在网络中的角色(如 ZigBee 协调器和终端设备) ,发起和响应绑定请求,在网络设备之间建立安全机制。ZigBee 设备对象还负责发现网络中的设备,并且决定向他们提供何种应用服务。

ZigBee 应用层除了提供一些必要函数以及为网络层提供合适的服务接口外, 一个重要的功能是应用者可在这层定义自己的应用对象。

 应用支持子层(APS)(也有的把该层单独列为一层)

 应用程序框架(AF)
运行在 ZigBee 协议栈上的应用程序实际上就是厂商自定义的应用对象,并且遵循规范(profile)运行在端点 1~240 上。在 ZigBee 应用中,提供 2 种标准服务类型:键值对(KVP)或报文(MSG)。

 ZigBee 设备对象(ZDO )
远程设备通过 ZDO 请求描述符信息,接收到这些请求时,ZDO 会调用配置对象获取相应描述符值。另外,ZDO 提供绑定服务。

好了,说了一大堆抽象的东西,如果你以前对体系架构没有一点了解,我想你还是占时不要深究ZStack体系结构,等到你学到一定程度再回过头来看看体系架构,也许就明白了。

先看看官方提供的ZStack-,如果你是根据教程一步一步学过来的,那么协议栈ZStack-CC2530-2.3.0就已经安装好了,如果你没有安装的的话,请参看入门篇协议栈相关软件的安装。打开C:\Texas Instruments\ZStack-CC2530-2.3.0-1.4.0协议栈的目录(安装路径视自身情况而定,看你安装选择的路径),如图1-4:

这里写图片描述

图1-4

Components,顾名思义这个是放我们的库的文件夹,里面放了一些我们用到的 ZDO,driver,hal, zcl 等库的代码;

Documents,这个不用说大家都知道是放 TI 的开发文档的,里面很多都是讲述协议栈的API 的 有空时可以看看;

Projects,这个文件夹放的是 TI 协议栈的例子程序,一个个例子程序都是以一个个 project的 形式给我们的,学好这些例子程序里面的一两个,基本你能做事情了。

Tools,这个文件夹是放 TI 的例子程序的一些上位机之类的程序,作为工具使用。好了,基本明 白了基本架构之后,我们以一个简单的实验开始 。先掌握一点必要的理论再实验效 果比较好。

Getting Started Guide - CC2530当然就是指导手册了。

我们看个官方给出的协议栈相关内容,具体怎用用官方提供的源文件怎么建工程文件以后会将,暂时就行看已经建好的一步一步来就行,不要太着急嘛。好了,我们还是来已经建好的工程文件吧,这个相对具体点,也有助于更快进入状态。首先,打开工程:ZStack-CC2530-2.3.0-1.4.0\Projects\zstack\Samples\SampleApp\CC2530DB\SampleApp.eww。

这里写图片描述
App:应用层目录,这是用户创建各种不同工程的区域,在这个目录中包含了应用层的内容和这个项目的主要内容。HAL: 硬件层目录,包含有与硬件相关的配置和驱动及操作函数。

MAC: MAC 层目录,包含了 MAC 层的参数配 置文件及其 MAC 的 LIB 库的函数接口文件。

MT: 实现通过串口可控制各层,并与各层进 行直接交互。

NWK:网络层目录,包含网络层配置参数文件网络层库的函数接口文件及 APS 层库的函数接口。

OSAL:协议栈的操作系统。

Profile:Application framework 应用框架层目录,包含 AF 层处理函数文件。应用框架层是应用程序和 APS 层的无线数据接口。

Security:安全层目录,包含安全层处理函数,比如加密函数等

Services:地址处理函数目录,包括地址 模式的定义及地址处理函数。

Tools:工程配置目录,包括空间划分及 ZStack 相关配置信息。

ZDO:ZDO 目录

ZMac: MAC 层目录,包括 MAC 层参数配置 及 MAC 层 LIB 库函数回调处理函数。

ZMain: 主函数目录,包括入口函数及硬件配置文件。

Output: 输出文件目录,由 IAR IDE 自动生成。

对协议栈体系结构进行归纳,下表表协议栈体系分层架构与协议栈代码文件夹主要文件夹:

表 1-1 协议栈体系分层架构与协议栈代码文件夹对应

这里写图片描述

注意:从软件开发专业角度讲建议大家复制工程到非中文目录,因为有些开发环境对中文路径支持的不好,虽然 IAR 支持但在实际工作中你想别人看到你的工程,认为你很专业就照着上面做吧。

1.3 协议栈的基本概念

在上一讲我们介绍了ZStack2.3框架结构进行了剖析,也许很多初学者都在云里雾里,大家也不用着急,大家只需了解大概即可,等学完该后面的例子你再回头看上一讲,你就会恍然大悟的。上一讲对协议栈宏观上有了大概的了解,本讲将对对一些基本概念进行讲解。

1.3.1设备类型(Device Types)

在 ZigBee 网络中存在三种逻辑设备类型: Coordinator(协调器), Router(路由器)和End-Device(终端设备)。 ZigBee 网络由一个 Coordinator 以及多个 Router 和多个End_Device 组成。

注意: 在 ZStack-CC2530-2.3.1-1.4.0 中一个设备的类型通常在编译的时候通过编译选项确定。所有的应用例子都提供独立的项目文件来编译每一种设备类型。对于协调器, 在 Workspace 区域的下拉菜单中选择 CoordinatorEB-Pro; 对于路由器, 在 Workspace 区域的下拉菜单中选择 RouterEB-Pro;对于终端设备,在 Workspace 区域的下拉菜单中选择 EndDeviceEB-Pro。

表 1-2

这里写图片描述

这里写图片描述

图1-5

上图是一个简单的 ZigBee 网络示意图。 其中红色节点为 Coordinator,黄色节点为 Router, 绿色节点为 End-Device。
 Coordinator(协调器)
协调器负责启动整个网络。 它也是网络的第一个设备。 协调器选择一个信道和一个网络 ID(也称之为 PAN ID, 即 Personal Area Network ID), 随后启动整个网络。协调器也可以用来协助建立网络中安全层和应用层的绑定(bindings)。
注意,协调器的角色主要涉及网络的启动和配臵。一旦这些都完成后,协调器的工作就像一个路由器(或者消失 go away)。 由于 ZigBee 网络本身的分布特性,因此接下来整个网络的操作就不在依赖协调器是否存在。
 Router(路由器)
路由器的功能主要是:允许其他设备加入网络,多跳路由和协助它自己的由电池供电的终端设备的通讯。通常, 路由器希望是一直处于活动状态, 因此它必须使用主电源供电。 但是当使用树状网络拓扑结构时, 允许路由间隔一定的周期操作一次, 这样就可以使用电池给其供电。
 End-Device(终端设备)
终端设备没有特定的维持网络结构的责任,它可以睡眠或者唤醒,因此它可以可以是一个电池供电设备。通常,终端设备对存储空间(特别是 RAM 的需要)比较小。

1.3.2拓扑结构

ZigBee 网络支持星状、树状和网状三种网络拓扑结构,如下图所示,分别依次是星状网络,树(簇)状网络和网状网络。

这里写图片描述

图 1-6 拓扑结构

星状网络由一个 PAN 协调器和多个终端设备组成,只存在 PAN 协调器与终端的通讯,终端设备间的通讯都需通过 PAN 协调器的转发。

树状网络由一个协调器和一个或多个星状结构连接而成, 设备除了能与自己的父节点或子节点进行点对点直接通讯外,其他只能通过树状路由完成消息传输。网状网络是树状网络基础上实现的,与树状网络不同的是, 它允许网络中所有具有路由功能的节点直接互连,由路由器中的路由表实现消息的网状路由。 该拓扑的优点是减少了消息延时,增强了可靠性,缺点是需要更多的存储空间开销。
这里写图片描述

1.3.3地址相关

1.3.3.1地址定义

ZigBee 设备有两种类型的地址。 一种是 64 位 IEEE 地址, 即 MAC 地址, 另一种是16 位网络地址。

64 位地址使全球唯一的地址, 设备将在它的生命周期中一直拥有它。它通常由制造商或者被安装时设臵。 这些地址由 IEEE 来维护和分配。

16 位网络地址是当设备加入网络后分配的。 它在网络中是唯一的,用来在网络中鉴别设备和发送数据。 其中, 协调器的网络地址为 0x00

#define NWK_PAN_COORD_ADDR 0x0000

1.3.3.2网络地址分配

ZigBee2006 和 ZigBee 2007 使用分布式寻址方案来分配网络地址。这个方案保证在整个网络中所有分配的地址是唯一的。这一点是必须的,因为这样才能保证一个特定的数据包能够发给它指定的设备, 而不出现混乱。同时,这个寻址算法本身的分布特性保证设备只能与他的父辈设备通讯来接受一个网络地址。不需要整个网络范围内通讯的地址分配,这有助于网络的可测量性。假设父设备可拥有的最大子设备数为 Cm, 其拥有的最大路由子设备数为 Rm,网络的最大深度为 Lm, 则父设备所能分配子区段地址数为:

若 Rm=1, Cskip(d)= 1+Cm*(Lm-d-1);
若 Rm 不为 1, 则 Cskip(d)=(1+Cm-Rm-Cm*( Rm)^(Lm-d-1))/(1-Rm).
子节点为父设备的第 n 个子路由器的短地址分配:
Achild =Aparent +(n-1)*Cskip( d) +1, n=1
Achild =Aparen +(n-1)*Cskip( d), n>1
子节点为父设备的第 n 个子终端设备的短地址分配:
Achild = Aparent + Rm*Cskip( d) + n

ZigBee 2007 PRO 使用的随机地址分配机制,对新加入的节点使用随机地址分配, 为保证网络内地址分配不重复,使用其余的随机地址再进行分配。 当一个节点加入时, 将接收到父节点的随机分配地址,然后产生“ 设备声明”( 包含分配到的网络地址和 IEEE 地址) 发送至网络中的其余节点。如果另一个节点有着同样的网络地址, 则通过路由器广播“ 网络状态-地址冲突” 至网络中的所有节点。所有发生网络地址冲突的节点更改自己的网络地址, 然后再发起“ 设备声明” 检测新的网络地址是否冲突。

终端设备不会广播“ 地址冲突”, 他们的父节点会帮助完成。 如果一个终端设备发生了“ 地址冲突”, 他们的父节点发送“ 重新加入” 消息至终端设备, 并要求他们更改网络地址。 然后, 终端设备再发起“ 设备声明” 检测新的网络地址是否冲突。

当接收到“ 设备声明” 后, 关联表和绑定表将被更新使用新的网咯地址, 但是路由表不会被更新。在每个路由加入网络之前, 寻址方案需要知道和配臵一些参数。 这些参数是MAX_DEPTH( 最大网络深度)、 MAX_ROUTERS( 最多路由数) 和MAX_CHILDREN( 最多子节点数)。 这些参数是栈配臵的一部分, ZigBee2007协议栈已经规定了这些参数的值:

#if ( STACK_PROFILE_ID == ZIGBEEPRO_PROFILE )
#define MAX_NODE_DEPTH      20
#elif ( STACK_PROFILE_ID == HOME_CONTROLS )
#define MAX_NODE_DEPTH 5
#elif ( STACK_PROFILE_ID == GENERIC_STAR )
#define MAX_NODE_DEPTH 5
#elif ( STACK_PROFILE_ID == NETWORK_SPECIFIC )
#define MAX_NODE_DEPTH 5
#endif
#define NWK_MAX_ROUTERS 6
#define NWK_MAX_DEVICES 21

MAX_DEPTH 决定了网络的最大深度。 协调器(Coordinator)位于深度 0, 它的儿子位于深度 1, 他的儿子的的儿子位于深度 2, 以此类推。 MAX_DEPTH 参数限制了网络在物理上的长度。

MAX_CHILDREN 决定了一个路由(Router)或者一个协调器节点可以处理的儿子节点的最大个数。

MAX_ROUTER 决定了一个路由(Router)或者一个协调器(Coordinator)节点可以处理的具有路由功能的儿子节点的最大个数。 这个参数是 MAX_CHILDREN 的一个子集, 终端节点使用(MAX_CHILDREN – MAX_ROUTER)剩下的地址空间。
本节中 ZigBee 网络示意图所示的 MAX_DEPTH=3, MAX_CHILDREN=5,MAX_ROUTER=2。

如果开发人员想改变这些值, 则需要完成以下几个步骤:
首先, 你要保证这些参数新的赋值要合法。 即, 整个地址空间不能超过 216, 这就限制了参数能够设臵的最大值。 可以使用 projects\ZStack\tools 文件夹下的CSkip.xls 文件来确认这些值是否合法。 当在表格中输入了这些数据后, 如果你的数据不合法的话就会出现错误信息。

当选择了合法的数据后, 开发人员还要保证不再使用标准的栈配臵, 取而代之的是网络自定义栈配臵(例如: 在 nwk_globals.h 文件中将 STACK_PROFILE_ID 改为 NETWORK_SPECIFIC)。 然后 nwk_globals.h 文件中的 MAX_DEPTH 参数将被设臵为合适的值。

此外, 还必须设臵 nwk_globals.c 文件中的 Cskipchldrn 数组和 CskipRtrs 数组。这些数组的值由 MAX_CHILDREN 和 MAX_ROUTER 构成。

1.3.3.4寻址

为了向一个在 ZigBee 网络中的设备发送数据, 应用程序通常使用AF_DataRequest()函数。 数据包将要发送给一个 afAddrType_t(在 AF.h中定义)类型的目标设备。
这里写图片描述
注意, 除了网路地址之外, 还要指定地址模式参数。 目的地址模式可以设臵为以下几个值(在 ZComDef.h中定义):
这里写图片描述
因为在 Zigbee 中, 数据包可以单点传送(unicast), 多点传送(multicast)或者广播传送, 所以必须有地址模式参数。 一个单点传送数据包只发送给一个设备, 多点传送数据包则要传送给一组设备, 而广播数据包则要发送给整个网络的所有节点。 这个将在下面详细解释。

 单点传送(Unicast)
Uicast 是标准寻址模式, 它将数据包发送给一个已经知道网络地址的网络设备。将 afAddrMode 设臵为 Addr16Bit 并且在数据包中携带目标设备地址。

 间接传送(Indirect)
当应用程序不知道数据包的目标设备在哪里的时候使用的模式。 将模式设置为AddrNotPresent 并且目标地址没有指定。 取代它的是从发送设备的栈的绑定表中查找目标设备。 这种特点称之为源绑定。

当数据向下发送到达栈中, 从绑定表中查找并且使用该目标地址。 这样, 数据包将被处理成为一个标准的单点传送数据包。 如果在绑定表中找到多个设备, 则向每个设备都发送一个数据包的拷贝。

上一个版本的 ZigBee(ZigBee2004), 有一个选项可以讲绑定表保存在协调器(Coordinator)当中。 发送设备将数据包发送给协调器, 协调器查找它栈中的绑定表, 然后将数据发送给最终的目标设备。 这个附加的特性叫做协调器绑定(Coordinator Binding)。

 广播传送(broadcast)
当应用程序需要将数据包发送给网络的每一个设备时, 使用这种模式。 地址模式设臵为 AddrBroadcast。目标地址可以设臵为下面广播地址的一种:
NWK_BROADCAST_SHORTADDR_DEVALL(0xFFFF)—数据包将被传送到网络上的所有设备, 包括睡眠中的设备。 对于睡眠中的设备, 数据包将被保留在其父亲节点直到查询到它, 或者消息超时(NWK_INDIRECT_MSG_TIMEOUT 在f8wConifg.cfg 中)。NWK_BROADCAST_SHORTADDR_DEVRXON(0xFFFD)——数据包将被传送到网络上的所有在空闲时打开接收的设备(RXONWHENIDLE), 也就是说, 除了睡眠中的所有设备。NWK_BROADCAST_SHORTADDR_DEVZCZR(0xFFFC)——数据包发送给所有的路由器, 包括协调器。

1.3.3.5组寻址(Group Addressing)

当应用程序需要将数据包发送给网络上的一组设备时, 使用该模式。 地址模式设臵为 afAddrGroup 并且 addr.shortAddr 设臵为组 ID。

在使用这个功能呢之前, 必须在网络中定义组。 (参见 Z-stack API 文档中的aps_AddGroup()函数)。

注意组可以用来关联间接寻址。 再绑定表中找到的目标地址可能是是单点传送或者是一个组地址。 另外, 广播发送可以看做是一个组寻址的特例。下面的代码是一个设备怎样加入到一个 ID 为 1 的组当中:

aps_Group_t group;
// Assign yourself to group 1
group.ID = 0x0001;
group.name[0] = 0; // This could be a human readable string
aps_AddGroup( SAMPLEAPP_ENDPOINT, &group );

1.3.3.6重要设备地址(Important Device Addresses)

应用程序可能需要知道它的设备地址和父亲地址。 使用下面的函数获取设备地址(在 ZStack API 中定义):
NLME_GetShortAddr()——返回本设备的 16 位网络地址
NLME_GetExtAddr()—— 返回本设备的 64 位扩展地址
使用下面的函数获取该设备的父亲设备的地址:
NLME_GetCoordShortAddr()——返回本设备的父亲设备的 16 位网络地址
NLME_GetCoordExtAddr()—— 返回本设备的父亲设备的 64 位扩展地址
 属性
属性 Attribute 是一个反映物理数量或状态的数据值, 比如开关值( On/Off) ,温度值、 百分比等。
 群集
群集 Cluster 是包含一个或多个属性( attribute) 的群组。 简单的说, 群集就是属性的集合。 每个群集都被分配一个唯一的群集 ID 且每个群集最多有 65536 个属性。
 设备描述
指定群集是输入还是输出。 描述符有: 节点描述符、 电源描述符、 简单描述符、端点描述符。端点描述符: typedef struct

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

简单描述符:

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 *pAppOutClust
} SimpleDescriptionFormat_t;

 端点
端点 EndPoint 是协议栈应用层的入口, 也可以理解应用对象( ApplicationObject) 存在的地方, 它是为实现一个设备描述而定义的一组群集。 每个 ZigBee设备可以最多支持 240 这样的端点, 这也意味着在每个设备上可以定义 240 个应用对象。 端点 0 被保留用于与 ZDO 接口, 这是每个 ZigBee 设备必须使用的端点,而端点 255 被保留用于广播, 端点 241-254 则被保留用于将来做扩展使用。

 节点
节点 Node 也可以理解为一个容器, 包含一组 ZigBee 设备, 分享一个无线信道。每个节点有且只有一个无线信道使用。

 绑定( banding)
在 zigaee 协议中定义了一种特殊的操作, 叫做绑定(binding)操作。它能够通过使用 ClusterID 为不同节点上的独立端点建立一个逻辑上的连接。 以下图为例来
说明绑定操作:

这里写图片描述

图 1-7

图中 ZigBee 网络中的两个节点分别为 Z1 和 Z2, 其中 Zl 节点中包含两个独立端点分别是 EP3 和 EP21, 它们分别表示开关 1 和开关 2。 Z2 节点中有 EP5、 EP7、 EP8、EPl7 四个端点分别表示从 l 到 4 这四盏灯。 在网络中, 通过建立 ZigBee 绑定操作,可以将 EP3 和 EP5、 EP7、 EP8 进行绑定, 将 EP21 和 EPl7 进行绑定。 这样开关 I 便可以同时控制电灯 l、 2、 3,开关 2 便可以控制电灯 4。 利用绑定操作,还可以更改开关和电灯之间的绑定关系, 从而形成不同的控制关系。 从这个例子, 可以看出绑定操作能够使用户的应用变得更加方便灵活。

要实现绑定操作, 端点必须向协调器发送绑定请求, 协调器在有限的时间间隔内接收到两个端点的绑定请求后, 便通过建立端点之间的绑定表在这两个不同的端点之间形成了一个逻辑链路。 因此, 在绑定后的两个端点之间进行消息传送的过程属于消息的间接传送。 其中一个端点首先会将信息发送到 ZigBee 协调器中,ZigBee 协调器在接收到消息后会通过查找绑定表, 将消息发送到与这个端点相绑定的所有端点中, 从而实现了绑定端点之间的通信。

1.3.4路由(Routing)

1.3.4.1概述(Overview)

路由对与应用层来说是完全透明的。 应用程序只需简单的向下发送去往任何设备的数据到栈中, 栈会负责寻找路径。 这种方法, 应用程序不知道操作是在一个多跳的网络当中的。

路由还能够自愈 ZigBee 网络, 如果某个无线连接断开了, 路由功能又能自动寻找一条新的路径避开那个断开的网络连接。 这就极大的提高了网络的可靠性, 同时也是 ZigBee 网络的一个关键特性。

1.3.4.2协议栈规范( Stack Profile)

协议栈规范由 ZigBee 联盟定义指定。 在同一个网络中的设备必须符合同一个协议栈规范( 同一个网络中所有设备的协议栈规范必须一致)。

ZigBee 联盟为 ZigBee 协议栈 2007 定义了 2 个规范: ZigBee 和 ZigBee PRO。 所有的设备只要遵循该规范, 即使在不同厂商买的不同设备同样可以形成网络。如果应用开发者改变了规范, 那么他的产品将不能与遵循 ZigBee 联盟定义规范的产品组成网络, 也就是说该开发者开发的产品具有特殊性, 我们称之为“ 关闭的网络”,也就是说它的设备只有在自己的产品中使用, 不能与其他产品通信。更改后的规范可以称之为“ 特定网络” 规范。协议栈规范的 ID 号可以通过查询设备发送的 beacon 帧获得。 在设备加入网络之前, 首先需要确认协议栈规范的 ID。“ 特定网络” 规范 ID 号为 0; ZigBee协议栈规范的 ID 号为 1; ZigBee PRO 协议栈规范的 ID 号为 2。 协议栈规范的ID( STACK_PROFILE_ID) 在 nwk_globals.h 中定义。
这里写图片描述

这里写图片描述

1.3.4.3路由协议(Routing Protocol)

ZigBee 执行基于用于 AODV 专用网络的路由协议。 简化后用于传感器网络。ZigBee 路由协议有助于网络环境有能力支持移动节点, 连接失败和数据包丢失。当路由器从他自身的应用程序或者别的设备那里收到一个单点发送的数据包, 则网络层(NWK Layer)根据一下程序将它继续传递下去。 如果目标节点是它相邻路由器中的一个, 则数据包直接被传送给目标设备。 否则, 路由器将要检索它的路由表中与所要传送的数据包的目标地址相符合的记录。 如果存在与目标地址相符合的活动路由记录, 则数据包将被发送到存储在记录中的下一级地址中去。 如果没有发现任何相关的路由记录, 则路由器发起路径寻找, 数据包存储在缓冲区中知道路径寻找结束。

ZigBee 终端节点不执行任何路由功能。 终端节点要向任何一个设备传送数据包,它只需简单的将数据向上发送给它的父亲设备, 由它的父亲设备以它自己的名义执行路由。同样的,任何一个设备要给终端节点发送数据, 发起路由寻找, 终端节的的父亲节点都已它的名义来回应。

注意 ZigBee 地址分配方案使得对于任何一个目标设备, 根据它的地址都可以得到一条路径。 在 Z-Stack 中, 如果万一正常的路径寻找过程不能启动的话(通常由于缺少路由表空间), 那么 Z-Stack 拥有自动回退机制。

此外, 在 Z-Stack 中, 执行的路由已经优化了路由表记录。 通常, 每一个目标设备都需要一条路由表记录。 但是, 通过把一定父亲节点记录与其子所有子结点的记录合并, 这样既可以优化路径也可以不丧失任何功能。ZigBee 路由器, 包括协调器执行下面的路由函数: (i)路径发现和选择; (ii)路径保持维护; (iii)路径期满。路径的发现和选择(Route Discovery and Selection)路径发现是网络设备凭借网络相互协作发现和建立路径的一个过程。 路由发现可以由任意一个路由设备发起, 并且对于某个特定的目标设备一直执行。 路径发现机制寻找源地址和目标地址之间的所有路径, 并且试图选择可能的最好的路径。

路径选择就是选择出可能的最小成本的路径。 每一个结点通常持有跟它所有邻接点的“ 连接成本(link costs)”。 通常, 连接成本的典型函数是接收到的信号的强度。 沿着路径, 求出所有连接的连接成本总和, 便可以得到整个路径的“ 路径成本”。路由算法试图寻找到拥有最小路径成本的路径。路径通过一系列的请求和回复数据包被发现。 源设备通过向它的所有邻接节点广播一个路由请求数据包,来请求一个目标地址的路径。 当一个节点接收到 RREQ数据包, 它依次转发 RREQ 数据包。但是在转发之前, 它要加上最新的连接成本,然后更新 RREQ 数据包中的成本值。这样,沿着所有它通过的连接, RREQ 数据包携带着连接成本的总和。这个过程一直持续到 RREQ 数据包到达目标设备。 通过不同的路由器, 许多 RREQ 副本都将到达目标设备。 目标设备选择最好的 RREQ数据包, 然后发回一个路径答复数据包(a Route Reply)RREP 给源设备。RREP 数据包是一个单点发送数据包, 它沿着中间节点的相反路径传送直到它到达原来发送请求的节点为止。一旦一条路径被创建,数据包就可以发送了。当一个结点与它的下一级相邻节点失去了连接(当它发送数据时,没有收到 MAC ACK),该节点向所有等待接收它的 RREQ 数据包的节点发送一个 RERR 数据包, 将它的路径设为无效。 各个结点根据收到的数据包 RREQ、RREP 或者 RERR 来更新它的路由表。

1.3.4.5路径保持维护(Route maintenance)

网状网提供路径维护和网络自愈功能。 中间节点沿着连接跟踪传送失败, 如果一个连接被认定是坏链, 那么上游节点将针对所有使用这条连接的路径启动路径修复。 节点发起重新发现直到下一次数据包到达该节点, 标志路径修复完成。 如果不能够启动路径发现或者由于某种原因失败了, 节点则向数据包的源节点发送一个路径错误包(RERR), 它将负责启动新路径的发现。 这两种方法, 路径都自动重建。

1.3.4.6路径期满(Route expiry)

路由表为已经建立连接路径的节点维护路径记录。 如果在一定的时间周期内,没有数据通过沿着这条路径发送, 这条路径将被表示为期满。 期满的路径一直保留到它所占用的空间要被使用为止。 这样, 路径在绝对不使用之前不会被删除掉的。在配臵文件 f8wConfig.cfg 文件中配臵自动路径期满时间。 设臵ROUTE_EXPIRY_TIME 为期满时间, 单位为秒。 如果设臵为 0, 则表示关闭自动期满功能。

1.4 ZStack 工作原理

对于基本概念有了一定了解,现在我们来看看ZStzck的工作原理。

这里写图片描述

图 1-8 Z-Stack 工作流程图

Z-Stack采用操作系统的思想来构建,采用事件轮循机制,而且有一个专门的Timer2 来负责定时。从 CC2530 工作开始,Timer2 周而复始地计时,有采集、发送、接收、显示…等任务要执行时就执行。

当各层初始化之后,系统进入低功耗模式,当事件发生时,唤醒系统,开始进入中断处理事件,结束后继续进入低功耗模式。如果同时有几个事件发生,判断优先级,逐次处理事件。这种软件构架可以极大地降级系统的功耗。

整个 ZStack 的主要工作流程,如图所示,大致分为以下 6 步:
(1) 关闭所有中断;
(2) 芯片外部(板载外设)初始化;
(3) 芯片内部初始化;
(4) 初始化操作系统;
(5) 打开所有中断;
(6) 执行操作系统。
其中,初始化操作系统和执行操作系统这两步是最为关键的两步。这两步都是在位于 ZMain 文件夹下的 ZMain.c 文件里的 main 函数里进行的。 main 函数如下面代码所示:

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 );         //初始化I/O ,LED 、Timer 等
  // Initialze HAL drivers
  HalDriverInit();              //初始化芯片各硬件模块
  // Initialize NV System
  osal_nv_init( NULL );         //初始化Flash 存储器
  // Initialize the MAC
  ZMacInit();                   //初始化MAC 层
  // Determine the extended address
  zmain_ext_addr();             //确定IEEE 64位地址
  // 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();             //初始化LCD
#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.
} 

然后,我们就进入 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_init_system()函数里面是一些初始化操作,我们需要关心的是 osalInitTasks()函
数,至于其他的初始化函数,都是关于芯片正常工作所需要的配置,所以,用户可以不用考虑。osalInitTasks()函数的代码如下所示:

void osalInitTasks( void )
{
uint8 taskID = 0;

// 分配内存,返回指向缓冲区的指针
tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);

// 设置所分配的内存空间单元值为 0
osal_memset(tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));



//任务优先级由高向低依次排列,高优先级对应 taskID的值反而小
macTaskInit( taskID++ ); //macTaskInit(0) ,用户不需考虑
nwk_init( taskID++ ); //nwk_init(1),用户不需考虑
Hal_Init( taskID++ ); //Hal_Init(2) ,用户需考虑
#if defined( MT_TASK )
MT_TaskInit(taskID++ );
#endif
APS_Init( taskID++ ); //APS_Init(3) ,用户不需考虑
#if defined ( ZIGBEE_FRAGMENTATION )
APSF_Init(taskID++ );
#endif
ZDApp_Init( taskID++ ); //ZDApp_Init(4) ,用户需考虑
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT)
ZDNwkMgr_Init(taskID++ );
#endif
SampleApp_Init(taskID ); // SampleApp_Init _Init(5) ,用户需考虑

}

osalInitTasks()函数一眼看上去,好像复杂,其实,这个函数就是做了一些任务的初始化,然后用 taskID 这个变量来登记任务。从上面代码可以看到,有些函数后面的注解写着“用户需考虑” ,有的写着“用户不需考虑” ,为什么这样?这是因为每个使用Zstack 的用户,搭建出来的硬件平台都是不一样的,写着“用户需考虑”的函数,就是用户可以根据自己的硬件平台来设置;写着“用户不需考虑”的函数,就是大家都是同样的配置就可以了,TI 已经为用户配置好了,所以用户就不用去考虑了。

在 osalInitTasks()函数里,有 MAC 层、网络层、硬件层…等等的任务初始化,但是我们只用关心为用户提供各层接口的用户层就可以了, 也就是说 osalInitTasks()函数的重点就在 SampleApp_Init( taskID )函数,但是这里我们先不讲,待会讲到 SampleApp.c 时再细讲。

最后,我们就进入 osal_start_system()函数(执行操作系统),其代码如下所示:

void osal_start_system( void )
{
#if !defined ( ZBIT ) && !defined ( UBIT )
for(;;) // Forever Loop
#endif
{
uint8 idx = 0;

osalTimeUpdate();
//1、扫描哪个事件被触发了,然后置相应的标志位


Hal_ProcessPoll(); // 此函数代替了 MT_SerialPoll() and osal_check_timer().

do {
if (tasksEvents[idx]) // Task is highest priority that is ready.
{
break; //2、得到待处理的最高优先级任务索引号 idx

}
} while (++idx<tasksCnt);

if (idx<tasksCnt)
{
uint16 events;
halIntState_tintState;

HAL_ENTER_CRITICAL_SECTION(intState); // 进入临界区, 保护

events = tasksEvents[idx];
//3、提取需要处理的任务中的事件
tasksEvents[idx] = 0; // Clear the Events for this task. 清除本次任务的事件

HAL_EXIT_CRITICAL_SECTION(intState); // 退出临界区

events = (tasksArr[idx])( idx, events );
//4、通过指针调用任务处理函数

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
}
}

osal_start_system()函数主要在重复地做 4 件事:
(1) 扫描哪个事件被触发了,然后置相应的标志位;
(2) 得到待处理的最高优先级任务索引号 idx;
(3) 提取需要处理的任务中的事件;
(4) 通过指针调用任务处理函数。
osal_start_system()函数是 main 函数里主循环。对于这个函数,我们只需要了解它大概的工作流程就可以了,关键还是上面提到的 SampleApp.c 文件。

1.5 Z-stack 中如何实现自己的任务

我们学习 Z-Stack 的目的就是开发自己的应用程序,那么我们自己的应用程序是在
哪里进行编写的?其实,就是在我们上文一直提到的 SampleApp.c 文件里,下面我们就对其进行剖析。

1.5.1 SampleApp.c 文件分析

由于 SampleApp.c 的内容较多,这里就不贴出来了,具体大家可以打开工程阅读。SampleApp.c 文件里面的内容可以分为以下 4 类:
(1) 包括进去的头文件;
(2) 各种变量定义;
(3) 函数声明;
(4) 6 个函数的原型。
其中,最重要的就是 6 个函数的原型,我们往后的用户代码的编写基本上都是在这里进行,当然,hal 层的一些驱动修改,中断服务函数的编写,以及一些跟数据收发有关的宏定义(在 SampleApp.h 里进行)等是个例外。这 6 个函数,有关于初始化用户功能的;有关于所有事件的处理的;有按键服务功能编写的;有接收数据的处理的;有数据发送函数的编写的。下面我们就对这 6 个函数进行分析:
 void SampleApp_Init( uint8 task_id )
这是一个用户用于初始化任务的函数,它是在系统初始化阶段被调用的,而且,在调用它的时候,它里面应该有相应任务的初始化(也就是说,用户有什么初始化,都是放在这个函数里),比如:硬件初始化、数据表初始化、电源初始化等等。

 uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
这是一个事件处理函数(所有事件都是它管理),当有已登记的事件发生时,它就被主函数调用来对事件进行处理,这些事件包括:定时器、消息、以及用户定义的事件等。

这个函数的功能包括:按键处理,数据接收,数据发送。当然,用户可以自己添加其它事件,如“基于协议栈的串口透传”实验里,我们就在这个函数添加自己的串口事件。

 void SampleApp_HandleKeys( uint8 shift, uint8 keys )
这个函数是给 SampleApp_ProcessEvent( uint8 task_id, uint16 events )调用的: 当按键按下后,就会执行 SampleApp_ProcessEvent 函数里的 case KEY_CHANGE ,这个case 的服务函数就是 SampleApp_HandleKeys( uint8 shift, uint8 keys ),我们可以在这个函数里添加按键判断,判断哪个按键,然后执行相关任务,具体在“基于协议栈的按键实验”里讲解。

 void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
这个函数管理所有接收到的数据,至于数据来自哪个设备,它是根据簇 ID 来分辨的。

函数里面就是一个 switch 语句,关键是 case 及其后面的服务函数。用户可以根据不同的功能,定义不同的簇 ID(在 SampleApp.h 里进行),然后在这个 switch 语句里添加一个以簇 ID 来命名的 case,并在 case 里面编写自己的应用程序,如“基于协议栈的串口透传”实验里会有这个知识的讲解。

 void SampleApp_SendPeriodicMessage( void )
这个函数,是一个周期性数据发送函数,它是设备设置为周期性广播的时候调用的,它是我们设置发送数据的地方,我们要注意:

①SAMPLEAPP_PERIODIC_CLUSTERID //这是一个簇,定义的作用是和接收方建立联系,协调器收到这个标号,如果是 1,就证明是由周期性广播方式发送过来的。
②1 //数据长度
③(uint8*)&SampleAppPeriodicCounter //要发送的内容(指针形式)
void SampleApp_SendFlashMessage( uint16 flashTime )

这个函数是 void SampleApp_HandleKeys( uint8 shift, uint8 keys )函数的一个服务函数,我们也是极少用到这个函数的,故不作详解

1.5.2 ZStack 的数据传输关系

我们使用 Zigbee 的目的就是为了使用它强大的数据收发功能, 那么我们有必要分析它的数据传输关系,也就是分析:数据发送函数的数据从哪里来、以及数据接收函数的数据从哪里来的问题。

1.5.3数据发送函数的数据从哪里来

要分析数据发送函数的数据从哪里来,就得研究一下数据发送函数本身,下面是数据发送函数(周期性广播)的代码:

void SampleApp_SendPeriodicMessage( void )
{
    if( AF_DataRequest( &SampleApp_Periodic_DstAddr, &SampleApp_epDesc,
                     SAMPLEAPP_PERIODIC_CLUSTERID,
                    1,
                    (uint8*)&SampleAppPeriodicCounter,
                    &SampleApp_TransID,
                    AF_DISCV_ROUTE,
                    AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
    {
    }
    else
    {
        // Error occurred in request to send.
    }
}

在上文讲解 SampleApp.c 的时候,我们讲解过这个函数。实际上这个函数是通过调用 AF_DataRequest 函数来进行数据发送的,而 AF_DataRequest 函数里面的一个形参 (uint8*)&SampleAppPeriodicCounter,就是指向用户数据(需要发送的数据)的指针。比如,我们要发送数组 Data[]={0,1,2,3,4,5,6,7,8,9};里的数据, 则只要用数组的首地址 Data 将形参(uint8*)&SampleAppPeriodicCounter 替换掉即可。

1.5.4数据接收函数的数据从哪里来

下面是接收函数的代码:

void SampleApp_MessageMSGCB(afIncomingMSGPacket_t *pkt )
{
    uint16 flashTime;

    switch ( pkt->clusterId )
    {
        case SAMPLEAPP_PERIODIC_CLUSTERID:
        break;

        case SAMPLEAPP_FLASH_CLUSTERID:
        flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );
        HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );
        break;
    }
}

从上面的代码可以看到 SAMPLEAPP_PERIODIC_CLUSTERID,这个就是我们发送函数里用到的簇,当判断到这个簇是我们预先设置好的标号,所有接收到的数据就会存 入 afIncomingMSGPacket_t *pkt 所 指 向 的 存 储 区 域 内 。 我 们 进 入afIncomingMSGPacket_t,发现它是一个结构体,如下代码所示。

typedef struct
{
    osal_event_hdr_thdr; /* OSAL Message header */
    uint16 groupId; /* Message's group ID - 0 if not set */
    uint16 clusterId; /* Message's cluster ID */
    afAddrType_tsrcAddr; /* 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 */
    afMSGCommandFormat_t cmd; /* Application Data */
}afIncomingMSGPacket_t;

从上面的代码可以看到,结构体 afIncomingMSGPacket_t 有很多多的成员,那么究竟哪个是存放数据的?我们先往下面看,看到/* Application Data */没有?ApplicationData 就是用户数据的意思,那么数据就在里面,但是 afMSGCommandFormat_t 也是一个结构体, 所以我们再进去看看, 结构体 afMSGCommandFormat_t 的代码如下所示:

typedef struct
{
    byte TransSeqNumber;
    uint16 DataLength; // Number of bytes in TransData
    byte *Data; //接收到的数据的指针---by Cavani
}afMSGCommandFormat_t;

终于找到了,接收到的数据就用 Data 这个指针来获取的。其实,我们可以看到 SAMPLEAPP_FLASH_CLUSTERID 这个 case,它里面就告诉了我们使用接收到的数据的方法了:pkt->cmd.Data[1]

1.5.5基于 ZStack 的用户程序开发

注意,我们往后的用户程序开发,基本上都是在 SampleApp.c 里进行的,所以读者要好好理解上面关于 SampleApp.c 的知识。
然后,下面以一般性的步骤,给大家讲解如何基于 ZStack 开发用户程序(具体在往后的实验里都会有详细的讲解):
 步骤一:驱动移植;
步骤二:打开工程,在 application 文件夹下添加,并在 SampleApp.c 里将驱动的头文件包括进去;
步骤三:在 SampleApp_Init( uint8 task_id )函数里进行初始化;
步骤四:修改数据发送函数;
步骤五:修改数据接收函数。

附:ZStack 协议里相关名称解释

AIB 应用支持层的信息库
AF 应用框架
APDU 应用支持子层协议数据单元
APL 应用层
APS 应用支持子层
APSDE 应用支持子层数据实体
APSDE-SAP 应用支持子层数据实体-服务接入点
APSME 应用支持子层管理实体
APSME-SAP 应用支持子层管理实体—服务接入点
ASDU APS 服务数据单元
BRT 广播重试计时器
BTR 广播事务记录
BTT 广播事件务表
CCM* CBC-MAC 模式增强计数器选项
CSMA-CA 载波侦听多路访问——冲突检测
EPID 扩展 PAN ID
FFD 全功能设备
GTS 同步时隙
HDR 头
IB 信息库
LQI 链路质量指标
LR-WPAN 低速率无线个人区域网
MAC 媒体访问控制
MCPS-SAP 媒体访问控制公用部分子层—服务接入点
MIC 消息完整性代码
MLME-SAP 媒体访问控制子层管理实体—服务接入点
MSC 消息序列图
MSDU 介质访问控制子层服务数据单元
MSG 信息服务类型
NBDT 网络广播传送时间
NHLE 上层实体
NIB 网络层信息库
NLDE 网络层数据实体
NLDE-SAP 网络层的数据实体—服务接入点
NLME 网络层管理实体
NLME-SAP 网络层管理实体—服务接入点
NPDU 网络层协议数据单元
NSDU 网络服务数据单元
NWK 网络
OSI 开放式系统互连
PAN 个人区域网络
PD-SAP 物理层数据—服务接入点
PDU 协议数据单元
PHY 物理层
PIB 个人区域网络信息库
PLME-SAP 物理层管理实体—服务接入点
POS 个人操作空间
QOS 服务质量
RFD 精简功能设备
RREP 路由应答
RREQ 路由请求
RN 路由节点
SAP 服务接入点
SKG 密钥生成
SKKE 对称密钥的密钥建立
SSP 安全服务提供商
SSS 安全服务规范
WPAN 无线个人区域网
XML 可扩展标记语言
ZB ZigBee
ZDO ZigBee 设备对象

猜你喜欢

转载自blog.csdn.net/u013162035/article/details/80959317