You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS Swift BLE UART问题:didUpdateValueFor仅触发两次后停止

解决BLE UART仅接收两次数据后停止的问题

我之前踩过几乎一模一样的BLE UART坑——外设每隔一段时间发数据,iOS连好后只收几次就停了,后来排查发现核心是BLE的流量控制机制没处理到位,要么是iOS没给外设“已接收”的确认,要么是外设没等确认就硬发导致缓冲区堵死。结合你的场景(发单个'0'、250ms间隔),给你几个针对性的解决步骤:

iOS端排查与修复

1. 收到数据后务必回复外设确认信号

大多数BLE UART实现(比如Nordic的标准UART服务)都要求中心设备(iPhone)在收到数据后,主动向外设的RX特征写入一个确认(可以是空数据,或者特定字节),告诉外设“我收好了,继续发”。如果没做这一步,外设的发送缓冲区会很快被占满,或者iOS的CoreBluetooth内部缓冲区满了就会暂停通知。

举个Swift代码示例(假设用的是Nordic标准UART UUID):

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
    guard error == nil, let data = characteristic.value else {
        print("接收数据出错: \(error?.localizedDescription ?? "未知错误")")
        return
    }
    
    // 这里处理你的数据解析(比如打印收到的'0')
    if let receivedStr = String(data: data, encoding: .utf8) {
        print("收到数据: \(receivedStr)")
    }
    
    // 关键:找到UART的RX特征,写入确认信号(空数据即可)
    if let uartService = peripheral.services?.first(where: { $0.uuid == CBUUID(string: "6E400001-B5A3-F393-E0A9-E50E24DCCA9E") }),
       let rxCharacteristic = uartService.characteristics?.first(where: { $0.uuid == CBUUID(string: "6E400002-B5A3-F393-E0A9-E50E24DCCA9E") }) {
        // 用withoutResponse类型,避免额外的ACK开销
        peripheral.writeValue(Data(), for: rxCharacteristic, type: .withoutResponse)
    }
}

2. 确保通知订阅状态稳定

有时候didUpdateValueFor停止触发,是因为通知被意外取消了。你可以在didUpdateNotificationStateFor里检查状态,确保订阅成功:

func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
    guard error == nil else {
        print("通知状态更新失败: \(error!.localizedDescription)")
        return
    }
    
    if characteristic.isNotifying {
        print("UART通知已开启")
    } else {
        print("UART通知被关闭,重新订阅")
        peripheral.setNotifyValue(true, for: characteristic)
    }
}

外设端排查与修复

1. 实现“发完等确认再发”的流量控制

你现在是每隔250ms硬发一个'0',但BLE是半双工通信,外设不能一直发而不管中心设备是否接收完毕。正确的逻辑应该是:

  • 发送一个'0'后,暂停定时器
  • 收到iOS发来的确认信号(也就是刚才写RX特征的操作)后,再重启定时器发送下一个'0'

举个伪代码例子(以Nordic SDK为例):

// 定时器回调:发送单个'0'
static void timer_timeout_handler(nrf_timer_event_t event_type, void * p_context) {
    uint8_t send_data = '0';
    // 发送数据
    ble_uart_string_send(&m_uart, &send_data, 1);
    // 发送后禁用定时器,等待iOS的确认
    nrf_drv_timer_disable(&m_timer);
}

// UART事件回调:处理iOS的确认
static void uart_event_handler(ble_uart_t * p_uart, ble_uart_evt_t * p_evt) {
    switch(p_evt->evt_type) {
        case BLE_UART_EVT_TX_COMPLETE:
            // 收到iOS的确认,重启定时器发下一个'0'
            nrf_drv_timer_enable(&m_timer);
            break;
        case BLE_UART_EVT_RX_DATA:
            // 这里可以处理iOS发来的其他数据,确认信号可以是空数据,所以不用额外解析
            break;
        default:
            break;
    }
}

2. 检查外设的发送缓冲区配置

如果外设的BLE发送缓冲区太小,连续发数据会导致溢出。可以查看你的嵌入式SDK文档,调整缓冲区大小,或者限制单次发送的数据量(你这里发1字节,问题不大,但缓冲区还是要足够)。

额外优化:协商更大的MTU

默认BLE MTU是23字节,虽然你每次只发1字节,但协商更大的MTU(比如512)可以提升传输效率,减少缓冲区压力。iOS端可以这样请求:

func peripheral(_ peripheral: CBPeripheral, didConnect error: Error?) {
    if error == nil {
        peripheral.discoverServices(nil)
        // 协商MTU
        peripheral.requestMTU(512)
    }
}

外设端也要开启MTU协商支持,具体配置参考你的SDK文档。

按照上面的步骤调整后,应该就能建立稳定的UART连接,持续接收外设发来的'0'了。

内容的提问来源于stack exchange,提问作者pholowko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:08:01