蓝牙 - BLE中的连接和扫描参数设定

本文介绍了蓝牙低功耗连接参数,包括连接间隔、从机延迟和监督超时,强调它们对设备功耗和通信效率的影响。连接间隔决定了两个设备通信的频率,从机延迟允许外围设备节省电力,而监督超时则设置了无数据交换时的最大等待时间。正确设置这些参数对功率优化至关重要。

连接参数 / Connection Parameters

连接参数由发起设备与连接请求一起发送,当连接建立后,任何一个设备都可以修改。这些参数如下:

连接间隔 / Connection Interval

在低功耗蓝牙连接中,使用了跳频方案。两台设备发送各自数据和接收对方数据,只在特定时间的特定信道上。两个设备在一段特定的时间后在一个新的信道上通信(蓝牙低功耗协议栈的链接层处理信道切换)。每次两个设备建立通讯,发送和接收数据,被称为连接事件。如果没有应用数据要发送或接收,两个设备交换链接层数据以维持连接。连接间隔是两个连接事件之间的时间量,以1.25ms为一个单位。连接间隔的范围可以从最小值6(7.5ms)到最大值3200(4.0s)。

不同的应用可能需要不同的连接时间间隔,并会影响设备的功耗。

从机延迟 / Slave Latency

该参数使从机(外围)设备可以选择跳过一些连接事件。这种能力给外围设备一些灵活性。如果外围设备没有任何数据要发送,它可以跳过连接事件,保持睡眠状态,并节省电力。外围设备在每个连接事件的基础上选择是否唤醒。外设可以跳过连接事件,但跳过的时间不能超过从机延迟参数所允许的时间,否则连接会失败。

从机延迟值指的是跳过的连接事件的个数,范围一般从0到500。

监督超时  / Supervision Time-out

这个超时是两个成功的连接事件之间的最大时间量。如果这个时间过去了,没有发生成功的连接事件,设备就会终止连接并返回到未连接状态。这个参数值以10毫秒为单位表示。监督超时值的范围可以从最小的10(100ms)到3200(32.0s)。超时必须大于有效连接间隔。

有效连接间隔 / Effective Connection Interva

有效连接间隔等于两个连接事件之间的时间,取决于从机设备在从机延迟允许的情况下跳过的连接事件数量(如果从机延迟设置为0,则有效连接间隔等于实际连接间隔)。

从机延迟值表示可以跳过的最大事件数。这个数字的范围可以从最小值0(意味着不能跳过任何连接事件)到最大值499。最大有效连接间隔的值不能大于16秒,间隔可以用以下公式计算:

有效连接间隔=(连接间隔)*(1+从机延时)

Effective Connection Interval = (Connection Interval) * (1 + [Slave Latency])

考虑下面例子:

- 连接间隔:80 (100 ms)

- 从机延迟:4

- 有效连接间隔:(100ms)*(1+4)=500ms

当从属设备没有需要向主设备发送的数据时,连接中的从属设备在连接事件中发送数据的间隔是500毫秒。

连接参数设置的影响 / Connection Parameter Considerations

在大多数应用场景中,从属设备跳过最大数量的连接事件。在选择或申请连接参数时就要考虑有效的连接间隔。选择正确的配套连接参数,在蓝牙低功耗设备使用中,对功率优化起着重要作用。下面的列表概述了连接参数设置的影响:

缩短连接间隔的影响如下:

- 增加两个设备的耗电量

- 增加两个方向的吞吐量

- 减少任何一个方向上的数据发送时间

延长连接间隔的影响如下:

- 减少两个设备的耗电量

- 减少两个方向的吞吐量

- 增加任何一个方向上的数据发送时间

减少从机延迟(或将其设置为零)的作用如下:

- 增加外围设备的功率消耗

- 减少外围设备接收来自中央设备的数据的时间。

增加从属延迟的作用如下:

- 在外设没有数据要发送给中央设备的期间,减少外设的耗电量

- 增加外围设备接收从中央设备发送的数据的时间

连接参数更新/ Connection Parameter Update

在某些情况下,中央设备请求与外围设备的连接,但其发出的连接参数对外围设备并不友好。而在其他一些情况下,在已经连接的情况下,外围设备可能会根据其应用情况而请求改变参数。外围设备可以通过发送连接参数更新请求来要求中央设备改变连接设置。对于支持蓝牙4.1和4.2的设备,该请求直接由链接层处理。对于蓝牙4.0设备,协议栈的L2CAP层处理该请求。蓝牙低功耗协议栈会自动选择更新方法。

