ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

无线脑电采集原型:BW16与ESP32-CYD的BLE转UART显示方案

无线脑电采集原型:BW16与ESP32-CYD的BLE转UART显示方案 脑电采集这件事很多人卡在同一个地方电极、放大、ADC 都调通了数据也出来了但怎么把它从贴在头上的那块板子稳稳当当地送到你能盯着看的屏幕上这一步反而最折腾。我这次做的原型链路核心目标就一个——让脑电模块采到的数据不靠一根线拴着直接无线飞到另一块带屏的开发板上显示同时还能开个网页看波形。整条链路用的是 BW16 做无线桥接ESP32-CYD 做接收和显示中间走 BLE 转 UART 的思路。关键词里提到的 BW16、ESP32-CYD、EEG、BLE、UART基本就是这条链路的全部骨架。这篇文章适合两类人看一类是手上已经有脑电采集模块、想把数据无线传出来做可视化的另一类是玩 ESP32 和 BW16、想找个真实场景把 BLE 和 UART 串起来的。我不打算讲太多脑电信号处理的理论重点放在链路怎么搭、为什么这么搭、哪里容易翻车上。整套东西我反复调了几天踩的坑不少下面按我实际的搭建顺序拆开讲。1. 先想清楚这条链路到底要解决什么问题1.1 有线方案的死结在哪脑电采集最传统的做法是电极接放大板放大板通过 USB 转串口芯片把数据送到电脑。关键词里出现的 FT231X、FT232R 这类 USB 转 UART 驱动就是这条老路上的常客。这套方案稳定、延迟低但它有个绕不开的问题人被一根线拴在电脑旁边。做运动相关的脑电实验、做需要走动的场景这根线就是灾难。线一扯电极就松信号全是运动伪迹采出来的数据基本没法用。所以无线化不是锦上添花而是很多场景的刚需。但无线化又会引入新问题延迟、丢包、供电、配对稳定性。我一开始想的是直接用 ESP32 自带的 BLE 接收脑电模块的数据但实测下来发现脑电模块的无线协议和 ESP32 的 BLE 栈对接起来很别扭尤其是脑电模块那边用的是自定义的 BLE 服务UUID 和特征值都得自己扒。关键词里ble鼠标uuid这种搜索说明很多人也在为 UUID 匹配头疼。1.2 为什么选 BW16 做中间桥BW16 这块板子最大的价值在于它同时有 BLE 和 Wi-Fi而且能跑 UART 透传。我的思路是脑电模块通过 BLE 把数据发给 BW16BW16 收到后通过 UART 把数据吐给 ESP32-CYDESP32-CYD 负责显示和开网页。这样分工的好处是BW16 专心做无线协议对接ESP32-CYD 专心做显示和网络服务两边通过最简单的 UART 通信协议连接耦合度低哪边出问题都好排查。这里有个关键决策为什么不让 ESP32-CYD 直接收 BLE因为 ESP32-CYD 这块板子带屏跑显示和网页服务已经占了不少资源再让它同时处理 BLE 接收和协议解析容易卡顿。把 BLE 这块交给 BW16ESP32-CYD 只处理 UART 进来的干净数据负担小很多。这是我实测后的选择不是拍脑袋定的。1.3 整条链路的数据流数据从脑电模块出来经过 BLE 到 BW16BW16 通过 UART 发给 ESP32-CYDESP32-CYD 一边在屏幕上画波形一边通过 Wi-Fi 开一个网页把数据推出去。整条链路里UART 是唯一的有线环节但它只是两块板子之间短短几厘米的连线不影响人的活动自由。提示这条链路里最容易被低估的是 UART 的波特率匹配。脑电数据虽然单通道采样率不高但多通道叠加后数据量不小波特率设低了会丢数据设高了又容易出错。后面会专门讲怎么定这个值。2. BW16 端的 BLE 接收与 UART 转发怎么落地2.1 BLE 服务发现先把脑电模块的 UUID 扒清楚BW16 要接收脑电模块的数据第一步是搞清楚脑电模块暴露了哪些 BLE 服务和特征值。这一步没有捷径只能用 BLE 扫描工具先把设备扫出来然后逐个服务去看。关键词里ble蓝牙助手 小牛这类工具就是干这个用的。我实际操作时先扫到脑电模块的设备名然后展开它的服务列表找到那个带 notify 属性的特征值——脑电数据通常是通过 notify 推过来的。找到 notify 特征值后要记下三样东西服务 UUID、特征值 UUID、以及数据格式。数据格式这块最容易翻车因为不同脑电模块打包方式不一样有的是每包一个采样点有的是每包多个通道交错。我建议先把原始字节流抓下来用十六进制看一眼确认包头包尾和通道顺序再写解析代码。// BW16 BLE 接收回调的简化结构 void notifyCallback(BLERemoteCharacteristic* chr, uint8_t* data, size_t len) { // data 就是脑电模块推过来的原始字节 // 这里直接通过 UART 转发出去解析交给 ESP32-CYD Serial1.write(data, len); }上面这段是简化逻辑实际用的时候要注意BLE 回调里不要做耗时操作否则会丢包。我一开始在回调里做了解析结果数据断断续续后来改成回调里只做转发解析全部丢给 ESP32-CYD问题就没了。2.2 UART 参数怎么定波特率的取舍UART 通信协议里波特率、数据位、停止位、校验位这几个参数必须两边完全一致。我用的配置是 115200 波特率、8 数据位、1 停止位、无校验也就是常说的 8N1。为什么选 115200因为脑电模块单通道 250Hz 采样、每点 3 字节的话单通道就是 750 字节每秒8 通道也就 6KB 每秒左右115200 波特率理论能跑 11KB 每秒以上留了余量。但这里有个坑BW16 的 UART 缓冲和 ESP32-CYD 的接收缓冲如果没配好高速率下会丢数据。我的做法是在 ESP32-CYD 端开一个环形缓冲区UART 中断里只往缓冲区塞数据主循环再慢慢取出来解析。这样即使主循环偶尔卡一下也不会丢 UART 数据。参数取值说明波特率115200留足余量兼顾稳定性数据位8标准配置停止位1标准配置校验位无脑电数据对单字节错误不敏感省开销流控无数据量不大暂不需要2.3 转发格式的设计给数据加个简单的帧头直接透传原始字节有个问题ESP32-CYD 不知道一包数据从哪开始、到哪结束。所以我在 BW16 转发时加了一个极简的帧结构帧头两个字节 0xAA 0x55然后一个字节表示数据长度后面跟实际数据最后加一个校验字节。这样 ESP32-CYD 收到数据后先找帧头再按长度取数据校验通过才解析。这个帧结构是我自己定的不是什么标准协议但胜在简单可靠。关键词里uart传输通信时序16550行业标准uart这些讲的是 UART 底层的时序和硬件标准那是芯片层面的事我们应用层不用管那么细但理解 UART 是异步、按字节传输的对设计帧结构有帮助——正因为它是字节流没有天然边界才需要我们自己加帧头。3. ESP32-CYD 端的接收、显示与网页服务3.1 环形缓冲区UART 数据不丢的关键ESP32-CYD 这边UART 接收我用的是中断加环形缓冲区的组合。中断服务函数里每收到一个字节就往环形缓冲区写主循环里再判断缓冲区里有没有完整的一帧。环形缓冲区的大小我设的是 4096 字节足够缓冲几帧数据。这里要注意中断里操作环形缓冲区的读写指针时要考虑主循环也在读得用原子操作或者临界区保护否则指针会错乱。// 环形缓冲区简化实现 #define BUF_SIZE 4096 uint8_t ringBuf[BUF_SIZE]; volatile int head 0, tail 0; void IRAM_ATTR uartISR() { uint8_t b Serial1.read(); int next (head 1) % BUF_SIZE; if (next ! tail) { // 缓冲区没满 ringBuf[head] b; head next; } }上面这段是核心逻辑实际用的时候Serial1.read()在中断里调用要小心不同 Arduino 核心版本行为不一样有的版本在中断里读串口会出问题。我后来改成用 ESP32 的 UART 驱动注册事件回调更稳。3.2 屏幕波形绘制别让刷新拖垮主循环ESP32-CYD 带的那块屏是 TFT用 TFT_eSPI 库驱动。画波形最直接的做法是每来一个点就画一个像素但这样刷新太频繁主循环会被拖垮。我的做法是维护一个屏幕宽度的采样点数组每收到一批数据就更新数组然后整屏重绘。重绘频率控制在 20 到 30 帧每秒人眼看着就够流畅了。这里有个经验TFT 整屏重绘其实很慢尤其是 SPI 接口的屏。我一开始整屏重绘帧率只有个位数。后来改成只重绘波形区域坐标轴和文字不动帧率一下就上去了。这个优化思路和很多嵌入式 GUI 的做法一样——只刷新变化的部分。3.3 网页服务用 WebSocket 推数据比轮询香ESP32-CYD 开网页最省事的方案是起一个 HTTP 服务器网页里用 WebSocket 连上来ESP32 主动把数据推给网页。为什么不用 HTTP 轮询因为轮询要么延迟高要么请求太频繁把 ESP32 压垮。WebSocket 是长连接数据来了就推延迟低开销也小。网页端我用的是 Canvas 画波形和屏幕上一样的思路维护一个数组收到数据就更新数组重绘。这里要注意ESP32 的内存有限WebSocket 发送频率不能太高我控制在每秒 20 次左右每次发一批数据而不是每个点发一次。注意ESP32-CYD 同时跑屏幕刷新和 WebSocket 推送时Wi-Fi 和 SPI 屏会抢资源。我的经验是把屏幕刷新和 WebSocket 推送错开比如屏幕 30 帧、网页 20 帧别让它们在同一时刻都满负荷。4. 实测中踩过的坑和排查链路4.1 数据断流从 BLE 回调一路查到 UART 缓冲第一次跑通后我发现屏幕上波形时不时断一下。排查思路是这样的先确认脑电模块本身数据是否连续用 BLE 工具直接看 notify 数据发现模块那边是连续的问题不在源头。然后查 BW16 的 UART 转发用逻辑分析仪抓 UART 波形发现转发有间隙。再查 BW16 代码发现 BLE 回调里做了字符串格式化耗时太长导致丢包。把格式化去掉改成纯字节转发断流消失。这个排查链路的关键是分段验证源头、桥接、接收一段一段确认别一上来就怀疑最复杂的部分。很多人一遇到断流就怀疑无线其实往往是代码里的耗时操作在作怪。4.2 网页波形和屏幕波形不同步屏幕和网页显示的数据来自同一个 UART 流但两边刷新节奏不一样看起来就不同步。这个问题其实无解也不需要解——它们本来就是两个独立的显示端。我后来干脆让网页端显示稍微滞后一点作为回看用屏幕端显示实时波形。这样分工反而更合理。4.3 供电问题两块板子抢电流BW16 和 ESP32-CYD 都挺吃电流尤其是 ESP32-CYD 带屏还开 Wi-Fi 的时候。我一开始用同一个 USB 口供电结果屏幕偶尔闪一下BLE 也断。后来分开供电BW16 单独一路ESP32-CYD 单独一路问题解决。这个坑很隐蔽因为电压看起来是够的但电流瞬时不够就会出问题。现象可能原因排查方法波形断流BLE 回调耗时逻辑分析仪抓 UART屏幕闪烁供电电流不足分开供电测试网页延迟高WebSocket 发送太频繁降低推送频率配对失败UUID 不匹配BLE 工具核对服务列表4.4 UART 电平匹配3.3V 和 5V 别混BW16 和 ESP32-CYD 都是 3.3V 逻辑直接连就行。但如果你中间接了别的模块比如某些 USB 转 UART 芯片是 5V 逻辑直接连会烧。关键词里ft232r usb uart驱动安装ft231x usb uart驱动这些说明很多人用这类芯片做调试接线前一定确认电平。我习惯在 UART 线上串一个 1K 电阻做保护虽然会影响一点信号质量但低速下没影响安全第一。5. 这条链路还能怎么扩展5.1 加 SD 卡做本地记录ESP32-CYD 有 SD 卡槽可以把 UART 收到的数据同时写到 SD 卡里做离线记录。这样即使网页断了数据也还在。写 SD 卡要注意别和屏幕刷新抢 SPI 总线我的做法是攒一批数据再写别一个点一个点写。5.2 加简单的脑电去噪关键词里eeg去噪是个大话题。在这条链路上最轻量的去噪就是滑动平均或者简单的带通滤波。我在 ESP32-CYD 端加了一个 50Hz 陷波因为工频干扰太明显了。陷波用二阶 IIR 实现计算量不大ESP32 跑得动。更复杂的去噪比如独立成分分析就别指望在 ESP32 上跑了老老实实把数据传到电脑上处理。5.3 多设备组网如果要做多通道、多设备BW16 的 Wi-Fi 能力就能派上用场。可以让多个 BW16 通过 Wi-Fi 把数据汇总到一个接收端ESP32-CYD 只负责显示。这样扩展性更好但复杂度也上去了看需求取舍。5.4 关于 EEG 源定位的说明关键词里出现了eeg 源定位 最小范数估计这属于脑电信号处理的高阶内容需要多通道高密度采集和复杂的数学计算不是这条原型链路能覆盖的。这条链路的价值在于把数据可靠地采出来、传出来、显示出来为后续处理提供干净的数据源。源定位那套东西得在电脑上用专业工具做别想着在单片机上实现。整条链路搭下来我最大的体会是无线脑电原型的难点不在无线本身而在数据流的每一段衔接处。BLE 回调、UART 缓冲、屏幕刷新、WebSocket 推送每一段单独看都不难但串起来就容易在衔接处出问题。分段验证、逐段排查是我觉得最靠谱的方法。另外别小看供电和电平这些低级问题它们往往比协议问题更让人抓狂。
返回列表