bluedroid源代码分析之ACL包发送和接收(一)

本文详细解析了蓝牙设备间通过ACL链路发送音乐数据流的过程,包括关键API L2CA_DataWrite的使用及底层实现流程,展示了如何在L2CAP层处理数据流。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

很多其它内容请參照我的个人网站: http://stackvoid.com/

ACL 链路在 Bluetooth 中很重要,一些重要的应用如 A2DP, 基于 RFCOMM 的应用,BNEP等都要建立 ACL 链路,发送/接收ACL 包。今天跟大家一起来分析 ACL 包发送/接收流程,以及涉及到的重要 command/event。


ACL包发送

以下的图(点击大图)是各种应用层使用 L2CAP 的 API:L2CA_DataWrite 发送数据流的过程,此API继续往下走,我仅分析了正常数据流的走向(临时没有考虑别的情况)。

ACL_01


应用层数据到 L2CAP 的入口

我们如果一个听音乐的场景,Mike跟大家一起分析音乐数据流 AVDTP 下面层的传送。

在 AVDTP 中,全部的功能想发送 Data,必须调用 avdt_ad_write_req 这个函数。Mike 就从这个函数入手分析。

 1 //当CCB或SCB给l2cap的 Channel 发送数据时。他们终于都会使用到L2CAP的 API:L2CA_Data_Write
 2 UINT8 avdt_ad_write_req(UINT8 type, tAVDT_CCB *p_ccb, tAVDT_SCB *p_scb, BT_HDR *p_buf)
 3 {
 4     UINT8   tcid;
 5 
 6     /* get tcid from type, scb */
 7     tcid = avdt_ad_type_to_tcid(type, p_scb);
 8 
 9 
10     return L2CA_DataWrite(avdt_cb.ad.rt_tbl[avdt_ccb_to_idx(p_ccb)][tcid].lcid, p_buf);
11 }
12 //L2CA_DataWrite的返回形式有三种,各自是:
13 //1. L2CAP_DW_SUCCESS:此数据写成功
14 //2.L2CAP_DW_CONGESTED:写数据成功,可是当前信道拥堵
15 //3.L2CAP_DW_FAILED:写数据失败
16 UINT8 L2CA_DataWrite (UINT16 cid, BT_HDR *p_data)
17 {
18     L2CAP_TRACE_API2 ("L2CA_DataWrite()  CID: 0x%04x  Len: %d", cid, p_data->len);
19     return l2c_data_write (cid, p_data, L2CAP_FLUSHABLE_CH_BASED);
20 }

当我们的音乐数据流到达 l2c_data_write 这个函数时,标志数据流正式进入到L2CAP层。 我们在以下的源代码中深入分析 l2c_data_write 这个函数。

