1.1、实验目的

本实验旨在帮助学习者掌握 OpenHarmony 轻量系统中 BLE(Bluetooth Low Energy,低功耗蓝牙)GATT 服务端 的基本使用方法。通过本实验,你将学会:

  1. 理解 BLE GAP(Generic Access Profile) 广播与连接机制,以及 GATT(Generic Attribute Profile) 服务/特征模型;
  2. 在开发板上实现 BLE UART 服务端,完成 GATT 服务注册、特征添加与广播启动;
  3. 使用 CMSIS-RTOS v2 创建独立的收发任务,实现与客户端的双向数据通信;
  4. 掌握 BLE 连接状态回调、Notify 通知发送与 Write 写请求接收的处理流程;
  5. 完成案例代码的编译、烧录,并与 d2_ble_connect_client 客户端案例进行双板联调。

1.2、实验内容

本案例在 LZ3863-星闪开发板上实现 BLE GATT 服务端 功能:初始化蓝牙协议栈并注册 UART 服务,启动可连接广播;连接建立后,通过 Notify 周期性向客户端发送数据,同时接收客户端写入的数据并打印到串口。

项目 说明
应用层源文件 ble_server_app.c(任务与收发逻辑)
协议栈封装 ble_server_lib.c / ble_server_lib.h(GATT 服务、广播、回调)
设备名称 ble_uart(GAP 本地名)/ ble_uart_server(广播名)
设备 MAC 地址 11:22:33:44:55:66
服务 UUID 0xABCD
TX 特征 UUID 0xCDEF(读 / 写 / Notify,服务端 → 客户端)
RX 特征 UUID 0xEFEF(写 / 无应答写,客户端 → 服务端)
发送数据 "hello ble:from server"
发送周期 约 1 秒(osDelay(100),tick = 10 ms)
接收缓冲区 g_ble_rx_data[256]
初始化入口 APP_FEATURE_INIT(ble_server_app)

典型联调拓扑:

┌─────────────────────┐    BLE 连接     ┌─────────────────────┐
│  开发板 A(本案例)   │ ◄────────────► │  开发板 B(d2 客户端)│
│  d1_ble_connect_    │  GATT Notify   │  d2_ble_connect_    │
│  server(Server)    │  GATT Write    │  client(Client)    │
│  广播: ble_uart_    │                │  扫描并连接服务端     │
│  server             │                │  发送: hello ble:     │
│  发送: hello ble:   │                │  from client        │
│  from server        │                │                     │
└─────────────────────┘                 └─────────────────────┘

说明:本案例为 BLE 服务端,需配合 d2_ble_connect_client 客户端案例或手机 BLE 调试工具完成联调。建议先烧录并启动服务端,再启动客户端进行扫描连接。

1.3、实验环境

项目 说明
硬件 两块 LZ3863-星闪开发板(服务端 + 客户端)、USB 数据线
软件 OpenHarmony v5.1.0 源码、hb 编译工具
联调案例 d2_ble_connect_client(客户端,烧录至第二块开发板)
调试工具 串口助手(波特率 115200,8N1)
案例路径 applications/sample/wifi-iot/app/d1_ble_connect_server/

注意:完整双向通信实验需要 两块开发板,分别烧录服务端固件(本案例)和客户端固件(d2_ble_connect_client)。


2、基础知识

2.1、BLE 协议栈概述

BLE(Bluetooth Low Energy) 是一种面向低功耗、短距离通信的无线技术,广泛应用于可穿戴设备、传感器和物联网场景。BLE 协议栈从下到上可分为:

层次 英文名称 主要功能
物理层 PHY 2.4 GHz 射频收发
链路层 LL 连接建立、数据包封装
主机控制器接口 HCI 控制器与主机间通信
逻辑链路控制与适配 L2CAP 多路复用、分段重组
属性协议 ATT 属性读写、句柄管理
通用属性配置文件 GATT 基于 ATT 的服务/特征抽象
通用访问配置文件 GAP 广播、扫描、连接、配对
安全管理协议 SMP 加密与配对安全

