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收到后转发到UART |
| TX 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特征值要设成可通知,并且要加CCCD(Client 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减3(ATT头开销)。
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连接间隔15ms,MTU 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指令是最通用的做法,手机端发AT+NAME=XXX,设备解析后改名字并保存到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缓存就能解决。