zstack协议栈是TI公司发布的针对CC2530开发的工具,使zigbee的开发变得简单高效。本文着重介绍zstack使用的技巧,适合有一定基础的作为参考。
基于zstack协议栈的开发可以遵循OSAL操作系统配置->硬件功能配置->通信配置。其中OSAL操作系统配置主要实现创建任务和事件;硬件功能配置主要是根据自己的开发板添加或修改协议栈的硬件操作代码;通信配置主要完成组网及数据收发。
OSAL(Operating System Abstraction Layer),能够实现任务调度的微操作系统,在OSAL操作系统中非常重要的两个概念是任务和事件,还有消息也比较重要。
OSAL的启动流程如下图所示:
系统启动后,先完成一系列的初始化,然后进入如无轮询主循环。
在zstack协议栈工程目录ZMAIN下的ZMain.c包含了主程序的入口地址
main函数中完成了一系列的初始,其中对开发者比较重要有两个函数
// Initialize the operating system
osal_init_system();
其中包含的任务初始化函数完成系统任务和用户自定义任务
// Initialize the system tasks.
osalInitTasks();
osal_start_system(); // No Return from here
其中包含了一个无限循环的系统运行函数
void osal_start_system( void )
{
#if !defined ( ZBIT ) && !defined ( UBIT )
for(;;) // Forever Loop
#endif
{
osal_run_system();
}
}
其功能是不断查询任务事件数组,如果有事件产生,则根据事件在taskArr数组中查询任务事件处理函数进行处理。
任务相当于公司中的某个部门,用来处理某一或某些特定的事件。
zstack协议栈的任务有系统任务和用户自定义任务,每个任务都有一个编号taskID,用于标识任务身份。
上图是TI官方模板Generic工程下定义的任务,用户可以在此函数末尾添加其他的自定义任务。
事件是系统需要处理的事情,必须有对应的任务进行处理。
事件分为系统任务和用户自定义任务,采用16位独热码,即有16种事件(event_flag),其中系统事件已被定义,其编码为0x8000
除此之外还有15种用户可定义的事件。
当事件数量较多,编码无法表示时,可采用消息,系统事件就是采用消息进行事件传递的。
osal定义了用于存储事件状态的数组tasksEvents[idx],此数组与任务处理函数数组tasksArr[tasksID]一一对应。
系统任务处理的事件代码由zstack协议栈完成,不需要用户干预。
我们主要在用户任务中完成特定事件的处理。
系统事件采用消息形式传递,包含多种事件类型,其中有两个比较常用的事件类型:
用户事件主要由两条函数指令触发,可以在程序需要的位置或采用特定的硬件中断触发(如:定时器周期中断、按键等):
osal_set_event( byte task_id, UINT16 event_flag )
此函数用于立刻产生一个包含于某任务(任务号task_id)的事件(用户定义的事件event_flag)
osal_start_timerEx( uint8 taskID, uint16 event_id, uint16 timeout_value )
此函数是延时(timeout_value)产生一个包含于任务(taskID)的事件(event_id)。
这连个函数都可以产生用户事件,不同之处在于产生事件的时刻不同。
zigbee常用的组网方式有三种:
单播:点对点通信,通过向指定的16位短地址或64位物理地址发送数据
广播:网络内所有的节点都可以接收数据,通信地址0x0000
组播:先将网络中的节点分组,组内节点可以接收数据,通信地址为组号
组网过程:网络由协调器组建,网络建立后协调器的作用与路由器相同,终端节点可以直接加入网络或者通过路由器加入网络。
协调器创建网络时要指定PANID和Channel,配置文件如下:
通过取消注释来指定信道,信道编码采用64位独热码,当需要多信道通信时,可以采用或运算,如编码为0x00001800表示采用11和12信道。需要注意的是,zigbee采用的2.4GHZ频段和WIFI的频段重叠,为了防止WIFI的干扰要避免使用WIFI 常用信道如1,6,11,zigbee联盟建议使用11、14、15、19、20、24、25这些信道。
默认的PANID为0xFFFF,对于协调器将随机生成一个PANID,对于路由器和终端节点将随机加入一个网络。
如果需要产生或加入一个特定网络只需要赋值非0xFFFF的具体参数,如
-DZDAPP_CONFIG_PAN_ID=0x1234
网络创建成功后,协调器的网络短地址固定为0x0000。
路由器和终端节点加入网络后随机分配网络短地址,通常第一个与协调器建立联系的节点为0x796F,第二个与协调器建立联系的节点为0x7970,依此类推。网络短地址相当于互联网中IP地址的概念,网络中的每一个设备都有一个唯一的IP地址。
通过下列指令可以查询设备的短地址
NLME_GetShortAddr()——返回本设备的16位网络地址
NLME_GetCoordShortAddr()——返回本设备的父亲设备的16位网络地址
同时每台zigbee设备都有一个64位的物理地址,相当于网络设备的MAC地址。
NLME_GetExtAddr()—— 返回本设备的 64 位扩展地址
NLME_GetCoordExtAddr()—— 返回本设备的父亲设备的 64 位扩展地址
端点(endpoint),相当于网络中的端口号,8位编码,理论上可以有256(0 ~ 255)个端点,但0号端点保留用于ZDO数据接口,端点255保留用于广播数据接口,用户可用的端点范围为1 ~ 240,其他端点保留。
簇ID,指明了消息的接收者。
因此,要在网络中实现数据的收发,必须指明上述的网络参数(目标短地址/物理地址、端点及簇)。
发送函数:
AF_DataRequest( &GenericApp_DstAddr, //目标地址描述,包括通信方式、端点和目标短地址/物理地址
&GenericApp_epDesc,//端点描述,包括端点号、对应的任务号
GENERICAPP_CLUSTERID,//簇ID
(byte)osal_strlen( theMessageData ) + 1,//发送数据的长度
(byte *)&theMessageData,//发送的数据
&GenericApp_TransID,//消息序号,用来计算丢包率
AF_DISCV_ROUTE, //默认
AF_DEFAULT_RADIUS//默认 )
目标地址描述主要通过下列指令修改:
afAddrType_t GenericApp_DstAddr;
// Setup for the periodic message's destination address
// 点对点方式
GenericApp_DstAddr.addrMode = (afAddrMode_t) Addr16Bit;//广播方式
GenericApp_DstAddr.endPoint = GENERICAPP_ENDPOINT;//端点
GenericApp_DstAddr.addr.shortAddr = 0x0000;//短地址,数据发送到协调器
// 广播方式
GenericApp_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast;//广播方式
GenericApp_DstAddr.endPoint = GENERICAPP_ENDPOINT;//端点
GenericApp_DstAddr.addr.shortAddr = 0xFFFF;//短地址,数据发送给网络中所有的设备
// 组播方式 - Group 1
GenericApp_DstAddr.addrMode = (afAddrMode_t) AddrGroup;//组播方式
GenericApp_DstAddr.endPoint = GENERICAPP_ENDPOINT;//端点
GenericApp_DstAddr.addr.shortAddr = SAMPLEAPP_FLASH_GROUP;//组号,数据发送到组内所有设备
在进行组播通信前,除了要定义通信方式,还要将设备加入到组内,并且组号必须与已定义的端点关联
默认情况下,所有设备都在组Group 1
aps_Group_t SampleApp_Group;
SampleApp_Group.ID = 0x0001;//组号
osal_memcpy( SampleApp_Group.name, "Group 1", sizeof("Group 1");//组名,方便用户分辨
aps_AddGroup( SAMPLEAPP_ENDPOINT, &SampleApp_Group );//组号与端点关联
常用的组操作函数还有
aps_Group_t *grp;
grp = aps_FindGroup( GENERICAPP_ENDPOINT, SAMPLEAPP_FLASH_GROUP );//查找端点是否与组关联
aps_RemoveGroup( GENERICAPP_ENDPOINT, SAMPLEAPP_FLASH_GROUP );//从组中删除端点
端点描述:
endPointDesc_t SampleApp_epDesc;
// Fill out the endpoint description.
SampleApp_epDesc.endPoint = SAMPLEAPP_ENDPOINT;//端点号
SampleApp_epDesc.task_id = &SampleApp_TaskID;//与之对应的任务
SampleApp_epDesc.simpleDesc= (SimpleDescriptionFormat_t *)&SampleApp_SimpleDesc;//端点描述
SampleApp_epDesc.latencyReq = noLatencyReqs;
// Register the endpoint description with the AF
afRegister( &SampleApp_epDesc );//注册端点
在系统事件SYS_EVENT_MSG中有很多不同类型的事件,其中就包含了无线数据接收事件,即当有AF接收到数据时会自动触发
if ( events & SYS_EVENT_MSG )
{
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );
while ( MSGpkt )
{
switch ( MSGpkt->hdr.event )
{
…
case AF_INCOMING_MSG_CMD:
GenericApp_MessageMSGCB( MSGpkt );
break;
…
default:
break;
}
在GenericApp_MessageMSGCB( MSGpkt );中解析消息即可
void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
if(pkt-> endpoint== SAMPLEAPP_ENDPOINT)//先判断是否为接收端点
{
switch ( pkt->clusterId )//按不同的簇ID处理消息
{
case SAMPLEAPP_PERIODIC_CLUSTERID:
…
break;
case SAMPLEAPP_FLASH_CLUSTERID:
…
break;
}
}
}
zstack协议栈提供了硬件接口HAL库,如果使用的是TI官方的zigbee开发板,可以直接使用HAL库进行硬件的操作,但如果使用的开发板是非官方开发板,则需要根据开发板的实际情况进行自定义的硬件配置。
以串口使用为例,要使用串口功能,需要做如下配置
这样就可以使用串口功能了,对于HAL库操作串口的函数可以在hal_uart.h文件查看
在需要使用串口功能的文件中加载hal_uart.h头文件即可使用这些函数操作串口了。
使用自定义的硬件驱动有两种方法,一种是直接在官方HAL库函数上修改,如果比较熟悉HAL库推荐使用这种方法;还有一种方法就是自己编写硬件驱动函数然后添加到工程中调用,这种方式在使用时需要将HAL库中对应的硬件功能设置为FALSE,以免HAL库与自己的硬件驱动代码冲突。
使用zstack协议栈开发zigbee比较简单,只要大家掌握了zstack的基本工作原理及相关的概念,初学者可以很快独立进行简单的工程开发,当然在此过程中会出现很多问题,但是随着问题的逐步解决,自身技艺也会不断精进,加油吧,少年!