本案例主要涉及 GAP(广播与连接)和 GATT Server(服务与特征)两层。

2.2、GAP:广播与连接

GAP 定义了 BLE 设备的发现与连接方式。本案例服务端采用 可连接非定向广播(Connectable Undirected Advertising)

服务端启动
    ↓
注册 GATT 服务与特征
    ↓
配置广播数据(Flags + Appearance + Local Name)
    ↓
gap_ble_start_adv()  →  开始广播,等待客户端扫描连接
    ↓
客户端发起连接  →  GAP_BLE_STATE_CONNECTED
    ↓
连接断开  →  重新广播

本案例广播参数:

参数 说明
广播类型 BLE_ADV_TYPE_CONNECTABLE_UNDIRECTED 可连接、非定向
广播间隔 min=0x30, max=0x60 单位 0.625 ms
广播信道 37 / 38 / 39(默认全信道) BLE_ADV_CHANNEL_MAP_CH_DEFAULT
过滤策略 任意设备可扫描、可连接 SCAN_ANY_CONNECT_ANY
广播时长 永久 BTH_GAP_BLE_ADV_FOREVER_DURATION

2.3、GATT:服务与特征

GATT 采用 Client/Server 模型。服务端维护一个或多个 Service(服务),每个服务包含若干 Characteristic(特征),特征可附带 Descriptor(描述符)

本案例实现了一个 BLE UART 透传服务,结构如下:

Service: 0xABCD (BLE UART Service)
    ├── Characteristic TX: 0xCDEF
    │       属性: Read | Write | Write No Response | Notify
    │       描述符: Client Characteristic Configuration (0x2902)
    │       用途: 服务端通过 Notify 向客户端发送数据
    │
    └── Characteristic RX: 0xEFEF
            属性: Write | Write No Response
            用途: 客户端向服务端写入数据
角色 数据方向 本案例实现方式
服务端 → 客户端 TX 特征 Notify ble_server_send_input_report()gatts_notify_indicate()
客户端 → 服务端 RX 特征 Write ble_receive_write_req_cbk() → 写入 g_ble_rx_data

2.4、BLE 事件回调机制

BLE 协议栈通过回调函数异步通知应用层状态变化。本案例注册了 GAP 与 GATT Server 两类回调:

GAP 回调:

回调函数 触发时机 处理逻辑
ble_server_adv_enable_cbk 广播启动成功 打印日志
ble_server_adv_disable_cbk 广播停止 打印日志
ble_server_connect_change_cbk 连接状态变化 更新连接 ID/状态;断开时重新广播
ble_server_pair_result_cb 配对完成 打印配对结果

GATT Server 回调:

回调函数 触发时机 处理逻辑
ble_server_service_add_cbk 服务添加完成 添加 TX/RX 特征与描述符,启动服务
ble_server_characteristic_add_cbk 特征添加完成 保存 TX 特征值句柄(用于 Notify)
ble_server_descriptor_add_cbk 描述符添加完成 打印日志
ble_server_service_start_cbk 服务启动完成 所有服务就绪后启动广播
ble_receive_write_req_cbk 收到客户端写请求 将数据拷贝至 g_ble_rx_data,置位 g_ble_new_data_ready
ble_receive_read_req_cbk 收到客户端读请求 打印日志
ble_mtu_changed_cbk MTU 协商完成 打印 MTU 大小

2.5、软件调用层次

本案例的软件调用层次如下:

应用层(ble_server_app.c)
    ├── ble_server_app()          ← APP_FEATURE_INIT 注册入口
    ├── ble_rx_server()           ← 接收任务:打印客户端写入的数据
    └── ble_tx_server()           ← 发送任务:周期性 Notify 发送数据
            │
封装层(ble_server_lib.c)
    ├── ble_server_init()         ← 初始化:注册回调、启用 BLE、添加服务
    ├── ble_set_adv_data()        ← 配置广播数据
    ├── ble_start_adv()           ← 启动广播
    ├── ble_server_send_input_report() ← GATT Notify 发送
    └── ble_get_connection_state() ← 查询连接状态
            │
