Zephyr RTOS中bt_conn_le_create 函数用法详细介绍

目录

概述

[1 函数原型与参数详解](#1 函数原型与参数详解)

[1.1 函数原型](#1.1 函数原型)

[1.2 关键参数结构体详解](#1.2 关键参数结构体详解)

[1.3 注意事项](#1.3 注意事项)

[1.4 建立连接链路的步骤](#1.4 建立连接链路的步骤)

[2 典型使用流程](#2 典型使用流程)

[2.1 基本连接](#2.1 基本连接)

[2.2 连接到蓝牙LE心率监测器并读取心率测量值](#2.2 连接到蓝牙LE心率监测器并读取心率测量值)


概述

在Zephyr RTOS中,bt_conn_le_createGAP层 的核心函数,用于主动与扫描到的低功耗蓝牙(BLE)外围设备(Peripheral)建立连接。它使你的设备能够作为中心设备(Central,如手机),与目标设备(如传感器)建立链路,为后续的GATT操作(服务发现、数据读写)奠定基础。简单说,它让你的设备"伸出手"去连接已发现的蓝牙设备。

1 函数原型与参数详解

1.1 函数原型

1)函数

cpp 复制代码
int bt_conn_le_create(const bt_addr_le_t *addr,
                      const struct bt_conn_le_create_param *create_param,
                      const struct bt_le_conn_param *conn_param,
                      struct bt_conn **conn);

2) 参数

参数 类型 作用与描述
addr const bt_addr_le_t * 目标设备的蓝牙地址 。这是通过 bt_le_scan_start 扫描回调获得的最关键信息。它包含了地址值和地址类型(如 BT_ADDR_LE_PUBLICBT_ADDR_LE_RANDOM)。
create_param const struct bt_conn_le_create_param * 控制连接创建行为的参数 。例如,指定是否为定向连接 。通常使用宏 BT_CONN_LE_CREATE_CONN(默认连接)或 BT_CONN_LE_CREATE_DIRECT(定向高 Duty Cycle 连接)。可设为 NULL,默认为 BT_CONN_LE_CREATE_CONN
conn_param const struct bt_le_conn_param * 连接参数 。这是最重要也最需谨慎设置的参数 ,它直接影响连接稳定性、速率和功耗。指向 bt_le_conn_param 结构体。重要:不可为NULL。
conn struct bt_conn ** 输出参数 。用于返回新创建的连接对象的指针。连接建立是异步的,此指针在函数调用后立即有效,但连接本身可能尚未建立成功。
返回值 int 成功时返回 0,失败时返回负的错误码(如 -EINVAL 参数无效,-ENOMEM 内存不足)。注意 :返回成功仅表示连接启动过程开始,不表示连接已建立。

1.2 关键参数结构体详解

1) **bt_le_conn_param - 连接参数(核心),**此结构体定义了链路的物理时序,对功耗和吞吐量有决定性影响。

cpp 复制代码
struct bt_le_conn_param {
    uint16_t interval_min; // 最小连接间隔 (单位:1.25 ms)
    uint16_t interval_max; // 最大连接间隔 (单位:1.25 ms)
    uint16_t latency;      // 从机延迟 (允许跳过的连接事件数)
    uint16_t timeout;      // 监督超时 (单位:10 ms)
};

参数选择指南与典型值

参数 说明 范围与典型值
interval_min / interval_max 连接间隔 :两个连接事件之间的时间。值越小,数据交换越快,功耗越高 。必须为 7.5ms 的倍数 (即 0x0006)。 范围0x0006 (7.5 ms) ~ 0x0C80 (4000 ms) • 高速传输0x0006 ~ 0x0012 (7.5ms ~ 22.5ms) • 均衡模式0x0014 ~ 0x0028 (25ms ~ 50ms) • 低功耗0x00C8 ~ 0x01F4 (250ms ~ 500ms)
latency 从机延迟 :Peripheral(从机)可以跳过 的连接事件数。0 表示必须监听每个事件(最快响应,最高功耗)。10 表示可每10个事件监听一次。 范围0 ~ 499低延迟0省电模式10 ~ 100
timeout 监督超时 :在此时间内未收到有效数据则判定连接断开。必须大于 (1+latency) * interval_max * 2 范围0x000A (100 ms) ~ 0x0C80 (32000 ms) 经验公式timeout(1+latency) * interval_max * 2
约束条件 必须满足:interval_mininterval_max,且 timeout > (1+latency) * interval_max * 2。否则连接参数协商会失败。

2) create_param 选项

常用选项为宏定义:

  • BT_CONN_LE_CREATE_CONN: 标准连接模式(默认)。

  • BT_CONN_LE_CREATE_DIRECT: 定向连接模式,不使用白名单,具有较高的 Duty Cycle,用于快速连接特定设备。

1.3 注意事项

1) 参数生命周期

传递给 bt_conn_le_createaddrcreate_paramconn_param 指针所指向的数据,只需在函数调用期间有效。通常使用局部变量或全局常量即可,因为函数内部会复制这些值。

2) 连接对象的生命周期管理

  • bt_conn_le_create 成功返回的连接对象 conn 具有初始的引用计数。你应通过 bt_conn_ref(conn) 增加引用计数来"持有"它,防止被协议栈意外回收。

  • 当不再需要时(如断开后),必须调用 bt_conn_unref(conn) 减少引用计数。

3) 异步操作

连接建立是异步的。结果通过全局连接回调bt_conn_cb 中的 .connected 成员)返回。务必注册此回调以获知连接成功或失败。

