跳转至

星闪开发指南

本文以仓库中的星闪 Native 示例为基础,说明 SLE 协议栈、中央设备(Central)/外围设备(Peripheral)角色和 SSAP 数据业务的开发方法。按照本文可以完成扫描、广播、配对、服务发现、读写和通知联调,并在真实示例上扩展自己的星闪业务。这里的 Central 和 Peripheral 是 SLE 协议及 SDK 接口中的连接角色,不是名为 Peripheral 的模块或特性。

星闪驱动背景知识

模块职责与调用关系

SLE(SparkLink Low Energy,星闪低功耗接入)用于设备间近距离无线通信。SDK 的 SLE Service 位于应用与协议栈之间,负责角色和本地信息配置、协议栈生命周期、发现、配对、连接以及 SSAP 客户端/服务端业务。

星闪模块调用关系图

接口返回成功通常表示请求已提交。协议栈使能、发现、配对、连接、服务发现和数据收发均包含异步阶段,应用必须根据相应回调推进状态,不能仅根据接口同步返回值判断业务已经完成。

中央设备、外围设备与 SSAP

本示例同时提供两个角色:

角色 主要行为 示例入口
中央设备角色(客户端) 扫描外围设备、发起配对、发现 SSAP 服务、读写特征值、接收通知或指示。 sle_client.c
外围设备角色(服务端) 注册 SSAP 服务、广播自身、处理读写请求、主动发送通知或指示。 sle_server.c

一次完整业务由以下几层组成:

  1. 使用 bs_sle_config_template()bs_sle_config() 配置模板、角色、本地地址和名称。
  2. 注册协议栈状态、发现、配对和连接回调,再调用 bs_sle_enable()
  3. 外围设备角色注册 SSAP 服务并开始广播;中央设备角色开始扫描并从日志或应用筛选逻辑取得对端地址。
  4. Central 发起配对,等待连接回调确认建链,然后交换 MTU 和版本信息。
  5. 中央设备角色发现服务并保存特征句柄,随后完成读写;外围设备角色可使用设备 ID、服务端 ID、实例 ID 和项 ID 主动通知。

星闪 Central 示例设计流程

回调与对象生命周期

  • bs_sle_stack_state_callbacks_t 上报协议栈使能和去使能状态。bs_sle_enable() 成功后仍需等待 SLE_STATE_ENABLED
  • bs_sle_callbacks_t 上报发现结果、发现状态、配对结果和连接状态。发现回调中的地址如果要交给其他任务使用,应复制地址内容。
  • 连接回调给出的 dev_id 是 SSAP 读写、服务发现和通知所需的连接标识。断开后不能继续使用旧的设备 ID。
  • SSAP 客户端通过 bs_sle_ssapc_operation_cbk_t 接收读、写、通知和指示结果;SSAP 服务端通过 bs_sle_ssaps_callbacks_t 处理服务启动、读写请求和指示确认。

示例中的 bt_sle_init()bt_sle_deinit()bt_sle_switch_discovery() 是位于 /samples/native_samples/sle/sle_com.c 的示例辅助函数,不是 SDK 公共 API。它们把多个公开接口和状态管理封装为 AT 场景使用的公共流程。

核心数据结构

数据结构 用途
sle_addr_t SLE 设备地址。
bs_sle_config_role_t 本端中央设备(Central)/外围设备(Peripheral)角色。
bs_sle_connection_result_t 连接状态、设备 ID、加密状态、失败原因和本端角色。
bs_sle_ssapc_write_param_t Central 写请求的句柄、类型、确认方式和数据。
bs_sle_ssaps_service_template_t Peripheral 的服务 UUID、服务项和回调模板。
bs_sle_ssaps_ntf_ind_t Peripheral 通知或指示的实例、项和数据。

示例使用 bt_stack_info_t 保存栈、扫描、广播和连接状态:

typedef struct {
    bt_stack_state_t stack_status;
    bt_discovery_state_t scan_status;
    bt_discovery_state_t adv_status;
    bs_sle_connect_state_t conn_status;
} bt_stack_info_t;

业务扩展时至少应保存“协议栈状态、当前角色、发现状态、连接状态、设备 ID、客户端/服务端 ID、服务实例 ID 和特征句柄”,并在断开、去使能或服务重建时同步清理。

主要 API