协议栈 / 驱动层
    ├── gap_ble_*()               ← GAP 广播与连接管理
    ├── gatts_*()                 ← GATT Server 服务/特征管理
    └── enable_ble()              ← 启用 BLE 协议栈

2.6、核心 API 介绍

2.6.1、头文件
#include "ble_server_lib.h"
#include "cmsis_os2.h"
#include "ohos_init.h"
2.6.2、应用层 API
API 名称 功能说明
ble_server_init() 初始化 BLE 服务端:注册回调、设置设备名/MAC、注册 GATT Server、添加 UART 服务
ble_get_connection_state() 获取连接状态,返回 0 表示未连接,非 0 表示已连接
ble_server_send_input_report(data, len) 通过 GATT Notify 向已连接客户端发送数据
osThreadNew(func, arg, &attr) 创建 RTOS 线程
osDelay(ticks) 线程延时,100 ticks ≈ 1 秒(tick = 10 ms)
APP_FEATURE_INIT(func) 注册应用特性初始化入口,系统启动后自动执行
2.6.3、协议栈 API(ble_server_lib.c 内部调用)
API 名称 功能说明
enable_ble() 启用 BLE 协议栈
gap_ble_set_local_name() 设置 GAP 本地设备名称
gap_ble_set_local_addr() 设置 BLE 设备 MAC 地址
gap_ble_set_adv_data() 设置广播数据与扫描响应数据
gap_ble_set_adv_param() / gap_ble_start_adv() 配置并启动广播
gatts_register_server() 注册 GATT Server
gatts_add_service() 添加 GATT 服务
gatts_add_characteristic() 添加特征
gatts_add_descriptor() 添加描述符(如 CCC)
gatts_start_service() 启动 GATT 服务
gatts_notify_indicate() 发送 Notify/Indicate 通知

3、程序设计

3.1、程序架构

本案例目录结构

d1_ble_connect_server/
├── ble_server_app.c      # 应用层:收发任务与入口
├── ble_server_lib.c      # 协议栈封装:GATT 服务、广播、回调
├── ble_server_lib.h      # 头文件:UUID 宏定义与 API 声明
├── BUILD.gn              # GN 编译配置
├── README_zh.md          # 案例简要说明
└── 实验手册.md            # 本实验手册

程序执行流程:

系统启动
    │
    ▼
ble_server_app()                ← APP_FEATURE_INIT 注册,自动执行
    │
    ├── ble_server_init()       ← 延时 3s → 注册回调 → 启用 BLE
    │       ├── 设置设备名/MAC
    │       ├── gatts_register_server()
    │       ├── ble_add_service()  → 添加 0xABCD 服务
    │       └── 回调链:添加特征 → 启动服务 → 开始广播
    │
    ├── osThreadNew(ble_rx_server)  ← 创建接收任务
    └── osThreadNew(ble_tx_server)  ← 创建发送任务
            │
            ▼
    ┌───────────────────────────────────────────┐
    │  ble_rx_server(接收任务)                  │
    │  轮询 g_ble_new_data_ready → 打印接收数据   │
    └───────────────────────────────────────────┘
    ┌───────────────────────────────────────────┐
    │  ble_tx_server(发送任务)                  │
    │  连接建立后 → 每 1s Notify 发送 send_date   │
    └───────────────────────────────────────────┘

3.2、源文件说明

文件 说明
ble_server_app.c 应用层主程序,定义收发数据缓冲区,创建 RX/TX 两个 RTOS 任务
ble_server_lib.c BLE 协议栈封装,实现 GATT 服务注册、广播配置、GAP/GATT 回调处理
ble_server_lib.h UUID 宏定义、数据结构、全局变量声明与 API 接口
BUILD.gn 编译配置,生成 ble_connect_server 静态库

3.3、关键代码分析

(1)UUID 与服务配置
#define BLE_UUID_SERVER_SERVICE 0xABCD
#define BLE_CHARACTERISTIC_UUID_TX 0xCDEF
#define BLE_CHARACTERISTIC_UUID_RX 0xEFEF
#define BLE_CLIENT_CHARACTERISTIC_CONFIGURATION 0x2902

