当前位置: 首页 > news >正文

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内核(了解)

CC2530RF内核是其无线通信能力的核心,它集成了多种功能以支持高效的无线通信:

  • 调制器: 把原始数据转换为 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(k11)[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支持速率为40kbps10个信道
  • 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) APIBDB层指定了基本设备的运行环境、初始化、调试和操作程序,这些接口使设备ZigBee网络中合理可行(定义了设备在ZigBee网络中的基本行为)
  • 需要使用基本的OSAL(OS Abstraction Layer) APIOSAL层提供任务注册及初始化任务间信息交换任务同步中断处理定时器内存分配等功能的接口
  • 一些应用可能用到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 );  // 自定义任务初始化
}
  • 在例子SampleSwitchOSAL_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   // 自定义任务事件处理
};
  • 在例子SampleSwitchOSAL_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()创建一个网络,模式为FORMATIONFINDING_BINDING,并调用NLME_PermitJoiningRequest()允许其他设备入网

  • 对于终端使用bdb_StartCommissioning()加入网络,模式为STEERINGFINDING_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 开发指南

相关文章:

  • LeetCode hot 100—数组中的第K个最大元素
  • 【OpenGauss源码学习 —— (SortGroup算子)】
  • 蓝桥杯备考:数学问题模运算---》次大值
  • dfs(十八)98. 验证二叉搜索树
  • Linux 驱动开发笔记--1.驱动开发的引入
  • 海康ISAPI协议在智联视频超融合平台中的接入方法
  • CIR-Net:用于 RGB-D 显著性目标检测的跨模态交互与优化(问题)
  • 蓝桥杯十四届C++B组真题题解
  • DeDeCMS靶场获取wenshell攻略
  • 【B站电磁场】Transformer
  • 【QT5 多线程示例】互斥锁
  • QWen 和 DeepSeek 入门指南
  • 天梯赛 L2-012 关于堆的判断
  • 光谱仪与光谱相机的核心区别与协同应用
  • 使用 AnythingLLM 轻松部署本地知识库!
  • 雷池SafeLine-自定义URL规则拦截非法请求
  • 【MySQL】触发器与存储引擎
  • 基于开源模型的微调训练及瘦身打造随身扫描仪方案__用AI把手机变成文字识别小能手
  • 第二章 EXI协议原理与实现--7.5 Efficient XML库和OpenEXI.jar编解码交叉测试
  • Linux网络相关概念和重要知识(2)(UDP套接字编程、聊天室的实现、观察者模式)
  • 自然资源部:不动产登记累计化解遗留问题房屋2000多万套
  • 涉案资金超2亿元 “健康投资”骗局,专挑老年人下手
  • 郑钦文憾负高芙,止步WTA1000罗马站四强
  • 丰富“互换通”产品类型,促进中国金融市场高水平对外开放
  • 泰山、华海、中路等山东险企综合成本率均超100%,承保业务均亏损
  • 基金经理调仓引发大金融板块拉升?公募新规落地究竟利好哪些板块