阶段 接口 作用
配置 bs_sle_config_templatebs_sle_config 选择配置模板并设置角色、名称和地址。
生命周期 bs_sle_register_stack_state_callbacksbs_sle_enablebs_sle_disable 注册状态回调并使能或去使能协议栈。
发现与连接 bs_sle_register_callbacksbs_sle_start_discoverybs_sle_stop_discoverybs_sle_start_pair 接收发现/连接事件,控制扫描或广播并发起配对。
SSAP 客户端 bs_sle_ssapc_registerbs_sle_ssapc_exchange_info_reqbs_sle_ssapc_find_all_servicebs_sle_ssapc_register_callbacksbs_sle_ssapc_readbs_sle_ssapc_write 注册客户端,交换信息,发现服务并读写数据。
SSAP 服务端 bs_sle_ssaps_register_serverbs_sle_ssaps_service_instance_initbs_sle_ssaps_notify_indicate 注册服务端和服务实例,并向 Central 主动发送数据。

快速跑通星闪 Demo

功能说明

真实示例归档在 /samples/native_samples/sle/README.md,已由 diting-community 目标中的 CONFIG_ENABLE_SLE_SAMPLE 纳入构建。示例通过 AT 命令切换角色,使用两块开发板完成以下闭环:

  • 外围设备角色注册一个包含可读写特征和描述符的 SSAP 服务并广播。
  • Central 扫描、配对、交换 MTU/版本、发现服务并取得句柄。
  • Central 向特征写入 hello server,再读取特征数据。
  • 外围设备角色向中央设备角色发送 hello client 通知。

准备工作

准备两块 HiDiTing V100 开发板、两路可用串口以及同一版本的固件。两块板分别配置为外围设备角色和中央设备角色;同一块设备在一次验证中不要同时执行两个角色的使能命令。

三种开发环境均可完成构建和烧录,推荐使用一站式 CLI:

开发环境 适用场景 使用指南
一站式 CLI(推荐) 快速选择目标、构建、烧录和监视串口。 一站式 CLI 开发环境使用指南
HiSpark Studio for VS Code 图形化编辑、构建、烧录和调试。 HiSpark Studio for VS Code 开发环境使用指南
WSL 与 Docker 在 Windows 上使用一致的 Linux 容器构建环境。 WSL 与 Docker 环境使用指南

构建和烧录

在已创建的一站式 CLI 工程中执行:

fbb set-target pack_diting_community
fbb build

使用一站式 CLI 烧写完整固件并打开 UART2 串口监视器。以下为 Windows USB DFU 示例;将 COM3 替换为实际日志串口,其他平台和串口烧写参数参见一站式 CLI 开发环境使用指南

fbb flash -f "$env:FBB_SDK_DIR\output\3322\fwpkg\diting-community.fwpkg" --chip 3322 -d --timeout 180
fbb monitor --port COM3 --baud 750000

AT 命令

角色 命令 作用
中央设备角色 AT+SLECLIENTENABLE 配置中央设备模板、角色、名称和地址,注册公共回调并使能协议栈。
中央设备角色 AT+SLECLIENTINIT 注册 SSAP 客户端并取得客户端 ID。
中央设备角色 AT+SLECLIENTSCAN=0/1 0 启动扫描,1 停止扫描。
中央设备角色 AT+SLECLIENTPAIR=a1,a2,a3,a4,a5,a6 使用扫描日志中的 6 字节地址发起配对。
中央设备角色 AT+SLECLIENTFIND 发现对端全部 SSAP 服务并输出实例和项句柄。
中央设备角色 AT+SLECLIENTCB 为已发现的应用 UUID 注册读、写、通知和指示回调。
中央设备角色 AT+SLECLIENTWRITE=handle 向指定句柄写入 hello server
中央设备角色 AT+SLECLIENTREAD=handle,type 读取指定句柄,示例中 type 使用 0
外围设备角色 AT+SLESERVERENABLE 配置外围设备模板、角色、名称和地址,注册公共回调并使能协议栈。
外围设备角色 AT+SLESERVERINIT 注册 SSAP 服务端并创建服务实例。
外围设备角色 AT+SLESERVERBROADCAST=0/1 0 启动广播,1 停止广播。
外围设备角色 AT+SLESERVERNOTIFY=inst_id,item_id 向已连接的中央设备发送 hello client

AT 命令发送方式如下:

AT 命令使用方法

外围设备角色操作

依次发送:

AT+SLESERVERENABLE
AT+SLESERVERINIT
AT+SLESERVERBROADCAST=0

每条命令均应先返回 OK,并出现对应的协议栈或服务日志。

使能和初始化

启用星闪服务端

初始化星闪服务端

