新闻详情

新闻详情

首页 / 资讯中心 / 详情

无线EEG原型链路:BW16与ESP32-CYD的BLE实时采集与显示

发布时间:2026/9/29 15:13:27来源:尧图网络
无线EEG原型链路:BW16与ESP32-CYD的BLE实时采集与显示
1. 这条无线 EEG 原型链路到底在做什么脑电采集这件事很多人第一反应是电极贴头皮、放大器、电脑上跑分析软件整套设备动辄几万块线缆缠一身被试稍微动一下就是一片工频干扰。我这次想验证的是一个更轻量的思路把脑电模块采到的原始数据通过 BLE 无线传到一块带屏幕的开发板上屏幕实时画出波形同时这块板子再把数据转发到网页端做可视化。整条链路里没有电脑直连采集卡被试可以相对自由地坐着屏幕端和网页端都能看到实时信号。核心硬件就两块一块是负责采集和 BLE 发送的BW16模块另一块是负责接收、显示和转发的ESP32-CYDCYD 是 Cheap Yellow Display 的缩写就是那块自带 2.8 寸 ILI9341 屏和触摸的廉价 ESP32 开发板。中间靠BLE做无线传输板子与屏幕、板子与网页之间则用UART和 WebSocket 两条路径分发数据。这套东西适合谁如果你手上有一个能输出数字信号的脑电前端模块比如常见的 ADS1299 类采集板或者某些集成好的 EEG 模块想快速搭一个无线 本地屏显 网页可视化的原型又不想一上来就啃复杂的上位机软件那这条链路值得参考。它不追求医疗级精度追求的是链路通、延迟低、能看见波形用来做算法预研、教学演示、交互原型验证都够用。我先把整条链路的角色分工讲清楚后面再逐段拆解。BW16 这边负责三件事从脑电模块读数据、做最基础的去噪和打包、通过 BLE 发出去。ESP32-CYD 这边负责四件事BLE 接收、UART 转发、屏幕绘制、WebSocket 推送到网页。听起来简单但每一段都有坑尤其是 BLE 的 MTU 和 UART 的波特率匹配后面会重点讲。提示这条链路是原型验证性质不是医疗设备。任何涉及人体采集的操作都要确保采集前端本身有隔离和安规设计开发板只做数据搬运不要让它承担电气安全责任。2. 为什么选 BW16 加 ESP32-CYD 这个组合2.1 BW16 做采集端的三个理由BW16 是瑞昱 RTL8720DN 方案的模组双频 WiFi 加 BLE 5.0主频 200MHzRAM 相对充裕。选它做采集端主要看中三点。第一是BLE 和 WiFi 共存。很多廉价模组要么只有 BLE要么 BLE 和 WiFi 抢资源抢得厉害。BW16 的双频设计让它在只跑 BLE 的时候非常稳我实测连续传输几个小时没有掉线。第二是UART 资源灵活它有多组 UART可以一路接脑电模块一路留作调试打印互不干扰。第三是Arduino 生态支持虽然 BW16 的 Arduino 核心不如 ESP32 成熟但基本的 BLE 库、串口库都能用上手成本可控。这里要解释一个关键选择为什么采集端不直接用 ESP32因为 ESP32 我要留给 CYD 做显示和网页转发那块板子要跑屏幕驱动、WebSocket 服务、BLE 客户端负载不轻。把采集和显示拆到两块板子上各司其职调试的时候也方便——采集端出问题就只看 BW16显示端出问题就只看 CYD不用在一堆日志里大海捞针。2.2 ESP32-CYD 做接收端的天然优势CYD 这块板子最大的价值是自带屏幕和触摸还便宜。它用的是 ESP32-WROOM-32双核 240MHz4MB Flash配上 ILI9341 驱动的 2.8 寸 320x240 屏。做波形显示320 像素宽刚好能画一段有意义的 EEG 波形240 像素高可以分几个通道。更关键的是 ESP32 的WebSocket 支持非常成熟。ESPAsyncWebServer 加 AsyncWebSocket 这套库能在一颗芯片上同时跑 HTTP 服务和 WebSocket 推送网页端用浏览器打开就能收数据不需要装任何客户端。这一点对原型验证太重要了——你改一行前端代码刷新浏览器就能看到效果迭代速度比写 Qt 上位机快十倍。还有一点是UART 转发能力。CYD 上引出的 UART 可以接一个 USB-UART 芯片比如 FT231X 或 FT232R把数据同时送到电脑串口助手做备份记录。这样屏幕看实时波形电脑存原始数据网页做交互展示三路并行。2.3 整条链路的数据流设计我把数据流画成文字版方便对照脑电模块 → UART → BW16采集、去噪、打包BW16 → BLE Notify → ESP32-CYD接收ESP32-CYD → 屏幕绘制本地实时波形ESP32-CYD → UART → USB-UART → 电脑备份记录ESP32-CYD → WebSocket → 浏览器网页远程可视化这个设计里BLE 是唯一的无线环节其余都是有线。为什么不让 CYD 也走 WiFi 直接收因为脑电模块和 CYD 之间如果走 WiFi功耗和延迟都不如 BLE 稳定而且 BLE 的配对机制让链路更可控。WiFi 只用在 CYD 到网页这一段走的是局域网延迟可以接受。注意BLE 和 WiFi 在 ESP32 上是共用射频的如果 CYD 同时跑 BLE 客户端和 WiFi AP/STA会有时分复用带来的抖动。我的做法是 BLE 接收用较高优先级任务WebSocket 推送做缓冲队列避免射频切换时丢包。3. 脑电数据从模块到 BW16 的采集细节3.1 脑电模块的 UART 输出格式约定大多数集成脑电模块输出的是定长帧常见格式是帧头加通道数据加校验。我手上这块模块输出的是 24 位 ADC 原始值每通道 3 字节加上帧头帧尾一帧大概几十字节。采样率设的是 250Hz这个速率对 BLE 来说很轻松但对 UART 解析的实时性有要求。这里有个关键点UART 接收必须用中断加环形缓冲不能在主循环里轮询。250Hz 意味着每 4ms 就有一帧如果主循环里还有 BLE 发送、去噪计算轮询很容易丢字节。我的做法是 UART 中断里只做一件事——把字节塞进环形缓冲主循环再从缓冲里取帧解析。这样即使主循环偶尔卡顿缓冲也能扛住。// BW16 端 UART 环形缓冲的简化示意 #define RING_SIZE 512 volatile uint8_t ringBuf[RING_SIZE]; volatile uint16_t ringHead 0, ringTail 0; void onUartRx() { while (Serial1.available()) { uint16_t next (ringHead 1) % RING_SIZE; if (next ! ringTail) { ringBuf[ringHead] Serial1.read(); ringHead next; } else { Serial1.read(); // 缓冲满丢弃防止阻塞 } } }3.2 帧同步与校验的实操要点帧同步是采集端最容易翻车的地方。脑电模块上电瞬间、或者受到干扰时可能输出半截帧如果解析器不做同步后面所有数据都会错位。我的做法是状态机加帧头匹配先找帧头字节找到后按固定长度收完一帧再校验和验证校验不过就丢弃并重新找帧头。校验和用简单的累加和就够了不用上 CRC因为 UART 本身有奇偶校验如果开了的话再加一层累加和能挡住绝大多数错误。实测下来250Hz 连续跑一小时错帧率在万分之一以下对波形显示完全够用。实操心得调试帧同步时先别急着接脑电模块用电脑串口助手手动发构造好的帧把解析器调通再上真实数据。这样能把解析逻辑错误和硬件信号问题分开排查省大量时间。3.3 采集端做多少去噪才合适标题里提到了EEG 去噪但采集端能做的去噪非常有限。BW16 的算力跑复杂滤波不现实我的策略是只做最必要的处理一个简单的滑动平均或者一阶 IIR 高通去掉直流漂移剩下的交给上位机。为什么不在采集端做 50Hz 陷波因为陷波滤波器对相位影响大而且如果采集端做了一半上位机再做一次反而可能引入伪影。原型阶段的原则是采集端保真处理端灵活。原始数据尽量干净地传出去去噪算法在网页端或电脑端用 JavaScript 或 Python 实现改起来方便。如果非要在采集端做点什么我建议只做降采样。比如模块输出 500Hz但显示和传输只需要 250Hz那就每两个点取一个或者做简单的两点平均。这样能直接减半 BLE 带宽压力而且降采样是线性操作不会引入奇怪的伪影。4. BLE 传输这一段的关键参数4.1 MTU 协商与数据分包BLE 默认的 ATT MTU 是 23 字节实际能用的 payload 只有 20 字节。脑电一帧几十字节肯定要分包。但分包不是随便切的MTU 协商决定了每包能装多少。BW16 作为 BLE 外设CYD 作为中心设备连接后第一件事就是协商 MTU。我实测 BW16 能协商到 247 字节的 MTUpayload 能到 244 字节。这个大小足够装下好几帧脑电数据大大减少了包数量。// CYD 端连接后请求更大 MTU client-setMTU(247); // 连接事件里确认实际 MTU void onConnect(BLEClient* c) { Serial.print(Negotiated MTU: ); Serial.println(c-getMTU()); }这里有个坑不是所有手机或中心设备都支持大 MTU。如果 CYD 协商失败会退回默认 23 字节这时候就要靠分包逻辑兜底。我的做法是发送端根据协商结果动态调整每包帧数MTU 大就多装几帧MTU 小就少装接收端按帧头重新组装。4.2 Notify 与 Indicate 的选择BLE 有两种数据推送方式Notify和Indicate。Notify 不需要接收方确认速度快但可能丢包Indicate 需要确认可靠但慢。脑电数据是流式的丢一两包对波形显示影响不大所以我选Notify。但 Notify 有个细节如果发送太快协议栈的缓冲会满这时候notify()会返回失败。我的处理是检查返回值失败就等下一轮不要死循环重试。实测在 250Hz、每包 5 帧的情况下Notify 成功率在 99.9% 以上偶尔丢一包波形上看不出来。注意BLE 的连接间隔Connection Interval直接影响吞吐。默认可能是 30ms 到 50ms如果数据量大要在中心设备端请求更短的间隔比如 15ms 甚至 7.5ms。但间隔太短会增加功耗原型阶段插着电用可以设短一点。4.3 用 BLE 调试助手快速验证链路调试 BLE 的时候别一上来就写 CYD 的代码。先用手机上的BLE 调试助手比如常见的 nRF Connect 或者各类 BLE 串口助手连上 BW16看看能不能收到 Notify 数据。这一步能快速确认BW16 的 BLE 服务有没有正确建立、特征值有没有正确配置、Notify 有没有正常发。我踩过的坑是BW16 的 BLE 服务 UUID 和特征值 UUID 配错了手机连上却看不到数据。用调试助手一看特征值的属性里没有 Notify问题立刻定位。所以先用通用工具验证再写专用代码这个顺序能省很多时间。5. ESP32-CYD 端的接收与屏幕绘制5.1 BLE 客户端接收的缓冲设计CYD 作为 BLE 中心设备收到 Notify 数据后不能直接拿去画屏因为屏幕刷新和 BLE 回调不在一个节奏上。BLE 回调可能一次来好几包屏幕刷新是固定帧率。我的做法是BLE 回调里只把数据塞进一个队列屏幕刷新任务从队列取数据。队列用 FreeRTOS 的xQueueSendFromISR或者简单的环形缓冲都行。关键是队列要有溢出保护如果屏幕刷新跟不上队列满了就丢最旧的数据保证显示的是最新波形而不是卡在历史数据上。// CYD 端 BLE 回调塞队列 void onNotify(BLERemoteCharacteristic* ch, uint8_t* data, size_t len, bool isNotify) { for (size_t i 0; i len; i) { xQueueSend(eegQueue, data[i], 0); // 不阻塞满了就丢 } }5.2 ILI9341 屏幕画波形的刷新策略320x240 的屏幕画 EEG 波形最直接的方法是滚动刷新每来一个新点整屏左移一列最右边画新点。但整屏左移在 SPI 屏上很慢320x240 全屏刷新一次要几十毫秒根本跟不上 250Hz。我的优化方案是分块刷新加双缓冲。把屏幕横向分成若干块每次只刷新最右边一块用pushImage或者fillRect局部更新。更进一步用 TFT_eSPI 库的 Sprite精灵做离屏缓冲先在内存里画好一屏再整块推上去。但 Sprite 占内存大320x240x2 字节就是 150KBESP32 的 RAM 扛不住全屏 Sprite。折中方案是只对波形区域用 Sprite比如波形区 320x100那就是 64KB可以接受。状态栏、坐标轴这些静态内容直接画在屏幕上不参与滚动。这样刷新率能到 30fps 以上波形看起来是流畅的。实操心得TFT_eSPI 的配置在 CYD 上要特别注意引脚定义。CYD 的屏幕引脚和标准 ESP32 开发板不一样User_Setup.h 里要按 CYD 的实际接线改。改错了就是白屏或者花屏别怀疑代码先查引脚。5.3 屏幕端做哪些实时处理屏幕端不只是画原始波形还可以做一些轻量实时处理让显示更有意义。我加了两个一个是简单的阈值检测波形超过阈值时该段用不同颜色画方便看伪影另一个是通道选择如果模块是多通道的屏幕上可以切换显示哪个通道。这些处理都在 CYD 上做因为 CYD 的算力比 BW16 强而且处理结果直接影响显示放在一起延迟最低。但要注意别做太重屏幕刷新任务本身已经占了不小负载再加复杂算法会掉帧。6. 从 CYD 到网页的 WebSocket 推送6.1 网页端可视化的整体架构CYD 上跑一个AsyncWebServer同时提供 HTTP 服务和 WebSocket 端点。网页端就是一个 HTML 文件里面用 JavaScript 的 WebSocket API 连上 CYD收到数据后用 Canvas 画波形。整个网页可以存在 CYD 的 SPIFFS 或者 LittleFS 里浏览器访问 CYD 的 IP 就能打开。这个架构的好处是零客户端安装。任何能连上局域网的设备手机、平板、电脑打开浏览器就能看波形。做演示的时候特别方便不用在每台设备上装软件。// 网页端 WebSocket 接收与绘制 const ws new WebSocket(ws:// location.host /ws); const canvas document.getElementById(eeg); const ctx canvas.getContext(2d); let x 0; ws.onmessage (evt) { const data JSON.parse(evt.data); // data.samples 是本次收到的采样点数组 data.samples.forEach(v { const y mapToCanvas(v); ctx.fillRect(x, y, 1, 1); x (x 1) % canvas.width; if (x 0) ctx.clearRect(0, 0, canvas.width, canvas.height); }); };6.2 数据格式与带宽控制WebSocket 传的是文本还是二进制我选JSON 文本因为调试方便浏览器控制台直接能看。但 JSON 有开销每个数字都要转成字符串。250Hz 单通道每个点大概 5 个字符加上 JSON 结构每秒几 KB局域网完全扛得住。如果通道多、采样率高就要考虑二进制。WebSocket 支持 ArrayBuffer把采样点打包成 Int16 数组直接发带宽能省一半以上。但二进制调试麻烦原型阶段先用 JSON等链路稳定了再优化。注意CYD 的 WebSocket 推送要限速。如果 BLE 收到多少就往 WebSocket 推多少网络抖动时可能积压。我的做法是网页端定时请求或者 CYD 端按固定帧率推送比如 30fps每次推最近积累的采样点。这样网页端刷新节奏稳定不会因为网络波动卡顿。6.3 网页端做 EEG 去噪和源定位的扩展空间标题热词里出现了EEG 源定位和最小范数估计这些是脑电分析的高级话题。原型链路本身不做源定位但网页端是个天然的扩展点。因为网页端可以用 JavaScript 跑一些轻量算法或者把数据转发到后端 Python 服务做重计算。我的思路是CYD 只负责搬运和显示网页端负责交互和轻处理重计算交给后端。比如网页端可以做一个简单的带通滤波显示用 JavaScript 实现 Butterworth 滤波器源定位这种需要头模型和导联位置的计算就通过 WebSocket 再转发到 Python 后端用 MNE 之类的库处理结果回传网页显示。这样分层的好处是每一层职责清晰改哪一层都不影响其他层。原型阶段先把链路跑通算法慢慢加。7. 常见问题与排查技巧实录7.1 BLE 连不上或频繁断开这是最常见的问题。排查顺序我总结成一张表现象可能原因排查方法扫描不到设备BW16 没广播检查 BLE 服务是否启动用调试助手确认能扫描连不上UUID 不匹配核对服务 UUID 和特征 UUID连上就断连接参数太激进放宽连接间隔降低发送速率传一会就断射频冲突或供电不足检查 WiFi 是否同时开启检查供电我遇到过一次连上就断查了半天是供电问题。BW16 在 BLE 发送瞬间电流会跳变如果供电不稳模组会复位。换了个大电容就好了。所以调试 BLE 稳定性先保证供电干净。7.2 UART 丢数据或乱码UART 问题的根源通常是波特率不匹配或者地线没接好。脑电模块和 BW16 之间除了 TX、RX地线一定要接否则电平参考不一致数据必乱。波特率方面两边都要设成一样的常见的是 115200 或 460800。如果模块输出速率高波特率要相应提高。还有一个隐蔽的坑UART 电平。有些模块是 3.3V 电平有些是 5V直接对接可能烧或者收不到。用万用表量一下 TX 空闲时的电平确认是 3.3V 再接。7.3 屏幕花屏或白屏CYD 花屏九成是TFT_eSPI 配置错误。检查 User_Setup.h 里的引脚定义CYD 的屏幕 CS、DC、RST、MOSI、SCLK、背光引脚都要对。另外 CYD 有些版本屏幕驱动是 ST7789 而不是 ILI9341虽然都是 2.8 寸但初始化序列不同选错了就是白屏。实操心得拿到 CYD 第一件事不是写业务代码而是跑一个屏幕测试例程把颜色、方向、触摸都验证一遍。这一步花十分钟能避免后面几小时的困惑。7.4 网页端收不到数据或延迟大网页端问题先看浏览器控制台。WebSocket 连接失败会有明确报错比如跨域、端口不对、路径不对。如果连上了但没数据检查 CYD 端有没有真的在推。延迟大通常是缓冲积压检查 CYD 的推送队列是不是只增不减。还有一个容易忽略的点CYD 的 IP 地址。如果路由器 DHCP 分配变了网页端连的还是旧 IP自然收不到。原型阶段建议给 CYD 设静态 IP省得每次找地址。8. 几个让链路更稳的实操经验8.1 供电与接地的处理整条链路里BW16 和 CYD 最好各自独立供电不要互相从对方的 3.3V 取电。BLE 发送和屏幕刷新都是电流跳变大的操作共用电源容易互相干扰。我用的是两个独立的 USB 供电地线通过 USB 地连在一起信号参考一致但电源噪声隔离了。脑电模块的供电更要小心如果模块对电源噪声敏感最好用线性稳压或者电池供电。原型阶段可以用充电宝给脑电模块供电彻底隔离市电噪声波形会干净很多。8.2 时间戳与数据对齐多路数据屏幕、UART、WebSocket如果要做对齐分析时间戳很重要。我的做法是在 CYD 收到 BLE 数据时打一个本地时间戳这个时间戳跟着数据一起进队列、一起转发。这样即使三路输出有延迟事后也能按时间戳对齐。时间戳用millis()或者esp_timer_get_time()都行后者精度更高微秒级。如果要做长时间记录注意millis()会溢出用 64 位的esp_timer_get_time()更省心。8.3 固件升级与调试接口的保留原型阶段改代码频繁保留一个调试串口非常必要。BW16 和 CYD 各留一路 UART 做日志输出出问题时看日志比猜快得多。另外CYD 支持 OTA 的话尽量把 OTA 调通这样改网页端代码不用每次都插线烧录。我自己的习惯是每个模块上电先打印版本号和关键配置比如 BLE 的 MTU、UART 的波特率、屏幕的分辨率。这样一看日志就知道当前跑的是什么配置避免我明明改了怎么没生效这种问题。8.4 从原型到可用产品的差距这条链路跑通只是原型离可用产品还有距离。主要差距在抗干扰真实环境电磁噪声复杂、长时间稳定性跑几小时和跑几天不一样、数据完整性丢包对分析的影响需要评估、用户体验配网、重连、错误提示。我的建议是原型阶段就把日志和错误处理做扎实别等到产品化再补。比如 BLE 断开自动重连、UART 超时报警、WebSocket 断线重连这些机制在原型阶段加上后面会省很多事。最后分享一个我踩过的坑一开始我图省事把 BLE 接收、屏幕刷新、WebSocket 推送全塞在一个任务里结果屏幕一刷新BLE 就丢包。后来拆成三个任务用队列通信各跑各的节奏链路立刻稳了。ESP32 是双核的善用多任务别什么都往一个循环里塞这是我从这个项目里得到的最实在的一条经验。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

