你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32WB Zigbee 自定义集群开发实战:AN5491 应用笔记深度解读

[复制链接]
攻城狮Melo 发布时间:2026-6-11 14:59

物联网智能家居和工业自动化领域,Zigbee 协议凭借其低功耗、自组网和高可靠性的特点,成为了最主流的无线通信标准之一。意法半导体的 STM32WB 系列微控制器集成了双核架构(Cortex-M4 应用处理器 + Cortex-M0 + 无线协处理器),为 Zigbee 应用提供了强大的硬件平台。虽然 Exegin ZSDK(Zigbee 软件设计套件)已经提供了绝大多数标准 ZCL(Zigbee 集群库)集群的模板,但在实际项目开发中,我们经常会遇到需要实现特定功能的场景,这时就需要开发自定义的 ZCL 集群。本文将基于意法半导体 AN5491 应用笔记,详细讲解如何在 STM32WB 系列上创建和使用自定义 Zigbee 集群。

资料获取:【应用笔记】DS13252 蓝牙® 低功耗5.0和802.15.4模块

1. 为什么需要自定义 Zigbee 集群?

Zigbee 联盟定义了一套完整的标准集群库,涵盖了照明、温控、安防、能源管理等常见应用场景。这些标准集群确保了不同厂商设备之间的互操作性,是 Zigbee 生态系统的基础。

然而,在实际开发中,我们经常会遇到以下情况:

  • 产品需要实现标准集群中没有的特殊功能
  • 希望简化标准集群,只保留必要的属性和指令
  • 需要添加厂商特定的扩展功能
  • 对设备的功耗或性能有特殊要求

在这些情况下,开发自定义集群就成为了必要的选择。AN5491 应用笔记正是为了解决这个问题而编写的,它详细介绍了如何按照与 Exegin ZSDK 标准集群相同的方式,构建和集成自定义 ZCL 集群。

2. ZCL 集群架构基础

在开始开发自定义集群之前,我们需要先了解 ZCL 集群的基本架构和工作原理。

2.1 集群的基本概念

Zigbee 集群库(ZCL)定义了应用节点之间通过网络进行交互的机制。它将特定的功能整理成 "集群",每个集群包含一组相关的属性和指令。例如,"开 / 关" 集群定义了控制设备开关状态的功能,包含一个表示当前状态的属性和几个控制开关的指令。

每个集群都有客户端和服务器两种角色:

  • 服务器端:存储属性值并执行客户端发送的指令
  • 客户端:向服务器发送指令并读取或写入服务器的属性

以常见的照明系统为例,开关是客户端,灯泡是服务器。开关通过发送 "开 / 关" 指令来控制灯泡的状态,同时也可以读取灯泡的当前状态属性。

2.2 ZCL 在协议栈中的位置

ZCL 基于 APS(应用支持子层)消息构建,而 APS 消息又使用 NWK(网络层)的功能。Exegin ZSDK 提供了通用的 ZCL 基础服务,而集群模板则在这些通用服务之上实现了特定的功能。

在应用开发中,我们的工作就是在集群模板中填充设备特定的细节。例如,在服务器端实现如何控制物理灯泡的开关,在客户端实现如何读取物理开关的状态。

3. 属性操作:从定义到端到端通信

属性是 ZCL 集群的核心组成部分,它代表了设备的状态或配置信息。理解属性的工作原理是开发自定义集群的基础。

3.1 属性的定义与添加

每个属性都由一个唯一的标识符、数据类型和访问权限组成。在 ZSDK 中,我们使用struct ZbZclAttrT结构来定义属性。例如,下面是 "开 / 关" 集群中 "开启时间" 属性的定义:

static const struct ZbZclAttrT attr_list[] = {
    {
        ZCL_ONOFF_ATTR_ON_TIME,
        ZCL_DATATYPE_UNSIGNED_16BIT,
        ZCL_ATTR_FLAG_NONE,
        0,
        NULL,
        {0, 0},
        {0, 0}
    },
};

定义好属性列表后,我们使用ZbZclAttrAppendList()函数将属性添加到集群中:

ZbZclAttrAppendList(cluster, attr_list, ZCL_ATTR_LIST_LEN(attr_list));

3.2 属性的两种管理方式

ZSDK 提供了两种属性管理方式,开发者可以根据应用需求选择:

方式一:协议栈自动管理

这是最简单的方式,如上面的例子所示,将属性回调函数设置为 NULL。在这种情况下,ZCL 集群基库会自动管理属性的值。当收到读取请求时,基库会直接返回当前存储的值;当收到写入请求时,基库会更新存储的值。

应用程序可以通过ZbZclAttrIntegerWrite()等函数来更新属性的值。这种方式的优点是简单易用,不需要编写额外的回调代码。

