1. 从一个抓包现场说起BLE PHY到底在干什么如果你调过nRF52840的BLE抓包大概率见过这样的场景手机和手环明明只隔了半米连接却时不时断一下抓包器上看到的数据包一会儿是37字节一会儿又变成251字节同一个连接事件里有的包被标记成CRC OK有的直接CRC Error。这些问题往下挖最后都会落到同一个地方——BLE PHY。BLE PHY全称Physical Layer物理层。很多人第一次接触这个词是在看BLE协议栈分层图的时候最底下那一层写着PHY上面是LL、L2CAP、ATT、GATT一路往上。但真正做产品的时候你会发现PHY不是图上一个框那么简单它直接决定了你的设备能跑多远、多快、多稳甚至决定了你的功耗能不能达标。这篇文章面向的是已经上手过BLE开发、但被连接稳定性、吞吐量、功耗这几个指标反复折磨的工程师。我会从PHY的基本机制讲起把1M、2M、Coded PHY这三种模式的选择逻辑拆开再结合nRF52840这类常见芯片的实际配置把广播、扫描、发起连接、连接态这几个阶段的PHY行为讲清楚。中间会穿插一些我实际踩过的坑比如为什么你的2M PHY协商成功了但吞吐量没上去为什么Coded PHY在室内反而更容易断。先给一个最直观的结论BLE PHY不是设一下就行的参数它和连接间隔、MTU、数据长度扩展、射频前端设计是绑在一起的。你单独调PHY大概率调不出效果。2. BLE PHY的基础机制GFSK、符号率与信道2.1 为什么BLE用GFSK而不是QPSKBLE物理层用的是GFSKGaussian Frequency Shift Keying高斯频移键控。这个选择不是随便定的。BLE的目标场景是低功耗、低成本、短距离GFSK的解调可以用简单的非相干检测实现射频前端不需要复杂的载波恢复电路芯片面积和功耗都能压下来。对比一下Wi-Fi用的是OFDM蓝牙经典BR/EDR用的是GFSK但符号率是1Msym/sBLE也是1Msym/s。BLE和经典蓝牙在物理层上有一定的相似性但BLE把信道带宽压到了2MHz信道间隔也是2MHz总共40个信道其中37个是数据信道3个是广播信道37、38、39。GFSK的调制指数BLE规定在0.45到0.55之间典型值0.5。调制指数这个参数决定了频偏和符号率的关系。BLE的频偏是±250kHz对于1M PHY符号率1Msym/s所以调制指数h 2 × Δf / Rs 2 × 250k / 1M 0.5。这个值不是随便选的它影响解调器的灵敏度和抗干扰能力。调制指数太低频偏小解调困难太高频谱扩展太宽邻道干扰严重。2.2 1M PHY、2M PHY、Coded PHY的区别BLE从4.0到5.0最大的物理层变化就是引入了2M PHY和Coded PHY。这三种模式的核心参数对比如下参数1M PHY2M PHYCoded PHY (S2)Coded PHY (S8)符号率1 Msym/s2 Msym/s1 Msym/s1 Msym/s数据速率1 Mbps2 Mbps500 kbps125 kbps调制方式GFSKGFSKGFSK 前向纠错GFSK 前向纠错纠错编码无无卷积码扩频因子2卷积码扩频因子8接收灵敏度基准比1M低约4dB比1M高约5dB比1M高约12dB典型距离基准约为1M的70%约为1M的2倍约为1M的4倍2M PHY的符号率翻倍意味着同样的数据用一半的时间发完。这对吞吐量和功耗都有好处连接事件里射频开启时间短了平均功耗就下来了。但代价是接收灵敏度下降因为符号率高了每个符号的能量少了解调门限就上去了。实测下来2M PHY的灵敏度比1M PHY差4dB左右这个差距在远距离场景下很致命。Coded PHY是另一条路。它不提高符号率而是通过前向纠错编码卷积码和扩频来换取灵敏度。S2表示每个信息比特编码成2个符号S8就是8个符号。扩频因子越大灵敏度越高但有效数据速率越低。S8的125kbps听起来很慢但在一些远距离、低数据量的场景比如资产追踪标签里非常实用。2.3 广播信道和数据信道的PHY行为差异这里有一个容易被忽略的点广播信道和数据信道的PHY配置是分开的。广播信道37、38、39在BLE 5.0之前只能用1M PHY。BLE 5.0引入了扩展广播Extended Advertising才允许在广播信道上使用Coded PHY。但即使支持扩展广播主广播信道Primary Advertising Channel仍然只能用1M PHYCoded PHY只能用在次广播信道Secondary Advertising Channel上。数据信道则可以在连接建立后通过PHY Update Procedure切换。这个切换是连接态的LL层过程主从双方通过LL_PHY_REQ和LL_PHY_RSP协商最终由主设备发起LL_PHY_UPDATE_IND来确定新的PHY。注意PHY切换不是立即生效的。LL_PHY_UPDATE_IND里有一个Instant参数表示从哪个连接事件开始使用新PHY。这个Instant必须留出足够的余量否则双方不同步会导致连接丢失。3. PHY选择背后的工程权衡距离、速率、功耗三角3.1 灵敏度与链路预算的计算链路预算是射频系统设计的核心概念。简单说链路预算 发射功率 - 接收灵敏度 天线增益 - 路径损耗。BLE的发射功率典型值是0dBm到10dBm接收灵敏度1M PHY典型值是-93dBm到-97dBm。以nRF52840为例数据手册给出的灵敏度1M PHY: -95dBm2M PHY: -91dBmCoded PHY S2: -100dBmCoded PHY S8: -103dBm假设发射功率4dBm天线增益0dBi那么1M PHY的链路预算是4 - (-95) 99dB。自由空间路径损耗公式FSPL(dB) 20log10(d) 20log10(f) - 147.55其中d是距离米f是频率Hz。2.4GHz下f 2.4e920log10(f) 187.6。所以FSPL 20log10(d) 187.6 - 147.55 20log10(d) 40.05令FSPL 99dB解得20log10(d) 58.95d ≈ 886米。这是理想自由空间下的理论距离。实际室内环境有墙壁、人体遮挡、多径衰落通常要打3到6折也就是150到300米左右。换成Coded PHY S8链路预算 4 - (-103) 107dB比1M多了8dB。8dB在距离上是什么概念距离每翻一倍FSPL增加6dB。所以8dB大约对应距离增加2.5倍。这就是为什么Coded PHY在远距离场景下有价值。3.2 2M PHY的吞吐量陷阱很多人看到2M PHY的第一反应是速率翻倍吞吐量也翻倍。实际测下来往往只有1.3到1.5倍的提升。原因有几个第一协议开销不变。每个数据包都有前导码、接入地址、LL头、CRC这些开销在1M和2M PHY下是一样的字节数但2M PHY下传输时间减半所以开销占比相对提高了。第二连接间隔和包数限制。一个连接事件里能发多少包受连接间隔、MTU、数据长度扩展DLE共同限制。如果你没开DLE最大payload是27字节2M PHY的优势发挥不出来。第三PHY切换本身有开销。LL_PHY_REQ/RSP/IND这三个包要在1M PHY下传输切换过程中还有Instant等待实际有效传输时间被压缩。我实测过一组数据nRF52840作为从设备连接间隔15msMTU 247DLE开启。1M PHY下吞吐量约700kbps2M PHY下约1.2Mbps。提升约1.7倍没有到2倍。3.3 Coded PHY的代价不只是速率Coded PHY S8的有效速率是125kbps听起来还能接受。但实际使用中连接事件的时间窗口会被拉长。因为每个包传输时间变成8倍如果连接间隔设得短主设备可能来不及在窗口内完成收发。更麻烦的是广播阶段的Coded PHY。扩展广播里主广播包在1M PHY上发次广播包在Coded PHY上发。扫描设备需要先收到主广播包知道次广播信道的参数再去次广播信道接收。这个过程中如果主广播包丢失整个广播就丢了。而主广播包用的是1M PHY灵敏度没有提升。所以Coded PHY的远距离优势在广播阶段会被主广播信道拖后腿。实操心得如果你的应用是远距离广播比如信标Coded PHY的实际增益没有理论值那么大。真正受益的是连接态因为连接态没有主广播信道这个瓶颈。4. nRF52840上的PHY配置实操4.1 广播阶段的PHY设置在nRF5 SDK里广播PHY的配置通过ble_gap_adv_params_t结构体里的primary_phy和secondary_phy字段设置。如果你用的是SoftDevice S140 v7.0以上支持扩展广播。ble_gap_adv_params_t adv_params; memset(adv_params, 0, sizeof(adv_params)); adv_params.properties.type BLE_GAP_ADV_TYPE_EXTENDED_CONNECTABLE_NONSCANNABLE_UNDIRECTED; adv_params.primary_phy BLE_GAP_PHY_1MBPS; adv_params.secondary_phy BLE_GAP_PHY_CODED; adv_params.duration 0; adv_params.interval 320; // 200ms adv_params.filter_policy BLE_GAP_ADV_FP_ANY;这里primary_phy必须是1Msecondary_phy可以选1M、2M或Coded。如果你把primary_phy设成2MSDK会返回错误因为协议规定主广播信道只能用1M。4.2 连接态的PHY更新连接建立后默认使用1M PHY。要切换到2M或Coded需要调用sd_ble_gap_phy_update()ble_gap_phys_t phys; phys.tx_phys BLE_GAP_PHY_2MBPS; phys.rx_phys BLE_GAP_PHY_2MBPS; uint32_t err sd_ble_gap_phy_update(conn_handle, phys);这个调用会触发LL_PHY_REQ从设备回复LL_PHY_RSP然后主设备发LL_PHY_UPDATE_IND。整个过程是异步的结果通过BLE_GAP_EVT_PHY_UPDATE事件返回。case BLE_GAP_EVT_PHY_UPDATE: { ble_gap_evt_phy_update_t const *p_phy_evt p_ble_evt-evt.gap_evt.params.phy_update; if (p_phy_evt-status BLE_HCI_STATUS_CODE_SUCCESS) { // PHY切换成功 NRF_LOG_INFO(PHY updated: tx%d, rx%d, p_phy_evt-tx_phy, p_phy_evt-rx_phy); } else { // PHY切换失败保持原PHY NRF_LOG_WARNING(PHY update failed: %d, p_phy_evt-status); } }注意PHY更新不是必须成功的。如果从设备不支持2M PHY或者射频条件太差协商会失败。你的代码必须处理失败情况不能假设切换一定成功。4.3 数据长度扩展与PHY的配合数据长度扩展Data Length ExtensionDLE是BLE 4.2引入的特性把最大payload从27字节扩展到251字节。DLE和2M PHY是绝配DLE减少协议开销占比2M PHY减少传输时间。在nRF5 SDK里DLE通过sd_ble_gap_data_length_update()设置ble_gap_data_length_params_t dl_params; dl_params.max_tx_octets 251; dl_params.max_rx_octets 251; dl_params.max_tx_time_us BLE_GAP_DATA_LENGTH_AUTO; dl_params.max_rx_time_us BLE_GAP_DATA_LENGTH_AUTO; sd_ble_gap_data_length_update(conn_handle, dl_params, NULL);实测数据不开DLE1M PHY吞吐量约200kbps开DLE1M PHY约700kbps开DLE 2M PHY约1.2Mbps。所以PHY升级要和DLE一起做单独升PHY效果有限。4.4 抓包验证PHY切换用nRF52840 Dongle配合Wireshark抓包可以看到PHY切换的完整过程。在Wireshark里过滤btle找到LL_PHY_REQ、LL_PHY_RSP、LL_PHY_UPDATE_IND这三个包。LL_PHY_UPDATE_IND里的Instant字段表示切换的连接事件号。抓包时有一个坑nRF Sniffer默认只抓1M PHY。如果你的连接已经切到2M PHYSniffer可能抓不到后续的数据包。需要在Sniffer配置里把PHY设成Auto或者手动指定2M。这个设置在nRF Connect for Desktop的Sniffer插件里可以改。5. 常见问题与排查实录5.1 PHY协商成功但吞吐量没提升这是最常见的问题。排查顺序检查DLE是否开启。如果payload还是27字节2M PHY的优势发挥不出来。用sd_ble_gap_data_length_update()确认。检查连接间隔。连接间隔太长每个连接事件能发的包数有限。15ms以下比较合适。检查MTU。ATT MTU默认23字节即使DLE开了ATT层还是按23字节分片。需要调用sd_ble_gattc_exchange_mtu_request()协商更大的MTU。检查从设备的PHY支持。有些从设备只支持1M PHY协商会失败。看BLE_GAP_EVT_PHY_UPDATE事件的status。5.2 Coded PHY连接不稳定Coded PHY灵敏度高但连接稳定性反而可能变差。原因通常是连接间隔和传输时间不匹配。Coded PHY S8下一个251字节的包传输时间约16ms251×8/125k ≈ 16ms。如果连接间隔是15ms主设备还没发完包连接事件就结束了导致丢包。解决办法把连接间隔拉长到30ms以上或者减小payload。实测S8下连接间隔至少要是单包传输时间的2倍才比较稳。5.3 广播包在Coded PHY下收不到前面说过扩展广播的主广播信道只能用1M PHY。如果你的扫描设备只监听Coded PHY是收不到主广播包的。扫描时需要同时监听1M PHY的主广播信道和Coded PHY的次广播信道。在nRF5 SDK里扫描参数设置ble_gap_scan_params_t scan_params; scan_params.phy BLE_GAP_PHY_1MBPS; // 主扫描PHY scan_params.extended 1; // 开启扩展扫描 scan_params.interval 160; // 100ms scan_params.window 80; // 50ms扩展扫描会自动处理次广播信道的PHY切换。5.4 PHY切换导致连接丢失PHY切换的Instant参数如果设置不当会导致双方不同步。Instant是连接事件计数器必须大于当前事件号加上一个安全余量。协议规定最小余量是6个连接事件。如果主设备设置的Instant太近从设备来不及准备就会丢连接。排查方法抓包看LL_PHY_UPDATE_IND的Instant和当前事件号的差值。如果小于6就是主设备的问题。这种情况在自研主设备时容易出现用手机做主设备一般不会有这个问题。5.5 常见问题速查表现象可能原因排查方法解决措施PHY协商成功但吞吐量低DLE未开、MTU小、连接间隔长检查DLE、MTU、连接间隔开DLE、协商MTU、缩短连接间隔Coded PHY连接不稳连接间隔小于传输时间计算单包传输时间拉长连接间隔或减小payload扫描不到Coded广播只监听Coded PHY检查扫描PHY配置主扫描用1M开启扩展扫描PHY切换后连接丢失Instant余量不足抓包看Instant差值增大Instant余量2M PHY灵敏度差符号率高导致灵敏度下降对比1M和2M的RSSI远距离场景用1M或Coded实操心得PHY调试一定要配合抓包。nRF Sniffer Wireshark是最顺手的组合。抓包时注意Sniffer的PHY设置否则会漏包。另外RSSI值可以作为PHY选择的参考RSSI低于-80dBm时2M PHY的误包率会明显上升这时候切回1M更稳。6. 从PHY延伸到系统设计几个容易被忽略的点6.1 射频前端对PHY的影响PHY的性能不只是芯片决定的射频前端天线、匹配网络、滤波器同样关键。2M PHY对射频前端的带宽要求更高如果天线的带宽不够2M PHY的灵敏度会进一步下降。实测中遇到过天线匹配在2.4GHz边缘的情况1M PHY正常2M PHY误包率飙升。用矢量网络分析仪测一下S11确保在2.4GHz到2.48GHz范围内回波损耗优于-10dB。6.2 PHY与功耗的关系2M PHY的射频开启时间短理论上功耗更低。但实际功耗还取决于连接间隔和包数。如果连接间隔很短射频频繁开启2M PHY的功耗优势会被抵消。用nRF Power Profiler Kit测过一组数据连接间隔15ms1M PHY平均电流约1.2mA2M PHY约0.9mA。连接间隔100ms时两者差距缩小到0.1mA以内。6.3 多PHY共存的设计有些产品需要同时支持1M、2M、Coded三种PHY。比如一个设备在近距离用2M传数据远距离自动切到Coded。这种设计需要在连接态动态切换PHY切换策略可以基于RSSI或误包率。nRF5 SDK提供了sd_ble_gap_phy_update()可以在连接态随时调用。但频繁切换PHY会增加协议开销建议设置一个滞回区间比如RSSI低于-75dBm切Coded高于-65dBm切回2M中间保持当前PHY。6.4 PHY与BLE Mesh的关系BLE Mesh的底层也是BLE连接和广播。Mesh的Remote Provisioning过程中Provisioner和Device之间建立的是GATT连接这个连接的PHY可以协商。如果Mesh网络里节点距离远可以考虑在Provisioning阶段用Coded PHY提高成功率。但Mesh的广播信道Provisioning Beacon用的是1M PHY这个改不了。我在一个Mesh项目里试过Provisioning阶段用Coded PHY成功率从70%提升到95%。但Provisioning完成后Mesh消息还是走1M PHY的广播远距离节点还是丢包。所以PHY优化要分阶段看不能一刀切。6.5 测试与验证方法PHY相关的测试建议至少做这几项灵敏度测试用信号发生器加衰减器逐步降低信号强度测误包率。1M PHY的误包率10%点作为灵敏度参考。吞吐量测试用两个nRF52840一个发一个收测不同PHY下的有效吞吐量。注意要开DLE和协商MTU。距离测试在开阔场地和室内分别测记录RSSI和误包率随距离的变化。功耗测试用Power Profiler Kit测不同PHY下的平均电流。这些测试数据是PHY选型的依据不要凭感觉选。7. 最后分享几个实操中的小技巧第一个技巧PHY切换前先确认对端支持。在发起sd_ble_gap_phy_update()之前可以通过BLE_GAP_EVT_CONNECTED事件里的peer_params或者GATT服务发现来确认对端支持的PHY。虽然协议允许协商失败但提前知道能减少不必要的切换尝试。第二个技巧抓包时把Sniffer的PHY设成Auto。nRF Sniffer的默认配置可能只抓1M PHY导致2M PHY的数据包看不到。在nRF Connect的Sniffer插件里把PHY设成Auto或者手动指定能抓到完整的PHY切换过程。第三个技巧Coded PHY下把连接间隔设成非整数倍。比如单包传输时间16ms连接间隔设成37ms而不是32ms避免多个连接事件的传输窗口重叠减少冲突概率。这个技巧在多个Coded PHY设备共存时特别有用。第四个技巧用RSSI做PHY切换的滞回判断。不要单纯看RSSI绝对值要看RSSI的变化趋势。如果RSSI在-70dBm附近波动频繁切换PHY反而增加开销。设置一个5dB的滞回区间比如低于-75dBm切Coded高于-70dBm切回2M中间保持。这些经验都是实际项目中一点点试出来的不同芯片、不同射频前端可能会有差异但思路是通用的。PHY这一层看起来简单真正调起来涉及的东西不少希望这些内容能帮你少走点弯路。