TX 特征支持 Read、Write 和 Notify,并附带 CCC 描述符(默认启用 Notify);RX 特征支持 Write 和 Write No Response,用于接收客户端数据。

(2)全局发送数据
uint8_t send_date[20] = "hello ble:from server";

服务端连接建立后,发送任务每隔约 1 秒通过 Notify 向客户端发送该字符串。

(3)接收任务 — ble_rx_server

接收任务通过轮询全局标志位 g_ble_new_data_ready 检测客户端写入的新数据(由 GATT 写请求回调 ble_receive_write_req_cbk 置位):

void ble_rx_server(void *arg)
{
    unused(arg);
    while (1)
    {
        if (g_ble_new_data_ready == true)
        {
            printf("ble rev date:%s", g_ble_rx_data);
            g_ble_new_data_ready = false;
        }
        osDelay(1);
    }
}
(4)发送任务 — ble_tx_server

发送任务在检测到 BLE 连接已建立后,周期性调用 Notify 发送接口:

void ble_tx_server(void *arg)
{
    unused(arg);
    while (1)
    {
        if (ble_get_connection_state() != 0)
        {
            ble_server_send_input_report(send_date, sizeof(send_date));
        }
        osDelay(100);
    }
}

注意:仅在 ble_get_connection_state() 非 0(已连接)时才发送数据,避免无连接时的无效 Notify 调用。

(5)程序入口 — ble_server_app
static void ble_server_app(void)
{
    ble_server_init();

    osThreadAttr_t rx_attr = {0};
    rx_attr.name = "ble_rx_server";
    rx_attr.stack_size = 8192;
    rx_attr.priority = osPriorityNormal;
    osal_kthread_lock();
    if (osThreadNew(ble_rx_server, NULL, &rx_attr) == NULL)
    {
        printf("[LedTest] Failed to create led_pwm_main thread!\n");
    }
    osal_kthread_unlock();

    osThreadAttr_t tx_attr = {0};
    tx_attr.name = "ble_tx_server";
    tx_attr.stack_size = 8192;
    tx_attr.priority = osPriorityNormal;
    osal_kthread_lock();
    if (osThreadNew(ble_tx_server, NULL, &tx_attr) == NULL)
    {
        printf("[LedTest] Failed to create led_pwm_main thread!\n");
    }
    osal_kthread_unlock();
}

APP_FEATURE_INIT(ble_server_app);

系统启动后自动调用 ble_server_app(),先完成 BLE 服务端初始化,再创建收发两个独立任务。收发任务优先级相同,通过 RTOS 时间片轮转调度,互不阻塞。

(6)服务端初始化 — ble_server_init
void ble_server_init(void)
{
    (void)osal_msleep(3000);   /* 延时 3s,等待 BLE 协议栈初始化完毕 */

    ble_server_register_callbacks();   /* 注册 GAP/GATT 回调 */
    enable_ble();                    /* 启用 BLE */

    gap_ble_set_local_name(g_ble_name_value, sizeof(g_ble_name_value));
    gap_ble_set_local_addr(&ble_addr);   /* MAC: 11:22:33:44:55:66 */

    gatts_register_server(&app_uuid, &g_server_id);
    ble_add_service();               /* 添加 0xABCD UART 服务 */
    bth_ota_init();
}

初始化完成后,GATT 回调链自动执行:添加特征 → 启动服务 → 配置广播数据 → 开始广播。

(7)GATT Notify 发送 — ble_server_send_input_report
errcode_t ble_server_send_input_report(uint8_t *data, uint16_t len)
{
    gatts_ntf_ind_t param = {0};
    param.attr_handle = g_notify_indicate_handle;  /* TX 特征值句柄 */
    param.value_len = len;
    param.value = data;
    gatts_notify_indicate(BLE_SERVER_ID, g_ble_conn_id, &param);
    return ERRCODE_BT_SUCCESS;
}

Notify 发送依赖 TX 特征的值句柄(在 ble_server_characteristic_add_cbk 中保存至 g_notify_indicate_handle)和当前连接 ID(在 ble_server_connect_change_cbk 中保存至 g_ble_conn_id)。

