
脑电采集近几年越来越大众化不再是实验室里的专利。但很多人搭脑电原型时都会碰到同一个问题线缆。电极往头皮上一贴放大器、采集板、电脑之间的连线就绕成一团稍微动一下头波形就乱跳。我这次动手的目标很直接做一条从脑电模块到屏幕和网页端都能实时查看的无线EEG原型链路核心器件是BW16和ESP32-CYD这两块板子。整条链路最后跑通的形态是脑电模拟前端出波形BW16 负责采样和无线转发ESP32-CYD 负责在屏幕上画实时曲线同时把数据通过网页推送出去。这篇文章就是完整的过程记录包括选型逻辑、接线要点、协议设计、联调参数和踩过的坑。如果你想复刻一条能动的无线脑电链路又不想一上来就啃 ADS1299 这种专业芯片方案那这篇应该能帮你省下不少弯路。1. 从线缆到无线这条原型链路的来龙去脉1.1 传统脑电采集的麻烦传统脑电采集链路一般是这样的电极 - 脑电放大模块 - 数据采集板 - 串口/USB - 电脑。在桌面上调试还好一旦想模仿真实体验比如戴在头上走动几步、演示给朋友看线缆就变成最大的干扰源。导线本身就是天线身体一动、线一甩波形上就多出一堆伪迹。而且人一旦被线绑在桌面上整个演示的灵活性和观赏性都会大打折扣。我这次想解决的问题其实很朴素脑电模块采集到的信号能不能不经过任何物理线缆直接出现在屏幕上和浏览器里这里的屏幕指的是小屏最好是一块集成了显示器的开发板这样演示时不用背着电脑到处跑网页端则是为了方便围观群众用手机或笔记本打开一个网址就能看实时波形。两者都要而且最好共用同一条数据流不做两套采集。1.2 为什么选 BW16 和 ESP32-CYD选型的时候我第一反应是用 ESP32 干所有事ESP32 自带 ADC、WiFi、BLE外接一块 TFT 屏幕也不是不行。但实际想想就会发现一个问题ESP32 的 ADC 在低摆幅生物信号场景下并不好伺候线性度、零点漂移都有点看运气而且 WiFi 和屏幕同时跑的时候中断会互相挤兑。与其在采集端把 ESP32 的性能透支不如把采集和展示拆成两个角色。BW16 就是我这次选的采集端。这颗东西用的是瑞昱 RTL8720DN支持 WiFi 和 BLE 双模功耗比 ESP32 低不少Arduino 生态也能用。更重要的是它把我的采集端和无线发送端合到了一块小板上可以做成一个独立的脑电无线发射器挂在模块旁边不占地方。接收展示端则选了 ESP32-CYD。这个名字你可能不熟但Cheap Yellow Display应该有印象就是市面上很常见的那块带 2.4 寸彩屏的 ESP32 开发板型号一般是 ESP32-2432S028R。板上集成了 ILI9341 显示屏、触摸芯片、SD 卡槽、RGB LED一套下来成本很低。我选它不是因为性能多强而是因为它的屏幕和 WiFi 在同一个芯片上非常适合做无线接收 本地显示 网页服务这个角色。1.3 整条链路的分工链路设计其实很清晰四个角色各管一段脑电模块负责从电极取信号做前置放大和带通滤波输出稳定可采的模拟波形。BW16负责 ADC 采样把连续模拟信号变成数字流按固定格式打包成 UDP 数据包通过 WiFi 广播出去。ESP32-CYD负责接收 UDP 数据流一边在 TFT 屏幕上画波形一边启动 WebSocket 服务把数据实时推给浏览器。浏览器/手机打开 CYD 提供的网页看到的是和屏幕几乎同步的波形曲线。有人可能问为什么不直接让脑电模块通过蓝牙连到 CYD这里有个工程细节。脑电数据是连续流讲究低延迟和稳定吞吐。BLE 虽然在短距离上挺好但协议栈的表现受连接间隔影响很大丢包重传容易引入抖动。WiFi UDP 在局域网内反而更稳定丢包率极低而且 ESP32-CYD 可以同时做 AP 和 STA数据接收和网页服务互不冲突。1.4 为什么端到端都用无线还有一点是这次特别想说的无线不只是为了拆掉线它还能解决一个很要命的干扰问题——接地环路。当脑电信号通过 USB 接到电脑时电脑的开关电源地、USB 地、放大器地会形成一个环路工频干扰会让波形看起来像长了毛一样。换成纯电池供电的无线采集端之后模拟地和数字地完全隔离50Hz 的底座噪声明显降下去。这是纯无线架构比有线架构占便宜的地方做生物电采集的人应该深有体会。2. 脑电信号前端与 BW16 采集端从模拟到数字流2.1 脑电模块的接入方式我用的脑电模块是市面上常见的那种单通道模拟输出模块内部已经完成差分放大、带通滤波和直流偏置输出范围一般在 0V 到 3.3V 之间零点在 1.65V 附近。这种模块的好处是不用碰 ADS1292 这类 SPI 配置流程接上电极就能看到信号。缺点是内部细节不透明增益和滤波参数是固定的但做原型链路验证完全够用。电极方面我建议至少用三根线信号电极、参考电极、地电极。地电极非常关键不只是安全兜底它直接决定共模抑制效果。脑电模块的差分输入能抑制掉一部分共模干扰但如果地线悬空工频干扰会从人体直接耦合进信号。我实际测试时把地电极从腕带换成耳夹之后50Hz 分量肉眼可见地变矮了一截。连接到 BW16 的方式很简单模块输出引脚直接连到 BW16 的 ADC 输入。但这里有几个细节需要注意模块输出和 ADC 输入之间最好串一个 1kΩ 电阻再对地并一个 0.1uF 电容组成一阶低通防止高频噪声在采样时混叠。如果模块输出是 0~3.3V而 BW16 的 ADC 参考电压不是满量程 3.3V你需要确认实际量程必要时用电阻分压把信号压到 ADC 许可范围内。采样线尽量短和电源线、WiFi 天线拉开距离这条线是模拟信号进入数字世界的最后一段噪声在哪个环节注入后面就永远甩不掉。2.2 BW16 的 ADC 精度够不够这是搭建这条链路时争议最大的一点。BW16 板载 ADC 的位数和精度跟专业脑电采集芯片肯定没法比12 位分辨率在 3.3V 参考下每个 LSB 大约对应 0.8mV。如果直接采集头皮上几十微伏的原始脑电信号这个分辨率远远不够。但我的链路里脑电模块已经把信号放大了几百倍输出已经是几百毫伏级的动态范围所以 12 位 ADC 用来数字化放大后的信号是够用的量化噪声不会成为主要矛盾。如果你手头的脑电模块输出的是未放大信号那就不能这么干了必须外接或改用带 PGA 的采集芯片否则波形会直接被量化噪声吞掉。这里给个经验值ADC 的 LSB 至少要比信号峰峰值小 10 倍才能看到平滑的脑电形态。采样率方面脑电的主要频率集中在 0.5Hz 到 100Hz 之间理论上 250Hz 采样就够。但我实际用的是 500Hz原因有二一是后续做滤波和伪迹去除时高采样率的波形边缘更清楚二是无线传输需要攒一批样本再发包采样率太低了包频会提高无线协议开销就变大。500Hz 配合每包 8 个样本每秒也就传 60 多个包WiFi 完全无压力。2.3 数据打包和无线协议设计采样归采样要传得稳还得把数据组织好。我一开始图省事每个采样点单独发一个 UDP 包结果 CYD 那边接收中断几乎被打爆WiFi 吞吐也完全浪费。后来改成攒一批再发每包 8 个样本帧率大概 60 帧/秒效果立刻好了。数据包格式用的是一个非常朴素的结构体typedef struct { uint16_t magic; // 帧头固定 0xAA55 uint8_t seq; // 包序号用于检测丢包 uint8_t samples; // 本包采样点数 uint32_t ts; // 毫秒时间戳BW16 启动起累计 int16_t data[8]; // 8 个 16 位采样点 uint16_t crc; // 简单 CRC16原型阶段可以省略 } eeg_packet_t;为什么时间戳要进包而不是靠接收端本地时钟算因为 WiFi 传输有队列延迟包到达时间不等于采样时间。如果后续要做 EEG 去噪、频谱分析甚至源定位时间戳对齐是绕不开的前置条件。我在这一版就把时间戳加上了后面做离线分析时省了很多麻烦。传输协议我用的是 UDP 而不是 TCP。有朋友可能会担心 UDP 丢包。实测在同一个路由器局域网内UDP 丢包率基本在 0.1% 以下而 TCP 的重传机制在这种实时流场景下反而会引入不确定延迟丢一个包可能挡住后面一堆包。对于实时波形显示来说丢一个样本远比重传导致的延迟堆积更容易接受。BW16 发送端用固定端口CYD 接收端绑定同一个端口不做握手、不做确认链路保持极简。3. ESP32-CYD 这一端的真实工作屏幕和网页同时跑3.1 先弄清 CYD 的外设和引脚ESP32-CYD 看起来是一块开发板实际是个小型嵌入式终端。它集成的 ILI9341 屏幕走 SPI触摸芯片和 SD 卡也都挂在 SPI 总线上。我在接这个板子时踩了个典型坑默认例程里屏幕、触摸、SD 卡的初始化都开着这几个外设会轮流占用 SPI 总线导致波形刷新和 WiFi 收发互相干扰。我的处理方式是把触摸和 SD 卡初始化干脆去掉只用屏幕。你可能会说触摸和 SD 卡又不占 CPU但 SPI 总线是共享的同一时刻只能有一个设备在总线上通信触摸扫描和 SD 卡枚举都会打断屏幕刷新代价就是屏幕会出现撕裂感。CYD 的引脚映射不同批次略有差异但常见的对应关系是屏幕 SPI 的 MOSI/SCK/CS/DC 分别接到了特定的 GPIO。这块板子资料比较多直接用 TFT_eSPI 库的时候一定要按你的板子批号改 User_Setup.h 里的引脚定义初学者最容易卡在这一步——屏幕白屏、花屏九成是引脚没对上。3.2 TFT 波形渲染双缓冲和缓冲策略屏幕上画实时波形最直观的做法是每收到一个点就画一个点。但这个做法在 320x240 分辨率下会有问题画点操作走 SPI 总线每画一个点都要发起一场传输几百个点整屏刷新下来总线占用率极高WiFi 接收就会饿死。我的做法是把波形显示区域固定为 220x180从左到右滚动。ESP32-CYD 收到数据后先写进一个波形环形缓冲区然后每 30 毫秒刷新一次屏幕的最新一列数据。也就是说我不会整屏重画只会在屏幕右侧画出一条新的波形列然后把整幅画面向左平移一个像素。TFT_eSPI 库提供了高效的块级绘制函数我用它来画那一列实测 SPI 负载从每点一次传输降为每列一次传输刷新率反而上去了。这里有一个特别值得说的经验波形平移如果用盲目 pushImage 整屏复制速度反而很慢。应该先读出一块显存区域或自己维护一个屏幕内容的数组再平移最后画最新列。CYD 没有独立的显存控制器SPI 读操作很慢所以更优的做法是自己维护一个和屏幕等宽的波形像素数组每一帧都基于这个数组推算出新的像素内容然后一次性把变化区域推到屏幕避免从屏幕读数据。3.3 内嵌网页服务器WebSocket 推流网页端其实和 TFT 共用同一份数据源只是消费方式不同。ESP32-CYD 上我跑了一个 WebSocket 服务器浏览器打开页面后跟它建立连接CYD 每 30 毫秒把最新一批样本推给浏览器浏览器端用 Canvas 画波形。为什么用 WebSocket 而不是 HTTP 轮询因为脑电波形是连续变化的轮询需要不断地建立连接、传请求头、传响应头延迟高且资源浪费。WebSocket 是长连接数据到了就推送延迟基本只取决于网络本身。CYD 的 WiFi 有两种工作模式可选一种是让 CYD 连到路由器手机和它连同一网络另一种是 CYD 自己开热点手机直接连它的热点。我倾向于后者因为现场演示时不需要依赖外网路由CYD 分配一个固定 IP手机浏览器直接访问就行。不过要注意当 CYD 开热点时它的 WiFi 模块同时承担接收 BW16 数据和网页推送两个任务这会让 CYD 的 WiFi 吞吐压力变大但实际数据量很小体感上没有太大问题。网页端的页面我做了极简处理一个标题、一个 Canvas、一个断开重连按钮。Canvas 的宽度对应显示区域的 220 像素浏览器端将收到的样本映射成 Y 坐标绘制连续的折线。为了让波形不至于一条直线平铺页面里加了一个缩放参数方便调节增益显示。如果你想把这个项目包装得好看一点可以加网格、峰值读数、通道标签但核心逻辑就是WebSocket 收数据 Canvas 画波形。3.4 延迟补偿与断线重连无线链路不可能永远稳定所以我在 CYD 端做了几层防御定义一个接收 FIFO数据到达后先入队显示任务每 30ms 取一批。队列长度固定如果队列满了就丢弃最旧的数据。这个缓冲能把网络抖动抹平但也会引入额外延迟所以要控制在一个合理的范围内我用的队列时长是 150ms 左右再长波形会变得迟钝。用包序号检测丢包。如果序号的间隔大于 1说明中间有丢包CYD 会在波形上做一个空白标记但不影响后续数据。断线重连。BW16 和 CYD 之间一旦 WiFi 断掉BW16 不会自动恢复必须自己做重连检测。我让 BW16 定期发心跳包CYD 超过设定时间没收到心跳就重新初始化 UDP socket并在网页上显示重连中。4. 联调实测参数、干扰和性能记录4.1 关键参数表联调时用的参数如下参数项数值备注采样率500 Hz覆盖脑电主要频段ADC 分辨率12 bitBW16 板载LSB 约 0.8mV脑电模块增益约 1000 倍具体值由模块决定滤波范围0.5 Hz ~ 40 Hz模块内部带通每包样本数8对应包率约 62.5 包/秒传输协议UDP端口 8888数据速率约 8 kbps含包头开销显示刷新率约 33 帧/秒每帧推 8 个样本网页刷新率约 30 帧/秒WebSocket 推流你会注意到实际数据速率非常低。脑电数据的原始采样率 500Hz每个样本 2 字节也就是 1KB/s加上包头也只有 2KB/s 左右。所以这条链路的瓶颈根本不在带宽而在采样任务的实时性和显示任务的调度上。这也是我做这个项目最大的感触无线脑电链路难的不是传数据而是让采集和渲染稳定地并行。4.2 50Hz 工频干扰和接地环路排查联调过程中最折磨人的就是 50Hz 工频干扰。刚开始我把整个系统接在 USB 充电器上供电屏幕上的波形是一片宽窄不一的毛刺FFT 之后 50Hz 处有一座高峰。排查链路是这样的先用锂电池给 BW16 供电CYD 仍接充电器。结果显示毛刺减少但没根治。把电极从手部改成干净的前额位置毛刺还有。把 USB 充电器换成充电宝给 CYD 供电毛刺才终于压下去。问题根源就是充电器的大地耦入了工频噪声。这给了一个非常明确的结论测生物电时任何一端连着充电器地都可能通过接地环路把工频噪声灌进信号。最终方案的供电方式是BW16 用锂电池CYD 用充电宝两边的地完全物理隔离。屏幕波形瞬间干净了很多。此外屏幕本身的背光驱动和开关电源也会产生干扰。CYD 的显示区域恰好靠近 WiFi 天线好在干扰频率主要落在高频段后续通过模块内部的低通滤波已经基本压掉。如果对波形质量有更高要求可以做金属屏蔽罩或把天线和模拟信号线拉开距离。4.3 无线延迟实测延迟这个指标直接决定波形跟手不跟手。我做了个简单测试BW16 侧把某个 GPIO 拉高作为时间起点同包数据到达 CYD 时CYD 立刻拉高另一个 GPIO用示波器测两个 GPIO 的间隔。这个间隔包含采样、打包、WiFi 传输、CYD 解包的全过程测得大约 8~15ms加上接收端的 150ms 显示缓冲总的端到端延迟在 160ms 左右。这个数值对看波形趋势完全够用但如果以后要做实时反馈类的 BCI 应用这个缓冲延迟就得想办法压。把缓冲队列减到 50ms端到端延迟可以到 60ms 以内。丢包率我也做了统计连续跑半小时发送端发出约 11 万个包CYD 端收到 11 万个包中丢失 3 个丢包率约 0.003%。这个水平对实时绘图没有任何影响偶尔丢一个样本在曲线上甚至看不出来。4.4 安全边界DIY 设备不是医疗设备这里必须认真说一句这整条链路只是原型演示用的不能直接当成医疗设备使用更不能作为诊断脑电数据的依据。脑电涉及人体有几个底线要守住所有电极必须是合格的一次性 Ag/AgCl 电极不要用裸露的金属线直接贴皮肤。使用电池供电保证人体支路和市电之间有完全的电气隔离。我全程用锂电池/充电宝不敢用可以随时拔插的充电器给采集端供电。不要在家用电线网上做任何形式的漏电测试。模块的输出只是一个模拟电压不代表医学意义上的脑电测量。我自己实测时只是在额头贴了两个电极测的是平稳状态下的波动并不涉及任何临床用途。5. 后续演进EEG 去噪、源定位和产品化的思路5.1 工程角度做 EEG 去噪市面上聊 EEG 去噪的文章很多但工程上和学术上的视角不太一样。学术上会把去噪当成一个信号处理问题咬牙用各种自适应滤波、小波变换、独立成分分析对每个伪迹单独建模。工程上我更倾向于先搞清楚噪声是哪来的。在联调过程中我遇到的噪声源按贡献排序是50Hz 工频、运动伪迹、眼电、电极接触噪声、基线漂移、高频白噪声。这六类噪声的压制手段完全不同。50Hz 工频靠硬件隔离和物理接地解决这是我前面说的供电方案运动伪迹靠固定电极和缩短导线解决眼电的频谱集中在低频看起来像缓慢的漂移和真正的脑电节律叠加在一起单通道很难自动分离只能靠阈值法把包含大漂移的片段剔掉电极接触噪声有个明显特征——波形会出现突然的电压台阶这个可以通过对一阶差分做阈值检测来卡。原型阶段我用的是一个简单的 IIR 带通滤波1~40Hz加一个 50Hz 陷波器肉眼观察波形边缘已经平滑不少。如果后续做更严格的离线分析建议用 Python 的 MNE 和 SciPy 做带通滤波、伪迹剔除和 ICA。这一条链路的无线数据格式天然适合离线回放。BW16 发送的数据包走到 CYD 端之后可以由 CYD 再转发到 USB 或记录到 SD 卡我后面就是这么干的保留的时间戳对于离线分帧非常有价值。5.2 往源头走EEG 源定位和最小范数估计的实用性视角大家搜索脑电资料时经常看到EEG 源定位和最小范数估计这两个词。我这一条只有单通道的链路说起来似乎离源定位很远但其实这条链路最大的价值在于训练了完整的数据采集、无线传输、时间戳对齐和去噪链路这是做源定位最容易被忽略的前置条件。先简单解释一下原理。EEG 源定位要解决的问题是电生理活动发生在大脑皮层电极只能记录到头皮表面的电位分布怎么由表面电位反推脑内活动的位置和强度这本质上是个逆问题在数学上高度欠定。最小范数估计是常用解法之一它把这个问题处理成在满足头皮电位观测结果的所有电流源分布中找一个能量范数最小的分布。因为解无穷多所以必须引入正则化约束L2 范数是最常见的选择MNE-Python 里那句pick_metrics之类的 API 背后的东西其实就是这个逻辑。但源定位要真正有意义有几个硬性前提一是需要多通道数据至少 16 导、64 导更好单通道没法做空间逆向二是需要有被试的结构像或者标准模板用来构建头模型三是对采样同步性要求极高每个通道的时间戳必须完全一致。我在无线链路上把时间戳放到数据包里就是为了以后扩展成多通道时不用重新设计协议。你如果想把这条链路升级建议直接加一个多通道 AFE 模块比如 ADS1299把 SPI 数据交给 BW16 或者 ESP32数据格式里预留通道 ID 字段即可。5.3 原型之后的演进方向这一版只验证了核心链路距离一个完整的脑电前端产品还有很多距离。我大概梳理了一下可以继续投入的方向把 BW16 换成带 BLE 的模组或者直接用 RTL8720DN 的 BLE 能力设计低功耗穿戴端用锂电池供电能撑一天。增加电池电量监测、数据本地存储、断线续传。当前版本断了就断了数据直接丢这在演示时可以接受但做真实记录时不能接受。把网页端从实时波形扩展到历史回放 频域视图。浏览器端其实很容易加 FFT把波形旁边画一个实时频谱图体验会提升不少。多通道化。一旦单通道真的把链路跑顺了上多通道只是改协议的问题关键还是模拟前端的通道数和参考电极方案。最后再分享一个调试小技巧我在这条链路上学到最值得记住的一件事是无线脑电调试时先确认数字链路再关心模拟质量。你可以让 BW16 发一段已知波形比如正弦波给 CYD确认屏幕和网页端的波形形态正确再去接脑电模块。否则你会发现一旦屏幕画出的波形奇怪无比你完全分不清是电极接触问题、模拟模块问题、ADC 采样问题还是无线传输问题。我之所以能在半天内把链路调通就是因为先做了这个数字联调把链路切成了模拟前端和数字传输两段每段单独验证最后合流时只需要看波形形态是否正常即可。这条文章写下来基本上就是把一个脑电无线原型从零搭起的所有环节过了一遍。BW16 做采集端、ESP32-CYD 做显示和网页服务这个组合的成本不高、资料也多很适合手头有脑电模块但还没来得及想清楚无线/可视化方案的朋友直接参考。接下来如果你准备把它跑通记得先从锂电池供电和 50Hz 干扰排查开始。