启动广播

启动星闪广播

建立连接后,示例的连接状态回调会自动停止外围设备广播;断开后会自动重新广播。需要手动结束广播时发送 AT+SLESERVERBROADCAST=1

停止星闪广播

Central 操作

先使能协议栈并注册 SSAP 客户端:

AT+SLECLIENTENABLE
AT+SLECLIENTINIT
AT+SLECLIENTSCAN=0

启用星闪客户端

初始化星闪客户端

启动星闪扫描

扫描日志会输出类似 11:23:34:45:33:64 的地址。将实际地址逐字节填入配对命令:

AT+SLECLIENTPAIR=0x11,0x23,0x34,0x45,0x33,0x64

连接星闪服务端

配对接口是异步请求。示例会等待连接状态回调确认 SLE_CONNECT_CONNECTED,再交换 MTU 和版本信息;出现 pair request submitted 但随后超时,应检查 Peripheral 是否仍在广播以及地址是否正确。

连接后可停止扫描并发现服务:

AT+SLECLIENTSCAN=1
AT+SLECLIENTFIND

停止星闪扫描

服务发现日志分段展示服务、特征和描述符信息:

发现星闪服务第一部分

发现星闪服务第二部分

发现星闪服务第三部分

从日志中记录目标特征的句柄,然后注册回调并完成读写:

AT+SLECLIENTCB
AT+SLECLIENTWRITE=<特征句柄>
AT+SLECLIENTREAD=<特征句柄>,0

注册星闪客户端回调

写入星闪特征数据

读取星闪特征数据

外围设备角色发送通知

连接日志和服务启动回调会给出设备 ID、服务端 ID 和实例 ID;服务发现日志会给出项 ID。在外围设备侧串口发送:

AT+SLESERVERNOTIFY=<实例ID>,<特征项ID>

星闪服务端发送通知

Central 应进入 notification_cb 并打印通知数据。命令返回成功只表示通知请求已提交;业务若使用指示或要求确认,还应处理对应确认回调。

通过标准

以下结果同时满足才算完成通信闭环:

  1. 两端都收到 SLE_STATE_ENABLED,且没有配置或回调注册错误。
  2. 中央设备扫描日志中的名称和地址与外围设备配置一致。
  3. 连接回调报告 SLE_CONNECT_CONNECTED,Central 的 MTU/版本交换成功。
  4. 服务发现输出服务 UUID、特征 UUID 和有效句柄。
  5. 中央设备写回调成功,外围设备收到写请求;中央设备读回调返回数据。
  6. 外围设备通知请求成功,中央设备的通知回调收到 hello client

文件结构与代码走读

文件结构

samples/native_samples/sle/
├── CMakeLists.txt
├── README.md
├── sle_com.c
├── sle_com.h
├── sle_client.c
├── sle_client.h
├── sle_server.c
└── sle_server.h
文件 职责 重点入口
CMakeLists.txt 构建 sle_sample 组件并导出 AT 注册宏。 AT_DITING_EXAMPLE_SLE
sle_com.c/.h 管理协议栈和连接状态,注册公共回调,切换扫描/广播。 bt_sle_init()bt_sle_deinit()bt_sle_switch_discovery()
sle_client.c/.h 实现 Central 的 AT 参数、配对、服务发现和 SSAP 读写。 at_sle_client_enable()at_client_connect_to_peripheral()sle_ssapc_write()
sle_server.c/.h 定义服务模板,处理读写请求并发送通知。 service_templatesle_demo_service_init()sle_ssaps_notify_indicate()

构建接入

/samples/native_samples/sle/CMakeLists.txt 将三个实现文件编入 sle_sample,并定义 AT_DITING_EXAMPLE_SLE

set(COMPONENT_NAME "sle_sample")

set(SOURCES
    ${CMAKE_CURRENT_SOURCE_DIR}/sle_com.c
    ${CMAKE_CURRENT_SOURCE_DIR}/sle_client.c
    ${CMAKE_CURRENT_SOURCE_DIR}/sle_server.c
)

set(PUBLIC_DEFINES
    AT_DITING_EXAMPLE_SLE
)

set(WHOLE_LINK true)
build_component()

上层 /samples/native_samples/CMakeLists.txt 只在目标包含 CONFIG_ENABLE_SLE_SAMPLE 时加入该目录。at_adapter.c 检测 AT_DITING_EXAMPLE_SLE 后调用 at_diting_sle_example_cmd_register(),因此无需在应用启动代码中重复注册命令。