l2c_data_write 这个函数做的事情主要有:

  1. 依据參数 cid(Channel ID) 找到 相应的 ccb(Channel Control Block), 找不到返回 L2CAP_DW_FAILED
  2. 假设測试者 打开 TESTER 这个宏,发送随意数据,当数据大小 大于 MTU 最大值,也会返回 L2CAP_DW_FAILED
  3. 通过检查 p_ccb->cong_sent 字段,TRUE。则说明当前 Channel 已经拥挤,此时L2CAP的这个Channel不在接收数据,返回 L2CAP_DW_FAILED
  4. 以上三个条件都通过。说明数据可发送。将数据通过 l2c_csm_execute 继续处理。

    进入 l2c_csm_execute 函数,标志着这笔数据已经成功交给 l2CAP 来处理。与上层已经没有关系了。

  5. l2c_csm_execute 函数运行结束后,再次检查 p_ccb->cong_sent 字段,看看当前的 Channel 是否拥挤,假设拥挤则告诉上层 L2CAP_DW_CONGESTED,否则返回 L2CAP_DW_SUCCESS,表示数据已经成功发送。

  1 //返回的数据跟上面的 L2CA_DataWrite 作用同样
  2 UINT8 l2c_data_write (UINT16 cid, BT_HDR *p_data, UINT16 flags)
  3 {
  4     tL2C_CCB        *p_ccb;
  5 
  6     //遍历l2cb.ccb_pool。通过Channel ID找到相应的Channel Control Block
  7     //l2cu_find_ccb_by_cid 见以下源代码凝视
  8     if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL)
  9     {
 10         L2CAP_TRACE_WARNING1 ("L2CAP - no CCB for L2CA_DataWrite, CID: %d", cid);
 11         GKI_freebuf (p_data);
 12         return (L2CAP_DW_FAILED);
 13     }
 14 
 15 #ifndef TESTER /* Tester may send any amount of data. otherwise sending message
 16                   bigger than mtu size of peer is a violation of protocol */
 17     if (p_data->len > p_ccb->peer_cfg.mtu)
 18     {
 19         L2CAP_TRACE_WARNING1 ("L2CAP - CID: 0x%04x  cannot send message bigger than peer's mtu size", cid);
 20         GKI_freebuf (p_data);
 21         return (L2CAP_DW_FAILED);
 22     }
 23 #endif
 24 
 25     /* channel based, packet based flushable or non-flushable */
 26     //Bluedroid中默认的是 L2CAP_FLUSHABLE_CH_BASED
 27     //这个 layer_specific 在 数据发送的 l2c_link_send_to_lower 中表示 ACL包分包 个数
 28     p_data->layer_specific = flags;
 29 
 30     //发现本 Channel 已经拥堵,直接返回L2CAP_DW_FAILED 告诉上层等会再发数据
 31     //当几个应用 共用 此 Channel 可能会出现这样的情况
 32     if (p_ccb->cong_sent)
 33     {
 34         L2CAP_TRACE_ERROR3 ("L2CAP - CID: 0x%04x cannot send, already congested  xmit_hold_q.count: %u  buff_quota: %u",
 35                             p_ccb->local_cid, p_ccb->xmit_hold_q.count, p_ccb->buff_quota);
 36 
 37         GKI_freebuf (p_data);
 38         return (L2CAP_DW_FAILED);
 39     }
 40     //毫无疑问啦,这个函数就是我们继续须要分析的函数
 41     l2c_csm_execute (p_ccb, L2CEVT_L2CA_DATA_WRITE, p_data);
 42 
 43     //已经将上层的这笔数据发送完,假设此 Channel 拥挤了(之前发送这笔包还没拥挤)
 44     //返回 L2CAP_DW_CONGESTED 告诉上层当前信道拥挤,你要给我L2CAP层发数据,是不发下来的
 45     if (p_ccb->cong_sent)
 46         return (L2CAP_DW_CONGESTED);
 47 
 48     //成功发送。而且此时 Channel 并不拥挤
 49     return (L2CAP_DW_SUCCESS);
 50 }
 51 
 52 //通过 Channel ID 找到 Channel Control Block
 53 tL2C_CCB *l2cu_find_ccb_by_cid (tL2C_LCB *p_lcb, UINT16 local_cid)
 54 {
 55     tL2C_CCB    *p_ccb = NULL;
 56 #if (L2CAP_UCD_INCLUDED == TRUE)
 57     UINT8 xx;
 58 #endif
 59 
 60     if (local_cid >= L2CAP_BASE_APPL_CID) //大于或等于 0x0040 说明不是 Fixed Channel
 61     {
 62         /* find the associated CCB by "index" */
 63         local_cid -= L2CAP_BASE_APPL_CID;
 64 
 65         if (local_cid >= MAX_L2CAP_CHANNELS)
 66             return NULL;
 67 
 68         p_ccb = l2cb.ccb_pool + local_cid; //直接通过地址偏移找到
 69 
 70         /* make sure the CCB is in use */
 71         if (!p_ccb->in_use)
 72         {
 73             p_ccb = NULL;
 74         }
 75         /* make sure it's for the same LCB */
 76         else if (p_lcb && p_lcb != p_ccb->p_lcb)
 77         {
 78             p_ccb = NULL;
 79         }
 80     }
 81 #if (L2CAP_UCD_INCLUDED == TRUE) //默认是关闭的,既然从上层来的都是 数据包了,我觉得不会用到 Fixed Channel
 82     else
 83     {
 84         /* searching fixed channel */
 85         p_ccb = l2cb.ccb_pool;
 86         for ( xx = 0; xx < MAX_L2CAP_CHANNELS; xx++ )
 87         {
 88             if ((p_ccb->local_cid == local_cid)
 89               &&(p_ccb->in_use)
 90               &&(p_lcb == p_ccb->p_lcb))
 91                 break;
 92             else
 93                 p_ccb++;
 94         }
 95         if ( xx >= MAX_L2CAP_CHANNELS )
 96             return NULL;
 97     }
 98 #endif
 99 
100     return (p_ccb);
101 }

