星闪开发指南
本文以仓库中的星闪 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 |
一次完整业务由以下几层组成:
- 使用
bs_sle_config_template()和bs_sle_config()配置模板、角色、本地地址和名称。 - 注册协议栈状态、发现、配对和连接回调,再调用
bs_sle_enable()。 - 外围设备角色注册 SSAP 服务并开始广播;中央设备角色开始扫描并从日志或应用筛选逻辑取得对端地址。
- Central 发起配对,等待连接回调确认建链,然后交换 MTU 和版本信息。
- 中央设备角色发现服务并保存特征句柄,随后完成读写;外围设备角色可使用设备 ID、服务端 ID、实例 ID 和项 ID 主动通知。
回调与对象生命周期
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_template、bs_sle_config | 选择配置模板并设置角色、名称和地址。 |
| 生命周期 | bs_sle_register_stack_state_callbacks、bs_sle_enable、bs_sle_disable | 注册状态回调并使能或去使能协议栈。 |
| 发现与连接 | bs_sle_register_callbacks、bs_sle_start_discovery、bs_sle_stop_discovery、bs_sle_start_pair | 接收发现/连接事件,控制扫描或广播并发起配对。 |
| SSAP 客户端 | bs_sle_ssapc_register、bs_sle_ssapc_exchange_info_req、bs_sle_ssapc_find_all_service、bs_sle_ssapc_register_callbacks、bs_sle_ssapc_read、bs_sle_ssapc_write | 注册客户端,交换信息,发现服务并读写数据。 |
| SSAP 服务端 | bs_sle_ssaps_register_server、bs_sle_ssaps_service_instance_init、bs_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 工程中执行:
使用一站式 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 命令发送方式如下:

外围设备角色操作
依次发送:
每条命令均应先返回 OK,并出现对应的协议栈或服务日志。
使能和初始化


启动广播

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

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



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

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

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



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



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

Central 应进入 notification_cb 并打印通知数据。命令返回成功只表示通知请求已提交;业务若使用指示或要求确认,还应处理对应确认回调。
通过标准
以下结果同时满足才算完成通信闭环:
- 两端都收到
SLE_STATE_ENABLED,且没有配置或回调注册错误。 - 中央设备扫描日志中的名称和地址与外围设备配置一致。
- 连接回调报告
SLE_CONNECT_CONNECTED,Central 的 MTU/版本交换成功。 - 服务发现输出服务 UUID、特征 UUID 和有效句柄。
- 中央设备写回调成功,外围设备收到写请求;中央设备读回调返回数据。
- 外围设备通知请求成功,中央设备的通知回调收到
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_template、sle_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, ¶m);
数据缓冲区在协议栈完成使用前必须保持有效。示例使用静态字符串;业务若使用任务栈上的临时缓冲区,应根据接口的复制和回调语义确认生命周期。
外围设备服务与通知
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, ¶m);
完整服务模板、读写请求回调和异常分支直接查看 /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. 设计服务模型
- 为产品分配服务 UUID 和特征 UUID,避免继续使用示例 UUID。
- 为每个特征确定读、写、通知或指示属性,以及访问权限和最大长度。
- 在
service_template.item_array中建立特征和描述符,确保item_count、desc_count与数组内容一致。 - 在读写请求回调中校验
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_SERVICE、sle_service 配置。 |
bs_sle_* 链接失败 |
仅包含头文件但没有链接 SLE Service 组件。 | 使用包含 sle_service 的目标,并确认新增组件继承了正确目标配置。 |
| 串口提示未知命令 | 示例未参与构建或 AT 注册宏未生效。 | 检查 CONFIG_ENABLE_SLE_SAMPLE、AT_DITING_EXAMPLE_SLE 和 at_adapter.c 注册路径。 |
| 使能后扫描不到设备 | 协议栈尚未就绪、角色/发现类型不匹配或外围设备没有广播。 | 先确认 SLE_STATE_ENABLED,再核对两端角色、地址、名称和广播日志。 |
| 配对请求已提交但超时 | 地址错误、外围设备已停止广播或连接回调没有到达。 | 使用本次扫描输出的地址,检查两端连接回调中的状态、原因和本端角色。 |
| 服务发现为空 | SSAP 服务未初始化、尚未连接或使用了错误设备 ID。 | 检查 AT+SLESERVERINIT、连接回调和 MTU/版本交换结果。 |
| 读写没有回调 | 未注册客户端回调、句柄错误或特征权限不允许。 | 先执行服务发现和 AT+SLECLIENTCB,再使用本次发现得到的特征句柄。 |
| 通知失败 | 设备 ID、服务端 ID、实例 ID 或项 ID 无效,或连接已断开。 | 从当前连接和服务启动日志取得 ID,并确认 Central 仍处于连接状态。 |