ARTICLE DETAIL

资讯详情

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

基于BW16与ESP32-CYD的脑电信号无线采集与实时显示系统

基于BW16与ESP32-CYD的脑电信号无线采集与实时显示系统 1. 项目缘起与整体链路设计脑电信号采集这件事早年给人的印象就是一堆笨重设备加专用软件动辄几万块还得在实验室里正襟危坐。但这两年开源硬件和低功耗无线模块的成熟让个人开发者也能搭出一条像样的原型链路。我这次折腾的目标很明确把一块干电极脑电模块采集到的原始信号通过无线方式送到一块带屏幕的开发板上实时显示波形同时再转发到网页端做进一步观察。整条链路的核心节点就两个——BW16负责无线透传ESP32-CYD负责接收、显示和转发。先说清楚这两个东西是什么。BW16 是安信可基于 RTL8720DN 方案做的一款双频 Wi-Fi 加蓝牙模块支持 BLE 5.0价格便宜社区资料也还算够用。ESP32-CYD 则是“CYD”即 Cheap Yellow Display 的缩写本质是一块带 2.8 寸 ILI9341 触摸屏的 ESP32 开发板板载了屏幕驱动和触摸芯片省去了自己接线焊屏的麻烦。把这两个凑在一起一个负责“无线搬运”一个负责“落地呈现”分工非常清晰。为什么选 BLE 而不是 Wi-Fi 做第一段链路这里有个实际考量。脑电模块本身是电池供电的小型设备功耗敏感BLE 在空闲和低占空比传输场景下的功耗远低于持续连接的 Wi-Fi。而且 BLE 的配对和连接流程对手机、电脑都友好调试阶段用现成的蓝牙调试工具就能先验证数据通不通不用一上来就写完整固件。至于第二段从 ESP32-CYD 到网页走的是 Wi-Fi 加 WebSocket 的路子因为网页端需要的是持续、低延迟的推送WebSocket 比轮询 HTTP 合适得多。整条链路的数据流是这样的脑电模块通过 UART 把采样数据吐给 BW16BW16 用 BLE 把数据以通知Notify方式发给 ESP32-CYDCYD 收到后一方面刷新屏幕上的波形另一方面通过 Wi-Fi 把数据推给浏览器里的网页。听起来简单但每一段都有坑下面逐个拆。提示这条链路是原型验证性质不是医疗级方案。脑电信号本身极其微弱干电极接触阻抗、工频干扰、运动伪迹都会严重影响数据质量做原型时先把“通不通”跑通再谈“准不准”。2. 核心硬件选型与接口原理拆解2.1 BW16 的角色定位与 UART 接口BW16 在这条链路里扮演的是“翻译官”加“搬运工”。脑电模块输出的是标准 UART 串口数据波特率常见的是 115200 或 460800具体看模块手册。BW16 通过自己的 UART 接收这些字节流然后原封不动地塞进 BLE 的 Notify 特征值里发出去。这里的关键是不要做协议解析让 BW16 只做透传把解析工作留给 ESP32-CYD。原因很简单BW16 的固件开发环境相对没那么顺手把复杂逻辑放上去调试成本高而 ESP32 这边有成熟的 Arduino 框架和丰富的库处理数据更灵活。UART 的接线要注意交叉脑电模块的 TX 接 BW16 的 RX模块的 RX 接 BW16 的 TX地线必须共地。如果脑电模块是 3.3V 电平直接连就行如果是 5V 电平中间要加电平转换否则可能烧掉 BW16 的 IO。我实测下来很多干电极模块标称 3.3V 但实际空闲电平会飘到 3.6V 左右保险起见可以串一个 100 欧姆电阻再接或者用一颗双向电平转换芯片。BW16 的 UART 波特率要和脑电模块严格一致误差超过 2% 就可能出现乱码。RTL8720DN 的 UART 时钟源可以配置在 Arduino 环境下用Serial1.begin(baudrate)即可。如果发现收到的数据偶尔错位先怀疑波特率再怀疑地线干扰。2.2 ESP32-CYD 的屏幕与触摸资源ESP32-CYD 这块板子最大的好处是把屏幕和触摸都集成好了引脚定义在社区里已经有公开的参考。它用的是 ESP32-WROOM-32 模组双核 240MHz跑波形刷新和 Wi-Fi 推送完全够用。屏幕是 ILI9341 驱动320x240 分辨率SPI 接口。触摸是 XPT2046也是 SPI和屏幕共用部分引脚但片选分开。这里有个细节值得说CYD 的屏幕背光是可以 PWM 调光的引脚是 GPIO21。原型阶段我建议把背光调到 80% 左右既省电又不会太刺眼。另外屏幕的 SPI 频率不要一上来就拉到最高ILI9341 在 40MHz 下通常稳定但如果你飞线较长或者板子质量一般降到 27MHz 更保险。我一开始用 80MHz 结果屏幕偶尔花屏降到 40MHz 后连续跑几小时都没问题。触摸功能在这条链路里其实不是必须的但留着有用——比如可以做个按钮切换显示通道、暂停波形、清屏等。XPT2046 的读数需要做简单的滤波否则触摸坐标会跳。我一般取连续 5 次采样去掉最大最小值再平均效果就稳很多。2.3 无线链路的频段与干扰考量BW16 支持 2.4GHz 和 5GHz 双频 Wi-Fi但 BLE 只在 2.4GHz。ESP32-CYD 的 Wi-Fi 也是 2.4GHz。这意味着如果 CYD 同时开着 Wi-Fi 和 BLE两者会在同一频段竞争。实际测试中BLE 连接和 Wi-Fi 上传同时工作时偶尔会出现 BLE 丢包。解决办法有两个一是把 Wi-Fi 信道固定在 1、6、11 中相对干净的一个避开 BLE 广播信道37、38、39 对应 2402、2426、2480MHz二是降低 Wi-Fi 上传频率比如每 50ms 推一次而不是每 10ms 推一次给 BLE 留出空口时间。注意BLE 的 Notify 单包有效载荷默认是 20 字节可以协商到 244 字节BLE 4.2 及以上。BW16 支持 MTU 协商在 ESP32 侧发起连接后主动请求更大的 MTU能显著减少包数量降低丢包概率。3. 从脑电模块到 BW16 的数据搬运实操3.1 脑电模块输出格式的确认不同脑电模块的输出协议差别很大。有的直接吐原始 ADC 值有的已经做了滤波和包封装。我手上这块是每帧 3 字节包含一个通道的 24 位有符号采样值帧头是 0xA0。拿到模块第一件事不是写代码而是用 USB 转 UART 工具接电脑用串口助手看原始字节。这一步能省掉后面大量猜测。看数据时重点关注三件事帧头是否稳定、采样率大概多少、数值范围是否合理。如果帧头频繁错位说明波特率不对或者模块本身在丢帧。采样率可以通过统计一秒内收到的完整帧数来估算。数值范围如果一直在满量程附近跳可能是电极没贴好或者增益设太大。3.2 BW16 透传固件的关键配置BW16 在 Arduino 环境下需要安装对应的板级支持包。配置 BLE 时核心是创建一个 Service 和一个 Notify Characteristic。UUID 可以自定义但建议避开标准 UUID 段用随机生成的 128 位 UUID减少和系统服务冲突的概率。透传逻辑很简单Serial1.available()有数据就读进来攒够一帧就characteristic.writeValue()发出去。但这里有个性能陷阱——如果每收到一个字节就发一次 Notify包数量会爆炸BLE 栈根本扛不住。正确做法是攒够一帧再发或者攒到接近 MTU 上限再发。我一般设一个 64 字节的缓冲区满了或者超时 5ms 就发。// BW16 透传核心逻辑示意 uint8_t buf[64]; int idx 0; unsigned long lastSend 0; void loop() { while (Serial1.available()) { buf[idx] Serial1.read(); if (idx 64) { sendNotify(buf, idx); idx 0; lastSend millis(); } } if (idx 0 millis() - lastSend 5) { sendNotify(buf, idx); idx 0; lastSend millis(); } }3.3 波特率与缓冲区的匹配计算假设脑电模块采样率是 250Hz每帧 3 字节那么每秒数据量是 750 字节。115200 波特率下UART 每秒能传约 11520 字节绰绰有余。但 BLE 这边如果 MTU 是 23默认每包 20 字节有效载荷750 字节需要约 38 包每秒。BLE 连接间隔如果是 30ms理论上每秒最多 33 个包就会不够。所以要么把连接间隔降到 15ms要么协商更大 MTU。我实测把 MTU 协商到 185 后每包能带 182 字节每秒只需 5 包左右非常轻松。提示连接间隔不是越短越好。太短会增加双方功耗而且如果从机处理不过来反而丢包。15ms 到 30ms 是原型阶段比较舒服的区间。4. ESP32-CYD 端的接收、显示与网页推送4.1 BLE 客户端连接与数据回调ESP32-CYD 作为 BLE 客户端需要先扫描到 BW16 的设备名然后发起连接再注册 Notify 回调。Arduino 的 BLE 库ESP32 自带用起来还算顺手但要注意回调函数里不要做耗时操作否则会阻塞 BLE 栈。我的做法是回调里只把数据塞进一个环形缓冲区主循环再去消费。环形缓冲区大小建议至少 4KB能扛住短暂的 Wi-Fi 卡顿。如果缓冲区满了新数据直接丢弃并计数这样能直观看到丢包情况。我一开始缓冲区只给了 512 字节结果 Wi-Fi 一抖动就丢一片数据波形上出现明显断档。4.2 屏幕波形刷新的性能取舍ILI9341 刷全屏 320x240 在 SPI 40MHz 下大约需要 30ms 左右如果每收到一帧就刷全屏根本来不及。所以必须做局部刷新——只重绘波形变化的那一列或那几行。我的做法是维护一个 320 点的波形数组每来一个新采样就左移一位然后只重绘波形区域。波形区域高度 200 像素宽度 320用pushImage或者逐列画线的方式更新。逐列画线虽然代码简单但每列都要设置窗口再写数据开销不小。更高效的方式是用一个 320x200 的 16 位色缓冲区在内存里画好波形然后一次性pushImage推过去。ESP32 有 520KB SRAM320x200x2 是 128KB放得下。实测这种方式刷新率能到 20fps 以上波形看起来很流畅。4.3 WebSocket 推送与网页端渲染ESP32 这边跑一个轻量 WebSocket 服务器网页用 JavaScript 的 WebSocket API 连接。数据格式我选的是二进制每帧 3 字节直接推网页端解析成数值后画到 Canvas 上。为什么不推 JSON因为 JSON 序列化和解析都有开销而且体积大。二进制传输在局域网里几乎零成本。网页端的 Canvas 渲染和 CYD 屏幕类似也是维护一个滚动数组每来一个新点就重绘。为了减少重绘量可以用双缓冲——在离屏 Canvas 上画好再整体贴过来。另外网页端可以做得比屏幕更丰富比如加个简单的频谱分析用 FFT或者显示信号统计值均值、方差、峰峰值。// 网页端 WebSocket 接收与 Canvas 绘制示意 const ws new WebSocket(ws://192.168.1.100:81); const canvas document.getElementById(wave); const ctx canvas.getContext(2d); const data new Array(600).fill(0); ws.onmessage (e) { const buf new Uint8Array(e.data); for (let i 0; i buf.length; i 3) { let v (buf[i] 16) | (buf[i1] 8) | buf[i2]; if (v 0x800000) v - 0x1000000; data.push(v); data.shift(); } draw(); };4.4 双端同步与延迟控制屏幕和网页显示的是同一份数据但两者刷新节奏不同。屏幕是每来一帧就更新网页是每收到一批就更新。如果网页端也每帧重绘浏览器会吃不消。我的做法是网页端用requestAnimationFrame做节流最多 60fps 重绘实际数据更新频率远高于此但视觉上已经足够流畅。延迟方面从脑电模块采样到网页显示整条链路实测在 80ms 到 150ms 之间。主要延迟来自 BLE 连接间隔和 Wi-Fi 传输。如果对延迟敏感可以把 BLE 连接间隔降到 15msWi-Fi 用 UDP 代替 WebSocket但 UDP 会丢包需要自己做重传或容忍。原型阶段 WebSocket 的可靠性更重要。5. 常见问题排查与避坑经验5.1 数据乱码与帧错位这是最常见的问题九成以上是波特率不匹配或者地线没接好。排查顺序先用串口助手直接接脑电模块确认原始数据正常再接 BW16 看透传出来的数据是否一致。如果直接接模块正常、经过 BW16 就乱那问题在 BW16 的 UART 配置或供电。BW16 在 Wi-Fi 和 BLE 同时工作时电流会冲到 200mA 以上如果供电不足会导致 UART 时序抖动表现就是偶发乱码。给 BW16 单独加一个 100uF 电容在电源脚附近能明显改善。5.2 BLE 连接不稳定或频繁断开先看连接间隔和从机延迟参数。如果从机延迟设得太大主机可能误判从机失联。另外 BW16 的天线如果被手挡住或者靠近金属信号会急剧下降。原型阶段尽量让 BW16 和 CYD 之间没有遮挡距离控制在 5 米以内。如果还是断把 BLE 的发射功率调到最大BW16 支持 7dBm 左右。5.3 屏幕花屏或触摸失灵花屏多半是 SPI 频率太高或者接线太长。CYD 是集成板接线问题少但如果你外接了其他 SPI 设备片选冲突会导致花屏。触摸失灵则可能是触摸中断引脚被其他功能占用。检查引脚定义确保触摸的 CS 和 IRQ 没有被复用。5.4 网页端连不上或数据不更新先确认 ESP32 的 IP 地址网页里写的地址要对。如果连上了但没数据看 WebSocket 的 readyState 是不是 OPEN。另外 ESP32 的 WebSocket 服务器默认端口是 81有些浏览器或防火墙会拦换成 8080 试试。还有一点如果 ESP32 同时跑 BLE 和 Wi-Fi内存会比较紧张WebSocket 服务器可能起不来。把 BLE 的 MTU 调小一点或者减少缓冲区能腾出内存。问题现象最可能原因快速验证方法解决方向数据乱码波特率不匹配串口助手直连模块统一波特率检查地线BLE 频繁断连接参数激进看断开间隔是否规律放宽连接间隔增大发射功率屏幕花屏SPI 频率过高降频测试降到 27-40MHz网页无数据IP 或端口错ping ESP32核对地址换端口波形断档缓冲区太小看丢包计数增大环形缓冲区5.5 电源与共地引发的隐性故障这条链路里有两个无线模块和一个屏幕功耗叠加起来不容小觑。如果都用同一路 USB 供电电流可能不够表现是设备随机重启或者 BLE 断连。我的做法是 CYD 用一路独立 5V 2A 供电BW16 和脑电模块共用另一路。共地是必须的但共地不代表共电源分开供电能减少相互干扰。提示脑电模块对电源噪声非常敏感。如果发现波形上有规律的尖刺先检查电源纹波可以在模块电源脚并一个 10uF 加 100nF 的组合电容。6. 链路优化与后续扩展方向6.1 数据压缩与协议精简原型跑通后如果想把采样率提上去比如到 500Hz 或 1000Hz数据量会翻倍。这时候可以考虑在 BW16 端做简单的差分编码——相邻采样值差值通常很小用 1 到 2 字节就能表示能省一半以上带宽。不过差分编码会增加解码复杂度而且一旦丢一帧后面全错需要定期发关键帧。原型阶段可以先不做等带宽真的不够再说。6.2 网页端的功能延展网页端目前只是画波形其实可以加很多东西。比如用 Web Audio API 把脑电信号转成声音虽然不好听但能直观感受信号变化或者加一个简单的阈值检测超过阈值就变色提醒再或者把数据存到 IndexedDB 里方便回放。这些都不需要改硬件纯前端就能做。6.3 从原型到可用产品的距离这条链路离“产品”还有距离。主要差距在信号质量、佩戴舒适度和续航。干电极的接触阻抗会随出汗和移动变化导致基线漂移。要解决这个问题要么用更好的电极材料要么在固件里加自适应高通滤波。续航方面BW16 和 CYD 都不是低功耗设计的板子真要长时间佩戴得换成更省电的方案。但作为原型验证和学习平台这套组合的性价比和灵活性已经相当能打了。我在实际搭建过程中最大的体会是先把每一段单独跑通再串起来。不要一上来就写完整固件那样出了问题根本不知道是哪一段的锅。先用串口助手验证脑电模块再用蓝牙调试工具验证 BW16 透传再用简单示例验证 CYD 屏幕和 Wi-Fi最后才做整合。这个顺序能帮你省下大量抓头发的时间。另外手边常备一个 USB 转 UART 工具和一台能看 BLE 广播的手机或电脑调试效率会高很多。
返回列表