Zigbee应用笔记
本应用笔记是建立在对TI的“ZStack-1.4.2-1.1.0”理解的基础上的,可以帮助利用TI的Zigbee协议栈开发应用程序。 一 创建主函数
程序运行入口是主函数:main() 这部分程序代码包含在ZMain文件夹的ZMain.c中。 在mian()中包括初始化和正常运行两个部分。 1初始化
初始化期间不接受中断,故首先要屏蔽中断,调用osal_int_disable( INTS_ALL )实现屏蔽中断功能。完成初始化后调用osal_int_enable( INTS_ALL )使能中断。
初始化主要包括:
协议栈寄存器初始化zmain_ram_init()
试验板初始化InitBoard( OB_COLD )这包括各个通用I/O口的设置,确保与硬件电路设计的一致,操作系统时钟设置。这部分在HAL/Target/Config/hal_board_cfg.h中,用户可以根据自己的硬件设计加以修改。
硬件驱动初始化 HalDriverInit () 这包括TIMER、ADC、LED、LCD、KEY、AES、DMA、UART,用户可以根据需要,通过设置相应的宏定义(HAL_ *)初始化相应的硬件。驱动的宏定义设置在hal_board_cfg.h中的driver configeration部分。
非易失性存储器初始化 osal_nv_init( NULL ) MAC初始化ZMacInit()
设置64位地址zmain_ext_addr() 每一个设备都有一个唯一的64位地址。 初始化协议栈全局变量zgInit()。
初始化应用框架(AF)afInit(),初始化时没有应用端口
操作系统初始化osal_init_system() 这包括寄存器初始化;操作系统时钟初始化;能量管理系统初始化;操作系统初始化osalTaskInit();添加任务 osalAddTasks()这包括网络层任务、应用支持子层任务等,用户可在此添加自己的应用任务;任务初始化osalInitTasks() 这包括为每个任务分配一个任务ID号(task_ID)以及调用每一个任务的初始化函数。
在整个初始化过程中涉及到用户应用的就是试验板初始化,硬件驱动初始化,添加用户应用任务。
2启动系统
完成所有的初始化工作后就可以进入正常工作阶段了,启动操作系统osal_start_system(),这是一个死循环,包括时钟查询和串口通信查询,任务查询以及调用事件处理程序。当没有要处理的任务时系统进入节能模式。
二 创建用户应用程序
1 添加用户任务
创建一个用户应用程序,首先要在主函数中添加一个用户任务,操作系统初始化时,调用osalAddTasks()添加任务,osalAddTasks()在App/OSAL_Sample.c中调用osalTaskAdd()实现任务添加:
Void osalTaskAdd ( pTaskInitFn pfnInit,
pTaskEventHandlerFn pfnEventProcessor, byte taskPriority)
pfnInit 用户任务初始化函数地址。
1
pfnEventProcessor 用户任务事件处理函数地址。 taskPriority 用户任务优先级,一般设为低。 2 用户任务初始化
用户任务初始化首先是由操作系统分配一个任务号(task_id),其次是实现对端口的配置,对于有收发数据功能的应用包括端口描述、目标地址、端口注册等部分。
2.1端口描述 typedef struct {
byte endPoint; byte *task_id;
SimpleDescriptionFormat_t *simpleDesc; afNetworkLatencyReq_t latencyReq; } endPointDesc_t;
endPoint 端口号,Zigbee协议中用户应用任户的有效端口号是1——240,0分配给ZDO,255是广播端口号,241——244保留,以备以后使用。
task_id 任务号,操作系统分配的任务号,由系统生成。 simpleDesc 简单描述符,包含有端点的所有信息。 typedef struct {
byte EndPoint; 端口号,同上
uint16 AppProfId; 表示该端口的应用属于哪一类,ID由Zigbee联盟分配 uint16 AppDeviceId; 表示该端口支持的设备,ID由Zigbee联盟分配 byte AppDevVer: 表示该端口所支持的版本 byte Reserved:
byte AppNumInClusters; 输入簇的数量
cId_t *pAppInClusterList; 输入簇表,用一个数组表示 byte AppNumOutClusters; 输出簇的数量
cId_t *pAppOutClusterList; 输出簇表,用一个数组表示, } SimpleDescriptionFormat_t;
latencyReq 网络启动模式,根据需要可设置为 noLatencyReqs、fastBeacons、slowBeacons中的某一个,示例中选择noLatencyReqs(这部分不是很清楚)
2.2、目标地址
在初始化时应根据需要确定目标地址,数据格式采用结构体:afAddrType_t typedef struct {
union {
uint16 shortAddr; } addr;
afAddrMode_t addrMode; byte endPoint; } afAddrType_t;
shortAddr 网络地址,这是设备加入网络时有些挑起或路由器分配的 addrMode 地址模式,
2
endpoint 端口号,包括afAddrNotPresent、afAddr16Bit、afAddrGroup、afAddrBroadcast四种模式,如果是广播数据则选择afAddrBroadcast ,网络地址设为0xFFFF;如果是单播数据,间接寻址时选择afAddrNotPresent,网络地址设为0,如果已知目标地址,采用直接寻址方式则选择afAddr16Bit,网络地址设备已知的目标地址。在TI协议栈中增加了设备分组这一功能,某些设备分在一个组,每个组有一个组地址,若要向该组广播数据,则选择afAddrGroup,网络地址设为0xFFFF。
2.3、端口注册
完成上述端口设置后,保存端口信息,调用afRegister(),这样接收到数据后就可以根据保存侧端口信息将数据送到对应的端口。
另外在初始化过程中还应初始化两个变量 SampleApp_NwkState = DEV_INIT,表示网络状态,SampleApp_TransID = 0;发送数据ID号,没法送一次加1。
同时如果在你的应用程序中使用到了其它的功能模块,比如AD转换,串口通信等,也需要在此进行相应的设置。
3 事件处理函数 3.1信息传递
任务内部和任务之间是通过事件来进行通信的,系统事件每一个任务都可能接收到,根据自己的功能作出相应处理,内部事件仅局限于某一个任务内部
3.1.1任务之间的信息交换
任务之间的信息交换内容统称为为系统事件,通过消息来实现的。任务之间通过osal_msg_send()发送消息,当接收到系统事件后,调用osal_msg_receive()获取消息内容,并采用循环的方式处理全部的系统事件,全部完成后释放信息空间,重新进入操作系统循环。对系统事件的处理由用户自己定义。系统事件定义在ZComDef.h中。消息一般采用如下的结构体,在应用函数的头文件中定义。
例:typedef struct
{
osal_event_hdr_t hdr; uint8 *msg;
} mtOSALSerialData_t;
hdr 系统事件,每个消息都必须包含这个变量,
typedef struct {
uint8 event; 系统事件 uint8 status; 状态
} osal_event_hdr_t;
Msg 地址指针,指向消息所包含的信息的地址,比如数据等
在消息的结构体中还可以添加其它的内容,比数据数据长度等,根据需要。
3.1.1.1 byte osal_msg_send( byte destination_task, byte *msg_ptr ) 向其它任务发送消息:
destination_task 接收该消息的任务ID号
msg_ptr 地址指针,指向要发送的信息首地址。 3.1.1.2 byte *osal_msg_receive( byte task_id )
任务收到系统任务后调用该函数获取消息内容,返回的是消息的地址。 Task_id 系统分配的任务ID号,即调用该函数的任务的ID号。 3.1.1.3 byte osal_msg_deallocate( byte *msg_ptr )
3
处理完所有的任务后调用该函数释放信息空间。 Msg_ptr 地址指针,指向已经处理完的消息地址。 3.1.2事件设置
在任务内部也存在信息的交换,这通过内部事件来实现,这里定义他们为一般事件,一般事件只在定义他的函数中有效,一般事件与系统事件处理方式不同,每处理完一个一般事件后即进入操作系统的大循环,未处理的事件被挂起,直到该任务再次激活,如果有优先级更高的则先处理优先级高的,否则按照排队的先后顺序。调用osal_set_event()可对每一位置位。
3.1.2.1 byte osal_set_event ( byte task_id, UINT16 event_flag ) Osal_set_event() 只告知发生了某个事件,没有附加信息。 Task_id 任务ID号,事件所属的任务ID号 Event_flag 一般事件。
3.1.2.2osal_start_timerEx( byte taskID, UINT16 event_id, UINT16 timeout_value ) 某一事件可能需要等待一定的时间再进行处理,比如发送数据时,如果在一定的时间内没有接收到确认帧,需要请求重发,则可以调用这样的函数。
taskID 任务ID event_id 一般事件
timeout_value 有效时间,以毫秒为单位 3.1. 2.3byte osal_stop_timer( UINT16 event_id ) 如果在规定的事件内收到了确认帧,则不需要重新发送,这时就需要取消之前定义的冲发事件,调用osal_stop_timer()实现。
Event_id 要取消的事件。 3.2事件(event)处理
evnet是一个16位的变量,每一位对应一个事件,由前面的介绍可以知道分为系统事件(SYS_EVENT_MSG)和一般事件,SYS_EVENT_MSG在ZComDef.h定义为0x8000,是一个全局变量,在每一个任务中都是有效的,在系统事件之下再定义了若干事件,暂且定义为系统子事件;一般事件由用户自己定义在头文件中,每个任务可以定义15个一般事件。任务接收到事件后系统调用事件处理函数,函数主要包括系统事件处理和一般事件处理两大部分。在进入处理之前首先要定义一个接收数据的变量:
afIncomingMSGPacket_t *MSGpkt typedef struct {
osal_event_hdr_t hdr; uint16 groupId; uint16 clusterId;
afAddrType_t srcAddr; byte endPoint; byte wasBroadcast; byte LinkQuality; byte SecurityUse; uint32 timestamp;
afMSGCommandFormat_t cmd;
} afIncomingMSGPacket_t; 函数可以从中提取相应的信息。 3.2系统事件处理
4
这部分以TI应用实例中的部分程序为例说明,每个任务对系统事件是一次性处理的,所有事件处理完成之后才进入系统循环。
if ( events & SYS_EVENT_MSG )
{ 接收到系统事件,提取消息内容,根据系统子事件调用相应的处理函数
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( NewSample_TaskID ); while ( MSGpkt ) {
switch ( MSGpkt->hdr.event ) {
如果有按键发生,调用按键处理程序,完成后跳出switch语句,提取下一个系统子事件。
case KEY_CHANGE:
NewSample_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys );
break;
接收到其他设备发送的数据,调用处理函数,完成后跳出switch语句,提取下一个系统子事件。
case AF_INCOMING_MSG_CMD:
NewSample_MessageMSGCB( MSGpkt ); break;
default: break; }
处理完一个事件后,释放存储空间
osal_msg_deallocate( (uint8 *)MSGpkt )
提取下一个系统子事件
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( NewSample_TaskID ); }
处理完所有的系统事件后返回除系统事件以外的其他未处理事件,用异或算法实现
return (events ^ SYS_EVENT_MSG); }
由于对外部设备发送的数据处理是比较普遍的,所以这里再补充说明一下该处理程序。前面已经给出了afIncomingMSGPacket_t,具体内容不在多说,用户根据自己的需要,提取相应的变量。数据在被AF根据初始化时注册的端口信息发送到了相应的端口,端口根据簇ID号调用相应的处理函数。
void NewSample_MessageMSGCB( afIncomingMSGPacket_t *pkt ) {
uint8 ssLevel;
// Figure out the incoming signal strength if ( pkt->LinkQuality < SS_LOW ) ssLevel = 0;
5