下一篇博客我们来看看 L2CAP 层的处理。


很多其它内容请參照我的个人网站: http://stackvoid.com/

#include <BLEDevice.h> #include <BLEScan.h> #include <BLEAdvertisedDevice.h> #include <BLEUtils.h> #include <BLE2902.h> #define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" #define CHARACTERISTIC_UUID_RX "beb5483e-36e1-4688-b7f5-ea07361b26a8" #define CHARACTERISTIC_UUID_TX "d0a0a1d0-0a1d-4b0f-9c6a-7b2b8c9d0e0f" static BLEAdvertisedDevice* myDevice; bool doConnect = false; bool connected = false; static int lock_status= 0; BLERemoteCharacteristic* pRemoteCharacteristic; BLEClient* pClient; // 扫描回调 class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { if (advertisedDevice.getName() == "ESP32_BLE_Server") { advertisedDevice.getScan()->stop(); myDevice = new BLEAdvertisedDevice(advertisedDevice); doConnect = true; } } }; // 通知回调 static void notifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { String data = String((char*)pData); // 直接转换为字符串 Serial.print("Received: "); Serial.println(data); if (data == "0x00") { Serial.println("关锁"); lock_status=0; //String txValue = "locked"; //pRemoteCharacteristic->writeValue(txValue.c_str(), txValue.length()); } else if (data == "0x01") { Serial.println("开锁"); lock_status=1; //String txValue = "unlocked"; //pRemoteCharacteristic->writeValue(txValue.c_str(), txValue.length()); } } void connectToServer() { Serial.print("Connecting to "); Serial.println(myDevice->getAddress().toString().c_str()); pClient = BLEDevice::createClient(); pClient->connect(myDevice); connected = true; // 获取服务 BLERemoteService* pRemoteService = pClient->getService(SERVICE_UUID); if (pRemoteService == nullptr) { Serial.println("Service not found"); return; } // 获取发送特征(用于接收通知) pRemoteCharacteristic = pRemoteService->getCharacteristic(CHARACTERISTIC_UUID_TX); if (pRemoteCharacteristic == nullptr) { Serial.println("TX characteristic not found"); return; } pRemoteCharacteristic->registerForNotify(notifyCallback); // 获取接收特征(用于写入数据) BLERemoteCharacteristic* pWriteCharacteristic = pRemoteService->getCharacteristic(CHARACTERISTIC_UUID_RX); if (pWriteCharacteristic == nullptr) { Serial.println("RX characteristic not found"); return; } // 主循环中用于发送数据 while (connected) { if(lock_status==0) { String txValue = (char*)lock_status; pWriteCharacteristic->writeValue(txValue.c_str(), txValue.length()); Serial.print("Sent: "); Serial.println(txValue); }else if(lock_status==1) { String txValue = (char*)lock_status; pWriteCharacteristic->writeValue(txValue.c_str(), txValue.length()); Serial.print("Sent: "); Serial.println(txValue); } delay(10); } } void setup() { Serial.begin(115200); Serial.println("Starting BLE Client..."); BLEDevice::init(""); // 开始扫描 BLEScan* pBLEScan = BLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setActiveScan(true); pBLEScan->start(30); } void loop() { if (doConnect) { connectToServer(); doConnect = false; } delay(1000); } 这是BLE客户端,为什么会不停连接断开
最新发布
07-03
<think>我们正在处理个关于ESP32 BLE客户端连接服务器后频繁断开的问题。根据用户提供的信息,我们需要分析可能的原因并提供解决方法。 首先,我们回顾下BLE连接的基本机制。BLE连接是建立在GAP(GenericAccess Profile)GATT(GenericAttribute Profile)之上的。连接断开可能是由多种原因引起的,例如信号干扰、电源管理问题、软件错误或配置不当。 根据引用[1][3],我们知道ESP32的BLE操作是异步的,通过回调函数接收结果。同时,BLE协议栈分为ControllerHost两部分,任何层的问题都可能导致连接断开。可能的原因及解决方法:1.**信号问题**:BLE连接对信号强度很敏感。如果信号弱或不稳定,可能导致连接断开。-解决方法:确保设备在有效范围内,避免障碍物干扰源。可以尝试调整天线的位置或使用外置天线。2.**电源管理问题**:ESP32在低功耗模式下可能会为了省电而断开连接。-解决方法:检查电源管理设置。在BLE连接期间,可以适当提高电源模式(例如,使用`esp_pm_configure`函数设置最小频率),避免进入深度睡眠。3.**连接参数设置不当**:BLE连接参数(如连接间隔、从机延迟、超时时间)如果不合适,可能导致连接不稳定。-解决方法:调整连接参数。例如,增加连接间隔(connection interval)可以减少功耗,但过大会导致响应慢;减小连接间隔可以增加数据传输速率,但会增加功耗。同时,确保超时时间(supervision timeout)足够长,以容忍短暂的信号丢失。-在ESP32中,作为客户端,可以在发起连接请求时设置连接参数;作为服务器,可以通过更新连接参数请求来调整。4.**软件错误**:程序中的逻辑错误或资源不足(如内存不足)可能导致连接断开。-解决方法:检查代码中是否有错误,例如在回调函数中进行了耗时操作,导致任务阻塞。确保使用异步操作,避免阻塞BLE任务。同时,确保有足够的内存资源。5.**协议栈错误**:ESP32的BLE协议栈可能存在bug,或者配置不当。-解决方法:更新ESP-IDF到最新版本,以获取最新的bug修复。同时,检查BLE配置是否正确。 6. **设备兼容性问题**:连接的BLE设备可能不兼容或实现有误。-解决方法:尝试连接其他BLE设备,看问题是否重现。具体到ESP32的代码实现,我们需要注意:-在客户端,连接后需要正确处理连接事件断开事件。在断开事件中,可以尝试重新连接。-确保在连接建立后,及时进行必要的服务发现特征读写操作,避免因超时而断开。以下是个简单的ESP32BLE客户端的代码框架,其中含了连接断开事件的处理:```c#include"esp_gap_ble_api.h"#include "esp_gattc_api.h" //全局变量staticesp_gattc_cb_tgattc_callback;static uint16_tconn_id;// GAP事件回调voidgap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t*param) {if(event== ESP_GAP_BLE_SCAN_RESULT_EVT){//处理扫描结果}else if(event== ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT) {//扫描参数设置完成}}// GATT客户端事件回调void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_tgattc_if, esp_ble_gattc_cb_param_t *param){if (event ==ESP_GATTC_CONNECT_EVT) {//连接建立conn_id =param->connect.conn_id;//保存连接ID,并开始服务发现}else if(event== ESP_GATTC_DISCONNECT_EVT){//连接断开//可以在这里尝试重新连接esp_ble_gap_start_scanning(scan_params);//重新开始扫描}} voidapp_main() {//初始化BLEesp_bt_controller_config_tbt_cfg =BT_CONTROLLER_INIT_CONFIG_DEFAULT();esp_bt_controller_init(&bt_cfg);esp_bt_controller_enable(ESP_BT_MODE_BLE);//初始化Bluedroidesp_bluedroid_init();esp_bluedroid_enable();//注册GAPGATT回调esp_ble_gap_register_callback(gap_event_handler);esp_ble_gattc_register_callback(gattc_event_handler);esp_ble_gattc_app_register(PROFILE_A_APP_ID); //注册应用//设置扫描参数并开始扫描esp_ble_scan_params_t scan_params ={.scan_type= BLE_SCAN_TYPE_ACTIVE,.own_addr_type =BLE_ADDR_TYPE_PUBLIC,.scan_filter_policy =BLE_SCAN_FILTER_ALLOW_ALL,.scan_interval=0x50,.scan_window=0x30};esp_ble_gap_set_scan_params(&scan_params);} ```在断开事件中,我们可以尝试重新连接。但是频繁断开也可能是因为重新连接过于频繁,导致设备无法稳定连接。因此,我们可以加入重连延时,避免立即重连。 另外,根据引用[2],如果使用ESP32同时作为主控无线模块,且数据量大,建议使用WiFi。如果数据量小,使用BLE。如果数据量较大,BLE可能无法稳定传输,导致断开。因此,需要评估数据量是否适合BLE传输。 总结解决方案: 1.检查物理环境,确保信号良好。 2.调整电源管理设置,避免进入低功耗模式。 3.优化连接参数(如增加连接间隔、增大超时时间)。4.检查代码逻辑,确保异步操作,避免阻塞。5.更新ESP-IDF到最新版本。 6.在断开事件中实现合理的重连机制,避免立即重连导致问题。 如果以上方法都不能解决问题,可能需要使用抓工具(如Ellisys)分析空中接口数据,以确定断开的具体原因。 相关问题:1.ESP32BLE连接参数如何设置? 2.如何优化ESP32的电源管理以维持稳定的BLE连接? 3.如何调试ESP32BLE连接断开的问题?4. ESP32 BLE传输数据量有什么限制?5.ESP32BLE客户端服务器之间如何实现自动重连?</think>### ESP32 BLE客户端连接频繁断开的分析与解决 #### 常见原因及解决方法 1. **连接参数不匹配** - **问题**:客户端与服务器协商的连接参数(间隔/延迟/超时)不兼容 - **解决**:在客户端代码中显式设置合理的连接参数: ```c esp_ble_conn_update_params_t conn_params = { .min_int = 16, // 最小连接间隔(20ms) .max_int = 32, // 最大连接间隔(40ms) .latency = 0, // 从机延迟 .timeout = 400 // 监控超时(4s) }; esp_ble_gap_update_conn_params(&conn_params); ``` 2. **电源管理问题** - **问题**:ESP32进入低功耗模式导致连接中断 - **解决**:禁用自动休眠模式: ```c esp_pm_lock_acquire(ESP_PM_NO_LIGHT_SLEEP); ``` 3. **协议栈资源不足** - **问题**:并发连接数超过ESP32 BLE协议栈限制 - **解决**: - 确保`make menuconfig`中配置: ```ini CONFIG_BTDM_CONTROLLER_BLE_MAX_CONN=3 # 最大连接数 CONFIG_BTDM_CONTROLLER_BR_EDR_MAX_ACL_CONN=0 # 禁用经典蓝牙 ``` - 优化GATT服务数量,移除未使用的特性 4. **信号干扰问题** - **问题**:2.4GHz频段干扰导致丢 - **解决**: - 添加天线选择代码: ```c esp_phy_rf_init(ESP_PHY_CAL_DATA_PARTITION, RF_CAL_PART); esp_phy_set_wifi_mode(ANT_MODE_ANT0); // 使用主天线 ``` - 在物理层添加重传机制 5. **回调函数阻塞** - **问题**:长时间阻塞BLE任务导致看门狗触发 - **解决**: ```c // 在回调中使用队列传递事件 static void gattc_callback(esp_gattc_cb_event_t event, ...) { xQueueSend(ble_event_queue, &event, portMAX_DELAY); } ``` #### 调试排查步骤 1. **启用调试日志** ```c esp_log_level_set("*", ESP_LOG_DEBUG); ``` 2. **监控连接事件** ```c case ESP_GATTC_DISCONNECT_EVT: ESP_LOGI(TAG, "断开原因: 0x%x", param->disconnect.reason); break; ``` 3. **检查错误代码对照表** | 错误码 | 含义 | 解决方案 | |--------|------|---------| | 0x08 | 连接超时 | 增大超时参数 | | 0x13 | 远端终止 | 检查服务器配置 | | 0x22 | 资源不足 | 减少并发连接 | | 0x3E | 不可接受参数 | 调整连接参数 | 4. **使用BLE嗅探器** - 推荐工具:nRF Sniffer或Ellisys BLE分析仪 - 检查空中接口的LL_CONNECTION_UPDATE_REQ报文 #### 最佳实践建议 1. **连接恢复机制** ```c static void reconnect_task(void *arg) { while(1) { if(!is_connected) { esp_ble_gap_start_scanning(SCAN_DURATION); vTaskDelay(pdMS_TO_TICKS(2000)); } vTaskDelay(pdMS_TO_TICKS(100)); } } ``` 2. **使用最新协议栈** - 更新至ESP-IDF v4.4+ (含BLE稳定性修复) - 启用`CONFIG_BT_BLE_FULL_SCAN_SUPPORT=y` 3. **硬件优化** - 添加22μF电源去耦电容 - PCB天线区域净空处理 - 使用屏蔽罩减少RF干扰 > 关键参考:ESP32 BLE连接在恶劣RF环境下需要至少-85dBm的RSSI值才能稳定维持连接[^3]。当连接间隔>180ms时需特别注意超时参数设置[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值