该请求包含四个参数:最小连接间隔、最大连接间隔、从机延迟和超时。这些值代表外围设备需要的连接参数(连接间隔是以范围的形式给出)。当中心设备收到这个请求时,它可以选择接受或拒绝新的参数。

发送连接参数更新请求是可选的,不要求中央设备接受或应用所请求的参数。一些应用试图以较快的连接间隔建立连接,以允许更快的服务发现和初始设置。这些应用在这之后,会要求一个较长(较慢)的连接间隔,以维持最佳的工作功耗。

根据GAPRole,连接参数更新可以用GAPRole_SendUpdateParam()或GAPCentralRole_UpdateLink()命令异步发送。外围的GAPRole可以被配置为在建立连接后的一定时间内自动发送参数更新。例如,simple_peripheral应用程序使用以下预处理器定义的符号:

连接建立后6秒,GAP层会自动发送一个连接参数更新。

终止连接 / Connection Termination

主机(master)或从机(slave)都可以因任何原因终止连接。一方发起终止,另一方必须在两个设备退出连接状态之前作出回应。

注:如果一个central设备连接到多个peripheral设备,这种多连接的情况下,对于连接参数设定,会有额外的要求。

参考:

https://software-dl.ti.com/lprf/simplelink_cc2640r2_latest/docs/blestack/ble_user_guide/html/ble-stack-3.x/gap.html#:~:text=The%20connection%20interval%20is%20the,of%203200%20(4.0%20s).

