3. BLE连接建立流程与连接参数协商

BLE连接建立流程与连接参数协商 学习卡片

任务定义

本案例基于 Nordic nRF52840 DK 开发板与 Zephyr RTOS,实现 BLE 5.0 中心设备(Central)主动扫描并连接外设(Peripheral),连接建立后触发连接参数更新请求,并正确监听与处理断开连接事件。最终通过串口日志观察连接状态迁移与断开原因。

硬件与软件准备

  • 硬件:两块 nRF52840 DK 开发板(一块作为 Central,一块作为 Peripheral)
  • 软件:Zephyr RTOS v3.4.0 及以上,nRF Connect SDK v2.5.0
  • 外设端固件:使用 Zephyr 自带 peripheral_hr 示例(或任意可广播的外设示例)

实现方案

中心设备端代码完成以下核心逻辑:

  1. 启动扫描,过滤可连接广播设备;
  2. 发现目标外设后停止扫描并发起连接;
  3. 连接建立后(CONNECTED 回调),发送连接参数更新请求(间隔 30ms,从延迟 0,超时 400ms);
  4. 注册断开回调,在断开事件中打印断开原因;
  5. 使用按键 1 主动断开当前连接,验证主动断开路径。

代码实现

创建 central_conn.c 文件,内容如下:

#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/printk.h>

#define DEVICE_NAME      "Central_Conn"
#define TARGET_NAME      "Peripheral_HR"
#define CONN_INTERVAL_MIN 0x18   /* 30ms */
#define CONN_INTERVAL_MAX 0x18
#define CONN_LATENCY       0
#define CONN_TIMEOUT       400   /* 400ms */

static struct bt_conn *default_conn;
static bool conn_active;
static struct gpio_dt_spec button = GPIO_DT_SPEC_GET(DT_NODELABEL(button0), gpios);

/* 扫描发现回调 */
static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
                         struct net_buf_simple *ad)
{
    char name[BT_MAX_NAME_LEN] = {0};
    int ret = bt_data_parse(ad, &parse_name, name, sizeof(name));

    if (ret && strcmp(name, TARGET_NAME) == 0) {
        printk("Found target: %s (RSSI %d)\n", name, rssi);
        bt_scan_stop();
        struct bt_conn *conn;
        int err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN,
                                    BT_LE_CONN_PARAM_DEFAULT, &conn);
        if (err) {
            printk("Create conn failed (err %d)\n", err);
        }
    }
}

/* 连接参数更新完成回调 */
static void param_updated(struct bt_conn *conn, uint16_t interval,
                          uint16_t latency, uint16_t timeout)
{
    printk("Conn params updated: interval %d.%02d ms, latency %d, timeout %d ms\n",
           interval * 1.25, (interval * 125) % 100, latency, timeout * 10);
}

/* 断开回调 */
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
    printk("Disconnected (reason 0x%02x)\n", reason);
    conn_active = false;
    default_conn = NULL;
    /* 重新开始扫描 */
    bt_scan_start(BT_SCAN_TYPE_SCAN_ACTIVE);
}

/* 连接回调注册 */
BT_CONN_CB_DEFINE(conn_callbacks) = {
    .connected = connected,
    .disconnected = disconnected,
    .le_param_updated = param_updated,
};

static void connected(struct bt_conn *conn, uint8_t err)
{
    if (err) {
        printk("Connection failed (err 0x%02x)\n", err);
        return;
    }
    default_conn = bt_conn_ref(conn);
    conn_active = true;
    printk("Connected\n");

    /* 发起连接参数更新请求 */
    struct bt_le_conn_param param = {
        .interval_min = CONN_INTERVAL_MIN,
        .interval_max = CONN_INTERVAL_MAX,
        .latency = CONN_LATENCY,
        .timeout = CONN_TIMEOUT,
    };
    int ret = bt_conn_le_param_update(default_conn, &param);
    printk("Param update request: %s\n", ret == 0 ? "sent" : "failed");
}

/* 按键回调:主动断开 */
void button_pressed(void)
{
    if (conn_active && default_conn) {
        printk("Button pressed, disconnecting...\n");
        bt_conn_disconnect(default_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
    }
}

/* 扫描参数与回调 */
static struct bt_scan_cb scan_cb = {
    .cb = device_found,
};

void main(void)
{
    int err;

    /* 初始化 GPIO 按键 */
    gpio_pin_configure_dt(&button, GPIO_INPUT);
    gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
    gpio_init_callback(&button_cb, button_pressed, BIT(button.pin));
    gpio_add_callback(button.port, &button_cb);

    err = bt_enable(NULL);
    if (err) {
        printk("Bluetooth init failed (err %d)\n", err);
        return;
    }
    printk("Bluetooth initialized\n");

    bt_scan_cb_register(&scan_cb);
    err = bt_scan_start(BT_SCAN_TYPE_SCAN_ACTIVE);
    if (err) {
        printk("Scan start failed (err %d)\n", err);
    }
    printk("Scanning for %s...\n", TARGET_NAME);
}

配置与构建

在 prj.conf 中添加以下配置:

CONFIG_BT=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_SCAN=y
CONFIG_BT_SCAN_FILTER_ENABLE=y
CONFIG_BT_SCAN_UUID_CNT=0
CONFIG_BT_SCAN_NAME_CNT=1
CONFIG_BT_SCAN_NAME="Peripheral_HR"
CONFIG_BT_CONN=y
CONFIG_BT_CONN_PARAM_UPDATE=y
CONFIG_GPIO=y

构建并烧录:

west build -b nrf52840dk_nrf52840 -d build/central
west flash -d build/central

验证方法

  1. 给外设板烧录 peripheral_hr 示例(广播名默认为 Peripheral_HR);
  2. 上电两块板卡,观察中心设备串口输出;
  3. 预期日志顺序:Bluetooth initialized → Scanning... → Found target → Connected → Param update request: sent → Conn params updated: interval 30.00 ms...;
  4. 按下中心设备按键 1,观察输出 Button pressed, disconnecting... 后出现 Disconnected (reason 0x13)(0x13 表示远端用户主动断开);
  5. 断开后设备自动重新扫描,可再次连接。

判定标准

  • 串口日志完整呈现“扫描→发现→连接→参数更新→断开”全流程;
  • 连接参数更新后实际间隔为 30ms(日志显示 30.00 ms);
  • 主动断开后 reason 为 0x13,且设备能重新进入扫描状态。
BLE 5.0 连接建立与断开 — 状态时序 Central (nRF52840) Peripheral (HR) 广播 (可连接) 扫描发现目标 → 停止扫描 CONNECT_REQ 连接建立 (Connected 回调) LL_CONNECTION_UPDATE_REQ (30ms) LL_CONNECTION_UPDATE_IND (接受) 参数更新完成 (interval=30ms) DISCONNECT_CMD (reason=0x13) 断开回调 → 重新扫描