1. 从一块ADS1299到64通道系统:这个项目到底在解决什么问题
脑电采集这件事,单通道做起来不难,难的是把几十个通道同时做稳、做同步、做小。市面上能买到的64通道EEG系统,价格基本都在六位数往上,而且大多是封闭系统,想改个采样率或者换个上位机协议都费劲。这个Modular 64-Channel EEG System项目的核心思路,就是用模块化的方式把成本打下来,同时保留完整的可定制性。
整个系统的架构可以拆成三层:模拟前端层负责脑电信号的放大和数字化,主控与数据汇聚层负责把多路数据打包上传,电源与隔离层保证安全性和噪声指标。关键词里出现的ADS1299、STM32、FPGA、ECP5,正好对应了这三层的核心器件选型。
ADS1299是TI的一款8通道24位生物电测量AFE,内置可编程增益放大器、24位ΔΣ ADC和内部参考,单芯片就能搞定8个通道的脑电采集。8颗ADS1299级联起来就是64通道,这是目前开源EEG项目里最主流的方案。STM32在这里的角色是系统管理——配置AFE寄存器、管理SD卡存储、跑USB或以太网协议栈、处理上位机命令。FPGA(ECP5)则负责硬实时的数据汇聚和时序控制,因为64通道×24位×250SPS的数据率虽然不算高,但要做到通道间严格同步、数据不丢包,用MCU轮询SPI的方式会很吃力。
这套系统适合谁?如果你是做脑机接口研究的研究生、想自己搭一套多通道采集系统的硬件工程师、或者在做神经反馈/睡眠监测类产品的团队,这个架构都值得参考。它不追求医疗级认证,但追求的是可复现、可修改、成本可控。
提示:ADS1299的 datasheet 是必读的,尤其是第9章寄存器映射和第10章SPI时序部分。很多人卡在通道数据错位上,根源都是没把datasheet里的时序图看清楚。
2. 模拟前端设计:8颗ADS1299级联的硬件细节
2.1 为什么选ADS1299而不是分立方案
分立方案(仪表放大器+独立ADC)做8通道以上时,通道间一致性很难保证,而且PCB面积会爆炸。ADS1299把8个通道集成在一颗芯片里,通道间的增益误差和失调误差都由出厂校准保证,这对EEG这种微伏级信号来说太重要了。它的输入参考噪声在250SPS、增益24时大约是1μVpp,对于头皮脑电(通常10-100μV)来说信噪比足够。
ADS1299的SPI接口支持菊花链(daisy-chain)模式,多颗芯片可以串成一条链,MCU只需要一组SPI片选就能读回所有通道数据。但菊花链模式有个坑:链上任何一颗芯片配置出错,整条链的数据都会错位。所以我在实际项目中更倾向于用独立片选+共享SPI总线的方式,虽然多占几个GPIO,但调试起来方便太多。
2.2 级联时的时钟与同步处理
8颗ADS1299要严格同步采样,时钟方案是关键。每颗ADS1299都需要一个主时钟(典型2.048MHz),如果各自用独立的晶振,温漂会导致采样时刻逐渐偏移。正确做法是用一颗芯片产生主时钟,其余芯片从模式接收。ADS1299支持CLK输出,可以配置为将内部振荡器或外部时钟缓冲后输出,供给下一颗芯片。
具体连接方式:第一颗ADS1299配置为master模式,CLK引脚输出2.048MHz;其余7颗配置为slave模式,CLK引脚作为输入接收这个时钟。同时所有芯片的DRDY引脚可以线与在一起,或者分别接到FPGA的IO上,由FPGA做同步采集触发。
这里有个细节:ADS1299的START引脚控制转换启动。如果8颗芯片的START信号不同步,采样时刻会有偏差。我的做法是用FPGA产生一个统一的START信号,经过缓冲后同时送到8颗芯片。缓冲器选的是74LVC244,传输延迟在3ns以内,对于250SPS(4ms周期)来说完全可以忽略。
2.3 输入端的RC滤波与偏置电路
ADS1299的输入端需要加RC低通滤波,截止频率一般设在1-2kHz。电阻选10kΩ,电容选10nF,截止频率约1.6kHz。电阻的精度和匹配度会影响共模抑制比,建议用0.1%精度的薄膜电阻。
偏置电路(bias drive)是EEG采集里容易被忽视的部分。ADS1299有一个BIASOUT引脚,可以输出共模电压到人体,把人体电位拉到ADC的共模范围内。如果不接偏置,采集到的信号很容易饱和。偏置电阻一般取1MΩ以上,太小会有安全风险,太大则偏置效果差。我通常用4.7MΩ,配合0.1μF的补偿电容。
注意:偏置电路的安全电阻必须用高耐压型号,而且整个模拟前端应该由电池供电或经过隔离电源,这是涉及人体安全的基本要求。
3. FPGA(ECP5)在数据汇聚中的角色与实现
3.1 为什么不用STM32直接读8颗ADS1299
有人会问:STM32的SPI速率也能跑到10MHz以上,64通道×24位=1536位,在250SPS下每秒只需要384k位,STM32完全能处理,为什么还要加FPGA?
问题不在带宽,在实时性和同步精度。8颗ADS1299的DRDY信号几乎同时到来,STM32如果用轮询方式逐个读取,从第一颗到第八颗之间会有几十微秒的延迟。虽然可以通过外部中断+SPI DMA来优化,但中断响应本身就有抖动。更麻烦的是,STM32在读取SPI数据的同时还要处理USB协议栈、SD卡写入、命令解析,任何一个高优先级中断都可能打断SPI读取,导致数据错位。
FPGA的方案是把数据汇聚做成纯硬件逻辑:8个SPI master并行工作,每个master只负责一颗ADS1299,读到数据后写入FIFO,再由一个仲裁模块把8路FIFO的数据打包成一个统一的数据帧,通过高速接口(如SPI或并口)送给STM32。整个过程没有软件参与,时序确定性极高。
3.2 ECP5上的SPI Master设计要点
ECP5是Lattice的一款中低端FPGA,逻辑资源从24k到85k LUT不等,做8路SPI master绰绰有余。每个SPI master的状态机可以这样设计:
// SPI master 状态机核心片段 localparam IDLE = 3'd0; localparam ASSERT = 3'd1; localparam SHIFT = 3'd2; localparam DEASSERT = 3'd3; always @(posedge clk) begin case (state) IDLE: begin if (start) begin cs_n <= 1'b0; state <= ASSERT; end end ASSERT: begin // 等待ADS1299的tCSSC时间 if (delay_cnt == 4'd8) state <= SHIFT; end SHIFT: begin // 发送命令字节,接收数据字节 if (bit_cnt == 5'd23) state <= DEASSERT; end DEASSERT: begin cs_n <= 1'b1; state <= IDLE; end endcase endADS1299的SPI时序要求:CS拉低到第一个SCLK上升沿之间至少需要6ns(tCSSC),SCLK周期最小50ns(20MHz)。ECP5跑在50MHz时,一个时钟周期20ns,所以ASSERT状态等8个周期(160ns)足够安全。
3.3 跨时钟域与数据打包
8路SPI master各自工作在50MHz时钟域,但ADS1299的DRDY信号是异步的。这里需要做跨时钟域处理:DRDY先经过两级触发器同步到50MHz时钟域,然后作为FIFO的写使能。
数据打包的格式我一般这样定义:每帧包含8颗芯片×8通道×24位=1536位,加上帧头(0xAA55)、帧计数器和CRC16校验。帧头用于STM32端做字节对齐,帧计数器用于检测丢帧,CRC16用于验证数据完整性。整个帧长大约200字节,在250SPS下每秒产生50KB数据,STM32通过SPI以10MHz读取完全来得及。
提示:ECP5的FIFO可以用IPexpress生成,也可以自己用Block RAM手写。手写的好处是深度和位宽可以精确控制,不浪费资源。我一般用分布式RAM做小FIFO(深度16),用Block RAM做大FIFO(深度256以上)。
4. STM32端的系统管理与数据链路
4.1 STM32的选型与资源分配
STM32在这个系统里不是做实时采集的,它的任务是系统管理:上电时配置FPGA和ADS1299、运行过程中接收FPGA打包好的数据、通过USB或以太网传给上位机、同时把数据写入SD卡做备份。
选型上,STM32F4系列(如F407或F429)是比较平衡的选择。F407有168MHz主频、192KB SRAM、带USB OTG和SDIO接口,价格也合适。如果要做以太网传输,F429或F767更合适,因为它们有内置的MAC控制器。F103系列虽然便宜,但SRAM只有64KB,做大数据缓冲会比较紧张。
资源分配上,我通常这样规划:SPI1用于和FPGA通信(10MHz),SPI2用于配置ADS1299(低速,1MHz),USB OTG FS用于上位机通信,SDIO用于SD卡存储,UART1用于调试输出。时钟树配置上,F407的主频168MHz,SPI1挂在APB2上(84MHz),分频后得到10.5MHz,接近FPGA端的SPI时钟上限。
4.2 数据流的缓冲与优先级管理
STM32端的数据流有三个方向:FPGA→STM32(SPI接收)、STM32→上位机(USB发送)、STM32→SD卡(SDIO写入)。这三个方向的速率和实时性要求不同,需要合理分配优先级。
FPGA→STM32的SPI接收是最高优先级,因为FPGA的FIFO深度有限,如果STM32不及时读取,FIFO满了之后数据就会丢。我的做法是用SPI DMA+双缓冲:DMA把数据搬到buffer A,搬完后触发中断,中断里切换DMA到buffer B,同时把buffer A的数据交给USB发送任务。这样SPI接收永远不会被其他任务阻塞。
USB发送和SD卡写入的优先级较低,可以放在主循环里轮询处理。但要注意USB的带宽:64通道×24位×250SPS=384kbps,USB FS(12Mbps)完全够用,但如果加上协议开销和SD卡写入的延迟,实际有效带宽可能只有几百kbps。所以USB发送要用批量传输(bulk transfer),不要用中断传输。
4.3 上位机协议的帧格式设计
STM32和上位机之间的协议我一般设计成命令+数据双通道。命令通道用于上位机发送配置参数(采样率、增益、通道使能等),数据通道用于STM32上传采集数据。
数据帧格式如下:
| 字段 | 长度 | 说明 |
|---|---|---|
| 帧头 | 2字节 | 0xAA55 |
| 帧类型 | 1字节 | 0x01=数据帧,0x02=状态帧 |
| 帧长度 | 2字节 | 后续数据的字节数 |
| 帧计数器 | 4字节 | 用于检测丢帧 |
| 数据区 | N字节 | 64通道×3字节 |
| CRC16 | 2字节 | 校验 |
命令帧格式类似,但数据区换成命令码和参数。上位机收到数据帧后,先检查帧头和CRC,然后按帧计数器判断是否有丢帧。如果发现丢帧,可以在上位机端做插值或者标记。
注意:STM32的USB CDC实现里,发送缓冲区大小要设够。如果一次发送的数据超过端点缓冲区,需要分多次发送,每次发送之间要等待上一次发送完成。我踩过的坑是连续调用CDC_Transmit_FS而不检查返回值,导致数据被覆盖。
5. 电源与隔离:决定噪声底的关键一环
5.1 模拟电源与数字电源的分离
EEG系统的噪声性能很大程度上取决于电源设计。ADS1299的模拟电源(AVDD)和数字电源(DVDD)必须分开供电,而且在PCB上要物理隔离。AVDD用LDO供电,DVDD可以用开关电源,但开关频率要避开ADS1299的采样频率及其谐波。
我的做法是:电池(3.7V锂电)→升压到5V→LDO降到3.3V给模拟部分,另一路LDO降到1.8V给数字部分。ADS1299的AVDD可以是3.3V或5V,选3.3V时功耗更低,但输入范围会小一些。如果信号幅度较大,建议用5V供电。
5.2 隔离方案的选择
涉及人体接触的设备,隔离是必须的。隔离可以在三个位置做:电源隔离、信号隔离、或者整体用电池供电实现浮地。
电池供电是最简单的方案,整个系统浮地,人体和市电之间没有回路。但电池供电有个问题:USB连接电脑时,电脑的USB地会把系统地拉到市电地,隔离就失效了。所以如果要用USB通信,必须在USB端加隔离器(如ADuM4160)或者用光纤USB。
信号隔离方面,SPI信号可以用数字隔离器(如ADuM7440)隔离,但会增加成本和PCB面积。我的建议是:如果系统用电池供电且不接USB,可以不做信号隔离;如果要接USB,至少要在USB端做隔离。
5.3 实测噪声水平与优化经验
我搭过一套8通道的原型,用ADS1299+STM32F407,在屏蔽箱里测得的输入参考噪声大约是1.2μVpp(0.5-100Hz带宽),和datasheet标称的1μVpp很接近。做到这个水平的关键是:
- 模拟地和数字地单点连接,连接点选在ADS1299的AGND引脚附近
- 输入端RC滤波的电阻用0.1%精度,电容用C0G材质
- 电源走线尽量宽,去耦电容靠近芯片引脚(100nF+10μF组合)
- PCB用4层板,模拟部分和数字部分分区域布局
如果噪声明显偏大,先检查电源纹波,再用示波器看SPI时钟是否有过冲。SPI时钟过冲会通过芯片内部耦合到模拟部分,在频谱上表现为时钟频率的谐波。
6. 调试过程中最容易卡住的几个问题
6.1 通道数据错位:SPI时序的隐形陷阱
ADS1299读数据时,每个通道24位,8通道就是192位。如果SPI的CPOL/CPHA设置不对,读回来的数据会整体偏移一位,导致所有通道的数据都错位。ADS1299的SPI模式是CPOL=0,CPHA=1,也就是数据在SCLK的下降沿改变,在上升沿采样。
我遇到过的情况是:STM32的SPI配置成CPHA=0,读回来的数据看起来有波形,但通道对应关系完全乱了。后来用逻辑分析仪抓SPI波形,发现数据在上升沿就已经稳定了,但STM32在下降沿采样,采到的是前一位的数据。改成CPHA=1之后问题解决。
提示:调试SPI接口时,逻辑分析仪比示波器好用。逻辑分析仪可以解码SPI协议,直接看到每个字节的值,比对着波形数位要快得多。
6.2 DRDY信号丢失与FIFO溢出
FPGA端的FIFO溢出是另一个常见问题。如果STM32读取速度跟不上,FPGA的FIFO写满后新数据会覆盖旧数据,导致数据丢失。判断方法是:在FPGA里加一个溢出计数器,每次FIFO满时计数加一,STM32定期读取这个计数器。如果计数不为零,说明STM32端有瓶颈。
解决FIFO溢出的思路有两个:一是加大FIFO深度,给STM32更多缓冲时间;二是优化STM32的读取流程,减少中断延迟。我一般先用逻辑分析仪测STM32的SPI读取周期,如果周期抖动超过10%,就要检查是否有其他中断在抢CPU。
6.3 ECP5的配置与启动问题
ECP5是SRAM型FPGA,掉电后配置丢失,需要外挂SPI Flash存储配置文件。上电时ECP5从Flash读取配置,这个过程如果失败,FPGA就不会工作。常见的失败原因有:Flash的SPI模式不匹配(ECP5支持SPI和QSPI模式)、配置文件没有正确生成、或者Flash的片选信号被其他电路拉低。
我的经验是:先用Lattice的Diamond Programmer通过JTAG直接配置ECP5,确认逻辑功能正常,然后再生成配置文件烧到Flash里。如果JTAG配置正常但Flash启动失败,检查Flash的型号是否在ECP5的支持列表里,以及配置文件的起始地址是否正确。
6.4 STM32的USB识别问题
STM32的USB OTG FS在Keil5环境下配置时,最容易出问题的是时钟配置。USB FS需要48MHz时钟,这个时钟可以从PLL输出分频得到,也可以从外部晶振直接提供。如果时钟不对,电脑会提示“无法识别USB设备”。
我一般用外部8MHz晶振,PLL配置成168MHz,然后USB预分频器设置成3.5,得到48MHz。在CubeMX里配置时,注意检查“USB OTG FS”的时钟源是否选对了。另外,USB的DP/DM线要加22Ω的串联电阻,不加的话信号完整性会变差,导致枚举失败。
7. 从原型到可用系统:模块化设计的取舍
7.1 模块划分与接口定义
这个项目的“Modular”体现在硬件模块化上。我把系统拆成三块板:AFE板(8颗ADS1299+输入滤波+偏置电路)、数字板(ECP5+STM32+电源)、接口板(USB/以太网/SD卡)。三块板之间用板对板连接器连接,接口定义如下:
| 连接器 | 信号 | 方向 | 说明 |
|---|---|---|---|
| J1 | SPI_CLK | 数字板→AFE板 | 10MHz |
| J1 | SPI_MOSI | 数字板→AFE板 | 配置数据 |
| J1 | SPI_MISO | AFE板→数字板 | 采集数据 |
| J1 | CS[7:0] | 数字板→AFE板 | 8路片选 |
| J1 | DRDY | AFE板→数字板 | 数据就绪 |
| J1 | START | 数字板→AFE板 | 转换启动 |
| J2 | USB_DP/DM | 接口板→数字板 | USB FS |
| J2 | SDIO | 接口板→数字板 | 4位SDIO |
模块化的好处是:AFE板可以单独测试,数字板可以单独调试,接口板可以根据需要更换(USB版或以太网版)。缺点是板间连接器会引入额外的寄生电容和电感,对高速信号有影响。SPI时钟10MHz时问题不大,但如果以后要提高到20MHz,可能需要改用高速连接器。
7.2 成本控制与器件选型
64通道EEG系统的成本大头在ADS1299上。一颗ADS1299的零售价大约30-40美元,8颗就是240-320美元。ECP5-25F大约20美元,STM32F407大约10美元,加上电源、连接器、PCB,整套BOM成本大约在400-500美元。相比商用系统动辄上万美元的价格,这个成本已经很有竞争力了。
如果想进一步降成本,可以考虑用ADS1298代替ADS1299。ADS1298是8通道24位AFE,但它是为ECG设计的,输入参考噪声比ADS1299高一些(大约4μVpp),对于EEG来说可能不够。另一个选择是国产的AFE芯片,但目前开源社区里用的人少,资料和驱动都不够成熟。
7.3 外壳与电极连接
EEG电极通常用干电极或湿电极。干电极用弹簧针直接接触头皮,不需要导电膏,但接触阻抗较高(几十kΩ),对ADS1299的输入阻抗要求高。湿电极用导电膏,接触阻抗低(几kΩ),信号质量更好,但使用麻烦。
电极连接器我一般用1.5mm的排针或者专用的EEG电极帽连接器。如果用排针,要注意屏蔽,否则会引入工频干扰。屏蔽的做法是用导电布包裹排针,导电布接到系统的模拟地。
外壳方面,3D打印的ABS外壳就够用,但要注意内部要贴导电屏蔽层,减少外部电磁干扰。如果做医疗级产品,外壳需要符合相关的安全标准,那就复杂多了。
8. 这套系统还能怎么扩展
8.1 增加通道数到128或256
64通道不是上限。ADS1299支持菊花链模式,理论上可以级联更多芯片。但菊花链模式下,所有芯片的数据都从一条SPI线读出,数据率会线性增加。128通道时数据率是768kbps,256通道时是1.5Mbps,STM32的SPI以10MHz读取仍然够用,但FPGA的FIFO深度需要加大。
另一个扩展方向是用多块AFE板并行工作,每块板8颗ADS1299,通过高速背板总线把数据汇聚到主FPGA。这种方式可以做到256通道以上,但同步和时钟分配会更复杂。
8.2 加入阻抗测量功能
ADS1299内置了阻抗测量功能,可以通过注入交流电流并测量电压来计算电极阻抗。这个功能在EEG采集前做电极检查非常有用。实现方式是在STM32端发送阻抗测量命令,FPGA控制ADS1299切换到阻抗测量模式,采集到的数据由STM32计算阻抗值并上传上位机。
阻抗测量的激励信号频率一般选31.25Hz或62.5Hz,电流幅度在6nA到24μA之间可调。测量时要注意,激励电流不能太大,否则会引起人体不适。
8.3 无线传输方案
如果要做便携式EEG系统,USB有线连接就不方便了。无线方案可以考虑WiFi(ESP32)或蓝牙(nRF52)。WiFi的带宽足够(几Mbps),但功耗较高;蓝牙功耗低,但带宽有限(BLE大约1Mbps),64通道数据需要压缩后才能传输。
我的建议是:如果只是做数据传输,用ESP32做WiFi透传最简单,STM32通过UART把数据发给ESP32,ESP32通过TCP发给上位机。如果要低功耗,可以用nRF52做BLE,但需要在STM32端做数据压缩(比如只传频段能量而不是原始波形)。
8.4 开源与社区协作
这个项目如果开源,最有价值的部分是硬件设计文件(原理图、PCB、BOM)和FPGA/STM32固件。硬件设计可以用KiCad,固件可以用Verilog和C。开源时要注意:ADS1299的寄存器配置需要仔细注释,否则别人很难修改;FPGA的时序约束文件要写清楚,否则在不同批次的PCB上可能跑不起来。
社区协作方面,可以先把单通道或8通道的版本做稳定,再扩展到64通道。这样别人可以先从简单的版本入手,逐步理解整个系统。文档要写清楚每个模块的功能和接口,最好配上实测波形和噪声数据,这样别人才能判断系统是否满足自己的需求。
提示:开源硬件项目里,最容易被忽视的是测试报告。附上一份详细的测试报告(噪声、带宽、通道间串扰、功耗),比多写几页原理说明更有说服力。