05_Z-Stack无线点灯
05_Z-Stack无线点灯
前言
本章介绍ZigBee协议栈和Z-Stack的使用方式,并以TI提供的例子讲解代码。在正式讲解ZigBee前,为读者补充无线电通信的基础以及射频芯片的相关知识。为了突出重点,Z-Stack的下载将在文章最后。
无线电基础(了解)
- 射频通信系统(RF System): 一种在接收方与发射方实现通信的无线电系统,如广播无线电、广播电视等。
- 无线通信系统: 由发送方(Transmitter)和接收方(Receiver)组成,在发送方将低频的信息信号和高频的载波信号经过调制器(Modulator)和放大器(Amplifier)后发送,接收方将收到的信号进行放大、解调后输出。下图是一个简单的无线电系统:
- 调制器和解调器: 调制的目的是把要传输的模拟信号或数字信号变换成适合信道传输的信号,解调则是从携带信息的已调信号中恢复信息的过程
- 调制方式:
- AM(Amplitude Modulation): 幅调,载波的幅值随信息信号的变化而变化
- FM(Frequency Modulation): 频调,载波的频率随信息信号的变化而变化
- PM(Phase Modulation): 相调,载波的相角随信息信号的变化而变化
- 数字信号的调制叫做键移(Shift Keying)
- ASK(Amplitude Shift Keying): 幅值键移,简单的调制手段,但对噪声敏感
- FSK(Frequency Shift Keying): 频率键移,不易受噪声影响
- PSK(Phase Shift Keying): 相角键移,不易受噪声影响,但需要频率和相角同步,**硬件更复杂 **(ZigBee调制方法即为PSK)
- 信道是信息传输所用的通道,不同的信源和信宿在不同的信道上传输信息,信道的带宽和频率决定了信号的质量和干扰
射频模块(了解)
一个基本的射频由晶振(Crystal)、射频集成电路(RF-IC)、Balun、匹配器(Matching)、滤波器(Filter)和天线(Antenna)组成
- 晶振: 提供基准频率
- RF-IC: 包含收发器,SOC或集成微控制器
- Balun: 平衡不平衡转换器,原理是按天线理论,偶极天线属平衡型天线,而同轴电缆属不平衡传输线,若将其直接连接,则同轴电缆的外皮就有高频电流流过,影响天线的辐射。因此,就要在天线和电缆之间加入平衡不平衡转换器,把流入电缆屏蔽层外部的电流扼制掉
- 匹配器: 用于匹配射频电源和负载之间的阻抗的设备,确保射频电源能够有效地将能量传输到负载中,以提供最大功率传输和系统效率
- 滤波器: 使信号中特定的频率成分通过,而极大地衰减其他频率成分(提取有用信号)
- 天线: 发射和接收电磁波 (增大信号传递距离)
CC2530-RF内核(了解)
CC2530的RF内核是其无线通信能力的核心,它集成了多种功能以支持高效的无线通信:
- 调制器: 把原始数据转换为 I/Q 信号发送到发送器 DAC
- 解调器: 负责从收到的信号中检索无线数据
- 帧过滤和源匹配: 帧过滤功能拒绝目标不明确的帧,源匹配对消息进行短地址匹配或扩展地址匹配
- 频率合成器(FS): 为RF信号产生载波,载波频率可以通过编程位于 FREQCTRL.FREQ[6:0]的7位频率字设置。IEEE802.1 5.4-2006 指定 16个通道,它们位于2.4GHz频段之内。步长为 5 MHz,编号为 11~26,
f c = 2405 + 5 ( k − 11 ) [ M H z ] , k ∈ [ 11 , 26 ] F R E Q C T R L . F R E Q 寄存器因此设置为 F R E Q C T R L . F R E Q = 11 + 5 ( k – 11 ) f_c=2405+5(k-11)[MHz], k\in[11,26]\\ FREQCTRL.FREQ 寄存器因此设置为 FREQCTRL.FREQ = 1 1 + 5 (k – 1 1 ) fc=2405+5(k−11)[MHz],k∈[11,26]FREQCTRL.FREQ寄存器因此设置为FREQCTRL.FREQ=11+5(k–11)
- 命令选通处理器(CSP): 处理CPU发出的所有命令。它还有一个24 字节的很短的程序存储器,使得它可 以自动执行CSMA-CA 机制。
- 无线电RAM: 为发送数据有一个 FIFO(TXFIFO),为接收数据有一个 FIFO(RXFIFO)。RAM 为帧过滤和源匹配存储参数。
- RSSI: 无线电有一个内置的接收信号强度指示器(RSSI),计算一个8位有符号的数字值,可以从寄存器读出,或自动附加到收到的帧
- 输出功率编程: RF输出功率由 TXPOWER 寄存器的7位值控制。
- 定时器2(MAC定时器)用于为无线电事件计时,以捕获输入数据包的时间戳
值得注意的是,实际开发中,在Z-Stack的框架下,仅需要在配置文件或调用接口即可完成无线电参数的配置。
ZigBee(重点)
ZigBee协议栈
- ZigBee协议栈分为五层
- 物理层(PHY):定义物理无线信道,在2.4GHz频段上提供250kbps速率的16个不同信道,868MHz 提供速率为20kbps速率的1个信道,915MHz支持速率为40kbps的10个信道
- MAC层:MAC层数据服务为物理层与网络层之间的数据传输提供一个接口, 以实现数据发送、接收和处理排列中清除一个MAC层服务数据单元。
- 网络层:提供网络安全管理, 信息代理,路由管理和网络管理四大功能
- APS(应用支持层):抽象层,为应用层提供服务,提供应用级安全管理、信息管理、映射器管理
- APL(应用层):又分为AF(应用框架)和ZDO(ZigBee设备对象)
- ZDO: 主要负责设备的管理、安全加密、网络维护和绑定管理等功能
- AF:侧重于为应用对象提供数据传输服务、端点管理和配置文件支持等
ZigBee设备类型
- 协调器(Coordinator): 建立网络,但没有加入网络的功能,它的职责是创建和管理网络,主要功能如下:
- 扫描存在的射频环境,选择合适的信道(根据射频环境)。选择一个网络ID(又叫PAN ID)。创建并开始网络
- 作为网络的信任中心,管理网络安全
- 分发密钥并允许其他设备加入网络
- 帮助在网络中建立应用级的绑定
- 除此之外,具有路由器的功能
- 路由器(Router): 主要功能如下:
- 允许其他设备加入网络(在Z3.0中,该设备已被授予编队能力,允许其创建分布式安全网络)
- 多跳路由
- 协助子终端设备的通信
- 一般来说要一直开启,因此要由电源供电
- 终端设备(End-device): 不维持网络的建设,因此可以进入睡眠或唤醒模式
ZigBee地址
地址类型
- 64-bit IEEE address(MAC address) 值得注意的是TCP/IP中的MAC地址为48-bit, 但二者在本质上没有区别
- 16-bit network address(logical address / short address) 只在同一个网络中唯一,若有多个网络,则有可能有相同的短地址
相关Z-Stack API
NLME_GetShortAddr()
: 返回当前设备的16位网络地址NLME_GetExtAddr()
: 返回当前设备的64位扩展地址NLME_GetCoordShortAddr()
: 返回当前设备父节点的16位网络地址NLME_GetCorrdExtAddr()
: 返回当前设备父节点的64位扩展地址
地址的作用
在发送消息时,通常在应用层调用AF_DataRequest()
接口, 需要提供目标设备的地址和地址模式,它们定义在结构体afAddrType_t
中(在ZcomDef.h
中定义)
- 地址类型结构体
typedef struct
{
union // union 关键字用于定义联合体(Union)。允许在同一内存位置存储不同类型的数据,但每次只能存储其中一种类型
{ // 短地址和MAC地址二选一
uint16 shortAddr;
ZLongAddr_t extAddr;
} addr; // addr才是成员变量
afAddrMode_t addrMode; // 地址模式
byte endPoint; // 端点,通常用于指定设备上的特定服务或功能
} afAddrType_t;
- 地址模式枚举体
typedef enum
{
afAddrNotPresent = AddrNotPresent,
afAddr16Bit= Addr16Bit,
afAddr64Bit= Addr64Bit,
afAddrGroup= AddrGroup,
afAddrBroadcast = AddrBroadcast
} afAddrMode_t;
-
单播(Unicast)时,要求目的设备的网络地址(短地址)已知,并将
addrMode
设置为Addr16Bit
-
间接传播(Indirect)时,由于不知道最终地点,从发送设备的"绑定表"中查找目的地,需将
addrMode
设置为AddrNotPresent
-
广播(Broadcast)时,
addrMode
设置为AddrBroadcast
,目的地址可设置为:NWK_BROADCAST_DEVALL
将消息广播给所有设备,包括睡眠中的设备(对于睡眠中的设备,其消息由父节点保持,保持时间通过宏NWK_INDIRECT_TIMEOUT
设置, 可以在f8wConfig.cfg
文件中找到)。NWK_BROADCAST_SHORTADDR_DEVRXON
将消息广播给所有设备,不包括睡眠中的设备NWK_BROADCAST_SHORTADDR_DEVZCZR
将消息广播给所有路由器,包括协调器
-
组播时(Group Addressing)时,
addrMode
设置为afAddrGroup
并将目的地址设置层组号,而在这之前必须要先定义一个组aps_Group_t group; // Assign yourself to group 1 group.ID = 0x0001; group.name[0] = 6; // First byte is string length osal_memcpy( &(group.name[1]), “Group1”, 6); aps_AddGroup( SAMPLEAPP_ENDPOINT, &group ); // 定义一个组
ZigBee组网(了解)
- 由协调器扫描环境,选择合适信道创建网络,并随机生成一个网络ID(PAN ID),PAN ID可以在配置文件中提前设置。创建好网络后,协调器会定期发送信标帧(Beacon),包含网络信息(例如 PAN ID、信道信息等),以便附近的设备能够检测到这个网络并决定是否加入。
- 加入网络时,设备先通过扫描信道寻找可用ZigBee网络。找到可用网络后,设备需要向协调器发送加入请求(Join Request),加入请求包括设备的地址(通常是一个唯一的 IEEE 地址)和设备希望加入的网络类型(例如,是否希望成为终端设备、路由器等)。
- 协调器会检查加入设备的请求,根据网络规则和安全策略决定是否授权设备加入网络。如果授权,它会向设备发送一个加入响应(Join Response)。加入响应中还会包含一个状态字段,表示加入是否成功。若成功,协调器为加入设备分配一个网络地址(短地址)。若失败,设备需要重新发起加入请求,或选择其他可用的网络。
- 网络建立完成后,由路由器和协调器对网络进行维护
ZigBee主要参数配置
- PAN ID: 通过配置文件
f8wConfig.cfg
中的宏定义ZDO_CONFIG_PAN_ID
进行配置,当其被设置成0xFFFF,协调器在新建网络时,会随机生成PAN ID,对于加入网络的设备来说,它可能加入任意存在的网络 - 信道设置: 通过配置文件
f8wConfig.cfg
中的宏定义DEFAULT_CHANLIST
进行配置,可以选择11-26号共16个信道 - 全局配置非易失活存储器: 在项目中启用编译标志
NV_INIT
,会将ZGlobal.c
中存储的全局配置加载到非易失活存储器中,该配置在TI给的样例中默认开启 - 网络层非易失活存储器: 在项目中启用编译标志
NV_RESTORE
,将网络层参数保存在非易失活存储器中,这样设备能从意外重启或者断电中快速恢复到网络中。在实际使用中,该选项必须启动。仅在开发过程中,由于某些特殊需要可以暂时关闭。 - 应用层非易失活存储器: 除了配置数据,非易失活存储器还保留了0x0401 to 0x0FFF 条目ID(Item ID)用于应用程序数据的存储,具体的ID列表在
ZComDef.h
文件中可以查到- 要保存一个变量到非易失活存储器中,首先要创建一个条目ID(Item ID)
- 调用
osal_nv_item_init()
初始化Item,需要的参数有条目ID(Item ID),变量长度和初始值 - 调用
osal_nv_write()
在对应Item中写入数据 - 调用
osal_nv_read()
在对应Item中读出数据 - 这些接口定义在
OSAL_Nv.c
文件中
ZigBee应用层开发(重点)
功能说明
- zigbee组网
- zigbee协调器创建网络
- zigbee终端加入网络
- 回调事件处理(如重连,连接成功后执行的功能等…)
- 协调器控制终端开关灯
- LED驱动配置
- 协调器定时触发事件,通过ZCL层,向终端发送命令
- 终端通过ZCL层,接收命令,并控制LED亮灭
需要用到的API
- 应用程序开发需用到BDB(Base Device Behavior) API, BDB层指定了基本设备的运行环境、初始化、调试和操作程序,这些接口使设备在ZigBee网络中合理可行(定义了设备在ZigBee网络中的基本行为)
- 需要使用基本的OSAL(OS Abstraction Layer) API,OSAL层提供任务注册及初始化、任务间信息交换、任务同步、中断处理、定时器、内存分配等功能的接口
- 一些应用可能用到HAL(Hardware Abstract Layer) API,提供了按键、LCD及串口等驱动,用户可以根据自己的需求选择性使用
- ZCL层相关API, Zigbee联盟规定的一些接口,
OSAL任务
OSAL设计的目标是,在不需要修改OSAL的前提下,分散式的使用Z-Stack接口,在OSAL的支持下,Z-Stack的主要子系统以OSAL任务的方式运行。对于用户来说,需要至少创建一个OSAL任务来运行其需要实现的应用。创建一个OSAL任务的步骤如下:
-
用户需要实现
osalInitTasks()
功能 -
将任务添加到
tasksArr
任务数组中(定义在OSAL_"Application Name".c
文件中,如OSAL_SampleSwitch.c
) -
需要实现自定义任务初始化函数
zcl"Application Name"_Init
(如zclSampleSw_Init()
)对任务配置进行初始化,并在osalInitTasks()
中调用它 -
需要实现自定义任务事件处理函数
zcl“Application Name”_event_loop
(如zclSampleSw_event_loop()
),并将其放入tasksArr
任务数组中。每一个OSAL任务可定义15种事件,其中SYS_EVENT_MSG(0x8000)
是OSAL任务的保留事件 -
Note: 任务的优先级由其在
tasksArr
任务数组中的位置决定,第一个任务的优先级最高
/*********************************************************************
* @fn osalInitTasks
*
* @brief This function invokes the initialization function for each task.
*
* @param void
*
* @return none
*/
void osalInitTasks( void )
{
uint8 taskID = 0;
tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
macTaskInit( taskID++ );
nwk_init( taskID++ );
#if !defined (DISABLE_GREENPOWER_BASIC_PROXY) && (ZG_BUILD_RTR_TYPE)
gp_Init( taskID++ );
#endif
Hal_Init( taskID++ );
#if defined( MT_TASK )
MT_TaskInit( taskID++ );
#endif
APS_Init( taskID++ );
#if defined ( ZIGBEE_FRAGMENTATION )
APSF_Init( taskID++ );
#endif
ZDApp_Init( taskID++ );
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
ZDNwkMgr_Init( taskID++ );
#endif
// Added to include TouchLink functionality
#if defined ( INTER_PAN )
StubAPS_Init( taskID++ );
#endif
// Added to include TouchLink initiator functionality
#if defined( BDB_TL_INITIATOR )
touchLinkInitiator_Init( taskID++ );
#endif
// Added to include TouchLink target functionality
#if defined ( BDB_TL_TARGET )
touchLinkTarget_Init( taskID++ );
#endif
zcl_Init( taskID++ );
bdb_Init( taskID++ );
zclSampleSw_Init( taskID ); // 自定义任务初始化
}
- 在例子
SampleSwitch
的OSAL_SampleSw.c
文件中,可以看到在osalInitTasks()
函数的最后一行,调用了自定义的zclSampleSw_Init()
函数。实际开发中,我们只需讲这里替换成自己的任务初始化函数
const pTaskEventHandlerFn tasksArr[] = {
macEventLoop,
nwk_event_loop,
#if !defined (DISABLE_GREENPOWER_BASIC_PROXY) && (ZG_BUILD_RTR_TYPE)
gp_event_loop,
#endif
Hal_ProcessEvent,
#if defined( MT_TASK )
MT_ProcessEvent,
#endif
APS_event_loop,
#if defined ( ZIGBEE_FRAGMENTATION )
APSF_ProcessEvent,
#endif
ZDApp_event_loop,
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
ZDNwkMgr_event_loop,
#endif
//Added to include TouchLink functionality
#if defined ( INTER_PAN )
StubAPS_ProcessEvent,
#endif
// Added to include TouchLink initiator functionality
#if defined ( BDB_TL_INITIATOR )
touchLinkInitiator_event_loop,
#endif
// Added to include TouchLink target functionality
#if defined ( BDB_TL_TARGET )
touchLinkTarget_event_loop,
#endif
zcl_event_loop,
bdb_event_loop,
zclSampleSw_event_loop // 自定义任务事件处理
};
-
在例子
SampleSwitch
的OSAL_SampleSw.c
文件中,可以看到在taskArr
任务数组的最后一行,保存了自定义的函数zclSampleSw_event_loop
的入口 -
通过
osal_start_timerEx
触发事件
zclSampleSw_Init初始化函数
void zclSampleSw_Init( byte task_id )
{
zclSampleSw_TaskID = task_id; // 保存任务ID
// Set destination address to indirect
zclSampleSw_DstAddr.addrMode = (afAddrMode_t)AddrNotPresent;
zclSampleSw_DstAddr.endPoint = 0;
zclSampleSw_DstAddr.addr.shortAddr = 0;
// Register the Simple Descriptor for this application
bdb_RegisterSimpleDescriptor( &zclSampleSw_SimpleDesc );
// Register the ZCL General Cluster Library callback functions
zclGeneral_RegisterCmdCallbacks( SAMPLESW_ENDPOINT, &zclSampleSw_CmdCallbacks );
zclSampleSw_ResetAttributesToDefaultValues();
// Register the application's attribute list
zcl_registerAttrList( SAMPLESW_ENDPOINT, zclSampleSw_NumAttributes, zclSampleSw_Attrs );
// Register the Application to receive the unprocessed Foundation command/response messages
zcl_registerForMsg( zclSampleSw_TaskID );
// Register for all key events - This app will handle all key events 注册按键事件
RegisterForKeys( zclSampleSw_TaskID );
// 注册回调函数
bdb_RegisterCommissioningStatusCB( zclSampleSw_ProcessCommissioningStatus );
// Register for a test endpoint
afRegister( &sampleSw_TestEp );
#ifdef ZCL_DIAGNOSTIC
// Register the application's callback function to read/write attribute data.
// This is only required when the attribute data format is unknown to ZCL.
zcl_registerReadWriteCB( SAMPLESW_ENDPOINT, zclDiagnostic_ReadWriteAttrCB, NULL );
if ( zclDiagnostic_InitStats() == ZSuccess )
{
// Here the user could start the timer to save Diagnostics to NV
}
#endif
#if defined (OTA_CLIENT) && (OTA_CLIENT == TRUE)
// Register for callback events from the ZCL OTA
zclOTA_Register(zclSampleSw_TaskID);
#endif
zdpExternalStateTaskID = zclSampleSw_TaskID;
/**LOOK HERE **/
/* 对于初始化函数来说,保存任务ID,注册按键是基本的. 其余就是根据需求,注册一些事件和回调函数
上面代码中zcl层, 是zigbee联盟规定的一些定义,各个厂商在开发时都遵循这些定义,从而实现互联互通
因此, 全删了或者留下影响都不大, 我们先专注于自己的功能即可(zcl层功能用到时再仔细学习) */
// 实现无线点灯,首先需要建立网络(由协调器完成)
#ifdef ZDO_COORDINATOR // 协议器需完成 建立网络/发送调试信息的工作
// Init Uart
zclSampleSw_InitUart(); // 串口初始化 调试可用
ZDO_RegisterForZDOMsg ( zclSampleSw_TaskID, Device_annce ); // 注册Device_annce簇ID
// 协调器收到 终端的地址广播会产生该事件, 需要在zclSampleSw_event_loop进行处理
bdb_StartCommissioning( BDB_COMMISSIONING_MODE_NWK_FORMATION |
BDB_COMMISSIONING_MODE_FINDING_BINDING ); // 建立一个网络
NLME_PermitJoiningRequest(255); // 允许加入网络
#else // 若不是协调器 只需加入网络,并广播地址
bdb_StartCommissioning( BDB_COMMISSIONING_MODE_NWK_STEERING |
BDB_COMMISSIONING_MODE_FINDING_BINDING ); // 加入网络
// 加入网络后 广播自己的地址 直接放这不管有没有成功都会执行
// zclSampleSw_DeviceAnnce();
// 注释掉, 移动到zclSampleSw_ProcessCommissioningStatus回调函数中会更好
#endif
}
zclSampleSw_event_loop事件处理
uint16 zclSampleSw_event_loop( uint8 task_id, uint16 events ) // 应用层各种事件的处理
{
afIncomingMSGPacket_t *MSGpkt;
(void)task_id; // Intentionally unreferenced parameter
//Send toggle every 500ms
if( events & SAMPLESW_TOGGLE_TEST_EVT )
{
osal_start_timerEx(zclSampleSw_TaskID,SAMPLESW_TOGGLE_TEST_EVT,500);
zclGeneral_SendOnOff_CmdToggle( SAMPLESW_ENDPOINT, &zclSampleSw_DstAddr, FALSE, 0 );
// return unprocessed events
return (events ^ SAMPLESW_TOGGLE_TEST_EVT);
}
if ( events & SYS_EVENT_MSG )
{
while ( (MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( zclSampleSw_TaskID )) )
{
switch ( MSGpkt->hdr.event )
{
case ZCL_INCOMING_MSG: //
// Incoming ZCL Foundation command/response messages
zclSampleSw_ProcessIncomingMsg( (zclIncomingMsg_t *)MSGpkt );
break;
case KEY_CHANGE:
zclSampleSw_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys );
break;
case ZDO_STATE_CHANGE:
break;
#if defined (OTA_CLIENT) && (OTA_CLIENT == TRUE)
case ZCL_OTA_CALLBACK_IND:
zclSampleSw_ProcessOTAMsgs( (zclOTA_CallbackMsg_t*)MSGpkt );
break;
#endif
#ifdef ZDO_COORDINATOR // LOOK Here
// 通过 ZDO_RegisterForZDOMsg注册
case ZDO_CB_MSG: // 收到 终端地址消息会产生该事件
zclSampleSw_processZDOMgs( (zdoIncomingMsg_t *)MSGpkt );
break;
#endif
default:
break;
}
// Release the memory
osal_msg_deallocate( (uint8 *)MSGpkt );
}
// return unprocessed events
return (events ^ SYS_EVENT_MSG);
}
#if ZG_BUILD_ENDDEVICE_TYPE
if ( events & SAMPLEAPP_END_DEVICE_REJOIN_EVT )
{
bdb_ZedAttemptRecoverNwk();
return ( events ^ SAMPLEAPP_END_DEVICE_REJOIN_EVT );
}
#endif
#ifdef ZDO_COORDINATOR // 协调器
if ( events & SAMPLEAPP_ONOFF_TEST_EVT ) // 开关灯事件
{
zclSampleSw_OnOffTest(); // 发送开关灯命令
osal_start_timerEx(zclSampleSw_TaskID,
SAMPLEAPP_ONOFF_TEST_EVT,
SAMPLEAPP_ONOFF_TEST_PERIOD); // 重复触发事件
return ( events ^ SAMPLEAPP_ONOFF_TEST_EVT ); // 返回剩余事件
}
#else
// Rejoin
if ( events & SAMPLEAPP_REJOIN_EVT ) // 重连事件 非协调器
{
// 重新连接
bdb_StartCommissioning(BDB_COMMISSIONING_MODE_NWK_STEERING |
BDB_COMMISSIONING_MODE_FINDING_BINDING );
return ( events ^ SAMPLEAPP_REJOIN_EVT ); // 返回剩余事件
}
#endif
// Discard unknown events
return 0;
}
组网功能详解
-
对于协调器使用
bdb_StartCommissioning()
创建一个网络,模式为FORMATION
和FINDING_BINDING
,并调用NLME_PermitJoiningRequest()
允许其他设备入网 -
对于终端使用
bdb_StartCommissioning()
加入网络,模式为STEERING
和FINDING_BINDING
,终端成功加入网络后,调用zclSampleSw_DeviceAnnce()
向网络广播自己的地址.
具体代码如下(已删除其他功能,zclSampleSw_Init
函数中):
#ifdef ZDO_COORDINATOR // 协议器需完成 建立网络/发送调试信息的工作
ZDO_RegisterForZDOMsg ( zclSampleSw_TaskID, Device_annce ); // 注册 Device_annce 簇ID
bdb_StartCommissioning( BDB_COMMISSIONING_MODE_NWK_FORMATION |
BDB_COMMISSIONING_MODE_FINDING_BINDING ); // 建立一个网络
NLME_PermitJoiningRequest(255); // 允许加入网络
#else // 若不是协调器
bdb_StartCommissioning( BDB_COMMISSIONING_MODE_NWK_STEERING |
BDB_COMMISSIONING_MODE_FINDING_BINDING ); // 加入网络
#endif
-
回调函数和重连事件:
- 在
zcl_samplesw.h
文件中定义重连事件和重连事件间隔
#ifdef ZDO_COORDINATOR #else // rejoin #define SAMPLEAPP_REJOIN_EVT 0x0080 #define SAMPLEAPP_REJOIN_PERIOD 1000 #endif
- 编写
bdb_StartCommissioning
的回调函数zclSampleSw_ProcessCommissioningStatus
,该函数在zclSampleSw_Init
中通过bdb_RegisterCommissioningStatusCB()
注册(可以往上找找看)
static void zclSampleSw_ProcessCommissioningStatus(bdbCommissioningModeMsg_t *bdbCommissioningModeMsg) { switch(bdbCommissioningModeMsg->bdbCommissioningMode) { case BDB_COMMISSIONING_FORMATION: if(bdbCommissioningModeMsg->bdbCommissioningStatus == BDB_COMMISSIONING_SUCCESS) { bdb_StartCommissioning(BDB_COMMISSIONING_MODE_NWK_STEERING | bdbCommissioningModeMsg->bdbRemainingCommissioningModes); } else { } break; case BDB_COMMISSIONING_NWK_STEERING: // 在这里处理 if(bdbCommissioningModeMsg->bdbCommissioningStatus == BDB_COMMISSIONING_SUCCESS) { // 如果连接成功 #ifdef ZDO_COORDINATOR zclSampleSw_DeviceAnnce(); // 加入网络后 广播自己的地址 #else #endif } else // 连接失败 { #ifdef ZDO_COORDINATOR #else // 启动重连事件 osal_start_timerEx(zclSampleSw_TaskID, SAMPLEAPP_REJOIN_EVT, SAMPLEAPP_REJOIN_PERIOD); #endif } break; case BDB_COMMISSIONING_FINDING_BINDING: if(bdbCommissioningModeMsg->bdbCommissioningStatus == BDB_COMMISSIONING_SUCCESS) { //YOUR JOB: } else { //YOUR JOB: //retry?, wait for user interaction? } break; case BDB_COMMISSIONING_INITIALIZATION: //Initialization notification can only be successful. Failure on initialization //only happens for ZED and is notified as BDB_COMMISSIONING_PARENT_LOST notification //YOUR JOB: //We are on a network, what now? break; #if ZG_BUILD_ENDDEVICE_TYPE case BDB_COMMISSIONING_PARENT_LOST: if(bdbCommissioningModeMsg->bdbCommissioningStatus == BDB_COMMISSIONING_NETWORK_RESTORED) { //We did recover from losing parent } else { //Parent not found, attempt to rejoin again after a fixed delay osal_start_timerEx(zclSampleSw_TaskID, SAMPLEAPP_END_DEVICE_REJOIN_EVT, SAMPLEAPP_END_DEVICE_REJOIN_DELAY); } break; #endif } }
- 在
-
事件处理
- (终端)在事件处理函数
zclSampleSw_event_loop
中,处理重连事件
#ifdef ZDO_COORDINATOR #else // Rejoin 重新加入网络 if ( events & SAMPLEAPP_REJOIN_EVT ) { bdb_StartCommissioning(BDB_COMMISSIONING_MODE_NWK_STEERING | BDB_COMMISSIONING_MODE_FINDING_BINDING ); return ( events ^ SAMPLEAPP_REJOIN_EVT ); } #endif
- (协调器)在事件处理函数
zclSampleSw_event_loop
中,处理ZDO事件(Device_annce)
switch ( MSGpkt->hdr.event ) { ... #ifdef ZDO_COORDINATOR case ZDO_CB_MSG: // 当协调器收到Device_annce消息会触发该事件 zclSampleSw_processZDOMgs( (zdoIncomingMsg_t *)MSGpkt ); // ZDO消息处理函数 break; ... #endif }
/** * @fn zclSampleSw_processZDOMgs * * @brief Process ZDO Message */ static void zclSampleSw_processZDOMgs(zdoIncomingMsg_t *pMsg) { switch ( pMsg->clusterID ) { case Device_annce: { zclSampleSw_OnOffTestAddr = pMsg->srcAddr.addr.shortAddr; // 保存地址 HalLcdWriteStringValue("Node:", pMsg->srcAddr.addr.shortAddr, 16, 3); } break; default: break; } }
- (终端)在事件处理函数
-
zclSampleSw_DeviceAnnce
/**
* @brief 终端广播自己的网络地址
*/
static void zclSampleSw_DeviceAnnce( void )
{
ZDP_DeviceAnnce(
NLME_GetShortAddr(),//获取本设备的网络地址(短地址)
NLME_GetExtAddr(),//获取本设备的物理地址(通常就是MAC地址)
ZDO_Config_Node_Descriptor.CapabilityFlags,
0
);
}
ZCL层On/Off命令收发
ZCL定义了On/Off Cluster
命令,借助ZCL层,可方便实现协调器对终端设备下达命令
- 在
zcl_samplesw.c
中的zclSampleSw_Init
中,可找到zcl
层的初始化代码
// Register the ZCL General Cluster Library callback functions
zclGeneral_RegisterCmdCallbacks(SAMPLESW_ENDPOINT,
&zclSampleSw_CmdCallbacks); // 注册回调函数列表
zclSampleSw_ResetAttributesToDefaultValues(); // 设置参数为默认值
// Register the application's attribute list 为设备注册一个属性列表。
zcl_registerAttrList(SAMPLESW_ENDPOINT,zclSampleSw_NumAttributes,zclSampleSw_Attrs);
// Register the Application to receive the unprocessed Foundation command/response messages
// 告诉设备需要接收未处理的Foundation(基础)命令或者响应消息。
zcl_registerForMsg( zclSampleSw_TaskID );
- 在
zclSampleSw_CmdCallbacks
添加On/Off
命令的回调函数zclSampleSw_OnOffCB
,它的定义如下:
/**
* @fn zclSampleSw_OnOffCB
*
* @brief ZCL Command - On/Off
*/
static void zclSampleSw_OnOffCB( uint8 cmd ) // 终端收到 On/Off命令后, 会执行该回调函数
{
if(cmd == COMMAND_ON)
{
HalLcdWriteString("Set ON", 4);
HalLedSet(HAL_LED_ALL, HAL_LED_MODE_ON); // 终端控制LED灯亮
}
else if(cmd == COMMAND_OFF)
{
HalLcdWriteString("Set OFF", 4);
HalLedSet(HAL_LED_ALL, HAL_LED_MODE_OFF); // 灭
}
}
- 在
zclSampleSw_processZDOMgs
函数中加入事件触发
/**
* @fn zclSampleSw_processZDOMgs
*
* @brief Process ZDO Message
*/
static void zclSampleSw_processZDOMgs(zdoIncomingMsg_t *pMsg)
{
switch ( pMsg->clusterID )
{
case Device_annce:
{
zclSampleSw_OnOffTestAddr = pMsg->srcAddr.addr.shortAddr; // 保存地址
HalLcdWriteStringValue("Node:", pMsg->srcAddr.addr.shortAddr, 16, 3);
HalLcdWriteString("On/Off Test...", 4);
osal_start_timerEx(zclSampleSw_TaskID,
SAMPLEAPP_ONOFF_TEST_EVT,
SAMPLEAPP_ONOFF_TEST_PERIOD); // 触发开关事件
}
break;
default:
break;
}
}
zclSampelSw_eventloop
中处理SAMPLEAPP_ONOFF_TEST_EVT事件
#ifdef ZDO_COORDINATOR
if ( events & SAMPLEAPP_ONOFF_TEST_EVT )
{
zclSampleSw_OnOffTest(); // 处理函数
osal_start_timerEx(zclSampleSw_TaskID,
SAMPLEAPP_ONOFF_TEST_EVT,
SAMPLEAPP_ONOFF_TEST_PERIOD); // 过一段时间重新触发事件, 实现命令发送的周期性
return ( events ^ SAMPLEAPP_ONOFF_TEST_EVT ); // 返回未处理的事件
}
#else
...
#endif
/**
* @fn zclSampleSw_OnOffTest
*
* @brief Send On/Off Command to device
*/
static void zclSampleSw_OnOffTest(void)
{
afAddrType_t destAddr;
static uint8 txID = 0;
static bool on = true;
destAddr.endPoint = SAMPLESW_ENDPOINT;
destAddr.addrMode = Addr16Bit;
destAddr.addr.shortAddr = zclSampleSw_OnOffTestAddr;
if(on)
{
HalLcdWriteString("Command: ON", 4);
zclGeneral_SendOnOff_CmdOn(SAMPLESW_ENDPOINT,
&destAddr,
TRUE,
txID++); // 协调器发送开灯命令
}
else
{
HalLcdWriteString("Command: OFF", 4);
zclGeneral_SendOnOff_CmdOff(SAMPLESW_ENDPOINT,
&destAddr,
TRUE,
txID++);// 协调器发送关灯命令
}
on = !on; // 切换开关
}
硬件驱动层移植
见硬件Zstack-驱动移植篇,提供LED、Uart以及LCD的参考方案
参考资料
RF Basics, RF for Non-RF Engineers
CC2530 Software Examples User’s Guide (Rev. A
射频百科|巴伦(balun)的基本原理与应用 - 知乎
射频电源匹配器工作原理及组成 - 知乎
【Zigbee】进阶篇(1) Zigbee协议栈创建简单项目,协议栈、事件、消息学习_zstack系统事件都有哪些-CSDN博客
ZigBee2007协议规范.PDF
CC2530中文数据手册.PDF
Z-Stack 3.0 Developer’s Guide.pdf
5.3 ZCL 开关命令收发实验 - ZigBee 3.0 开发指南