(8)BUILD.gn — 编译配置
static_library("ble_connect_server") {
  sources = [
    "ble_server_lib.c",
    "ble_server_app.c",
  ]
  // ...
}

3.4、程序执行流程

d2 客户端 GAP GATT Server ble_server_lib ble_server_app d2 客户端 GAP GATT Server ble_server_lib ble_server_app 广播 ble_uart_server,等待连接 g_connection_state = 已连接 loop [每 1 秒] loop [客户端写入] ble_server_init() gatts_register_server() gatts_add_service(0xABCD) service_add_cbk → 添加 TX/RX 特征 service_start_cbk → 所有服务就绪 ble_set_adv_data() + ble_start_adv() 创建 ble_rx_server / ble_tx_server 任务 扫描并发起连接 connect_change_cbk (CONNECTED) ble_server_send_input_report() gatts_notify_indicate() Notify: hello ble:from server Write RX 特征 write_request_cbk → g_ble_rx_data ble_rx_server 打印接收数据

4、编译步骤

本实验编译服务端固件并烧录至 开发板 A;客户端固件请参考 d2_ble_connect_client 案例编译烧录至 开发板 B。以下步骤只需在首次编译时完成 4.1~4.3 的配置注册。

4.1、确认案例目录

确认案例已位于 OpenHarmony 源码目录下:

applications/sample/wifi-iot/app/d1_ble_connect_server/
├── ble_server_app.c
├── ble_server_lib.c
├── ble_server_lib.h
├── BUILD.gn
└── 实验手册.md

若从外部复制,请将 d1_ble_connect_server 目录放到上述 app/ 路径下。

4.2、修改 BUILD.gn(注册编译组件)

编辑 applications/sample/wifi-iot/app/BUILD.gn,在 features 列表中添加本案例:

lite_component("app") {
  features = [
    "startup",
    "d1_ble_connect_server:ble_connect_server",   // 添加此行
  ]
}

4.3、修改 SDK 配置文件

步骤 1:编辑 device/soc/hisilicon/ws63v100/sdk/build/config/target_config/ws63/config.py

找到 'ws63-liteos-app' 配置段,在其 'ram_component' 列表中添加:

"ble_connect_server"

步骤 2:编辑 device/soc/hisilicon/ws63v100/sdk/libs_url/ws63/cmake/ohos.cmake

