小结:
Z-Stack协议栈 = OSAL操作系统 + CC2530硬件模块 + AF无线网络应用
ZigBee:基于IEEE802.15.4的RF无线接收协议标准
Z-Stack:TI公司实现ZigBee协议标准的具体代码
CC2530: 8051 + 2.4GRF
CC2530外设控制、RF应用
Z-Stack协议栈: 用OSAL来管理外设(任务轮询方式)和RF无线应用的软件系统
- 组成: OSAL + CC2530硬件 + AF无线网络应用
- 任务: 系统初始化 + 启动OSAL操作系统
- 任务轮询过程中,系统将不断查询每个任务是否有事情发生,有就会指向相应的事件处理函数,没有就会查询下一个任务
OSAL: 任务轮询式操作系统 / 操作抽象层
作用 | 函数 |
---|---|
分配消息缓存 | 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 |
建筑图纸 | 建筑物 |
协议: 定义一系列的通信标准,通信双方需要同时按照这一标准进行正常的数据收发
因特网协议栈:
- 应用层(Http Telnet DNS Email等)
- 运输层(TCP UDP)
- 网络层(IP)
- 链路层(WI-FI 以太网 令牌环 FDDI等)
协议栈: 协议的具体实现形式,即代码的实现函数库,以便开发人员调用
协议栈形象地反映了一个网络中数据传输的过程
ZigBee协议栈示意图:
ZigBee协议栈开发的基本思路:
- 借助例程SampleApp进行二次开发,不需要深入研究复杂的协议栈的具体实现
- 数据采集,只需在应用层加入传感器的读取函数和添加相应的头文件即可
- 根据数据采集周期定时唤醒ZigBee终端节点,采集、上传数据送给路由器或者直接发给协调器,即监测节点定时汇报监测数据
- 协调器(网关)根据下发的控制命令,将控制信息转发到具体的节点,即控制节点等待控制命令下发
static void SampleApp_SendFlashMessage( void )
该函数调用AF_ DataRequest来发送数据
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;
主要任务:
- 协调器地址始终为0x0000H
- 对于路由器和节点,短地址是由它们所在网络中的协调器分配的
基于如下三个参数的特定算法,保证唯一:
MAX_DEPTH
:决定网络最大深度,协调器深度为0,限制了网络的物理长度MAX_ROUTERS
:决定一个路由器或协调器可以处理的具有路由功能的子节点的最大个数,是MAX_ CHILDREN的一个子集MAX_CHILDREN
:决定一个路由器或协调器可以连接的子节点的最大个数ZigBee 2007协议栈规定了( 定义在nwk_globals.h):
- MAX_DEPTH=5
- MAX_ROUTERS=6
- MAX CHILDREN=20
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
无线网络的无线电磁信号是开放的→防止重要数据被窃取
- 协调器可以允许或者不允许节点加入网络(也可以只允许一个设备在很短的时间窗口加入网络)
- ZigBee协议可以使用AES/CCM安全算法,提供可选的安全功能
应用层主要包括应用支持子层(APS)和ZigBee设备对象(ZDO)
簇( Cluster) :一种网络变量(Attributes)集合,在同一个Profile中,ClusterlD是唯一的
任何任务所占用的实体都可以称为资源,如一一个变量、数组、结构体等
至少可以被两个任务使用的资源称为共享资源。为了防止共享资源被破坏,每个任务操作共享资源时,必须保证是独占该资源
线程: 程序中一个单一的顺序控制流程
- 在单个程序中同时运行多个线程完成不同的工作,称为多线程
- 线程和进程的区别在于子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文
- 多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定
一般情况下,用户只需额外添加3个文件就可以构建一个项目:
- “数据实体接口”的目标是向上层提供所需的常规数据服务
- “管理实体接口”的目标是向上层提供访问内部层的参数、配置和管理数据服务
- 物理层和媒体接入控制子层均属于IEEE 802.15.4标准,而IEEE802.15.4标准与网络1安全层、应用层-起构成了ZigBee协议栈
整个Z-Stack的主要工作流程分为以下四个阶段:
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的系统初始化所覆盖而失效!
- 事件表:保存各个任务对应的事件
- 函数表:保存各个任务事件处理函数的地址
将这两张表建立关联,当某一事件发生时则查找函数表即可
OSAL通过 tasksEvents指针访问事件表的每一项,如果有事件发生,则 查找函数表找到事件处理函数进行处理,处理完后,继续访问事件表,查看是否有事件发生,无限循环
任何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:用来指示网络状态的变化
- 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;//用户应用层任务处理函数,用户自己编写
OSAL维护了一个消息队列,每一个消息都会被放到这个消息队列中去,当任务接收到事件后,可以从消息队列中获取属于自己的消息,然后再调用消息处理函数进行相应的处理
每个消息都包含一个消息头osal_msg_hdr_t和用户自定义的消息,osal_msg_hdr_ t结构体的定义如下
typedef struct
{
void *next;
uint16 len;
uint8 dest_id;
} osal_msg_hdr_t; .
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;
在osallnitTasks()中实现了多个任务初始化的设置,其中mac TaskInit( taskID++ )到ZDApp_Init(taskID++ )的几行代码表示对系统运行初始化任务的调用,而用户自己实现的SampleApp_ Init()在最后,这里taskID随着任务的增加也随之递增。所以用户自己实现的任务的初始化操作应该osalInitTasks()中增加
例子:
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;
}
......
......
}
- 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)。
功能描述: 一个任务从消息队列接收属于自己的消息
osal_set_event()
函数原型: uint8 osal_set_event(uint8 task_id,uint16 event_flag)
功能描述:运行一个任务时设置某一事 件同时发生
- 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)。
功能描述:停止已经启动的定时器
- osal_init_system()
函数原型::uint8 osal_start_system(void)。
功能描述:初始化OSAL,该函数是第一个被调用的OSAL函数- osal_start_system()
函数原型: uint8 osal_start_system(void)
功能描述: 一个无限循环函数,查询所有的任务事件,如果有事件发生,则调用相应的事件处理函数,处理完该事件后,返回主循环继续检测是否有事件发生,如果开启了节能模式,当没有事件发生时,使处理器进入休眠模式,以降低功耗
原型: 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()分配的缓冲区
- 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条目