方式二:应用回调管理

如果需要在属性被读取或写入时执行特定的操作(例如直接读写硬件寄存器),可以为属性提供一个回调函数。例如:

enum ZclStatusCodeT on_time_cb(struct ZbZclClusterT *clusterPtr, struct ZbZclAttrCbInfoT *info)
{
    uint8_t *data = info->attr_data;

    switch(info->type) {
        case ZCL_ATTR_CB_TYPE_READ:
            /* 从硬件寄存器读取值 */
            data[0] = on_time[0];
            data[1] = on_time[1];
            break;
        case ZCL_ATTR_CB_TYPE_WRITE:
            /* 写入值到硬件寄存器 */
            on_time[0] = data[0];
            on_time[1] = data[1];
            break;
        case ZCL_ATTR_CB_TYPE_NOTIFY:
            /* 处理属性通知 */
            on_time[0] = data[0];
            on_time[1] = data[1];
            break;
    }

    return ZCL_STATUS_SUCCESS;
}

在使用回调方式时,需要在属性定义中设置相应的标志位:

  • ZCL_ATTR_FLAG_CB_READ:读取属性时调用回调
  • ZCL_ATTR_FLAG_CB_WRITE:写入属性时调用回调

AN5491 建议开发者要么同时设置这两个标志并提供完整的回调函数,要么不设置任何标志并将回调设为 NULL。不建议只处理读取或只处理写入请求,因为这可能导致读取和写入的值不一致。

3.3 属性读取的端到端流程

下面以客户端读取服务器的 "开启时间" 属性为例,说明属性操作的完整流程:

  1. 客户端应用调用ZbZclReadReq()函数,指定要读取的属性和回调函数
  2. ZCL 集群基库构建 ZCL 读取属性请求消息,并通过无线发送到服务器
  3. 服务器的 ZCL 集群基库收到请求后,检查属性是否存在
  4. 如果属性存在,根据属性的管理方式获取当前值(直接读取存储的值或调用回调函数)
  5. 服务器构建 ZCL 读取属性响应消息,并发送回客户端
  6. 客户端的 ZCL 集群基库收到响应后,调用应用提供的回调函数,将结果传递给应用

4. 指令处理:客户端请求与服务器响应

除了属性操作外,指令是 ZCL 集群的另一个重要组成部分。指令用于触发服务器执行特定的操作,例如打开灯光、设置温度等。

4.1 指令的基本结构

每个 ZCL 指令都有一个唯一的指令 ID 和特定的有效负载格式。例如,智能能源标准中的 "GetCalendar" 指令包含以下字段:

  • 最早开始时间(UTC 时间)
  • 最小颁发者事件 ID(32 位无符号整数)
  • 日历数量(8 位无符号整数)
  • 日历类型(8 位枚举)
  • 提供者 ID(32 位无符号整数)

对应的响应指令 "PublishCalendar" 则包含更复杂的结构,包括提供者 ID、颁发者事件 ID、日历 ID、开始时间、日历类型、日历名称、季节数量等多个字段。

4.2 客户端指令生成与发送

在客户端,每个指令都有一个对应的请求函数,用于构建和发送指令。例如,"GetCalendar" 指令的请求函数是ZbZclCalClientCommandGetCalReq()

客户端应用使用这个函数的步骤如下:

static void get_cal_cb(struct ZbZclCommandRspT *rsp, void *arg)
{
    struct application *app = (struct application *)arg;

    /* 处理响应 */
    if(rsp->status == ZCL_STATUS_SUCCESS) {
        /* 检查响应类型 */
        if((rsp->hdr.frameCtrl.frameType & ZCL_FRAMECTRL_TYPE) == ZCL_FRAMETYPE_CLUSTER &&
           rsp->hdr.cmdId == ZCL_CAL_SVR_PUBLISH_CALENDAR) {
            /* 解析PublishCalendar响应 */
            struct ZbZclCalServerPublishCalendarT cal;
            ZbZclCalClientParsePublishCalendar(rsp->payload, rsp->length, &cal);
            /* 处理日历数据 */
        } else if(rsp->hdr.cmdId == ZCL_COMMAND_DEFAULT_RESPONSE) {
            /* 解析默认响应 */
            struct ZbZclDefaultResponseT def_rsp;
            ZbZclParseDefaultResponse(rsp->payload, rsp->length, &def_rsp);
            /* 处理错误状态 */
        }
    } else {
        /* 处理传输错误 */
        if(rsp->aps_status == ZB_APS_STATUS_NO_ACK) {
            /* 服务器未确认 */
        } else if(rsp->aps_status == ZB_WPAN_STATUS_NO_ACK) {
            /* 网络连接问题 */
        }
    }
}