华为S12700E框式交换机产品详解:园区核心选型与冗余架构核对指南 2026/9/29 16:13:00

华为S12700E框式交换机产品详解:园区核心选型与冗余架构核对指南

简介:这份PDF面向园区网络规划、设计与运维工程师,系统讲解华为CloudEngine S12700E系列核心交换机的产品定位与技术细节,帮助读者应对Wi-Fi 6普及下混合现实、AI诊断、无人商场等新业务对带宽与时延的严苛要求。资源包共1个PDF文件&#xff…

阅读更多 →
MySQL InnoDB Cluster高可用实战:Router部署与故障排查 2026/9/29 16:12:53

MySQL InnoDB Cluster高可用实战:Router部署与故障排查

说实话,最开始我对MySQL InnoDB Cluster(简称MIC)是持保留态度的。早年搭过MHA、弄过半同步复制,总觉得官方这套东西太“重”——又是组复制又是Router又是Shell的,光名词就能劝退一批人。但真正被生产环境逼着把整套架…

阅读更多 →
MPAndroidChart可滑动柱状图实战:教务场景下的高性能优化方案 2026/9/29 16:12:53

MPAndroidChart可滑动柱状图实战:教务场景下的高性能优化方案

简介:本资源是一份面向Android开发者的实战型图表交互实现方案,聚焦于利用MPAndroidChart开源库构建支持手势滑动的柱状图,解决大数据量下图表横向浏览不畅的常见痛点,适用于金融数据展示、日志统计分析、IoT设备监控等需动态查看…

