news 2026/10/6 7:11:54

无线脑电采集系统:BW16+ESP32-CYD实时波形显示实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
无线脑电采集系统:BW16+ESP32-CYD实时波形显示实战

做EEG原型调试的时候,最折磨人的往往不是算法,而是那根线。电极刚在头皮上贴好,你稍微动一下,线就牵着整个系统晃,眨眼、吞咽、转头,波形里全是伪迹。我一直想把前级采集模块和上位机彻底分开,让参与实验的人可以戴着电极自由起身走动,于是翻了翻手头的元器件,最后用 BW16 当贴身采集端、ESP32-CYD 当桌面接收显示端,搭了一条无线脑电链路。

这条链路解决的核心问题是:把脑电模块输出的串口数据流,通过 WiFi 搬到一个带屏幕的接收机上,再顺带把实时波形同步到浏览器。整个流程可以分成三段:脑电模块 → BW16 采集端 → WiFi 无线链路 → ESP32-CYD 屏幕端。CYD 既能直接在屏幕上滚动显示波形,又能开一个 WebSocket 服务让电脑网页实时看。下面我把选型逻辑、固件思路、以及实际跑通时踩过的坑全部写出来。

1. 先交代一下这条链路的设计初衷

1.1 为什么我非要把脑电信号“无线”出去

做脑电实验的朋友应该都有过这种体会:传统的实验室方案里,被试戴着电极帽,后面拖着一根 USB 或者串口线连接电脑。坐姿实验还好,一旦需要被试坐起来接听电话、站起来走动,或者想做体感交互类 demo,这根线就成了最大的限制项。线缆晃动会产生位移伪迹,而 EEG 信号本身只有微伏级别,位移伪迹在幅度上能压过真实的皮层节律。更麻烦的是,线缆长度一旦超过两米,通讯接口的压降和串扰也随之上升,采集端和上位机之间必须加隔离,否则共地回路会把 50Hz 工频干扰引进来。

无线化之后,这些问题的处理方式完全变了。采集端跟身体一起动,线缆伪迹直接消失;接收端可以放在桌面或者口袋里,只负责显示和转发。当然无线化也会引入新的问题,比如射频干扰、丢包、供电噪声,这些我放到后面第 5 节单独讲。

1.2 链路概览:采集端 → 无线 → 显示端

我这条原型链路的具体构成如下:

  • 脑电模块:单通道 EEG 前端,输出标准串口数据包。
  • 采集端主控:BW16(瑞昱 RTL8720DN 双核 WiFi 芯片),负责读取脑电模块的串口数据,打包后通过 UDP 推送到局域网。
  • 接收端主控:ESP32-CYD(ESP32-2432S028R 低成本带屏开发板),负责监听 UDP 端口,解析数据包,把实时波形绘制在 240x320 的 LCD 上。
  • 网页端:ESP32-CYD 同时开启 WebSocket 服务,局域网内电脑浏览器连接后即可看到同样的波形。

整条链路的数据流就是:脑电模块每秒输出 N 个原始采样点 → BW16 串口读入并组帧 → WiFi UDP 发出 → CYD 收包解帧 → 屏幕滚动绘制 + WebSocket 转发。这个结构的好处是各端职责单纯,出了问题可以单独定位,不会一锅粥。

2. 选型对比:BW16 和 ESP32-CYD 各自吃了哪一块分工

2.1 BW16 适合做贴身采集端的三个原因

BW16 这颗模块用的瑞昱 RTL8720DN 芯片,双核架构(Cortex-M4F 主核 + Cortex-M0 低功耗核),支持 802.11b/g/n 和 BLE 5.0,关键是它便宜、体积小、功耗可控。我选择它做贴身采集端,有三个具体理由:

第一,待机功耗和射频功耗比 ESP32 更省。做可穿戴原型时,供电通常来自 3.7V 锂电池,BW16 在 WiFi 连接待机状态下的实测电流比 ESP32 低不少,这意味着同样的电池容量能多撑一段时间。第二,它的串口资源够用。脑电模块用一路串口接收数据,还能留一路串口打调试日志,不会出现引脚捉襟见肘的情况。第三,BW16 的 Arduino 生态相对成熟,Ameba 开发环境里面已经有现成的 WiFi 库和串口库,不必自己啃 RTOS SDK。

当然 BW16 也有明显的短板:外设资源偏少,模拟采样能力弱,不适合直接接模拟脑电前端做 ADC,所以我的分工明确——BW16 只做“读串口 + 推网络”,不做任何信号处理。

2.2 ESP32-CYD 当接收/显示端,性价比在哪

ESP32-CYD 是网上俗称“小黄屏”的板子,核心是 ESP32-WROOM-32,板载一块 2.4 寸 240x320 的 LCD,插上 USB 就能烧录调试。它做接收显示端太合适了:

  • 屏幕驱动简单:TFT_eSPI 库直接支持,接线固定,不需要自己按照具体 LCD 型号逐个调。
  • 算力足够:ESP32 双核 240MHz,跑 UDP 接收、波形绘制、WebSocket 转发完全没压力。
  • WiFi 收发稳定:因为它和 BW16 都在同一个局域网里,信道选择灵活,很容易避开互相干扰。

其实也可以用 ESP32 直接当采集端,把脑电模块接到 ESP32 的 UART,再开 WiFi 推流,整个链路还能少一个模块。但我在实际测试中发现,ESP32 的射频开启时电源纹波偏大,对微伏级 EEG 前端的影响比 BW16 更明显,所以我把采集端和接收端分开,中间用 WiFi 拉一条“无线隔离带”,反而让前级信号干净不少。

2.3 链路磨合时要注意的“窝里斗”:同频干扰与信道划分

两个 WiFi 设备放同一张桌子上,最烦的就是它们自己先打起来。BW16 负责往 AP 推数据,ESP32-CYD 得同时连同一个 AP 才能收到,两台设备实际上在同一个信道里跑。

如果 AP 开的是自动信道选择,某个瞬间跳到拥堵信道,UDP 丢包率会立刻上升。我的解决办法是:把路由器或者 AP 的 WiFi 信道固定在 1、6、11 中某个相对干净的信道,并且让 BW16 与 ESP32-CYD 都在 2.4G 频段工作,不要开蓝牙功能,减少带内干扰。实测下来固定信道后,丢包率从千分之几的有效波动降到了基本不可感知。

3. 脑电模块接入:从型号选择到串口数据包

3.1 我用的脑电模块及数据包格式

我这条链路上用的是一款常见的单通道脑电模块,输出 ThinkGear 系列的串口数据包。数据包以0xAA 0xAA开头,后续是长度、载荷和校验。重点关注的载荷有两种:0x80是原始脑电波形值,0x02是信号质量,0x04是注意力数值,这几个字段足够原型阶段用了。

帧结构大致这样:

0xAA 0xAA <长度> 0x80 <原始值两字节> ... 0x02 <信号质量> ... <校验字节>

每个原始采样点的数值范围通常落在 -32768 到 32767 之间,实际有效动态范围远没这么大,脑电信号的有效幅度一般只有几十到几百微伏,取决于前端增益。模块内部已经做了滤波和放大,所以我在固件里不需要做数字滤波,只需要做“取数”和“转发”。

如果你用的是 ADS1299 或者 OpenBCI 那种多通道方案,数据包格式会有较大差异,而且往往需要通过 SPI 读取寄存器、配置采样率。那种情况下,建议你在 BW16 前面加一颗 MCU 专门做寄存器和 SPI 时序,BW16 继续干串口收包的活,避免主核被 SPI 占用影响 WiFi 响应。

3.2 电平、共地、抗干扰:前端最容易翻车的地方