协议栈初始化

bt_sle_init() 的核心顺序是“创建状态锁 → 注册协议栈状态回调 → 注册发现/连接回调 → 标记初始化中 → 使能协议栈”:

static bs_sle_stack_state_callbacks_t sle_cbk = {
    .stack_state_cbk = process_sle_stack_state_change_cbk,
};

static bs_sle_callbacks_t sle_cb = {
    .connect_state_cb = process_sle_connect_state_changed_cb,
    .pair_complete_cb = process_sle_pair_cb,
    .pair_remove_cb = process_sle_remove_device_cb,
    .discovery_result_cb = process_sle_discover_cb,
    .pair_request_cb = process_sle_pair_request_cb,
    .discovery_state_cb = process_sle_discovery_state_cb,
};

ret = bs_sle_register_stack_state_callbacks(0, &sle_cbk);
ret = bs_sle_register_callbacks(0, &sle_cb);
ret = bs_sle_enable();

正式业务应逐次判断 ret,任一步失败都停止后续操作。示例的完整错误处理见 /samples/native_samples/sle/sle_com.c

Central 配对与服务发现

at_client_connect_to_peripheral() 将 AT 参数转换为 sle_addr_t,调用 bs_sle_start_pair() 后等待连接回调确认建链:

sle_addr_t sle_addr = {
    .type = SLE_SLE_ADDR_TYPE,
};
sle_addr.addr[0] = (uint8_t)addr->mac_addr1;
sle_addr.addr[1] = (uint8_t)addr->mac_addr2;
sle_addr.addr[2] = (uint8_t)addr->mac_addr3;
sle_addr.addr[3] = (uint8_t)addr->mac_addr4;
sle_addr.addr[4] = (uint8_t)addr->mac_addr5;
sle_addr.addr[5] = (uint8_t)addr->mac_addr6;

ret = bs_sle_start_pair(&sle_addr);
while (!bt_sle_is_connected() && wait_count < SLE_PAIR_WAIT_COUNT) {
    osal_msleep(SLE_PAIR_WAIT_INTERVAL_MS);
    wait_count++;
}

连接成功后,sle_ssapc_find_all_service() 通过回调输出服务、特征、描述符和句柄。生产应用不应仅打印结果,而应按目标 UUID 保存句柄,并在断开连接时清除。

SSAP 写请求

Central 使用 bs_sle_ssapc_write_param_t 组织写请求:

uint8_t *data = (uint8_t *)"hello server";
bs_sle_ssapc_write_param_t param = {
    .handle = handle,
    .type = SLE_WRITE_PARAM_TYPE,
    .is_need_confirm = SLE_WRITE_NEED_CONFIRM,
    .data_len = strlen((char *)data),
    .data = data,
};

ret = bs_sle_ssapc_write(dev_id, client_id, &param);

数据缓冲区在协议栈完成使用前必须保持有效。示例使用静态字符串;业务若使用任务栈上的临时缓冲区,应根据接口的复制和回调语义确认生命周期。

外围设备服务与通知

service_template/samples/native_samples/sle/sle_server.c 中定义服务 UUID、可读写特征、描述符、权限、初始值和服务端回调。服务初始化只保留两个关键调用:

ret = bs_sle_ssaps_register_server(&g_sle_app_uuid, &g_sle_demo_server_id);
ret = bs_sle_ssaps_service_instance_init(
    g_sle_demo_server_id,
    (bs_sle_ssaps_service_template_t *)&service_template,
    &g_sle_demo_mobile_inst_id);

主动通知使用连接回调保存的设备 ID 以及服务实例和项 ID:

uint8_t *value = (uint8_t *)"hello client";
bs_sle_ssaps_ntf_ind_t param = {
    .inst_id = inst_id,
    .item_id = item_id,
    .type = SLE_NOTIFY_PARAM_TYPE,
    .value_len = strlen((char *)value),
    .value = value,
};

ret = bs_sle_ssaps_notify_indicate(device_id, server_id, &param);

完整服务模板、读写请求回调和异常分支直接查看 /samples/native_samples/sle/README.md,文档不重复复制整个源文件。

基于星闪 Demo 开发自己的应用

1. 复制并接入组件

复制 samples/native_samples/sle 为新的业务目录,修改 COMPONENT_NAME,并在 samples/native_samples/CMakeLists.txt 中增加受独立配置宏控制的 add_subdirectory_if_exist()。在目标 config.py 中加入该宏后重新完整构建。