找到 "ws63-liteos-app" 对应的 set(COMPONENT_LIST 部分,添加:

"ble_connect_server"

4.4、编译固件

在 OpenHarmony 源码根目录下执行编译:

rm -rf out
hb set -root .
# 通过上下方向键选择 ws63 对应的编译分支(如 nearlink_txnl63 / nearlink_dk_3863)
hb build -f

编译成功后,将固件烧录到 开发板 A(作为 BLE 服务端)。

4.5、编译客户端固件(开发板 B)

按照 d2_ble_connect_client 案例的 README 或实验手册,完成客户端的 BUILD.gn 与 SDK 配置注册,编译后将固件烧录到 开发板 B

4.6、烧录固件

使用开发板配套的烧录工具,将编译生成的固件分别烧写到两块 LZ3863-星闪开发板。具体烧录步骤请参考开发板用户手册。


5、运行结果

5.1、硬件准备

  1. 开发板 A(服务端固件)上电或复位,等待 BLE 广播启动;
  2. 开发板 B(客户端固件)上电或复位,开始扫描并连接服务端;
  3. 两块开发板分别通过 USB 连接 PC,打开串口助手查看日志(波特率 115200,8N1)。

建议:先启动服务端开发板,确认广播启动日志输出后,再启动客户端开发板,以提高首次连接成功率。

5.2、串口配置

参数
波特率 115200
数据位 8
停止位 1
校验位
流控

5.3、服务端预期输出(开发板 A)

烧录服务端固件并复位后,串口助手可观察到 BLE 初始化与服务注册过程:

[ble server] beginning add service
[ble server] add characters_and_descriptors cbk service:1, srv_handle:xx, ...
[ble server] TX characters:1 srv_handle:xx
[ble server] RX characters:1 srv_handle: xx
[ble server] start service cbk , start adv
[ble uart adv] ble_uart_start_adv adv_id 1
[ble server] adv enable cbk adv_id:1 status:0

表示 GATT UART 服务注册成功,广播已启动,设备名 ble_uart_server 可被客户端扫描发现。

客户端连接成功后,服务端还会输出:

[ble server] connect state change conn_id: x, status: 2, ...
[ble server] send input report indicate_handle:xx
ble rev date:hello ble:from client

其中:

  • connect state change ... status: 2 表示 BLE 连接已建立(GAP_BLE_STATE_CONNECTED);
  • send input report 表示正在通过 Notify 向客户端发送数据;
  • ble rev date:hello ble:from client 表示收到客户端通过 RX 特征写入的数据。

5.4、客户端预期输出(开发板 B)

烧录 d2_ble_connect_client 固件并复位后,串口助手可观察到扫描、连接与数据收发过程:

ble enable: 0
[ble client] set scan param status: 0
[ble client] connect state change conn_id: x, status: 2, ...
[ble client] Discovery service callback client:x conn_id:x
[ble client] discover character ... properties:xx
ble rev date:hello ble:from server

其中:

  • ble enable: 0 表示 BLE 协议栈启用成功;
  • connect state change ... status: 2 表示已成功连接服务端;
  • Discovery service / discover character 表示 GATT 服务发现完成;
  • ble rev date:hello ble:from server 表示收到服务端 Notify 发送的数据。

5.5、双向通信验证

连接建立后,两块开发板应持续(约每秒一次)交换数据:

方向 发送内容 接收端串口输出
服务端 → 客户端 hello ble:from server 客户端:ble rev date:hello ble:from server
客户端 → 服务端 hello ble:from client 服务端:ble rev date:hello ble:from client

5.6、结果分析

现象 说明
服务端输出 start adv / adv enable cbk GATT 服务就绪,广播已成功启动
服务端输出 connect state change ... status: 2 客户端已成功连接
服务端周期性输出 send input report Notify 发送正常执行
服务端输出 ble rev date:hello ble:from client 客户端 Write 数据接收正常
客户端输出 ble rev date:hello ble:from server Notify 接收正常,双向通信成功
客户端长时间无连接日志 服务端未启动或广播未就绪,检查服务端日志
连接成功但无数据收发 GATT 服务发现未完成,检查 UUID 是否匹配

5.7、常见问题排查

问题 可能原因 解决方法
客户端扫描不到服务端 服务端未启动或广播失败 确认服务端输出 adv enable cbk;先启动服务端
连接后立即断开 信号干扰或协议栈未就绪 两块板靠近放置;确认服务端 init 延时 3s 已执行
服务端无 ble rev date 输出 客户端未写入或 RX 特征 UUID 不匹配 确认客户端已连接并完成服务发现
客户端无 ble rev date 输出 Notify 未启用或 TX 特征句柄错误 检查 CCC 描述符是否默认启用 Notify
编译报错找不到 ble_connect_server BUILD.gn 或 config.py 未正确修改 逐步核对 4.2、4.3 节的配置项
两块板烧录了相同固件 未区分服务端/客户端固件 分别编译烧录 d1(服务端)和 d2(客户端)

6、实验扩展

完成基本实验后,可尝试以下扩展练习:

  1. 修改设备名称:更改 g_local_nameg_ble_name_value,观察客户端扫描结果的变化;
  2. 修改发送内容:更改 send_date 字符串,验证 Notify 发送自定义数据;
  3. 调整发送频率:修改 ble_tx_serverosDelay(100) 的参数,观察收发频率变化;
  4. 手机 BLE 调试:使用手机 BLE 调试 App(如 nRF Connect)扫描连接服务端,手动读写特征值;
  5. 断开重连测试:断开 BLE 连接后观察服务端是否自动重新广播并成功重连;
  6. 结合后续案例:在 BLE 连接基础上,配合其他蓝牙或网络通信案例实现更复杂的物联网应用。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