脑电模块和 BW16 之间如果直接连 UART,最容易忽略三件事:

  • 电平匹配:很多脑电模块虽然是 3.3V 逻辑,但个别版本可能用 5V MCU。BW16 的引脚不能容忍 5V,一旦接错直接烧引脚。我特意用万用表量了模块 TX 输出的高电平,确认在 3.3V 左右,才敢连到 BW16 的 RX。
  • 共地:串口通讯要求两端共地。脑电模块的 GND 必须与 BW16 的 GND 连在一起,否则会出现随机乱码,而且信号参考点悬空,波形漂移特别离谱。
  • 串口波特率:某些脑电模块默认 57600,有些是 115200,还有极少数是 9600。如果模块和固件波特率不一致,数据包在帧头检测阶段就会全部丢弃。

安装连接时我还习惯在脑电模块的电源引脚并联一个 10uF 电解电容和一个 0.1uF 陶瓷电容,目的很简单:滤掉电源线上的低频波动和高频毛刺。这个动作对微伏级模拟前端的效果非常明显,不加电容和加电容的波形差距肉眼可辨。

3.3 把原始波形“喂”给 BW16 的初始化流程

BW16 端的串口初始化流程我建议这样写:

  1. 使能串口(例如 UART0 或 UART1),设置波特率和 8N1。
  2. 把接收缓冲区分成独立的环形缓冲,避免串口中断里做耗时的格式化。
  3. 在主循环里改用“按字节排队 + 状态机”解析帧头,不要等一帧到齐再解析,因为 WiFi 推流要求低延迟。
  4. 解析出来的原始波形、信号质量、注意力等字段,填进结构体,交给网络任务处理。

BW16 的 Arduino 环境里串口写法是:

void setup() { Serial.begin(57600); // 接脑电模块 WiFi.begin(SSID, PASS); udp.begin(LOCAL_PORT); } void loop() { while (Serial.available()) { uint8_t b = Serial.read(); feedToParser(b); // 按状态机解析脑电包 maybeSendPacket(); // 攒够一组就发 UDP } }

实际开发时可以开成两个线程:一个线程专门读串口解析,一个线程专门跑 WiFi 发送,这样串口高密度数据不会把网络任务饿死。BW16 的双核正好可以这么用,M4F 跑协议栈,M0 跑串口采集,互不拖累。

4. BW16 采集端固件实战:串口读入、WiFi 推流

4.1 串口资源与引脚分配

BW16 模块引出的 UART 数量是够用的,但有一个容易踩的细节:很多 BW16 开发板自身烧录使用的串口引脚和用户可用的串口引脚是复用的。你插上 USB 调试时,调试串口占用了某个 UART,如果再拿这个 UART 接脑电模块,会出现程序烧录正常、运行后数据却错乱的现象。

我的建议是:脑电模块必须接在开发板上明确标注的“用户串口”引脚上,烧录则用 USB 转串口芯片的调试口。如果你不确定引脚映射,去查对应板卡的 schematic,或者干脆第一天先写一个 loopback 测试:把 TX 和 RX 短接,看看串口能发能收,再接入脑电模块。这一步能省掉很多“为什么收不到数据”的排查时间。

4.2 UDP 还是 TCP:原型链路我选 UDP 的理由

脑电波形是高频连续数据流,延迟敏感,丢一两个点不影响整体波形,但我无法接受重传导致的排队延迟和波形“突跳”。所以在原型阶段我选 UDP,理由有三:

  • 脑电采样率通常只有 128Hz 或者 256Hz,单个数据包很小,UDP 报文正好合适。
  • TCP 的 Nagle 算法和重传机制会引入不可预测的延迟,实时性大打折扣。
  • UDP 不需要维护连接状态,BW16 断线重连后直接恢复发送,逻辑简单。

当然 UDP 也有代价:丢包不会自动补。我的补偿办法是给每个数据帧编一个序号,接收端通过序号检测丢包,插值或者标记异常区间,具体做法写在 5.3 节。

4.3 自定帧格式与序号机制

为了让接收端只靠网络层就能完成“断帧”和“对时”,我自定义了一个网络帧格式:

[0xEB 0x90] [数据长度 1字节] [设备ID 1字节] [包序号 2字节] [数据负载 N字节] [校验和 1字节]

0xEB 0x90是帧头,接收端用它同步。包序号是关键,每次发送前自增。数据负载里面直接放脑电模块的原始字节,这样接收端拿到负载后再按脑电帧格式解析一次即可,省去两次组包的重复代码。

在 BW16 端组包发送的核心代码大致如下:

void sendEegFrame(uint8_t* payload, uint8_t len) { uint8_t frame[32]; frame[0] = 0xEB; frame[1] = 0x90; frame[2] = len + 4; // 3到13字节是负载与序号 frame[3] = deviceId; frame[4] = seq >> 8; frame[5] = seq & 0xFF; memcpy(&frame[6], payload, len); frame[6 + len] = checksum(frame, 6 + len); udp.beginPacket(ip, REMOTE_PORT); udp.write(frame, 6 + len + 1); udp.endPacket(); seq++; }

这个帧格式看着简单,但解决了接收端最关键的问题:无论网络怎么抖动,只要收到了完整帧,就能按序号排序;如果序号出现跳变,说明中间丢了包,屏幕上就可以标记一个空窗。

4.4 断线重连与低功耗的取舍

可穿戴设备不能遇一次 WiFi 掉线就得断电重启。我在 BW16 里写了一个简单的状态机:WiFi 断开时自动重连,重连成功后 UDP 端口重新绑定,继续发送当前序号。

if (WiFi.status() != WL_CONNECTED) { WiFi.reconnect(); delay(500); return; }

但注意,delay(500)会让串口缓冲区在 500ms 内积压大量脑电数据,导致波形的实时性瞬间崩掉。更好的做法是重连期间不阻塞串口采集,把串口数据继续往环形缓冲里写,等网络恢复后立即按最新序号补发。这其实就是典型的“流量整形”,只是放在 MCU 里手动实现了。

功耗方面,如果你只是做桌面 demo,BW16 可以保持长开。要做真正可穿戴,就得让 BW16 在脑电模块无数据时进入低功耗睡眠,周期性唤醒发送保活包。我目前原型没有开睡眠,但已经预留了电源管理引脚,后续迭代会处理。

5. ESP32-CYD 接收端:屏幕绘制实时脑电波形

5.1 点亮 CYD 屏幕需要做的 TFT_eSPI 配置

ESP32-CYD 的屏幕是 SPI 接口,Arduino 里最常用的库是 TFT_eSPI。安装后必须先修改User_Setup_Select.h里对应的开发板配置,选择 CYD 定义文件,否则颜色发暗、背光不亮、触摸不响应。

以我手头这块 ESP32-2432S028R 为例,TFT_eSPI 库已经内置了User_Setup_ESP32_2432S028R.h这个配置,直接在 User_Setup_Select.h 里启用它即可。配置要点包括:

  • 屏幕驱动 ILI9341,分辨率 240x320。
  • 引脚定义:LCD_CS=15、LCD_DC=2、LCD_RST=4、LCD_BL=21、SPI_SCLK=14、SPI_MOSI=13。
  • 背光引脚 21 需要设为高电平,否则屏幕黑得像是没通电。

点亮后的下一步要处理坐标旋转。脑电波形通常是横向滚动,我一般设置屏幕旋转为横向(setRotation(1)),这样显示区变成 320x240,有更多横向空间画时间轴。

5.2 双缓冲 + 定时器刷新,把 FPS 稳定住

直接在主循环里画波形,最直观的问题是波形会闪。TFT_eSPI 提供pushImage方法,我采用的是双缓冲思路:

  • 先在内存里维护一个 320x240 的白底画布(或灰色网格画布)。
  • 新数据到达时,只在画布上增量绘制最新一列像素,旧列滚动左移。
  • 绘制完成后一次性pushImage到 LCD,避免逐像素操作的闪烁。

注意:ESP32 的 RAM 只有 328KB,240x320 的 RGB565 画布需要 240x320x2=153600 字节,也就是 150KB,压力有点大。我的做法是把画布降为 320x120,只画半屏波形,剩余区域留给数值文字显示。这样既能保证刷新率,又不会导致内存不足重启。

刷新节奏也值得聊聊。我不用死循环刷,而是用定时器设定固定 30FPS 刷新。脑电采样率如果是 256Hz,那么每帧大约要显示 8~9 个采样点。如果刷新率太高,波形反而显得跳;刷新太低,实时感就没了。30FPS 在演示时是平衡点。

5.3 用 UDP 包序号来插值,丢包不破图

UDP 丢包最直观的表现是波形突然断一截。如果断 1~2 个采样点还好,断 10 个点波形上就是一条竖线,很难看。我的接收端逻辑是:

  1. 收到一帧,读取包序号seq。
  2. 如果seq == 上一个 seq + 1,正常绘制。
  3. 如果seq > 上一个 seq + 1,判定丢包,计算丢失点数,用线性插值补齐。
  4. 如果丢失点数超过阈值(比如 50 点),说明 WiFi 链路已经出了严重问题,此时宁可画成空白区间,也不要用插值“脑补”,否则会给人造成数据没断的错觉。

插值的实现不复杂,但要注意脑电信号是带生理节律的,插值补出的波形不代表真实脑活动。我的标记办法是:插值区域用浅灰色绘制,真实数据用蓝色,让看的人一眼就能分辨哪些是真的、哪些是网络修补出来的。

5.4 屏幕之外:串口同步输出与 SD 卡写入

屏幕只是展示,实验场景下还得留原始数据。我在 CYD 端同时做了两件事:

  • 通过 USB 串口输出原始解析数据,方便 PC 接串口记录。
  • 如果插了 TF 卡,就把解析后的原始波形和包序号一起写进 CSV 文件,作为数据留存。

TF 卡的 SD 库在 ESP32 上可能会占用额外的 SPI 引脚,而 CYD 的 LCD 也占了 SPI。这时候要留意分配冲突,最好是让 SD 卡使用 VSPI 之外的 SPI 总线,或者干脆只在需要录数据时才挂载 TF 卡。原型阶段我基本只在串口输出,因为录数据的需求通常是在实验台上,而不是在演示现场。

6. 网页端怎么接:CYD 当 Web 服务器与 PC 网页浏览两条路线

6.1 路线 A:ESP32-CYD 开 WebSocket 服务,浏览器直连

这是最直接的方式。CYD 本身有 WiFi,我可以让它在 STA 模式下连到同一个路由器,然后开一个 WebSocket Server,监听8080端口。电脑浏览器访问http://<CYD的IP>:8080时,页面通过 WebSocket 连接 CYD,实时接收波形数据并用 Canvas 绘制。

CYD 上运行 WebSocket Server 的代码可以用 Arduino 的WebSocketsServer库。核心处理逻辑在webSocketEvent回调里,收到客户端连接后,把后续解析的脑电数据广播给所有已连接 WebSocket 客户端。因为脑电数据量不大(每帧最多十来字节),CYD 完全可以胜任几百毫秒间隔的推送,不会卡。

一个小提醒:浏览器端的 Canvas 也不适合每收到一个点就重绘一次,太低效。比较稳的做法是浏览器端维护一个环形数组,每 33ms 触发一次定时重绘,一次绘制近 1 秒的数据窗口。

6.2 路线 B:BW16 推送局域网,电脑网页做聚合展示

如果你不想让 CYD 既当网络接收端又当 Web 服务器,可以换一种做法:BW16 直接把 UDP 数据发给 PC,或者发给局域网里的一个简单 Web 服务;CYD 只是作为一个独立屏幕端同样收听同一路 UDP。这样 CYD 和 PC 网页都能显示相同的波形,CYD 不需要承担 WebSocket 的转发任务,CPU 占用更低。

我在实测中更推荐路线 B,因为它是“广播式”的:多个设备可以同时监听同一个 UDP 端口,CYD 看波形,PC 录数据,还能在旁边挂一台显示器做大屏展示,互不干扰。路线 A 适合没有电脑的情况下做单机展示,演示者端着 CYD 就能当临时仪器。

6.3 网页端帧同步的小坑:整数与浮点的字节序

网页端最容易踩的坑反而不是 WiFi,而是数据编码。脑电原始值常见的是两字节有符号整数,而 JavaScript 的ArrayBuffer默认是大端字节序,后端发来的是小端序,如果不做转换,波形就是一串乱跳的噪声。

我在 BW16 端故意把脑电原始值拆成高字节和低字节按“高在前、低在后”的顺序发送,网页端这样解析:

const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength); let raw = view.getInt16(0, false); // false 表示大端,与发送端对应

在 CYD 端也要用同样的解析顺序,否则屏幕波形和网页波形会不一致。这种细节属于典型的“协议约定问题”,写代码时定好一个规则,所有端都遵守,后面基本不会出错。

7. 实测中真的会踩的坑:五条现场经验

7.1 电源噪声:无线模块反而加剧了 EEG 毛刺

一开始我以为无线之后电源会更干净,实际上相反。BW16 和 CYD 的 WiFi 射频一开,板载稳压器的输出纹波立刻增大,脑电前端如果直接从同一个电源轨取电,波形上会出现明显的周期毛刺,频率大致对应 WiFi 的信标间隔。

解决方法是把脑电模块的电源和前级放大电源彻底分开:

  • 脑电模块单独用一块 LDO 或低噪声稳压芯片供电。
  • BW16 的电源轨和脑电模块电源轨之间串一个磁珠。
  • 尽量让脑电模块的模拟地单点接回电池负极。

这个改动我最开始没在意,后来用示波器看 3.3V 电源纹波,从 20mV 涨到了 60mV,波形上全是尖刺,排查了半天才定位到是共电源引起的。分开供电后尖刺立刻消失。

7.2 WiFi 天线离电极太近:射频干扰压过有效信号

这是困扰我最久的一个坑。电极贴在前额,BW16 天线大概在 10 厘米外,本来以为距离够远,但开机后脑电波形直接“罢工”,满屏都是高频振荡。后来我把天线挪到离电极 30 厘米以上,波形立刻恢复。

原因不复杂:2.4GHz 射频虽然不在 EEG 的通带内,但射频信号会在电极线和人体回路里产生解调效应,形成带内噪声。实测中 PIF 天线和 PCB 天线的干扰程度不同,但都不能贴太近。如果你也得把模块戴在头上,建议:

  • 天线位置尽量远离电极走线,垂直放置而不是平行贴头皮。
  • 电极线和模块连线使用短的屏蔽线。
  • 不用 WiFi 时,让 BW16 进入射频睡眠,能少一截干扰。

7.3 屏幕刷新闪烁:别用全屏清屏指令

CYD 屏幕分辨率不高,但 TFT_eSPI 的fillScreen()清空全屏很耗时,动画刷新率会掉到个位数,而且视觉上闪。双缓冲之后依然有闪,是因为内存画布那 150KB 完全送屏需要时间。

我的改进是增量绘制:不整个清空画布,而是把画布内容左移一列,只填充右侧新增的波形列,保留背景网格。这样每次刷新只需要推送一列像素,刷新率从 8FPS 直接提升到 30FPS。

屏幕上的数值文字也一样,不要每帧重画整个数字区域,判断数字有变化才更新,否则数字剧烈跳动时屏幕会闪烁。

7.4 波特率与丢包:流控没设置导致的“每秒少几十包”

BW16 与脑电模块的串口连接稳定后,我发现一个诡异现象:波形大部分时间正常,但每隔几秒会少几十个采样点。查来查去,不是网络丢包,而是串口中断处理被 WiFi 任务抢占,环形缓冲溢出丢数据。

解决思路是分层处理:

  • 串口接收用中断模式,中断里只往环形缓冲写字节,绝不做解析。
  • 解析和组包放在主循环或低优先级线程。
  • 适当调大环形缓冲,至少能容纳一个 WiFi 信标周期内的数据量。

我用的是 4096 字节环形缓冲,单通道 256Hz 采样率下足够应付,实测不再出现周期性缺数。

7.5 协议字段与前端解析对齐问题

最后这个坑比较隐蔽。脑电模块的数据包里有多个字段,不同版本的模块固件可能某些字段不存在。我刚开始按固定偏移量解析,结果信号质量值经常读成 200,一直以为是前端质量差,后来发现是字段错位。

改进方式是使用健壮的解析器:遇到0xAA 0xAA后扫描所有载荷段,根据段类型分别解析,不依赖固定偏移量。这样无论模块固件字段增删,正确字段总能被提取出来。这个经验同样适用于网页端,解析逻辑一旦写死,换个模块批次就翻车。

8. 从原型到可以戴出去的路:后续迭代我打算做什么

8.1 体积与功耗压缩:BW16 的睡眠模式与轻量供电

现在的原型链路能跑通,但体积其实不小:脑电模块一块板子、BW16 一块板子、电池一块,还有一堆杜邦线。下一步我准备做一块定制 PCB,把脑电前端、BW16、电源管理集成在一起,目标是戴在耳后或者额头上不突兀。

功耗方面,BW16 的睡眠模式是关键。理想状态是:没有有效脑电数据时,BW16 进入 Sleep,脑电模块进入低功耗待机;检测到信号后唤醒,WiFi 快速重连并推流。这个逻辑在 Ameba SDK 里有示例,但需要牺牲一些推送延迟,我正在衡量。

8.2 多通道同步与后续算法落地

单通道只能做最简单的节律展示,往深了走必然要上多通道。多通道脑电不仅需要同步采样,还要保证多个通道的数据在同一时刻被打包、同一时刻被无线发出。BW16 串口带宽有限,8 通道 256Hz 的原始数据量大约是每秒 8x256x2=4096 字节,算上协议开销其实还能撑,但我倾向于在采集端先做轻量特征提取(比如分频段能量),只推送压缩后的特征,把原始数据留给实验室离线分析。

算法层面的下一步,我打算把在线预处理放到 ESP32-CYD 端,比如实时计算频带能量、检测眨眼伪迹,然后在屏幕上直接展示。等这套流程稳定之后,再迁移到网页端,结合开源工具做拓扑图、脑电源定位与最小范数估计之类的离线分析。原型阶段先跑通,后面每一步都有得玩。

如果你也想复刻这条链路,我给的建议是先别急着一次跑通所有端,按“脑电模块 → 串口助手 → BW16 → UDP 调试工具 → CYD 收包”这个顺序逐级验证。每一段都确认无误再连下一段,能少折腾两晚上。我的实测体验是,最难的不是代码,而是那些信号完整性问题和射频干扰,这两类问题靠看代码是看不出来的,必须动手量、动手试。祝你也能顺利把脑电信号从有线世界里解放出来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 7:11:17

Mininet实验报告指南:网络模拟、链路参数与SDN控制器对接

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 7:09:33

STM32实战:软件模拟I2C读写AT24C02 EEPROM全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 7:09:29

STM32从上电到RTOS任务切换:复位向量、启动流程与PendSV深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 7:08:33

汇川AM401伺服张力控制系统实战:从硬件选型到PID调试全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 7:08:33

GD32F470开发板原理图深度解析:从电源树到外设设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/6 7:08:31

PCIe转网口硬件设计的17个生死关键点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华