/* 发送GetCalendar请求 */
struct ZbZclCalClientGetCalendarT req;
memset(&req, 0, sizeof(req));
req.earliestStartTime = app->calendar_start;
req.minIssuerEventId = ZCL_INVALID_UNSIGNED_32BIT;
req.numCalendars = 1;
req.calendarType = 0x02;
req.providerId = app->provider_id;

status = ZbZclCalClientCommandGetCalReq(cluster, ZbApsAddrBinding, &req, get_cal_cb, app);

4.3 服务器端指令接收与处理

在服务器端,每个指令都有一个对应的回调函数。当服务器收到客户端的指令时,ZCL 集群基库会解析指令,并调用相应的回调函数。

服务器端处理 "GetCalendar" 指令的代码如下:

static enum ZclStatusCodeT get_calendar(struct ZbZclClusterT *clusterPtr, void *arg,
                                        struct ZbZclCalClientGetCalendarT *req,
                                        struct ZbZclAddrInfoT *srcInfo)
{
    struct ZbZclCalServerPublishCalendarT rsp;
    memset(&rsp, 0, sizeof(rsp));

    /* 根据请求条件查找匹配的日历 */
    /* 填充响应数据 */
    rsp.providerId = req->providerId;
    rsp.issuerEventId = 12345;
    rsp.issuerCalendarId = 67890;
    rsp.startTime = req->earliestStartTime;
    rsp.calendarType = req->calendarType;
    /* ... 填充其他字段 ... */

    /* 发送PublishCalendar响应 */
    ZbZclCalServerSendPublishCalendar(clusterPtr, srcInfo, &rsp);

    /* 返回成功且不发送默认响应,因为我们已经发送了特定响应 */
    return ZCL_STATUS_SUCCESS_NO_DEFAULT_RESPONSE;
}

/* 注册回调函数 */
struct ZbZclCalServerCallbacksT callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.get_calendar = get_calendar;

/* 创建服务器集群 */
cluster = ZbZclCalServerAlloc(zb, endpoint, &callbacks, arg);

4.4 响应的三种情况

客户端在发送指令后,可能会收到以下三种情况的响应:

  1. 成功收到特定响应:服务器成功处理了指令,并返回了指令特定的响应(如 PublishCalendar)。此时rsp->statusZCL_STATUS_SUCCESS,帧类型为ZCL_FRAMETYPE_CLUSTER,指令 ID 为预期的响应指令 ID。
  2. 收到默认响应:如果指令没有定义特定的响应,或者服务器处理指令时发生错误,服务器会返回默认响应。此时rsp->status仍为ZCL_STATUS_SUCCESS,但指令 ID 为ZCL_COMMAND_DEFAULT_RESPONSE。默认响应中包含一个状态代码,指示指令处理的结果。
  3. 传输错误:如果请求无法发送到服务器,或者服务器没有响应,rsp->status将为ZCL_STATUS_FAILURE。此时需要检查rsp->aps_status字段,了解具体的错误原因。常见的错误包括:
    • ZB_APS_STATUS_NO_ACK:服务器收到了请求但没有确认
    • ZB_WPAN_STATUS_NO_ACK:无法到达网络中的下一跳,通常表示网络连接问题

通过本文的解读,我们了解了在 STM32WB 系列上开发自定义 Zigbee 集群的基本原理和方法。AN5491 应用笔记为我们提供了一个清晰的指导,让我们能够按照与标准集群相同的方式构建自定义集群。

在开发自定义集群时,有以下几点建议:

  1. 优先使用标准集群:如果标准集群能够满足需求,尽量使用标准集群,这样可以确保设备的互操作性。只有在标准集群无法满足需求时,才考虑开发自定义集群。
  2. 遵循 ZCL 规范:在设计自定义集群时,尽量遵循 ZCL 规范的设计原则和命名约定,这样可以使代码更易于理解和维护。
  3. 合理选择属性管理方式:对于简单的状态属性,可以使用协议栈自动管理方式;对于需要直接与硬件交互的属性,使用回调管理方式。
  4. 处理所有可能的响应情况:在客户端代码中,一定要处理所有三种可能的响应情况,包括成功响应、默认响应和传输错误。
  5. 测试互操作性:开发完成后,务必进行充分的测试,特别是与其他 Zigbee 设备的互操作性测试。

STM32WB 系列结合 Exegin ZSDK 为 Zigbee 应用开发提供了强大的平台。通过掌握自定义集群的开发技术,我们可以开发出功能更丰富、更具竞争力的物联网产品。

收藏 评论0 发布时间:2026-6-11 14:59

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版