如果只是验证业务协议,建议先直接修改本示例中的 UUID、数据和回调;流程稳定后再拆成独立组件,便于区分“链路问题”和“组件接入问题”。

2. 设计服务模型

  1. 为产品分配服务 UUID 和特征 UUID,避免继续使用示例 UUID。
  2. 为每个特征确定读、写、通知或指示属性,以及访问权限和最大长度。
  3. service_template.item_array 中建立特征和描述符,确保 item_countdesc_count 与数组内容一致。
  4. 在读写请求回调中校验 device_id、实例、项、偏移和长度,再访问业务缓冲区。

3. 建立应用状态机

推荐按以下事件推进业务:

未使能
  → 协议栈已使能
  → 正在发现
  → 正在配对
  → 已连接
  → 信息交换完成
  → 服务发现完成
  → 可读写和通知

任一步失败都保留错误码和回调原因,并回到明确状态。断开时清除设备 ID、句柄和订阅状态;是否重新扫描或自动回连由产品策略决定。

4. 替换业务数据

  • Central 写数据:修改 sle_ssapc_write() 的数据来源,并保留长度和生命周期校验。
  • 外围设备读写处理:修改 sle_read_request_callback()sle_write_request_callback(),将协议数据转换为业务事件。
  • 外围设备主动上报:修改 sle_ssaps_notify_indicate(),使用真实实例/项 ID 和稳定缓冲区。
  • Central 接收上报:修改 sle_ssap_client_notification_cbk()sle_ssap_client_indication_cbk(),回调内只做轻量校验、复制和投递。

5. 验证

至少覆盖以下场景:

  • 正常使能、发现、配对、服务发现、读写、通知和断开。
  • 对端不存在、地址错误、配对超时和重复配对。
  • 句柄错误、长度越界、没有写权限和通知未订阅。
  • 断开后重连、外围设备恢复广播、旧设备 ID 和旧句柄不再使用。
  • 多次启停后没有重复注册、互斥锁错误或资源泄漏。

注意事项

  • 中央设备角色和外围设备角色的模板必须匹配;切换角色前先完成上一角色的停止和去使能流程。
  • bs_sle_enable()bs_sle_start_pair()、服务发现、读写和通知均涉及异步事件,必须结合回调确认最终状态。
  • 示例中的地址、UUID、句柄、实例 ID 和项 ID 只用于演示,不可直接固化为量产配置。
  • 服务发现完成前不能执行读写;回调注册完成前不能期望收到读写结果、通知或指示。
  • 回调可能运行在协议栈上下文中,不要在回调内长时间阻塞、睡眠或执行文件操作。将数据复制后投递到应用任务。
  • 外围设备角色已在连接状态回调中实现“连接后停止广播、断开后恢复广播”,无需在正常配对流程中手动重复操作。
  • 若产品已有统一 SLE 生命周期管理模块,业务组件应复用其协议栈状态,不能由多个模块重复使能或去使能全局协议栈。

常见错误

现象 原因 处理方法
找不到 ssf_*.h 组件没有使用 SLE Service 头文件路径,或目标未包含相关能力。 对照示例 CMakeLists.txt 和目标中的 SUPPORT_SLE_SERVICEsle_service 配置。
bs_sle_* 链接失败 仅包含头文件但没有链接 SLE Service 组件。 使用包含 sle_service 的目标,并确认新增组件继承了正确目标配置。
串口提示未知命令 示例未参与构建或 AT 注册宏未生效。 检查 CONFIG_ENABLE_SLE_SAMPLEAT_DITING_EXAMPLE_SLEat_adapter.c 注册路径。
使能后扫描不到设备 协议栈尚未就绪、角色/发现类型不匹配或外围设备没有广播。 先确认 SLE_STATE_ENABLED,再核对两端角色、地址、名称和广播日志。
配对请求已提交但超时 地址错误、外围设备已停止广播或连接回调没有到达。 使用本次扫描输出的地址,检查两端连接回调中的状态、原因和本端角色。
服务发现为空 SSAP 服务未初始化、尚未连接或使用了错误设备 ID。 检查 AT+SLESERVERINIT、连接回调和 MTU/版本交换结果。
读写没有回调 未注册客户端回调、句柄错误或特征权限不允许。 先执行服务发现和 AT+SLECLIENTCB,再使用本次发现得到的特征句柄。
通知失败 设备 ID、服务端 ID、实例 ID 或项 ID 无效,或连接已断开。 从当前连接和服务启动日志取得 ID,并确认 Central 仍处于连接状态。