4. BLE连接建立流程

任务说明

本案例实现BLE主机从扫描到发起连接、并在连接建立后完成连接参数协商的完整流程。使用Zephyr RTOS的BLE主机API,通过一个最小可运行的中央设备示例,演示扫描发现从设备、发起连接、以及连接参数更新请求的处理过程。

实现方案

基于Zephyr的struct bt_le_conn_param和连接回调机制,实现以下核心功能:

  • 扫描并过滤指定MAC地址的广播设备
  • 调用bt_conn_le_create发起连接
  • 连接建立后,通过bt_conn_le_param_update发起连接参数协商(间隔、延迟、超时)
  • 通过回调函数输出连接状态变化和参数协商结果

代码实现


#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gap.h>

/* 目标从设备MAC地址(需按实际设备修改) */
#define PEER_ADDR {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}

static struct bt_conn *default_conn;
static bool scan_started;

/* 连接参数协商结果回调 */
static void conn_param_updated(struct bt_conn *conn, uint16_t interval,
                               uint16_t latency, uint16_t timeout)
{
    printk("连接参数协商成功: 间隔=%.2fms 延迟=%d 超时=%dms\n",
           interval * 1.25, latency, timeout * 10);
}

/* 连接断开回调 */
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
    printk("连接断开, reason=0x%02x\n", reason);
    if (conn == default_conn) {
        bt_conn_unref(default_conn);
        default_conn = NULL;
    }
}

/* 连接建立回调 */
static void connected(struct bt_conn *conn, uint8_t err)
{
    if (err) {
        printk("连接失败: 0x%02x\n", err);
        return;
    }
    default_conn = bt_conn_ref(conn);
    printk("连接已建立\n");

    /* 发起连接参数协商: 间隔30ms, 延迟0, 超时2s */
    struct bt_le_conn_param param = {
        .interval_min = 24,   /* 30ms / 1.25ms = 24 */
        .interval_max = 24,
        .latency = 0,
        .timeout = 200        /* 2000ms / 10ms = 200 */
    };
    int ret = bt_conn_le_param_update(conn, ¶m);
    printk("参数更新请求: %s\n", ret == 0 ? "已发送" : "失败");
}

static struct bt_conn_cb conn_callbacks = {
    .connected = connected,
    .disconnected = disconnected,
    .le_param_updated = conn_param_updated,
};

/* 扫描回调 */
static void scan_cb(const bt_addr_le_t *addr, int8_t rssi,
                    uint8_t adv_type, struct net_buf_simple *ad)
{
    /* 过滤目标MAC地址 */
    bt_addr_le_t target = {.type = BT_ADDR_LE_RANDOM, .a.val = PEER_ADDR};
    if (bt_addr_le_cmp(addr, &target) == 0) {
        printk("发现目标设备, 停止扫描并连接\n");
        bt_le_scan_stop();
        scan_started = false;

        /* 发起连接 */
        struct bt_le_conn_param conn_param = {
            .interval_min = 24,
            .interval_max = 24,
            .latency = 0,
            .timeout = 200
        };
        int ret = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN,
                                    &conn_param, &default_conn);
        if (ret) {
            printk("连接创建失败: %d\n", ret);
        }
    }
}

void main(void)
{
    int ret = bt_enable(NULL);
    if (ret) {
        printk("蓝牙初始化失败: %d\n", ret);
        return;
    }
    printk("蓝牙主机已初始化\n");

    bt_conn_cb_register(&conn_callbacks);

    /* 启动扫描 */
    struct bt_le_scan_param scan_param = {
        .type = BT_LE_SCAN_TYPE_ACTIVE,
        .options = BT_LE_SCAN_OPT_NONE,
        .interval = 0x0010,
        .window = 0x0010,
    };
    ret = bt_le_scan_start(&scan_param, scan_cb);
    if (ret) {
        printk("扫描启动失败: %d\n", ret);
        return;
    }
    scan_started = true;
    printk("开始扫描...\n");

    /* 主循环保持运行 */
    while (1) {
        k_sleep(K_SECONDS(1));
    }
}

关键逻辑说明

代码中bt_conn_le_create使用BT_CONN_LE_CREATE_CONN参数直接发起连接,连接参数在创建时即指定。连接建立后,connected回调中再次调用bt_conn_le_param_update演示连接参数协商过程——这是BLE协议中主机可随时发起的参数更新流程。从设备可能接受或拒绝该请求,结果通过le_param_updated回调反馈。

工程配置

在prj.conf中启用必要配置:


CONFIG_BT=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_SCAN=y
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_CONN=y
CONFIG_BT_SMP=n

验证方法

使用nRF52840DK或同类开发板,配合一个可广播的BLE外设(如手机nRF Connect应用模拟)。按以下步骤验证:

  1. 将代码中PEER_ADDR修改为目标外设的MAC地址
  2. 编译烧录后,观察串口日志:应依次输出“开始扫描”、“发现目标设备”、“连接已建立”、“参数更新请求: 已发送”
  3. 若外设接受参数更新,会输出“连接参数协商成功”及具体参数值
  4. 断开外设连接,应看到“连接断开”日志

判定标准:日志顺序与上述一致,且参数协商成功回调中显示的间隔值约为30ms(即interval=24),说明连接参数协商流程完整执行。

BLE连接建立与参数协商时序 BLE主机(中央) BLE从机(外设) 1. 广播(ADV_IND) 2. 扫描请求(SCAN_REQ) 3. 扫描响应(SCAN_RSP) 4. 连接请求(CONNECT_REQ) 5. 连接建立(事件) 6. LL_CONNECTION_UPDATE_REQ 连接参数: 间隔=30ms(24×1.25ms) 延迟=0 超时=2s(200×10ms)