ZigBee无线传感器的网络协议栈
Posted 364.99°
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ZigBee无线传感器的网络协议栈相关的知识,希望对你有一定的参考价值。
小结:
Z-Stack协议栈 = OSAL操作系统 + CC2530硬件模块 + AF无线网络应用
- 协议定义的是一系列的通信标准,通信双方需要共同按照这一标准进行正常的数据收发;协议栈是协议的具体实现形式
- afStatus_t AF_DataRequest( afAddrType_t *dstAddr, endPointDesc_t *srcEP,uint16 cID, uint16 len, uint8 *buf, uint8 *translD,uint8 options, uint8 radius )
用户调用该函数即可实现数据的无线发送 - TaskArr这个数组里存放了所有任务的事件处理函数的地址,在这里事件处理函数就代表了任务本身,也就是说事件处理函数标识了与其对应的任务。tasksCnt这 个变量保存了当前的任务个数,最大任务数量为9
- tasksEvents是一个指向数组的指针,此数组保存了当前任务的状态。OSAL每个任务可以有16个事件,其中SYS_EVENT_MSG定义为0x8000,为系统事件,用户可以定义剩余的15个事件
- 在Z-Stack中,对于每个用户自己新建立的任务通常需要两个相关的处理函数,包括新任务的初始化函数和新任务的事件处理函数
1.Z-Stack协议栈
1.1.ZigBee与Z-Stack的关系
ZigBee:基于IEEE802.15.4的RF无线接收协议标准
Z-Stack:TI公司实现ZigBee协议标准的具体代码
CC2530: 8051 + 2.4GRF
CC2530外设控制、RF应用
Z-Stack协议栈: 用OSAL来管理外设(任务轮询方式)和RF无线应用的软件系统
- 组成: OSAL + CC2530硬件 + AF无线网络应用
- 任务: 系统初始化 + 启动OSAL操作系统
- 任务轮询过程中,系统将不断查询每个任务是否有事情发生,有就会指向相应的事件处理函数,没有就会查询下一个任务
OSAL: 任务轮询式操作系统 / 操作抽象层
- 作用: 隔离(协议栈)软件和硬件系统
- 任务: 任务的初始化,任何事件的处理函数
- 事件: 每一个任务都有一个对应的事件(中断源(中断服务函数)----事件(事件处理函数))
- 图示:
- Z-Stack协议栈中OSAL层常用API函数:
作用 | 函数 |
---|---|
分配消息缓存 | uint8 * osal_msg_allocate( uint16 len ); |
回收消息缓存 | uint8 osal_msg_deallocate( uint8 *msg_ ptr ); |
发送消息 | uint8 osal_ msg_ send( uint8 destination_ task, uint8 *msg_ ptr ); |
接收消息 | uint8 *osal_msg_receive( uint8 task_ id ); |
查找消息 | osal_event_hdr_t *osal_msg_find(uint8 task id, uint8 event); |
设置事件 | uint8 osal_set_event( uint8 task id, uint16 event flag ); |
清除事件 | uint8 osal_clear_event( uint8 task id, uint16 event_ flag ); |
开启定时器计时 | uint8 osal_start_ timerEx( uint8 task id, uint16 event id, uint16 timeout_ value ); |
停止定时器计时 | uint8 osal_stop_timerEx( uint8 task_ id, uint16 event_ id ); |
自动加载时间点和超时值 | uint8 osal_start_reload_ timer( uint8 tasklD, uint16 event_ id, uint16 timeout_value |
中断使能 | uint8 osal_ int_ enable( uint8 interrupt_id ); |
中断禁止 | uint8 osal_int_disable( uint8 interrupt_id ); |
系统初始化 | uint8 osal_init_system( void ); |
开启OSAL系统 | void osal_start_system( void ) |
运行OSAL系统 | void osal_run_system( void ); |
初始化NV | uint8 osal_nv_item_init( uint16 id, uint16 len, void *buf ); |
读取NV | uint8 osal_nv_read( uint16 id, uint16 offset, uint16 len, void *buf ); |
写入NV | uint8 osal_nv_write( uint16 id, uint16 offset, uint16 len, void *buf ); |
删除NV | uint8 osal_nv_delete( uint16 id, uint16 len ); |
初始化系统任务 | void osallnitTasks( void ); |
ZigBee协议结构 | Z-Stack协议栈结构 |
---|---|
APP层 | APP层、SOAL |
ZDO层、APS层 | ZDO |
AF层 | Profile |
NWK层 | NWK层 |
MAC层 | ZMAC层、MAC层 |
PHY层 | HAL层、MAC层 |
安全服务提供商 | Security&Services |
建筑图纸 | 建筑物 |
1.2.协议与协议栈
协议: 定义一系列的通信标准,通信双方需要同时按照这一标准进行正常的数据收发
因特网协议栈:
- 应用层(Http Telnet DNS Email等)
- 运输层(TCP UDP)
- 网络层(IP)
- 链路层(WI-FI 以太网 令牌环 FDDI等)
协议栈: 协议的具体实现形式,即代码的实现函数库,以便开发人员调用
协议栈形象地反映了一个网络中数据传输的过程
ZigBee协议栈示意图:
ZigBee协议栈开发的基本思路:
- 借助例程SampleApp进行二次开发,不需要深入研究复杂的协议栈的具体实现
- 数据采集,只需在应用层加入传感器的读取函数和添加相应的头文件即可
- 根据数据采集周期定时唤醒ZigBee终端节点,采集、上传数据送给路由器或者直接发给协调器,即监测节点定时汇报监测数据
- 协调器(网关)根据下发的控制命令,将控制信息转发到具体的节点,即控制节点等待控制命令下发
1.3.使用Z-Stack协议栈传输
- SampleApp.c中定义了发送函数
static void SampleApp_SendFlashMessage( void )
该函数调用AF_ DataRequest来发送数据 - 该函数定义在Profile目录下的AF.c文件中
afStatus_tA_DataRequest( afAddrType_t *dstAddr, endPointDesc_t *srcEP, uint16 clD, uint16 len,uint8 *buf, uint8 *translD, uint8 options, uint8 radius )
afAddrType_t*dstAddr
:目的节点的网络地址、端点号及数据传送的模式,如单播、广播或多播等afAddrType_ t 是结构体类型:
typedef struct
union
{
uint16 shortAddr; //用于标识该节点网络地址的变量
}addr;
afAddrMode_t addrMode; //用于指定数据传送模式:单播、多播还是广播
byte endPoint;//端点号
} afAddrType_ t;//其定义在AF.h中
在Zigbee中,数据包可以単点传送(unicast),多点侍送(multicast)或者广播侍送,所以必須有地址模式参数。上述结构体中的
afAddrMode_t addrMode
就是用于指定数据侍送模式,是枚举类型,可以没置以下几个值:
注意: ZigBee设备有两种类型的地址,一种为64位IEEE地址(MAC 物理),另一种为16位的网络地址
typedef enum
{
afAddrNotPresent = AddrNotPresent;//表示通绑定关系指定目的地址
afAddr16Bit = Addr16Bit;//单播发送
afAddrGroup = AddrGroup;//組播
afAddrBroadcast = AddrBroadcast;//广播
} afAddrMode_t;
enum
{
AddrNotPresent = O,
AddrGroup= 1,
Addr16Bit= 2,
Addr64Bit= 3,
AddrBroadcast= 15
}
endPointDesc_t *srcEP
:发送节点的端点描述符指针,使用网络地址来区分不同的节点,使用端口号区分同一节点上的端口
typedef struct
{
byte endPoint; //端点号
byte *task_id; //哪一个任务的端点号(调用任务的ID).
SimpleDescriptionFormat_t*simpleDesc; //描述一个Zigbee设备节点,称为简单设备描述符
afNetworkLatencyReq_t latencyReq; //枚举结构,这个字段必须为noLatencyReqs
} endPointDesc_t; //其定义在AF.h中
typedef struct
{
byte EndPoint; //EP
uint16 AppProfld; //应用规范ID
uint16 AppDeviceld; //特定规范ID的设备类型
byte AppDevVer:4; //特定规范ID的设备的版本
byte Reserved:4; //AF_V1_SUPPORTusesforAppFlags:4.
byte AppNumInClusters; //输入簇ID的个数
cld_ t *pAppInClusterList; //输入簇ID的列表
byte AppNumOutClusters; //输出簇ID的个数
cld_ t *pAppOutClusterList; //输出簇ID的列表
}SimpleDescriptionFormat_t; //其定义在AF.h中
其中的
afNetworkLatencyReq_t
:
typedef enum
{
nol_atencyReqs;
fastBeacons;
slowBeacons;
}afNetworkLatencyReq_t;
uint16 cID
:ClusID簇ID号,一个Zigbee节点有很多属性,一个簇实际上是一-些相关命令和属性的集合,在整个网络中,每个簇都有唯一的簇ID,也就是用来标识不同的控制操作的命令号uint16 len
:发送数据的长度uint8 *buf
:指向发送数据缓冲的指针uint8 *transID
:指向发送序号的指针,每发送一个数据包,该发送序号会自动加1,因此在接收端可以查看接收数据包的序号来计算丢包率uint8 options
:发送选项,有如下选项
#defineAF_FRAGMENTED 0x01
#defineAF_ACK_REQUEST 0x10 //要求APS应答,这是应用层的应答,只在直接发送(单播)时使用。
#defineAF_DISCV_ROUT E0x20 //总要包含这个选项
#defineAF_EN_SECURITY 0x40
#defineAF_SKIP_ROUTING 0x80 //设置这个选项将导致设备跳过路由而直接发送消息
//终点设备将不向其父亲发送消息。在直接发送(单播)和广播消息时很好用
uint8 radius
:最大的跳数,取默认值AF_DEFAULT_RADIUS- 返回值: afStatus_t类型 枚举型
typedef enum
{
afStatus_SUCCESS,
afStatus_FAILED = 0x80,
afStatus_MEM_FAIL,
afStatus_INVALID PARAMETER
}afStatus_ t;
2.ZigBee无线传感器网络功能层简介
2.1.物理层
- 物理层(PHY)定义了无线信道和MAC子层之间的接口,在驱动程序的基础上,实现数据传输和管理
- 物理层数据服务从无线物理信道上收发数据,管理服务包括信道能量监测(ED)、链接质量指示(LQI)、载波检测(CS)和空闲信道评估(CCA)等,维护一个由物理层相关数据组成的数据库
2.2.介质访问控制层
- 介质访问控制层(MAC)提供点对点通信的数据确认(Per-hopAcknowledgments)以及一些用于网络发现和网络形成的命令
- 介质访问控制层不支持多跳(Multi-hop) 、网型网络( Mesh)等概念
2.3.网络层
主要任务:
- 设备加入和退出网络
- 路由管理,在设备之间发现和维护路由,发现邻设备,存储邻设备信息等
2.3.1.地址类型
- MAC地址:64位IEEE地址,全球唯一
- 网络地址:16位短地址,标识自身和识别对方
- 协调器地址始终为0x0000H
- 对于路由器和节点,短地址是由它们所在网络中的协调器分配的
2.3.2.网络地址分配
基于如下三个参数的特定算法,保证唯一:
MAX_DEPTH
:决定网络最大深度,协调器深度为0,限制了网络的物理长度MAX_ROUTERS
:决定一个路由器或协调器可以处理的具有路由功能的子节点的最大个数,是MAX_ CHILDREN的一个子集MAX_CHILDREN
:决定一个路由器或协调器可以连接的子节点的最大个数ZigBee 2007协议栈规定了( 定义在nwk_globals.h):
- MAX_DEPTH=5
- MAX_ROUTERS=6
- MAX CHILDREN=20
2.3.3.Z-Stack寻址
ZigBee节点通常使用AF-DataRequest()函数发送数据。该函数需要一个afAddrType_t
类型的目标地址作为参数
typedef struct
{
union
{ uint16 shortAddr;//用于标识该节点网络地址的变量
ZLongAddr_t extAddr;//用于标识该节点IEEE地址的变量
} addr;
afAddrMode_t addrMode; //用于指定数据传送模式,单播、多播还是广播
byte endPoint;//端点号
} afAddrType_t; //其定义在AF.h中
#define Z_EXTADDR_LEN 8
typedef byte ZLongAddr_t[Z_EXTADDR_LEN];
除了网络地址(短地址)和端点外,还要指定地址模式参数,地址模式参数可以设置为以下几个值
typedef enum
{
afAddrNotPresent = AddrNotPresent,//表示通过绑定关系指定目的地址
afAddr16Bit = Addr16Bit,//单播发送
afAddrGroup = AddrGroup,//组播
afAddrBroadcast = AddrBroadcast//广播
} afAddrMode_t;
enum
{
AddrNotPresent= 0,
AddrGroup= 1,
Addr16Bit= 2,
Addr64Bit= 3,
AddrBroadcast= 15
};
- 单点传送: 模式设为Addr16Bit/ Addr64Bit
- 多点传送: 模式设为AddrNotPresent (基于绑定表,无需网络地址)
- 广播传送: 模式设为AddrBroadcast
0xFFFF: 包含睡眠设备的所有设备
0xFFFD: 除了睡眠的其他所有设备
0xFFFC: 所有路由器和协调器- 组导寻址:模式设为afAddrGroup且shortAddr = group ID
2.3.4.路由
- 路由是ZigBee设备扩大覆盖范围的有效手段
- 协议栈基于特定的AODV(Ad hoc Ondemand Dstace Vector Routing)算法,路由对于应用层完全透明,应用程序只需将数据下发到协议栈中,协议栈会负责寻找路径,通过多跳的方式将数据传送到目的地址
- ZigBee网络路由具有自愈能力,极大地提高了网络的可靠性
- 协议开销:
4.1. 路径的寻找与选择
4.2. 路径保持与维护
4.3. 路径期满处理 - 表存储:
5.1. 路由表
5.2. 路径寻找表
2.3.5.安全
无线网络的无线电磁信号是开放的→防止重要数据被窃取
- 协调器可以允许或者不允许节点加入网络(也可以只允许一个设备在很短的时间窗口加入网络)
- ZigBee协议可以使用AES/CCM安全算法,提供可选的安全功能
2.4.应用层
应用层主要包括应用支持子层(APS)和ZigBee设备对象(ZDO)
- APS负责维护和绑定表、在绑定设备之间传送消息
- ZDO定义设备在网络中的角色,发起和响应绑定请求,在网络设备之间建立安全机制
2.4.1.绑定
- 绑定指的是两个节点在应用层上建立起来的一条逻辑链路
- 同一节点上可建立多个绑定服务对应不同种类的数据包(多对一绑定),绑定也允许有多个目标节点(一对多绑定)
- 一旦在源节点上建立了绑定,其应用服务即可向目标节点发送数据,而不需指定目标地址(调用zb_SendDataRequest(),目标地址可用一个无效值0xFFFE代替)
- 协议栈将会根据数据包的命令标识符,通过自身的绑定表查找到所对应的目标设备地址
2.4.2.配置文件
- 配置文件( Profile)就是应用程序框架,它是由ZigBee技术开发商提供的,应用于特定的应用场合,是用户进行ZigBee技术开发的基础。当然,用户也可以使用专用工具建立自己的Profile
- Profile是一种规范,规定不同设备对消息帧的处理行为,使不同的设备之间,可以通过发送命令、数据请求来实现互操作
2.4.3.端点
- 端点(EndPoint) , 如果选择“绑定”方式实现节点间的通信,就可以直接面对端点操作,而不需要知道绑定的两个节点的地址信息
- 每个ZigBee设备支持240个这样的端点。端点的值和IEEE长地址、16位短地址一样,是唯一确定 的设备标识,通常结合绑定功能一起使用
2.4.4.簇
簇( Cluster) :一种网络变量(Attributes)集合,在同一个Profile中,ClusterlD是唯一的
- 间接通信是指节点通过端点的绑定建立通信关系,无需目的地址信息。直接通信无需绑定,但需要知道目的地址信息
- 建立绑定关系的两个节点,Cluster的属性必须一个为“输入”,另一个为“输出”,且ClusterlD值相等
- 在直接寻址方式中,常用ClusterlD作为参数来将数据或命令发送到对应地址的Cluster(簇)上
3.OSAL多任务分配机制
3.1.OSAL基础知识
3.1.1.资源(Resource)
任何任务所占用的实体都可以称为资源,如一一个变量、数组、结构体等
3.1.2.共享资源(Shared Resource)
至少可以被两个任务使用的资源称为共享资源。为了防止共享资源被破坏,每个任务操作共享资源时,必须保证是独占该资源
3.1.3.任务(Task)
- 一个任务又称为一个线程,是一个简单程序的执行过程
线程: 程序中一个单一的顺序控制流程
- 在单个程序中同时运行多个线程完成不同的工作,称为多线程
- 线程和进程的区别在于子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文
- 多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定
- 单个任务中CPU完全是被该任务独占的
- 在任务设计时,需要将问题尽可能地分为多个任务,每个任务独立完成某种功能,同时,被赋予一定的优先级, 拥有自己的CPU寄存器和堆栈空间
- 一般将任务设计为一个无限循环
3.1.4.多任务运行(Muti-Task Running)
- 多任运行构成一个线程组,其实质只有一个任务在运行,但CPU可以使用任务调度策略将多个任务进行调度
- 每个任务执行特定的时间,时间片到了以后,就进行任务切换
- 由于每个任务执行时间都很短,因此,任务切换比较频繁,这就造成了多任务同时运行“假象”
3.1.5.内核(Kernel)
- 在多任务操作系统中,内核负责管理各个任务,主要包括为每个任务分配CPU时间,任务调度,负责任务间的通信
- 内核提供的基本的内核服务就是任务切换。使用内核可以大大简化应用系统的程序设计方法
- 借助内核提供的任务切换功能,可以将应用程序分为不同的任务来实现
3.1.6.互斥(Mutual Exclusion)
- 多任务通信最简单、最常用的方法是使用共享数据结构
- 对于嵌入式系统而言,所有任务都在单一的地址空间下,使用共享的数据结构包括全局变量、指针、缓冲区等
- 虽然共享数据结构的方法简单,但是必须保证对共享数据结构的写操作具有唯一性,以避免晶振和数据不同步,也就是共享资源保护
- Z-Stack中的OSAL多采用关中断的方法来实现共享资源保护
3.1.7.消息队列(Message Queue)
- 消息队列用于任务间传递消息(包含任务间同步的信息)。通过内核提供的服务、任务或者中断服务程序将一条消息放入消息队列,其他任务从消息队列中获取属于自己的消息
- 为了降低传递消息的开支,通常传递指向消息的指针
- 在ZigBee协议栈中,OSAL主要提供如下功能
3.1. 任务注册、初始化和启动
3.2. 任务间的同步、互斥
3.3. 中断处理
3.4. 存储器的分配和管理
3.2.OSAL简介
ZigBee协议的结构图:
一般情况下,用户只需额外添加3个文件就可以构建一个项目:
- 一个是主控文件,存放具体的任务事件处理函数
如:SampleApp_ProcessEven t域GenericApp_ProcessEvent - 一个是这个主控文件的头文件
如:SampleApp.h - 另外一个是操作系统接口文件
如:OSAL_SampleApp.c,该文件主要存放任务数组tasksArr[],任务数组的具体内容为每个任务的相应的处理函数指针
- “数据实体接口”的目标是向上层提供所需的常规数据服务
- “管理实体接口”的目标是向上层提供访问内部层的参数、配置和管理数据服务
- 物理层和媒体接入控制子层均属于IEEE 802.15.4标准,而IEEE802.15.4标准与网络1安全层、应用层-起构成了ZigBee协议栈
3.3.协议软件架构
整个Z-Stack的主要工作流程分为以下四个阶段:
- 系统启动
- 驱动初始化
- OSAL初始化和启动
- 进入任务轮询
Z-Stack系统运行流程图:
系统上电后,通过执行乙Main文件夹中ZMain.c的main()函数实现硬件系统的初始化:
- 关总中断→osal_int_disable(INTS_ALL)
- 初始化板上硬件设置→HAL_BOARD_INIT()
- 检查工作电压状态→zmain_vdd_check()
- 初始化/O口→InitBoard(OB_COLD)
- 初始化HAL层驱动→HalDriverlnit()
- 初始化非易失性存储器→osal_nv_init(NULL)
- 初始化MAC层→ZMacInit()
- 分配64位地址→zmain_ext_addr()
- 初始化Zstack的全局变量并初始化必要的NV项目→zgInit()
- 初始化操作系统→osal_init_system()
- 使能全局中断→osal_int_enable( INTS_ALL )
- 初始化后续硬件→InitBoard( OB_READY )
- 显示必要的硬件信息→zmain_dev_info()
- 最后进入操作系统调度→osal_start_system()
注意: 我们自己的初始化组要紧靠osa_start_system() 执行,否则我们的初始化,例如I/O的初始化,可能会被OSAL的系统初始化所覆盖而失效!
3.4.OSAL的运行机制
所有实现的功能都在这五个文件中:
- SampleApp.c包含SampleApp_Init和SampleApp_ProcessEvent
1.1. SampleApp_Init是任务的初始化函数
1.2. SampleApp_ProcessEvent则 负责处理传递给此任务的事件,此函数的主要功能是判断由参数传递的事件类型,然后执行相应的事件处理函数 - 完成初始化后
2.1. 执行osal_start_system()函 数开始运行OSAL系统
2.2. Osal_start_system() 一旦执行,则不再返回main()函数 - OSAL任务调度流程图
- SOAL运行机制
- 事件表:保存各个任务对应的事件
- 函数表:保存各个任务事件处理函数的地址
将这两张表建立关联,当某一事件发生时则查找函数表即可
OSAL通过 tasksEvents指针访问事件表的每一项,如果有事件发生,则 查找函数表找到事件处理函数进行处理,处理完后,继续访问事件表,查看是否有事件发生,无限循环
- 协议栈中的三个关键变量
5.1. tasksCnt:声明为const uint8 tasksCnt,保存了任务数,定义在OSAL SampleApp.c文件中
5.2. tasks Events:声明为uint16 *tasksEvents, 该指针指向了事件表的首地址,tasksEvents[]是一个指针数组,在OSAL_SampleApp.c文件进行定义
5.3. tasksArr:声明为pTaskEventHandlerFn tasksArrD,该数组的每一项都是一个函数指针,指向了事件的处理函数,其中pTaskEventHandlerFn的定义为typedef unsigned short(*pTaskEventHandlerFn) (unsigned char task_ id,unsigned short event) 。变量pTaskEventHandlerFn的定义OSAL_Tasks.h文件中 - OSAL调度机制
6.1. 入口程序为Zmain.c
6.2. 执行main()主程序
6.3. 任务调度初始化osal_init_system()
6.4. 默认启动了osallnitTasks(),最多9个任务,添加到队列,序号0~8。最大事件数量为16
6.5. 最后通过调用SampleApp_Init()实现用户 自定义任务的初始化(用户根据项目需要修改该函数)
3.4.1.OSAL任务启动和初始化
任何OSAL任务启动和初始化必须分两步:
- 进行任务初始化
- 处理任务事件
- 初始化应用服务变量
const pTaskEventHandlerFn tasksArr[]数组定义系统提供的应用服务和用户服务变量- 分配任务ID和分配堆栈内存
void osalInitTasks(void)主要功能是通过调用osal_mem_alloc()函数 给各个任务分配内存空间和定义任务标识号- 在AF层注册应用对象
通过填入endPointDesc_t数据格式的EndPoint变量,调用afRegister( )在AF层注册EndPoint应用对象
通过在AF层注册应用对象的信息,告知系统afAddrType_t地址类型数据包的路由端点- 注册相应的OSAL或者HAL系统服务
在协议栈中,Z-Stack提 供按键响应和串口活动响应两种系统服务,但是任何Z-Stask任务均不自行注册系统服务,两者均需要由用户应用程序注册。值得注意的是,有且仅有一个OSAL Task可以注册服务- 处理任务事件
处理任务事件通过创建"ApplicationName"_ProcessEvent()函数处理。一个OSAL任务可以响应16个事件,除了协议栈默认的强制事件(Mandatory Events)之外还可以再定义15个事件
SYS_EVENT_MSG ( 0x8000)是强制事件
该事件主要用来发送全局的系统信息,包括以下信息:
- AF_DATA_CONFIRM_CMD: 该信息用来指示通过唤醒AF_DataRequest()函数发送的数据请求信息的情况
- AF_INCOMING_MSG_CMD:用来指示接收到的AF信息
- KEY_CHANGE:用来确认按键动作
- ZDO_NEW_DSTADDR:用来指示自动目标地址匹配请求
- ZDO_STATE_CHANGE:用来指示网络状态的变化
3.4.2.OSAL任务的执行
- 两个函数:
1.1. Osal_start_system();//运行系统[OSAL.c],进入系统调度,无返回
1.2. osal_start_system(); //任务系统的主循环函数,它将轮询所有任务事件然后调用相关的任务处理函数,没有任务时将进入休眠状态 - 事件表与函数表的关系
蓝色部分为Z-Stack的任务处理函数的开源部分 - TaskArr[]里存放所有任务的事件处理函数的地址,事件处理函数就代表了任务本身,事件处理函数标识了与其对应的任务
3.1. tasksCnt变量保存当前的任务个数,最大为9
3.2. tasksEvents是指向数组的指针,此数组保存了当前任务的状态
3.3. OSAL每个任务可以有16个事件,其中SYS_EVENT_MSG定义为0x8000,为系统事件,用户可以定义剩余的15个事件 - 具体的任务处理函数
- macEventLoop;//MAC层任务处理函数
- nwk_event_loop;//网络层任务处理函数
- Hal_ProcessEvent; //硬件抽象层任务处理函数
- MT_ProcessEvent;//监控任务处理函数可选( 编译选项MT_TASK);
- APS_event_loop;//应用支持子层任务处理函数,用户不用修改
- APSF_ProcessEvent;//应用支持子层消息分割任务处理函数(编译选ZigBee_FRAGMENTATION)
- ZDApp_event_loop;//设备应用层任务处理函数,用户可以根据需要修改
- ZDNwkMgr_event_loop;//网管层任务处理函数(编译选项ZigBee_FREQ_AGILITY或ZigBee_ PANID_CONFIG)
- SampleApp_ProcessEvent;//用户应用层任务处理函数,用户自己编写
3.4.3.OSAL事件传递机制
- 从天线接收到数据后,会产生AF_INCOMING_MSG_CMD消息
- 但是任务的事件处理函数在处理这个事件的时候,还需要得到所接收到的数据
- 因此,这就需要将事件和数据封装成一个消息,将消息发送到消息队列
- 然后在事件处理函数中使用然后在事件处理函数中使用osal_ msg_ receive, 从消息队列中得到该消息,即MSGpkt = (aflncomingMSGPacket_t *) osal_msg_receive(SampleicApp_ TasklD)
OSAL维护了一个消息队列,每一个消息都会被放到这个消息队列中去,当任务接收到事件后,可以从消息队列中获取属于自己的消息,然后再调用消息处理函数进行相应的处理
每个消息都包含一个消息头osal_msg_hdr_t和用户自定义的消息,osal_msg_hdr_ t结构体的定义如下
typedef struct
{
void *next;
uint16 len;
uint8 dest_id;
} osal_msg_hdr_t; .
- OSAL进入事件轮询后的第一个事件是网络状态变化事件,其处理函数为Samp leApp_ ProcessEvent(), 触发该事件的情况有:
5.1. 协调器
从没有网络到组建起网络,触发网络状态变更事件ZD0_STATE_CHANGE
5.2. 路由/节点
从没有接入网络到接入网络,触发网络状态变更事件ZDO_STATE_CHANGE
case ZDO STATE CHANGE:
SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
if( (SampleApp_NwkState== DEV_ZB_COORD)
||(SampleApp_NwkState = DEV_ROUTER)
||(SampleApp_NwkState == DEV_END_DEVICE))
{
//Start sending the periodic message in a regular interval.
//默认启动第2个事件SAMPLEAPP_SEND_PERIODIC_MSG_EVT
osal_start_timerEx( SampleApp_TaskID,
SAMPLEAPP_SEND_PERIODIC_MSG_EVT,
SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT ); //5s 定时事件
}
else
{
//Device is no longer in the network
}
break;
3.4.4.OSAL添加新任务
- 在Z-Stack中,对于每个用户自己新建立的任务通常需要2个相关的处理函数,具体如下:
1.1. 新任务的初始化函数
例如: SampleApp_Init(), 这个函数是在osalInitTasks()这个OSAL中去调用的,其目的是把一些用户自己写的任务中的一些变量、网络模式、网络终端类型等进行初始化,并且自动给每个任务分配一个ID
1.2. 新任务的事件处理函数
例如: SampleApp_ _ProcessEvent(), 这个函数首先在const pTaskEventHandlerFntasksArr[ ]中进行设置,然后在osalInitTasks()中,如果发生事件进行调用绑定的事件处理函数 - 用户自己设计的任务代码在Z-Stack中的调用过程
2.1. 首先执行main() (在ZMain.c文件中)主程序,接着执行osal_init_system()
2.2. 接着在osal_init_system()调用osalInitTasks() (在OSAL.c文件中)
2.3. 最后在osalInitTasks()中调用SampleApp_Init() (在OSAL_SampleApp.c文件中)
在osallnitTasks()中实现了多个任务初始化的设置,其中mac TaskInit( taskID++ )到ZDApp_Init(taskID++ )的几行代码表示对系统运行初始化任务的调用,而用户自己实现的SampleApp_ Init()在最后,这里taskID随着任务的增加也随之递增。所以用户自己实现的任务的初始化操作应该osalInitTasks()中增加
- 对于不同事件发生后的任务处理函数的调用
3.1. osal_start_system()很重要,决定了当某个任务的事件发生后调用对应的事件处理函数
3.2. 对应调用第idx个任务的事件处理函数,用events说明是什么事件
3.3. events = (tasksArr[idx])( idx, events );
3.4. 用户自定义功能在taskApp.c文件中利用AddTask_Event()函数实现
3.4.5.事件捕捉
例子:
HAL\\Commen\\hal-drivers.c
uint16 Hal_ProcessEvent( uint8 task_id,uint16 events )
{
if (events & HAL_KEY_EVENT) //接收到事件HAL_KEY_EVENT
{
......
#if (defined HAL_KEY) && (HAL_KEY == TRUE)
/* Check for keys */
HalKeyPoll();//查询方式(查询周期为100ms,关闭中断)
/* if interrupt disabled, do next polling */
if (!Hal_KeylntEnable)/关闭中断,为真
{
//每经过100ms就给任务Hal _TaskID发送HAL_KEY_EVENT事件
osal_start_timerEx( Hal_TasklD,HAL_KEY_EVENT,100);
}
#endif 11 HAL_ KEY
return events^HAL_KEY_EVENT;
}
......
......
}
3.5.OSAL应用编程接口
- 消息管理API
消息管理APl主要用于处理任务间消息的交换,主要包括任务分配消息缓存、释放消息缓存、接收消息和发送消息等API函数
- osal_msg_allocate()
函数原型: uint8 *osal_msg_allocate(uint16 len)
功能描述:为消息分配缓存空间- osal_msg_deallocate()
函数原型: uint8 *osal_msg_allocate(uint8 *msg_ptr)
功能描述:释放消息的缓存空间- osal_msg_send()
函数原型: uint8 osal_msg_send(uint8 destination_task,uint8 *msg_ptr)。
功能描述:一个任务发送消息到消息队列- osal_msg_receive()
函数原型: uint8 *osal_msg_receive(uint8 task_id)。
功能描述: 一个任务从消息队列接收属于自己的消息
- 任务同步API
任务同步API主要用于任务间的同步,允许一一个任务等待某个事件的发生
osal_set_event()
函数原型: uint8 osal_set_event(uint8 task_id,uint16 event_flag)
功能描述:运行一个任务时设置某一事 件同时发生
- 时间管理API
时间管理API用于开启和关闭定时器,定时时间一般为毫秒级定时,用户不必关心底层定时器是如何初始化的,只需要调用即可,在ZigBee协议栈物理层已经将定时器初始化了
- osal_start_timerEx()
函数原型: uint8 osal_start_timerEx(uint8 task_id,uint16 event_id,uint16timeout_value)。
功能描述:设置一个定时器时间,定时时间到后,相应的事件被设置- osal_stop_timerEx()
函数原型: uint8 osal_stop_timerEx(uint8 task_id,uint16 event_id)。
功能描述:停止已经启动的定时器
- 中断管理API
中断管理API主要用于控制中断的开启和关闭,一般很少使用 - 电源管理API
电源管理API主要用于电池供电的ZigBee网络节点 - 任务管理API
任务管理API主要是对OSAL进行初始化和启动
- osal_init_system()
函数原型::uint8 osal_start_system(void)。
功能描述:初始化OSAL,该函数是第一个被调用的OSAL函数- osal_start_system()
函数原型: uint8 osal_start_system(void)
功能描述: 一个无限循环函数,查询所有的任务事件,如果有事件发生,则调用相应的事件处理函数,处理完该事件后,返回主循环继续检测是否有事件发生,如果开启了节能模式,当没有事件发生时,使处理器进入休眠模式,以降低功耗
- 内存管理API
内存管理API用于在堆栈上分配缓冲区,注意以下两个API函数必须成对使用,防止产生内存泄漏
原型: uint8 osal_mem_alloc(uint16 size)。
功能:在堆栈上分配指定大小的缓冲区
- osal_mem_alloc()
原型: uint8 osal_mem_alloc(uint16 size)。
功能:在堆栈上分配指定大小的缓冲区- osal_mem_free()
原型: uint8 osal_mem_free(void *ptr)。
功能: 释放使用osal_mem_alloc()分配的缓冲区
- 非易失性内存管理API
非易失性闪存(Non-Volatile Memory, NV),一般这里指的是系统Flash存储器(也可以是E2PROM),每个NV条目分配唯一的ID号
- osal_nv_item_init()
函数原型: byte osal_nv_item_init(uint16 id,uint16 len,void *buf)
功能描述:初始化NV条目,该函数检查是否存在NV条目,若如果不存在,它将创建并初始化该条目。若该条目存在,调用osal_nv_read(), osal_nv_write()时使用- osal_nv_read()
函数原型: byte osal_nv_read(uint16 id,uint16 offset,void *buf)
功能描述:从NV条目中读取数据;读取整个/部分条目- osal_nv_write()
函数原型: uint8 osal_nv_write(uint16 id,uint16 offset,uint16 len,void *buf)
功能描述: 写数据到NV条目
以上是关于ZigBee无线传感器的网络协议栈的主要内容,如果未能解决你的问题,请参考以下文章