4) 错误处理

connected 回调中的 err 参数和 disconnected 回调中的 reason 参数是重要的调试信息。可查阅 bt_hci_err.h 头文件了解错误码含义。

5) 连接参数协商

指定的 conn_param初始参数。实际连接参数由 Central(你的设备)提出,最终与 Peripheral 协商确定,实际值可能在你请求的范围内。

6) 扫描与连接的协调

发起连接前务必先停止扫描 (bt_le_scan_stop)。同时进行扫描和连接会相互干扰,可能导致连接失败。

1.4 建立连接链路的步骤

一个完整的BLE Central角色流程链已经清晰:启动扫描 -> 发现目标 -> 停止扫描 -> 创建连接 -> 发现服务 -> 订阅/读写特征bt_conn_le_create 是BLE中心设备建立连接链路的关键函数。其核心步骤是:

  1. 准备目标地址 :从扫描结果中获得准确的 bt_addr_le_t

  2. 设置连接参数 :根据应用需求(速度 vs 功耗)谨慎配置 bt_le_conn_param

  3. 注册连接回调 :通过 bt_conn_cb 接收异步结果。

  4. 调用并管理 :发起连接,并通过 bt_conn_ref/unref 妥善管理连接对象的生命周期。

2 典型使用流程

2.1 基本连接

建立连接是一个异步过程,通常遵循 "扫描 -> 发现目标 -> 停止扫描 -> 发起连接" 的流程。以下是核心代码示例,展示了从扫描回调中触发连接的典型模式:

cpp 复制代码
#include <bluetooth/bluetooth.h>
#include <bluetooth/conn.h>

static struct bt_conn *default_conn; // 全局连接对象

// 1. 连接建立后的回调函数(由协议栈调用)
static void connected(struct bt_conn *conn, uint8_t err) {
    if (err) {
        printk("连接失败 (错误码: 0x%02x)\n", err);
        // 错误码参考: bt_hci_err.h,如 0x08 - 连接超时,0x3B - 地址不可用
    } else {
        printk("连接已成功建立!\n");
        default_conn = bt_conn_ref(conn); // 增加连接引用计数,防止被意外释放
        // 后续可在此启动服务发现 (bt_gatt_discover)
    }
}

// 2. 连接断开后的回调函数
static void disconnected(struct bt_conn *conn, uint8_t reason) {
    printk("连接已断开 (原因: 0x%02x)\n", reason);
    if (default_conn) {
        bt_conn_unref(default_conn); // 减少引用计数
        default_conn = NULL;
    }
    // 可选:在此处重新启动扫描
}

// 3. 注册连接回调(在main函数或蓝牙启用后调用)
static struct bt_conn_cb conn_callbacks = {
    .connected = connected,
    .disconnected = disconnected,
};

void init_connection_manager(void) {
    bt_conn_cb_register(&conn_callbacks);
}

// 4. 在扫描回调中发起连接的示例函数
static void scan_cb(const bt_addr_le_t *addr, int8_t rssi,
                    bt_le_adv_evt_t evt, const struct net_buf_simple *buf) {
    char addr_str[BT_ADDR_LE_STR_LEN];
    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));

    // 示例:筛选设备名为 "MyDevice" 的目标
    struct bt_data data;
    size_t offset = 0;
    while (offset < buf->len) {
        offset += bt_data_parse(buf, offset, &data);
        if (data.type == BT_DATA_NAME_COMPLETE &&
            memcmp(data.data, "MyDevice", data.data_len) == 0) {
            
            printk("找到目标设备 %s,停止扫描并连接...\n", addr_str);
            bt_le_scan_stop(); // 先停止扫描

            // 定义连接参数(追求速度和响应)
            static const struct bt_le_conn_param conn_params = {
                .interval_min = BT_GAP_INIT_CONN_INT_MIN(20),  // 20*1.25=25ms
                .interval_max = BT_GAP_INIT_CONN_INT_MAX(40),  // 40*1.25=50ms
                .latency = 0,   // 无跳过的连接事件
                .timeout = 400, // 400*10=4000ms 超时
            };

            struct bt_conn *conn;
            int err = bt_conn_le_create(addr,
                                        BT_CONN_LE_CREATE_CONN,
                                        &conn_params,
                                        &conn);
            if (err) {
                printk("发起连接失败: %d\n", err);
                // 连接发起失败,可重新启动扫描
                start_scanning();
            } else {
                printk("连接请求已发起...\n");
                // conn 指针已由函数填充,可用于后续的 bt_conn_ref 等操作
            }
            break;
        }
    }
}

