【开源】基于TI ADS1299与ESP32的8通道脑电采集设备YuEEG全解析最近有不少朋友对脑机接口BCI感兴趣想自己动手做一个能采集脑电信号EEG的设备。市面上的成品设备动辄上万对创客和学生来说门槛太高。正好我最近基于TI的ADS1299芯片和ESP32做了一个开源的8通道脑电采集设备叫YuEEG。今天我就把这个项目的硬件设计、驱动开发和上位机软件从头到尾、掰开揉碎了讲给你听。就算你是嵌入式新手跟着这篇教程也能一步步把它做出来。1. 项目与硬件概览我们要做什么脑机接口听起来很科幻其实核心就是一套高精度的生物电信号采集系统。我们的大脑活动会产生微弱的电信号通过贴在头皮上的电极捕捉到这些信号再经过放大、滤波、数字化最后交给计算机分析这就是EEG采集。我们这个项目的核心是一颗专业级的芯片TI ADS1299。它是一颗24位、8通道、低噪声的模拟前端专门为ECG心电图和EEG脑电图设计。用它来做脑电采集精度和性能都有保障。主控我们选了大家都很熟悉的ESP32它自带Wi-Fi/蓝牙性能足够关键是生态好用Arduino框架开发起来特别快。整个设备包含三大部分硬件板卡承载ADS1299芯片、模拟前端滤波电路、电源管理以及ESP32主控。固件程序运行在ESP32上负责通过SPI驱动ADS1299读取数据并通过串口发送给电脑。上位机软件运行在电脑上的Python程序接收串口数据并实时绘制出8个通道的脑电波形图。注意本项目基于自定义的MIT协议开源但未经作者许可不能用于参加任何商业比赛。2. 硬件设计与连接电路板长啥样怎么接线2.1 核心硬件一览咱们先看看板子上都有哪些关键部件主芯片ADS1299负责采集和模数转换。主控MCUXiao ESP32S3模块小巧且功能全。电源支持两种供电方式推荐第一种USB直接供电用USB线连接电脑和ESP32模块同时供电和通信。电池供电可以在板子背面焊接一个TPS63070升降压模块接上3.7V锂电池。接口一个脑电电极接口用来接电极帽以及一些扩展接口如支持BMM150传感器、健康监测模块。电路设计文件和3D打印外壳模型都已经开源电路图与PCB可以在嘉立创开源平台查看访问密码是yutaov5。链接https://oshwhub.com/protodrive000/1299_pro3D外壳文件提供了脑电采集盒的模型方便你打印组装。链接https://a360.co/3AnxQdK访问密码同样是yutaov5。2.2 引脚连接ESP32如何“指挥”ADS1299ADS1299通过SPI接口与ESP32通信。你需要把下面这几个引脚连接起来。在代码里它们是这样定义的// 定义ADS1299与ESP32连接的引脚 #define CS_PIN A3 // 片选引脚低电平有效 #define SCLK_PIN SCK // SPI时钟线 #define MOSI_PIN MOSI // ESP32输出ADS1299输入主出从入 #define MISO_PIN MISO // ESP32输入ADS1299输出主入从出 #define DRDY_PIN A0 // 数据就绪引脚ADS1299拉低表示新数据好了 #define START_PIN A2 // 启动转换引脚 #define RESET_PIN A1 // 复位引脚这里解释两个关键引脚DRDY_PIN(A0)这是ADS1299给ESP32的“通知”。当ADS1299转换完一组新数据就会把这个引脚拉成低电平告诉ESP32“数据准备好了快来读”。CS_PIN(A3)SPI的片选。因为SPI总线可以挂多个设备这个引脚就是用来选中我们想要对话的那个设备这里是ADS1299。通信时把它拉低通信完再拉高。2.3 理解参考电极与偏置电压这是脑电采集电路的两个核心概念直接关系到信号质量。参考电极 (REF)想象一下测身高你需要一个固定的地面作为“0点”基准。参考电极就是脑电信号的“0点”基准。ADS1299很灵活你可以通过配置寄存器指定8个通道中的某一个作为参考电极。偏置电压 (BIAS)人体就像一根天线很容易接收到50Hz的工频干扰。偏置电压的作用是主动产生一个电压施加在人体上把整个测量系统的“共模电压”稳定在一个合适的范围内从而极大地抑制这种共模干扰。ADS1299内部有一个偏置放大器专门干这个事。在硬件上参考电极和偏置电压的输出是通过SRB1和SRB2引脚来连接的。在软件配置寄存器时你需要决定如何使用它们。3. 固件开发手把手写ESP32驱动代码驱动代码是用Arduino框架写的对初学者非常友好。咱们一步步来看。3.1 开发环境搭建安装Arduino IDE。在Arduino IDE中点击工具-开发板-开发板管理器搜索并安装“ESP32”开发板支持包。3.2 代码框架与核心函数整个固件的逻辑围绕以下几个核心函数展开初始化 (setup函数)void setup() { Serial.begin(115200); // 初始化串口用于调试和向上位机发数据 pinMode(CS_PIN, OUTPUT); pinMode(DRDY_PIN, INPUT); // DRDY是输入由ADS1299控制 pinMode(START_PIN, OUTPUT); pinMode(RESET_PIN, OUTPUT); // ... 其他引脚初始化 SPI.begin(); // 启动SPI通信 initADS1299(); // 初始化ADS1299芯片 // 设置ESP32的硬件定时器定期检查数据是否就绪 hw_timer_t *timer timerBegin(0, 80, true); // 定时器0预分频80向上计数 timerAttachInterrupt(timer, onDataTimer, true); // 关联中断函数 timerAlarmWrite(timer, 1000, true); // 每1000微秒1ms触发一次 timerAlarmEnable(timer); }主循环 (loop函数) 主循环主要做两件事监听串口指令切换模式、检查数据就绪标志并读取数据。void loop() { // 1. 检查串口命令 if(Serial.available() 0){ char cmd Serial.read(); if(cmd 1){ startContinuousReadMode(); // 切换到连续采集模式 } else if(cmd 3){ startLeadOffDetectionMode(); // 切换到导联脱落检测模式 } } // 2. 如果数据就绪标志被定时器置位则读取数据 if(startRead){ startRead false; // 清除标志 readData(); // 读取ADS1299的数据 } }数据就绪定时器中断 ESP32的定时器每隔1ms检查一次DRDY_PIN引脚。void IRAM_ATTR onDataTimer() { // 如果DRDY引脚为低电平说明新数据已就绪 if(digitalRead(DRDY_PIN) LOW){ startRead true; // 设置标志让主循环去读 } }3.3 与ADS1299通信SPI命令与寄存器配置和ADS1299打交道就是通过SPI发送各种命令和读写寄存器。芯片有一张命令表代码里是这么定义的命令 (宏定义)功能描述WAKEUP将芯片从待机模式唤醒STANDBY让芯片进入待机模式省电RESET软件复位让芯片恢复默认状态START开始进行模拟到数字的转换STOP停止转换RDATAC启动连续读取数据模式最常用SDATAC停止连续读取数据模式RDATA读取一次数据非连续模式用RREG读取指定寄存器的值WREG向指定寄存器写入值如何发送命令很简单在拉低CS_PIN选中芯片后直接通过SPI发送命令字节。例如复位芯片void resetADS1299() { digitalWrite(CS_PIN, LOW); // 选中芯片 SPI.transfer(RESET); // 发送复位命令 (0x06) delayMicroseconds(10); // 等待复位完成 digitalWrite(CS_PIN, HIGH); // 取消选中 }如何配置寄存器芯片的所有设置增益、采样率、通道开关等都通过寄存器控制。我们需要用WREG命令来写寄存器。下面这个函数是通用的写寄存器函数void writeRegister(uint8_t regAddress, uint8_t data) { digitalWrite(CS_PIN, LOW); SPI.transfer(WREG | regAddress); // WREG命令 寄存器地址 SPI.transfer(0x00); // 要写入的寄存器数量-1这里写1个寄存器所以是0 SPI.transfer(data); // 要写入的数据 digitalWrite(CS_PIN, HIGH); }比如我们要设置通道1的增益为6倍并打开SRB2连接将其作为参考就需要配置CH1SET寄存器地址0x01// 假设增益6倍二进制010SRB2打开bit71 // 则寄存器值应为1000 0010即0x82 writeRegister(0x01, 0x82);3.4 读取与转换脑电数据这是最核心的一步。在连续读取模式(RDATAC)下当DRDY引脚变低我们就知道有新的8通道数据包来了。读取原始数据void readData() { uint8_t data[27]; // 1个状态字节 8通道 * 3字节 25字节多留点空间 digitalWrite(CS_PIN, LOW); SPI.transfer(RDATA); // 发送读取数据命令 (0x12) for(int i0; i27; i){ data[i] SPI.transfer(0x00); // 逐个字节读取 } digitalWrite(CS_PIN, HIGH); // 接下来解析data数组... }数据包格式是1字节状态 (通道1的3字节) (通道2的3字节) ... (通道8的3字节)。每个通道的3个字节组成一个24位有符号整数。将数字量转换为电压值 ADS1299的24位数据需要转换成我们能理解的电压值。公式是电压 (微伏) (原始数据 * 满量程电压) / (2^23)其中满量程电压(Vref)由你设置的增益(Gain)决定。假设我们使用内部参考电压2.4V增益为6float convertToMicrovolts(int32_t rawData) { float vRef 2.4; // 内部参考电压单位V int gain 6; // 增益倍数 // 满量程电压 Vref / Gain float fullScaleVoltage vRef / gain; // 单位V // 转换为微伏 float voltage rawData * (fullScaleVoltage / 8388608.0); // 2^23 8388608 return voltage * 1000000.0; // 返回微伏值 }最后通过串口将8个通道的电压值打印出来格式如Channel 1: 123.456, Channel 2: -45.678, ...提示第一次给ESP32下载程序时需要先按住板上的BOOT按钮再上电然后松开按钮这时在Arduino IDE中点击上传即可。4. 上位机软件用Python实时绘制脑电波硬件数据采集到了我们需要在电脑上看到一个直观的波形。作者用Python写了一个图形界面程序。4.1 环境安装与运行把项目代码克隆到本地git clone https://github.com/YuTaoV5/YuEEG.git cd YuEEG安装必要的Python库pip install pyserial pyqt5 pyqtgraph PyQt-Fluent-Widgets scikit-learn运行上位机程序python plot_only.py4.2 软件使用指南运行plot_only.py后你会看到一个波形显示界面。连接设备在软件中选择ESP32对应的串口号如COM3或/dev/ttyUSB0波特率设置为115200然后点击连接。模式切换软件左下方有按钮或者你也可以在串口监视器里发送字符命令发送1进入连续读取模式开始实时绘制8通道脑电波形。发送3进入自检/导联检测模式此时ADS1299会输出测试信号用于检查硬件和连接是否正常。观察波形在连续读取模式下8个彩色的曲线会实时滚动这就是你采集到的脑电信号。你可以尝试眨眼、咬牙观察波形的变化。4.3 进阶功能SSVEP范式这个项目还提供了一个更高级的ssvep_paradigm.py程序。SSVEP稳态视觉诱发电位是一种常见的脑机接口范式让人盯着以特定频率闪烁的视觉刺激大脑枕叶区会产生同频率的响应。这个程序就实现了这样一个拼写界面可以用来做脑控打字等应用研究。5. 常见问题与调试心得读不到数据全是0检查电源确保ADS1299的模拟和数字电源都稳定。最好用示波器看看。检查SPI连线CS,SCLK,MOSI,MISO四根线是否接对、接牢。检查DRDY引脚用逻辑分析仪或示波器看在发送START命令后这个引脚是否有周期性的低电平脉冲如果没有可能是ADS1299没有正确启动转换。打印寄存器写一个函数读取并打印ADS1299的关键寄存器如ID寄存器0x00看通信是否正常芯片是否被正确复位和配置。波形噪声很大共模干扰确保偏置电压(BIAS)功能已正确配置并启用这是抑制50/60Hz工频干扰的关键。电源噪声模拟部分的电源最好用LDO单独供电并加上足够的滤波电容。电极接触电极与头皮接触不良是噪声的主要来源。确保使用导电膏电极阻抗要足够低。ESP32定时器中断不触发确保定时器初始化参数正确并且调用了timerAlarmEnable。中断处理函数onDataTimer前要加上IRAM_ATTR宏确保它被放在内部RAM中执行避免从缓存读取的延迟。这个项目从硬件到软件完全开源你可以把它当作一个学习高精度ADC驱动、SPI通信和生物信号处理的绝佳平台。无论是想了解脑机接口还是想深入学习嵌入式系统亲手做一遍收获绝对比只看书大得多。项目还在持续更新后续可能会增加无线传输、手机APP显示等功能值得保持关注。