<think>我们已经在之前的回答中分别提供了传统蓝牙BLE设备的扫描连接通信的C代码示例。现在,用户希望有一个统一的程序,能够同时处理传统蓝牙BLE设备。然而,由于传统蓝牙BLE使用不同的协议栈API,通常需要分别处理。我们可以设计一个程序,包含两种设备的处理模块,并允许用户选择模式。 但是,请注意: 1. 传统蓝牙使用RFCOMM或L2CAP进行数据传输,而BLE使用GATT协议(基于ATT)。 2. 扫描方式也不同:传统蓝牙使用`hci_inquiry`,而BLE使用LE扫描命令。 因此,我们可以设计一个程序,让用户通过命令行参数选择模式(经典或BLE),然后执行相应的操作。 以下是一个综合示例,包含两种模式的选择: ### 综合示例:蓝牙设备管理程序 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <bluetooth/bluetooth.h> #include <bluetooth/hci.h> #include <bluetooth/hci_lib.h> #include <bluetooth/rfcomm.h> #include <gattlib.h> // 需要安装gattlib // 传统蓝牙扫描 void scan_classic_bt(int duration) { // ... 使用之前提供的传统蓝牙扫描代码 ... } // BLE扫描 void scan_ble(int duration) { // ... 使用之前提供的BLE扫描代码(注意:需要事件处理回调来解析广播数据)... // 由于BLE扫描需要事件处理,我们这里简化处理,只扫描一段时间并打印设备地址 // 实际应用中需要实现事件处理函数 } // 连接传统蓝牙设备 int connect_classic_bt(const char *addr, int channel) { // ... 使用之前提供的RFCOMM连接代码 ... } // 连接BLE设备 gatt_connection_t* connect_ble(const char *addr) { // ... 使用之前提供的gattlib连接代码 ... } // 传统蓝牙数据传输 void classic_bt_transfer(int sock) { // ... 使用之前提供的传统蓝牙数据传输代码 ... } // BLE数据传输(通过GATT特性读写) void ble_transfer(gatt_connection_t* connection, const char* uuid_str) { uuid_t uuid; if (gattlib_string_to_uuid(uuid_str, strlen(uuid_str), &uuid) < 0) { fprintf(stderr, "Invalid UUID format\n"); return; } // 读取特性 uint8_t buffer[100]; size_t len = sizeof(buffer); if (gattlib_read_char_by_uuid(connection, &uuid, buffer, &len) == 0) { printf("Read value: "); for (int i = 0; i < len; i++) printf("%02X ", buffer[i]); printf("\n"); } else { perror("Read failed"); } // 写入特性(示例数据) uint8_t data[] = {0x01, 0x02, 0x03}; if (gattlib_write_char_by_uuid(connection, &uuid, data, sizeof(data)) == 0) { printf("Write successful\n"); } else { perror("Write failed"); } } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "Usage: %s [classic|ble] [scan|connect] [options]\n", argv[0]); fprintf(stderr, "Examples:\n"); fprintf(stderr, " %s classic scan 10\n", argv[0]); fprintf(stderr, " %s classic connect 00:11:22:33:44:55 1\n", argv[0]); fprintf(stderr, " %s ble scan 5\n", argv[0]); fprintf(stderr, " %s ble connect 00:11:22:33:44:55\n", argv[0]); return 1; } const char *mode = argv[1]; const char *command = argv[2]; if (strcmp(mode, "classic") == 0) { if (strcmp(command, "scan") == 0) { if (argc < 4) { fprintf(stderr, "Missing duration argument\n"); return 1; } int duration = atoi(argv[3]); scan_classic_bt(duration); } else if (strcmp(command, "connect") == 0) { if (argc < 5) { fprintf(stderr, "Missing address or channel\n"); return 1; } const char *addr = argv[3]; int channel = atoi(argv[4]); int sock = connect_classic_bt(addr, channel); if (sock < 0) { return 1; } // 示例:传输数据 classic_bt_transfer(sock); close(sock); } } else if (strcmp(mode, "ble") == 0) { if (strcmp(command, "scan") == 0) { if (argc < 4) { fprintf(stderr, "Missing duration argument\n"); return 1; } int duration = atoi(argv[3]); scan_ble(duration); } else if (strcmp(command, "connect") == 0) { if (argc < 4) { fprintf(stderr, "Missing address\n"); return 1; } const char *addr = argv[3]; gatt_connection_t* connection = connect_ble(addr); if (!connection) { return 1; } // 示例:传输数据(需要知道具体的UUID) // 这里假设用户提供了一个UUID,实际应用中可能需要发现服务 if (argc >= 5) { const char *uuid_str = argv[4]; ble_transfer(connection, uuid_str); } else { printf("Connected but no UUID provided for transfer\n"); } gattlib_disconnect(connection); } } else { fprintf(stderr, "Unknown mode: %s\n", mode); return 1; } return 0; } ``` ### 编译指令 ```bash # 安装依赖 sudo apt install libbluetooth-dev libgattlib-dev # 编译 gcc -o bt_manager bt_manager.c -lbluetooth -lgattlib ``` ### 使用示例 1. **扫描传统蓝牙设备(10秒)**: ```bash sudo ./bt_manager classic scan 10 ``` 2. **连接传统蓝牙设备**: ```bash sudo ./bt_manager classic connect 00:11:22:33:44:55 1 ``` 3. **扫描BLE设备(5秒)**: ```bash sudo ./bt_manager ble scan 5 ``` 4. **连接BLE设备并读写特性**: ```bash sudo ./bt_manager ble connect 00:11:22:33:44:55 00002a00-0000-1000-8000-00805f9b34fb ``` ### 注意事项 1. **权限问题**:运行程序需要蓝牙管理权限,可以通过`setcap`或`sudo`解决。 2. **BLE扫描事件处理**:上面的简化BLE扫描没有实现事件处理,实际应用中需要注册回调函数来解析广播数据。这需要更复杂的代码,使用`hci_le_set_scan_enable``hci_le_add_white_list`等函数,并处理`HCI_EVENT_LE_META`事件。 3. **传统蓝牙通道**:传统蓝牙连接通常需要知道RFCOMM通道号。可以通过SDP查询获取,但示例中直接指定(如通道1)。 ### 增强建议 - **自动通道发现**:为传统蓝牙设备添加SDP查询功能,自动获取RFCOMM通道号。 - **BLE扫描回调**:实现BLE扫描的完整事件处理,打印设备名称广播数据。 - **错误处理**:增加更详细的错误处理逻辑。 ### 引用说明 本文中引用的API库来自BlueZgattlib[^1][^3]。 §§相关问题§§ 1. 如何自动发现传统蓝牙设备的RFCOMM通道号? 2. 如何实现BLE扫描的广播数据解析回调? 3. 在Linux中如何同时扫描传统蓝牙BLE设备? 4. 如何处理蓝牙连接中的超时重连?</think>### Linux蓝牙(BR/EDR)BLE设备综合操作代码 以下是支持传统蓝牙BLE设备的完整C代码实现,包含扫描连接数据传输功能: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <bluetooth/bluetooth.h> #include <bluetooth/hci.h> #include <bluetooth/hci_lib.h> #include <bluetooth/rfcomm.h> #include <gattlib.h> // 需要安装gattlib: sudo apt install libgattlib-dev // 扫描所有蓝牙设备(传统+BLE) void scan_all_devices(int duration) { int dev_id = hci_get_route(NULL); int sock = hci_open_dev(dev_id); if (sock < 0) { perror("Failed to open HCI device"); return; } // 扫描传统蓝牙设备 inquiry_info *ii = NULL; int max_rsp = 255, num_rsp; num_rsp = hci_inquiry(dev_id, duration, max_rsp, NULL, &ii, IREQ_CACHE_FLUSH); if (num_rsp >= 0) { printf("\n=== Classic Bluetooth Devices ===\n"); char addr[19] = {0}; for (int i = 0; i < num_rsp; i++) { ba2str(&(ii+i)->bdaddr, addr); printf("Classic: %s\n", addr); } free(ii); } // 扫描BLE设备 printf("\n=== BLE Devices ===\n"); le_set_scan_parameters_cp scan_params = { .scan_type = 0x01, // 主动扫描 .interval = htobs(0x0010), .window = htobs(0x0010), .own_bdaddr_type = LE_PUBLIC_ADDRESS, .filter = 0x01 // 过滤重复 }; struct hci_request ble_req; memset(&ble_req, 0, sizeof(ble_req)); ble_req.ogf = OGF_LE_CTL; ble_req.ocf = OCF_LE_SET_SCAN_PARAMETERS; ble_req.cparam = &scan_params; ble_req.clen = LE_SET_SCAN_PARAMETERS_CP_SIZE; if (hci_send_req(sock, &ble_req, 1000) < 0) { perror("Set BLE scan params failed"); } le_set_scan_enable_cp scan_enable = {.enable = 0x01, .filter_dup = 0x01}; ble_req.ocf = OCF_LE_SET_SCAN_ENABLE; ble_req.cparam = &scan_enable; ble_req.clen = LE_SET_SCAN_ENABLE_CP_SIZE; if (hci_send_req(sock, &ble_req, 1000) < 0) { perror("Enable BLE scan failed"); } // 处理BLE扫描结果 unsigned char buf[HCI_MAX_EVENT_SIZE]; struct hci_filter nf; hci_filter_clear(&nf); hci_filter_set_ptype(HCI_EVENT_PKT, &nf); hci_filter_set_event(EVT_LE_META_EVENT, &nf); setsockopt(sock, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)); time_t start = time(NULL); while (time(NULL) - start < duration) { int len = read(sock, buf, sizeof(buf)); if (len < 0) continue; if (buf[0] == HCI_EVENT_PKT && buf[1] == EVT_LE_META_EVENT) { evt_le_meta_event *meta = (void *)(buf + 2); if (meta->subevent == EVT_LE_ADVERTISING_REPORT) { le_advertising_info *info = (void *)(meta->data + 1); char addr[18]; ba2str(&info->bdaddr, addr); printf("BLE: %s\n", addr); } } } // 停止扫描 scan_enable.enable = 0x00; hci_send_req(sock, &ble_req, 1000); close(sock); } // 连接传统蓝牙设备 int connect_classic_bt(const char *addr, int channel) { struct sockaddr_rc addr_rc = {0}; int sock = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); addr_rc.rc_family = AF_BLUETOOTH; addr_rc.rc_channel = (uint8_t)channel; str2ba(addr, &addr_rc.rc_bdaddr); if (connect(sock, (struct sockaddr *)&addr_rc, sizeof(addr_rc)) < 0) { perror("RFCOMM connect failed"); close(sock); return -1; } return sock; } // 连接BLE设备 gatt_connection_t* connect_ble(const char *addr) { gatt_connection_t* connection = gattlib_connect( NULL, addr, BDADDR_LE_PUBLIC, BT_SEC_LOW, 0, 0); if (!connection) { fprintf(stderr, "Failed to connect to BLE device\n"); return NULL; } return connection; } // 传统蓝牙数据传输 void classic_bt_transfer(int sock, const char *message) { // 发送数据 if (write(sock, message, strlen(message)) < 0) { perror("Write failed"); return; } // 接收数据 char buf[1024] = {0}; ssize_t len = read(sock, buf, sizeof(buf)-1); if (len < 0) { perror("Read failed"); } else { printf("Received: %s\n", buf); } } // BLE数据传输 void ble_transfer(gatt_connection_t* connection, const char* read_uuid, const char* write_uuid, const uint8_t* data, size_t data_len) { uuid_t uuid_read, uuid_write; gattlib_string_to_uuid(read_uuid, strlen(read_uuid), &uuid_read); gattlib_string_to_uuid(write_uuid, strlen(write_uuid), &uuid_write); // 读取特征值 uint8_t buffer[100]; size_t len = sizeof(buffer); if (gattlib_read_char_by_uuid(connection, &uuid_read, buffer, &len) == 0) { printf("Read value: "); for (size_t i = 0; i < len; i++) printf("%02X ", buffer[i]); printf("\n"); } // 写入特征值 if (gattlib_write_char_by_uuid(connection, &uuid_write, data, data_len) == 0) { printf("Write successful\n"); } } int main() { printf("Scanning all Bluetooth devices for 5 seconds...\n"); scan_all_devices(5); // 传统蓝牙示例 const char *classic_addr = "00:11:22:33:44:55"; int classic_sock = connect_classic_bt(classic_addr, 1); if (classic_sock >= 0) { classic_bt_transfer(classic_sock, "Hello Classic BT!"); close(classic_sock); } // BLE示例 const char *ble_addr = "AA:BB:CC:DD:EE:FF"; gatt_connection_t* ble_conn = connect_ble(ble_addr); if (ble_conn) { uint8_t data[] = {0x01, 0x02, 0x03}; ble_transfer(ble_conn, "00002a00-0000-1000-8000-00805f9b34fb", // 标准设备名称UUID "0000ffe1-0000-1000-8000-00805f9b34fb", // 示例自定义UUID data, sizeof(data)); gattlib_disconnect(ble_conn); } return 0; } ``` ### 关键功能说明 1. **统一扫描**: - 使用`hci_inquiry()`扫描传统蓝牙设备 - 使用LE扫描命令事件处理扫描BLE设备 - 同时显示两种设备类型的结果 2. **连接机制**: - 传统蓝牙:通过RFCOMM套接字连接 - BLE:使用gattlib库建立GATT连接 - 自动处理不同协议栈的连接参数 3. **数据传输**: ```c // 传统蓝牙:基于流的套接字通信 write(sock, data, len); read(sock, buffer, size); // BLE:GATT特征值读写 gattlib_read_char_by_uuid(conn, &uuid, buffer, &len); gattlib_write_char_by_uuid(conn, &uuid, data, data_len); ``` ### 编译与运行 ```bash # 安装依赖 sudo apt install libbluetooth-dev libgattlib-dev # 编译 gcc -o bt_manager bt_manager.c -lbluetooth -lgattlib # 设置权限(避免sudo) sudo setcap 'cap_net_admin,cap_net_raw+eip' ./bt_manager # 运行 ./bt_manager ``` ### 关键注意事项 1. **设备区分**: - 传统蓝牙:使用BR/EDR协议,设备类(class)字段通常非零 - BLE设备:广播数据包含LE特定标志(0x01)[^1] ```c // 在扫描回调中判断设备类型 if (info->bdaddr_type == LE_PUBLIC_ADDRESS || info->bdaddr_type == LE_RANDOM_ADDRESS) { // BLE设备 } else { // 传统蓝牙设备 } ``` 2. **错误处理**: - 检查所有蓝牙API调用的返回值 - 使用`perror()`输出系统错误信息 - 实现超时重连机制: ```c int retries = 0; while ((sock = connect_classic_bt(addr, channel)) < 0 && retries < 3) { sleep(1); retries++; } ``` 3. **资源管理**: - 确保关闭所有打开的套接字连接 - 使用`free()`释放动态分配的内存 - BLE操作后调用`gattlib_disconnect()` 4. **UUID处理**: - BLE服务使用标准UUID(16位或128位) - 常用标准UUID参考: - 设备信息: `0000180a-0000-1000-8000-00805f9b34fb` - 电池服务: `0000180f-0000-1000-8000-00805f9b34fb` - 自定义服务通常使用128位UUID ### 性能优化建议 1. **并行扫描**: ```c // 使用线程同时执行传统BLE扫描 pthread_t classic_thread, ble_thread; pthread_create(&classic_thread, NULL, classic_scan_func, NULL); pthread_create(&ble_thread, NULL, ble_scan_func, NULL); pthread_join(classic_thread, NULL); pthread_join(ble_thread, NULL); ``` 2. **连接池管理**: - 维护活跃连接列表 - 实现心跳机制保持连接 - 自动回收空闲连接 3. **数据缓存**: - 为高频数据传输设计环形缓冲区 - 使用批处理减少小数据包传输 ### 调试技巧 ```bash # 实时监控蓝牙流量 sudo btmon # 查看已连接设备 bluetoothctl devices Connected # 测试RFCOMM连接 sdptool browse 00:11:22:33:44:55 ``` > 此实现结合了Linux BlueZ栈的底层APIgattlib库的高级抽象,完整覆盖了蓝牙双模设备的操作需求[^1][^3]。对于需要深度优化的场景,可参考BlueZ官方文档调整HCI参数[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜流冰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值