2.2 连接到蓝牙LE心率监测器并读取心率测量值

cpp 复制代码
/* main.c - Application main entry point */

/*
 * Copyright (c) 2015-2016 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/types.h>
#include <stddef.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/sys/byteorder.h>

static void start_scan(void);

static struct bt_conn *default_conn;

static struct bt_uuid_16 discover_uuid = BT_UUID_INIT_16(0);
static struct bt_gatt_discover_params discover_params;
static struct bt_gatt_subscribe_params subscribe_params;

uint64_t total_rx_count; /* This value is exposed to test code */

static uint8_t notify_func(struct bt_conn *conn,
			   struct bt_gatt_subscribe_params *params,
			   const void *data, uint16_t length)
{
	if (!data) {
		printk("[UNSUBSCRIBED]\n");
		params->value_handle = 0U;
		return BT_GATT_ITER_STOP;
	}

	printk("[NOTIFICATION] data %p length %u\n", data, length);

	total_rx_count++;

	return BT_GATT_ITER_CONTINUE;
}

static uint8_t discover_func(struct bt_conn *conn,
			                const struct bt_gatt_attr *attr,
			                struct bt_gatt_discover_params *params)
{
	int err;

	if (!attr) {
		printk("Discover complete\n");
		(void)memset(params, 0, sizeof(*params));
		return BT_GATT_ITER_STOP;
	}

	printk("[ATTRIBUTE] handle %u\n", attr->handle);

	if (!bt_uuid_cmp(discover_params.uuid, BT_UUID_HRS)) 
    {
		memcpy(&discover_uuid, BT_UUID_HRS_MEASUREMENT, sizeof(discover_uuid));
		discover_params.uuid = &discover_uuid.uuid;
		discover_params.start_handle = attr->handle + 1;
		discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;

		err = bt_gatt_discover(conn, &discover_params);
		if (err) 
        {
			printk("Discover failed (err %d)\n", err);
		}
	} 
    else if (!bt_uuid_cmp(discover_params.uuid,
				BT_UUID_HRS_MEASUREMENT)) 
    {
		memcpy(&discover_uuid, BT_UUID_GATT_CCC, sizeof(discover_uuid));
		discover_params.uuid = &discover_uuid.uuid;
		discover_params.start_handle = attr->handle + 2;
		discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
		subscribe_params.value_handle = bt_gatt_attr_value_handle(attr);

		err = bt_gatt_discover(conn, &discover_params);
		if (err) {
			printk("Discover failed (err %d)\n", err);
		}
	} 
    else 
    {
		subscribe_params.notify = notify_func;
		subscribe_params.value = BT_GATT_CCC_NOTIFY;
		subscribe_params.ccc_handle = attr->handle;

		err = bt_gatt_subscribe(conn, &subscribe_params);
		if (err && err != -EALREADY) {
			printk("Subscribe failed (err %d)\n", err);
		} else {
			printk("[SUBSCRIBED]\n");
		}

		return BT_GATT_ITER_STOP;
	}

	return BT_GATT_ITER_STOP;
}

static bool eir_found(struct bt_data *data, void *user_data)
{
	bt_addr_le_t *addr = user_data;
	int i;

	printk("[AD]: %u data_len %u\n", data->type, data->data_len);

	switch (data->type) 
    {
	case BT_DATA_UUID16_SOME:
	case BT_DATA_UUID16_ALL:
		if (data->data_len % sizeof(uint16_t) != 0U) 
        {
			printk("AD malformed\n");
			return true;
		}

		for (i = 0; i < data->data_len; i += sizeof(uint16_t)) 
        {
			struct bt_conn_le_create_param *create_param;
			struct bt_le_conn_param *param;
			const struct bt_uuid *uuid;
			uint16_t u16;
			int err;

			memcpy(&u16, &data->data[i], sizeof(u16));
			uuid = BT_UUID_DECLARE_16(sys_le16_to_cpu(u16));
			if (bt_uuid_cmp(uuid, BT_UUID_HRS)) {
				continue;
			}

			err = bt_le_scan_stop();
			if (err) {
				printk("Stop LE scan failed (err %d)\n", err);
				continue;
			}

			printk("Creating connection with Coded PHY support\n");
			param = BT_LE_CONN_PARAM_DEFAULT;
			create_param = BT_CONN_LE_CREATE_CONN;
			create_param->options |= BT_CONN_LE_OPT_CODED;
			err = bt_conn_le_create(addr, create_param, param,
						             &default_conn);
			if (err) {
				printk("Create connection with Coded PHY support failed (err %d)\n",
				       err);

				printk("Creating non-Coded PHY connection\n");
				create_param->options &= ~BT_CONN_LE_OPT_CODED;
				err = bt_conn_le_create(addr, create_param,
							param, &default_conn);
				if (err) {
					printk("Create connection failed (err %d)\n", err);
					start_scan();
				}
			}

			return false;
		}
	}

	return true;
}