阅读更多 →
自动分区+冷分区迁移+压缩:Oracle流水表存储与查询性能优化实践 2026/9/29 16:12:53

自动分区+冷分区迁移+压缩:Oracle流水表存储与查询性能优化实践

接手过一张按天自动分区的流水表之后,我是真的体会到了“自动分区很省心,但省不了心”。自动分区帮你把“每个月/每天手工建分区”的重复劳动干掉了,可它不会替你考虑:旧分区还在昂贵的存储上躺着,查询还是会扫过大量历…

阅读更多 →
Oracle自动分区只是开始:冷分区压缩与治理方案实践 2026/9/29 16:12:53

Oracle自动分区只是开始:冷分区压缩与治理方案实践

先聊个真实场景。我有个朋友维护一套业务流水库,表是按月自动分区的,INTERVAL分区用得很溜,写入从不需要人工干预。结果半年后他发现两个问题:一是磁盘快满了,历史分区谁都没碰过,却占了整个库一大半空间&a…

阅读更多 →
数据结构栈和队列核心详解:从受限线性表到系统栈与消息队列 2026/9/29 16:12:53

数据结构栈和队列核心详解:从受限线性表到系统栈与消息队列

数据结构这门课有个很典型的感受:前面学顺序表、链表的时候,你觉得是在学“怎么把数据存起来”,存得整齐、找得快就行。但一学到栈和队列,画风突然变了——同样是线性表,它开始教你不允许随便存、不允许随便取。我第一…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