PHY6252 BLE串口透传实战:从环境搭建到量产测试
1. 为什么选PHY6252做BLE串口透传1.1 这颗芯片到底适合谁PHY6252是一颗面向低功耗蓝牙应用的SoC集成BLE射频、MCU内核和丰富外设主打的就是小体积、低功耗、低成本。我第一次接触它是因为一个工业传感器采集项目现场设备只有UART口但客户要求手机能直接看数据布线又不允许。这种场景下BLE串口透传就是最省事的方案——把有线串口翻译成无线BLE服务手机端当作一个虚拟串口来收发。如果你手上有STM32、nRF52这类平台的经验转到PHY6252不会太痛苦因为它的SDK结构类似底层驱动、协议栈、应用层分离。但如果你是完全没碰过BLE协议栈的新手我建议先把GATT、Service、Characteristic这几个概念搞清楚再动手否则调透传的时候会一头雾水。PHY6252的典型应用场景包括工业设备参数配置与数据回传医疗健康类设备的短距离数据同步智能家居中控与子设备的调试通道传感器节点的无线数据采集这些场景的共同点是数据量不大、实时性要求中等、对功耗敏感、成本敏感。PHY6252在这几个维度上表现均衡尤其是它的休眠电流控制得不错适合电池供电的场合。1.2 串口透传的本质是什么很多人以为BLE串口透传就是BLE协议里有个串口服务其实不是。BLE协议栈本身没有串口这个概念透传是我们自己定义的一套数据搬运逻辑把UART收到的字节通过BLE的Notify或Write发出去把BLE收到的字节通过UART的TX脚发出去。中间不解析、不打包、不改内容所以叫透传。理解这一点很关键因为它决定了你的代码结构。你需要两个数据缓冲区一个收UART一个收BLE一个状态机来管理连接状态以及一套流控机制防止缓冲区溢出。PHY6252的SDK里通常会有类似的例程但例程往往只做了最简版本实际项目里要补的东西不少。提示透传不等于无脑转发。UART的波特率和BLE的连接间隔如果不匹配很容易丢数据。后面我会专门讲怎么算这个账。1.3 开发前需要准备的东西动手之前把下面这些备齐能省掉很多来回折腾的时间硬件PHY6252开发板一块、USB转TTL模块一个、杜邦线若干软件官方SDK从官网或代理渠道获取、Keil或IAR看SDK支持哪个、串口调试助手、手机端BLE调试App比如nRF Connect或LightBlue文档PHY6252数据手册、SDK里的API参考、BLE协议栈的GATT说明这里有个坑要提前说PHY6252的SDK版本比较多不同版本之间的API可能有差异。我建议你拿到SDK后先看它的Release Note确认版本号和例程的对应关系别拿着旧教程调新SDK会浪费很多时间。2. 环境搭建与SDK工程结构拆解2.1 SDK目录里哪些东西必须看PHY6252的SDK目录通常长这样不同版本略有差异SDK_ROOT/ ├── components/ # 协议栈、驱动、中间件 │ ├── ble/ # BLE协议栈相关 │ ├── drivers/ # 外设驱动 │ └── ... ├── examples/ # 例程 │ ├── ble_uart/ # 串口透传例程如果有 │ └── ... ├── projects/ # 工程文件 └── tools/ # 烧录、调试工具你必须重点看的是components/ble/和examples/这两个目录。前者是协议栈的API后者是官方给的参考实现。我的习惯是先把例程跑通再在例程基础上改而不是从零建工程——从零建工程容易漏掉协议栈的初始化顺序排查起来很痛苦。2.2 工程配置里最容易忽略的三个地方第一协议栈的RAM分配。BLE协议栈需要一块独立的RAM区域通常在链接脚本或工程配置里指定。如果这块区域太小协议栈跑不起来太大应用层就没内存用了。PHY6252的SDK一般会给一个推荐值但你要根据自己应用的数据缓冲区大小调整。第二中断优先级。BLE协议栈对中断时序很敏感UART中断的优先级不能高于协议栈相关的中断否则会出现连接不稳定甚至断连。这个在SDK文档里通常有说明但很多人不看直接默认配置然后就遇到手机连上一会儿就掉的问题。第三时钟配置。PHY6252的BLE射频需要精确的时钟源通常是外部晶振。如果你用的是内部RC振荡器BLE的连接间隔和时序会不准表现为连接参数协商失败或者通信丢包。这个坑我在早期项目里踩过换了晶振之后问题消失。2.3 编译与烧录的实操步骤以Keil为例流程大致如下打开projects/下对应的工程文件.uvprojx在Options for Target里确认芯片型号和下载器配置编译看是否有报错。常见报错是头文件路径没配全去C/C选项卡里补上连接开发板选择下载算法烧录复位后用手机App扫描看是否能发现设备烧录的时候注意有些PHY6252开发板需要先按住某个按键再上电才能进入下载模式具体看板子说明。如果烧录失败先检查这个。注意烧录前最好把之前的固件擦除干净尤其是协议栈的绑定信息bonding info存在Flash里的时候残留数据会导致新固件行为异常。3. BLE服务与特征值的定义逻辑3.1 透传服务该怎么设计BLE的数据交互是通过GATT层完成的。你要定义一个Service里面至少放两个Characteristic一个用于手机发给设备Write一个用于设备发给手机Notify。这是最经典的透传模型。UUID的选择上调试阶段可以用16位的标准UUID或者自定义的128位UUID。正式产品建议用128位自定义UUID避免和标准服务冲突。PHY6252的SDK里通常有UUID生成的宏你按格式填就行。一个典型的透传服务定义如下角色方向属性说明RX Characteristic手机→设备Write / Write Without Response手机写入数据设备从BLE收到后转发到UARTTX Characteristic设备→手机Notify设备从UART收到数据后通过Notify推给手机这里有个细节Write Without Response和Write With Response的区别。前者速度快但不保证送达后者有ACK但吞吐低。透传场景下如果数据量小且允许偶尔丢包用Without Response如果要求可靠用With Response但要接受速度下降。3.2 连接参数对透传性能的影响BLE的连接参数包括连接间隔Connection Interval、从机延迟Slave Latency和超时时间Supervision Timeout。这三个参数直接决定了透传的吞吐量和延迟。连接间隔的范围是7.5ms到4s。间隔越小吞吐越高但功耗越大。对于串口透传我一般建议波特率9600以下连接间隔20~30ms足够波特率115200连接间隔建议7.5~15ms波特率更高需要评估MTU大小和连接间隔的配合MTU最大传输单元默认是23字节实际可用载荷20字节。你可以通过MTU协商把它提到247字节甚至更高这样每次Notify能带更多数据减少协议开销。PHY6252的协议栈支持MTU协商但需要手机端也支持。计算一下假设MTU协商到247连接间隔15ms那么理论吞吐大约是247字节/15ms ≈ 16.5KB/s。实际因为协议开销和调度延迟打个七折大概11KB/s。这对应115200波特率约11.5KB/s刚好够用。如果你的数据量更大要么减小连接间隔要么提高MTU要么两者都调。3.3 属性表配置的实操细节在PHY6252的SDK里属性表通常是一个数组每个元素描述一个Attribute的句柄、类型、权限和值。配置的时候要注意句柄顺序GATT要求Service Declaration在前Characteristic Declaration在后然后是Characteristic Value和Descriptor。顺序错了手机端可能识别不了。权限设置Write特征值要设成可写Notify特征值要设成可通知并且要加CCCDClient Characteristic Configuration Descriptor否则手机端无法开启Notify。CCCD的处理手机端开启Notify时会写CCCD你的代码要捕获这个写操作把对应的标志位置位后续才能往这个特征值发Notify。这部分代码在SDK例程里一般都有但例程可能只做了一个特征值。你要扩展到两个RX和TX需要复制并修改句柄和回调逻辑。改的时候仔细核对句柄索引错一个就会导致数据发到错误的特征值上。4. UART与BLE之间的数据搬运实现4.1 数据缓冲区的设计透传的核心是两个缓冲区UART接收缓冲区和BLE发送缓冲区。UART中断收到数据后先存入缓冲区然后由主循环或BLE事件回调把数据通过Notify发出去。反过来BLE收到Write后存入另一个缓冲区由主循环通过UART发送。缓冲区的设计要考虑几个问题大小太小会溢出太大会占RAM。一般建议UART接收缓冲区至少能存两倍于最大数据包的长度。环形还是线性环形缓冲区更适合持续数据流线性缓冲区适合定长包。透传场景推荐环形缓冲区。并发保护UART中断和BLE回调可能同时访问缓冲区需要关中断或加锁保护。我在实际项目里用的环形缓冲区结构大概是这样的typedef struct { uint8_t *buf; uint16_t size; volatile uint16_t head; volatile uint16_t tail; } ring_buf_t; bool ring_buf_put(ring_buf_t *rb, uint8_t data) { uint16_t next (rb-head 1) % rb-size; if (next rb-tail) return false; // full rb-buf[rb-head] data; rb-head next; return true; } bool ring_buf_get(ring_buf_t *rb, uint8_t *data) { if (rb-head rb-tail) return false; // empty *data rb-buf[rb-tail]; rb-tail (rb-tail 1) % rb-size; return true; }这个结构简单可靠head和tail用volatile修饰单生产者单消费者场景下不需要额外加锁。4.2 UART中断服务函数的写法UART中断里只做一件事把收到的字节塞进环形缓冲区。不要在里面做数据处理更不要在里面调用BLE的发送函数因为BLE发送可能阻塞会拖垮中断响应。void UART_IRQHandler(void) { if (UART_GetITStatus(UART, UART_IT_RX)) { uint8_t data UART_ReceiveData(UART); ring_buf_put(uart_rx_buf, data); UART_ClearITPendingBit(UART, UART_IT_RX); } }主循环里定期检查uart_rx_buf是否有数据有的话就通过BLE Notify发出去。发送的时候要注意MTU限制一次不要超过协商后的MTU减3ATT头开销。4.3 BLE Notify的触发时机Notify不能随便发必须在连接建立、CCCD被开启之后才能发。而且发送频率受连接间隔限制你不能在一个连接事件里发多次Notify除非协议栈支持队列。我的做法是在主循环里检查ble_tx_buf如果有数据且连接已建立且CCCD已开启就调用SDK的Notify API发送。发送成功后再从缓冲区取下一包。如果发送失败比如协议栈忙就等下一轮循环再试。这里有个经验不要在主循环里用while循环把缓冲区里的数据一次性全发完因为协议栈的发送队列有限发太快会丢包。正确的做法是每次循环发一包让协议栈有时间处理。4.4 流控与丢包处理透传最怕的就是丢包。丢包的来源有两个UART缓冲区溢出和BLE发送队列满。UART缓冲区溢出的预防如果UART数据来得太快主循环来不及搬运缓冲区就会满。解决办法是加流控——当缓冲区快满时通过BLE发一个暂停信号给手机端让手机端慢点发。或者用硬件流控RTS/CTS但PHY6252的UART是否支持硬件流控要看具体型号。BLE发送队列满的处理SDK的Notify API通常会返回一个状态码如果返回busy说明协议栈队列满了这时候不要重试等下一次连接事件再发。重试会导致协议栈状态混乱。提示调试透传的时候建议在手机端和UART端同时打时间戳对比数据到达时间这样能快速定位是UART慢了还是BLE慢了。5. 实测中遇到的典型问题与排查链路5.1 手机连不上或连上就断这个问题我遇到过好几次排查下来原因主要有三类第一类广播参数不对。广播间隔太长手机扫描不到广播数据格式错误手机解析失败。检查广播间隔是否在20ms到1s之间广播数据是否符合BLE规范。第二类连接参数不兼容。手机端发起的连接参数比如连接间隔超出了PHY6252协议栈的支持范围导致连接建立后立即断开。解决办法是在协议栈里配置可接受的连接参数范围或者让手机端用默认参数。第三类电源问题。BLE射频在连接建立瞬间电流会跳变如果电源供电不足芯片会复位。用示波器看电源纹波如果跳变超过100mV就要加电容或者换LDO。排查链路先用手机App看能否扫描到广播 → 能扫描到但连不上看广播数据 → 能连上但马上断看连接参数和电源 → 都正常看协议栈日志。5.2 数据发出去但手机收不到这种情况通常是CCCD没开启或者Notify的句柄用错了。排查步骤用手机App查看透传服务的TX特征值确认CCCD是否显示Notify enabled如果没有手动开启看是否能收到数据如果能收到说明代码里没有正确处理CCCD的写事件去检查CCCD回调如果还是收不到检查Notify API的句柄参数是否和TX特征值的句柄一致我踩过一次坑SDK例程里TX特征值的句柄是硬编码的我加了RX特征值之后句柄偏移了但Notify还是用的旧句柄结果数据发到了RX特征值上手机端当然收不到。这种问题看日志很难发现只能对着属性表一个个核对。5.3 大数据量下丢包严重数据量一大就丢包通常是缓冲区太小或者流控没做好。先算账假设UART波特率115200每秒最多11520字节。BLE连接间隔15msMTU 247每秒最多发66包每包244字节理论吞吐16KB/s。看起来够但实际因为协议栈调度有效吞吐可能只有10KB/s。如果UART持续以11520字节/秒的速度发BLE发不过来缓冲区就会满。解决办法增大UART接收缓冲区比如从256字节加到2048字节降低UART波特率或者让发送端加流控提高BLE吞吐减小连接间隔、增大MTU、用Write Without Response实测下来115200波特率配15ms连接间隔和247 MTU持续传输时偶尔会丢几个字节。把连接间隔降到7.5ms后丢包率明显下降。但7.5ms对手机端功耗有影响需要权衡。5.4 功耗比预期高PHY6252标称的休眠电流很低但实际项目里如果配置不当功耗会高一个数量级。常见的功耗问题广播间隔太短广播间隔从1s降到100ms平均功耗可能翻几倍连接间隔太短7.5ms的连接间隔比100ms的功耗高很多GPIO漏电未使用的GPIO如果悬空可能产生漏电流配置成下拉或模拟输入UART一直开着UART的RX引脚如果有数据翻转会唤醒芯片不用的时候关掉我的做法是在满足功能的前提下尽量用大的连接间隔和广播间隔。如果应用允许用Slave Latency让从机跳过一些连接事件进一步省电。6. AT指令扩展与量产考虑6.1 为什么要在透传基础上加AT指令纯透传只能传数据没法配置参数。实际产品里客户可能需要改设备名、改波特率、改连接参数这些都需要一套配置接口。AT指令是最通用的做法手机端发ATNAMEXXX设备解析后改名字并保存到Flash。AT指令的解析要和透传数据区分开。我的做法是在UART接收缓冲区里检测AT前缀如果是AT指令就进解析流程否则走透传。但这样有个问题如果透传数据里恰好包含AT会误判。更可靠的做法是用一个独立的配置特征值手机端通过写这个特征值来发AT指令透传数据走另一个特征值。这样物理隔离不会误判。6.2 AT指令解析的状态机设计AT指令解析用状态机最稳妥。状态包括空闲、收到A、收到AT、收到AT、解析命令、解析参数、执行、返回结果。typedef enum { AT_STATE_IDLE, AT_STATE_A, AT_STATE_AT, AT_STATE_CMD, AT_STATE_PARAM, AT_STATE_DONE } at_state_t;每个状态处理一个字符遇到非法字符就回退到IDLE。解析完成后查表找到对应的处理函数执行并返回结果。指令表可以用结构体数组typedef struct { const char *cmd; void (*handler)(const char *param); } at_cmd_t; const at_cmd_t at_cmds[] { {NAME, at_handler_name}, {BAUD, at_handler_baud}, {RESET, at_handler_reset}, {NULL, NULL} };这种结构扩展方便加新指令只需要加一行。6.3 参数保存与掉电恢复配置参数要存到Flash里掉电不丢。PHY6252的SDK通常提供Flash读写API但要注意Flash写入前要先擦除擦除是按扇区的写入次数有限不要频繁写参数变了才写写入过程中如果掉电数据可能损坏建议加校验和或者双备份我的做法是参数结构体加一个CRC字段上电时读出来校验校验失败就用默认值。双备份就是存两份读的时候选校验通过的那份。6.4 量产测试的注意事项量产的时候每台设备都要测BLE连接和串口透传。如果手动测效率太低。我的做法是做一个自动化测试工装工装上的主控通过UART连设备手机端或者另一个BLE主设备连设备然后自动跑一遍连接、发数据、收数据、断开的流程结果通过UART返回给工装。测试项包括测试项方法合格标准广播扫描10秒能发现设备连接发起连接3秒内连接成功透传发1000字节收到1000字节无误码断开主动断开1秒内断开可重新连接功耗测休眠电流小于规格值量产固件和调试固件要分开。调试固件开日志量产固件关日志否则日志输出会影响功耗和时序。7. 一些个人经验与后续扩展方向透传做完之后我还试过在PHY6252上跑一些简单的数据处理比如在透传通道上加一层校验和重传提高可靠性。这个思路适合数据量不大但对可靠性要求高的场景代价是吞吐下降。另一个方向是BLE主从切换。PHY6252支持同时做从机和主机这意味着它可以一边和手机透传一边去连别的BLE传感器。这个在网关类应用里很有用但协议栈的配置会复杂不少需要仔细规划连接数和资源分配。还有一点PHY6252的SDK更新比较频繁建议关注官方的Release Note新版本可能修复了旧版本的bug也可能引入了新的API。升级SDK之前先在测试环境验证别直接上量产。最后说一个调试技巧如果BLE行为异常先用手机端的抓包工具比如nRF Connect的日志功能看空口数据确认是设备端的问题还是手机端的问题。很多时候问题出在手机端的连接参数或者缓存上清一下手机端的BLE缓存就能解决。