static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
			 struct net_buf_simple *ad)
{
	char dev[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(addr, dev, sizeof(dev));
	printk("[DEVICE]: %s, AD evt type %u, AD data len %u, RSSI %i\n",
	       dev, type, ad->len, rssi);

	/* We're only interested in legacy connectable events or
	 * possible extended advertising that are connectable.
	 */
	if (type == BT_GAP_ADV_TYPE_ADV_IND ||
	    type == BT_GAP_ADV_TYPE_ADV_DIRECT_IND ||
	    type == BT_GAP_ADV_TYPE_EXT_ADV) {
		bt_data_parse(ad, eir_found, (void *)addr);
	}
}

static void start_scan(void)
{
	int err;

	/* Use active scanning and disable duplicate filtering to handle any
	 * devices that might update their advertising data at runtime. */
	struct bt_le_scan_param scan_param = {
		.type       = BT_LE_SCAN_TYPE_ACTIVE,
		.options    = BT_LE_SCAN_OPT_CODED,
		.interval   = BT_GAP_SCAN_FAST_INTERVAL,
		.window     = BT_GAP_SCAN_FAST_WINDOW,
	};

	err = bt_le_scan_start(&scan_param, device_found);
	if (err) {
		printk("Scanning with Coded PHY support failed (err %d)\n", err);

		printk("Scanning without Coded PHY\n");
		scan_param.options &= ~BT_LE_SCAN_OPT_CODED;
		err = bt_le_scan_start(&scan_param, device_found);
		if (err) {
			printk("Scanning failed to start (err %d)\n", err);
			return;
		}
	}

	printk("Scanning successfully started\n");
}

static void connected(struct bt_conn *conn, uint8_t conn_err)
{
	char addr[BT_ADDR_LE_STR_LEN];
	int err;

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

	if (conn_err) 
    {
		printk("Failed to connect to %s (%u)\n", addr, conn_err);

		bt_conn_unref(default_conn);
		default_conn = NULL;

		start_scan();
		return;
	}

	printk("Connected: %s\n", addr);

	total_rx_count = 0U;

	if (conn == default_conn) 
    {
		memcpy(&discover_uuid, BT_UUID_HRS, sizeof(discover_uuid));
		discover_params.uuid = &discover_uuid.uuid;
		discover_params.func = discover_func;
		discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
		discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
		discover_params.type = BT_GATT_DISCOVER_PRIMARY;

		err = bt_gatt_discover(default_conn, &discover_params);
		if (err) 
        {
			printk("Discover failed(err %d)\n", err);
			return;
		}
	}
}

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
	char addr[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

	printk("Disconnected: %s, reason 0x%02x %s\n", addr, reason, bt_hci_err_to_str(reason));

	if (default_conn != conn) {
		return;
	}

	bt_conn_unref(default_conn);
	default_conn = NULL;

	start_scan();
}

BT_CONN_CB_DEFINE(conn_callbacks) = {
	.connected = connected,
	.disconnected = disconnected,
};

int main(void)
{
	int err;
	err = bt_enable(NULL);

	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
		return 0;
	}

	printk("Bluetooth initialized\n");

	start_scan();
	return 0;
}
相关推荐
ScilogyHunter3 天前
Zephyr串口驱动开发及构建完全指南
驱动开发·uart·zephyr
ScilogyHunter3 天前
Zephyr Hello World应用开发构建完全指南
zephyr·hello world
ScilogyHunter3 天前
Zephyr Twister测试框架完全指南
zephyr·twister
ScilogyHunter4 天前
west init 命令详解
init·zephyr·west
ScilogyHunter4 天前
使用Kconfig配置Zephyr工程完全指南
kconfig·zephyr
ScilogyHunter4 天前
Zephyr设备树完全指南
zephyr
ScilogyHunter5 天前
Zephyr项目按需配置完全指南
zephyr
ScilogyHunter5 天前
Zephyr最简工程配置指南
zephyr
ScilogyHunter5 天前
Zephyr主仓库目录结构完全指南
zephyr
ScilogyHunter5 天前
Zephyr工程配置完全指南
zephyr