64通道脑电采集系统设计:ADS1299与FPGA架构实战
1. 从零搭建一套64通道脑电采集系统为什么我选了ADS1299加FPGA这条路线做脑电采集这行十来年从最早的16通道工频干扰满屏飞到后来32通道勉强能看再到现在手头这套64通道系统跑得稳稳当当中间踩过的坑能写一本书。今天聊的这套Modular 64-Channel EEG System核心思路就是用8片ADS1299做模拟前端STM32做系统调度和通信再用一颗ECP5 FPGA扛下所有64通道的实时数据搬运和预处理。整套系统模块化设计每块板子管8个通道需要多少通道就插多少块扩展起来非常灵活。先说清楚这套东西是给谁看的。如果你正在做脑机接口、神经科学研究、睡眠监测、或者认知科学实验需要一套能同步采集64通道脑电信号的硬件平台那这套方案可以直接参考。如果你只是想做单通道心率监测那ADS1299属实有点大材小用一颗ADS1292就够了。但如果你需要多通道、低噪声、高共模抑制比的生物电信号采集ADS1299几乎是绕不开的选择。为什么不用现成的商用设备一个字贵。64通道的科研级脑电系统动辄几十万而且很多场景下你需要自己改固件、改上位机、改同步协议商用设备根本不让你碰底层。自己搭一套成本能压到十分之一以内而且每个环节都透明可控。整套系统的架构可以这样理解模拟前端负责把微伏级的脑电信号放大、滤波、数字化FPGA负责把8片ADS1299的数据在硬件层面同步采集、打包、做初步的滤波和降采样STM32负责系统管理、和上位机通信、以及一些不需要FPGA介入的控制逻辑。三者各司其职谁也别抢谁的活。注意ADS1299的datasheet一定要反复看尤其是寄存器映射和SPI时序部分。我见过太多人栽在寄存器配置上数据读出来全是0或者全是噪声最后发现是某个寄存器的某个bit没配对。2. 核心器件选型与系统架构拆解2.1 为什么是ADS1299而不是其他ADCADS1299这颗芯片在生物电采集领域基本是标杆级的存在。8通道24位ΔΣ ADC内置可编程增益放大器输入参考噪声在增益为24时只有1μVpp左右共模抑制比达到-110dB这些参数放在脑电采集场景里非常能打。选它有几个硬理由。第一它自带偏置驱动电路可以直接做右腿驱动省掉一堆外围运放。第二它的输入多路复用器支持灵活配置你可以把某些通道设成短路来做噪声测试也可以设成内部测试信号来做校准。第三它的SPI接口虽然时序要求严格但一旦跑通就非常稳定。对比一下其他方案。TI自家的ADS1298是8通道24位但它是为心电设计的带宽和噪声指标不如ADS1299。ADI的AD7779是8通道24位性能也不错但价格更贵而且偏置驱动电路需要自己搭。国产的一些24位ADC在噪声和温漂上还有差距做科研级设备不太放心。ADS1299的数据手册我前后翻了不下二十遍每次都能发现之前忽略的细节。比如它的内部振荡器精度只有±2%如果你要做多片级联最好还是用外部晶振统一时钟源。再比如它的启动时间从掉电模式唤醒到输出稳定数据需要大约18ms这个时间在系统初始化的时候必须留够。2.2 STM32在系统中的角色定位STM32在这套系统里不是主力干活的它的核心任务是系统管理。具体来说包括上电时序控制、ADS1299的寄存器配置、FPGA的配置加载、和上位机的USB或以太网通信、以及系统状态监控。我选的是STM32F407168MHz主频带FPU和DSP指令集192KB SRAM1MB Flash。这个配置做系统管理绰绰有余而且它的FSMC接口可以很方便地和FPGA做并行通信。如果你手头有STM32H7系列那更好主频更高RAM更大跑一些轻量级的信号处理算法也不在话下。STM32和ADS1299之间通过SPI通信。这里有个细节要注意8片ADS1299如果共用一条SPI总线片选信号必须严格管理不能同时拉低两片。我的做法是用FPGA的IO扩展出8个片选信号STM32通过FSMC写FPGA的寄存器来控制片选这样时序更可控。STM32和FPGA之间的通信走FSMC并行总线16位数据宽度地址线和数据线复用。FPGA内部映射了几个寄存器命令寄存器、状态寄存器、数据FIFO。STM32往命令寄存器写指令从状态寄存器读系统状态从数据FIFO读FPGA预处理完的脑电数据。2.3 ECP5 FPGA为什么适合这个场景ECP5是Lattice出的一款中低密度FPGA性价比很高。我选的是ECP5-45F大概44K LUT够用了。它的优势在于功耗低、封装小、内部有硬核DSP块可以做乘加运算、支持DDR3接口、而且Lattice的Diamond工具链虽然不如Vivado和Quartus那么强大但做这种中等复杂度的逻辑设计完全够用。FPGA在这套系统里承担的任务很重。首先是8片ADS1299的数据采集每片ADS1299的DRDY信号都接到FPGAFPGA需要检测到DRDY下降沿后立即通过SPI读取24位数据。8片同时工作每片的采样率如果设成1kSPS那FPGA每毫秒要处理8次SPI读取每次读取24位数据加上状态位总共32位。这个数据率对FPGA来说很轻松。其次是数据打包和预处理。FPGA把8片ADS1299的数据拼成一个64通道的数据帧加上帧头、时间戳、校验位然后做初步的FIR滤波和降采样。FIR滤波器用FPGA内部的DSP块实现可以做到完全流水线化不占用额外的时钟周期。最后是和STM32的接口逻辑。FPGA内部实现了一个异步FIFOSTM32通过FSMC往FIFO里读数据。FIFO的深度设成4096足够缓冲STM32处理不及时的情况。提示ECP5的配置Flash建议用Lattice自家的W25Q系列兼容性最好。如果你用其他品牌的Flash要注意配置时序的差异可能需要调整Diamond里的配置参数。3. 硬件设计中的关键细节与避坑指南3.1 模拟前端的电源与接地设计脑电信号是微伏级别的电源噪声和接地设计直接决定系统能不能用。我的经验是模拟电源和数字电源必须完全分开用独立的LDO供电。ADS1299的AVDD和DVDD分别用两片超低噪声LDOAVDD的噪声要控制在10μVrms以下。接地方面模拟地和数字地单点连接连接点选在ADS1299的AGND引脚附近。PCB布局时模拟部分和数字部分严格分区模拟信号线绝对不要跨越数字区域。ADS1299的输入引脚周围要铺模拟地铜皮做屏蔽用。参考电压对ADS1299的精度影响很大。它内部有4.5V参考电压但温漂比较大。如果做科研级设备建议用外部参考电压源比如ADR4525初始精度0.02%温漂2ppm/°C比内部参考好一个数量级。3.2 多片ADS1299的级联与同步8片ADS1299要同步采样时钟必须统一。我的做法是用一颗24.576MHz的温补晶振通过时钟缓冲器分给8片ADS1299。每片ADS1299的CLK引脚都接同一个时钟源这样它们的内部采样时钟完全同步。DRDY信号的处理也很关键。8片ADS1299的DRDY信号都接到FPGAFPGA检测到任意一片的DRDY下降沿后启动SPI读取序列。这里有个细节ADS1299的DRDY在数据准备好后会保持低电平直到数据被读走。所以FPGA的SPI读取速度必须够快否则会错过下一轮数据。我实测下来8片ADS1299在1kSPS采样率下SPI时钟跑到8MHz读取一轮数据大概需要40μs完全来得及。如果你把采样率提到4kSPSSPI时钟要相应提高到16MHz以上这时候就要注意PCB走线的信号完整性了。3.3 PCB布局布线的实战经验PCB布局这块我踩过的坑最多。第一版板子做出来噪声大得没法看后来发现是ADS1299的输入走线太长而且没有做屏蔽。第二版改了布局输入走线尽量短周围铺地铜皮噪声立刻降下来了。具体来说ADS1299的输入引脚到电极连接器的走线要尽可能短最好控制在2cm以内。走线要等长差分输入的两根线要紧密耦合。如果实在避不开长走线就用屏蔽线屏蔽层接模拟地。电源去耦也很重要。每片ADS1299的AVDD和DVDD引脚旁边都要放100nF和10μF的电容越近越好。我一般会在每个电源引脚旁边放一个100nF的0402电容然后在芯片附近再放一个10μF的钽电容。FPGA和STM32的布局相对宽松但也要注意高速信号线的阻抗匹配。FSMC总线的走线要等长SPI时钟线要远离模拟信号线。ECP5的配置Flash要靠近FPGA放置配置走线尽量短。4. FPGA逻辑设计与STM32固件的协同实现4.1 ADS1299数据采集的FPGA状态机设计FPGA内部为每片ADS1299实现了一个独立的状态机8个状态机并行工作。每个状态机的流程是等待DRDY下降沿、拉低片选、发送读取命令、接收24位数据加状态位、拉高片选、把数据写入FIFO。状态机的时钟用50MHzSPI时钟分频到8MHz。这里要注意ADS1299的SPI时序片选拉低到第一个SCLK上升沿之间至少要等待6nsSCLK的占空比要在40%到60%之间。我在状态机里加了足够的等待周期确保时序满足要求。数据打包的格式是这样的每帧数据包含8片ADS1299的8通道数据每通道24位加上1个字节的帧头和1个字节的帧尾。帧头是0xAA帧尾是0x55。每帧总共8×8×32194字节。FPGA每采集完一轮数据就打包成一帧写入异步FIFO。4.2 FIR滤波器的FPGA实现与资源优化FPGA内部实现了一个64阶的FIR低通滤波器截止频率设成250Hz用来滤除高频噪声。滤波器的系数用MATLAB的fdatool生成定点化到16位。FPGA用DSP块实现乘加运算每个时钟周期完成一次乘加64阶滤波器需要64个时钟周期完成一次滤波。资源优化方面我用的是转置型FIR结构这种结构的好处是每个DSP块可以独立工作不需要额外的累加器。ECP5-45F内部有128个DSP块64阶滤波器用掉64个还剩64个可以做其他运算。降采样也在FPGA里做。滤波后的数据每4个点取1个采样率从1kSPS降到250SPS。降采样后的数据再写入FIFO这样STM32读到的数据量就小多了。4.3 STM32固件的任务调度与通信协议STM32的固件用FreeRTOS做任务调度主要任务有四个系统初始化任务、FPGA数据读取任务、上位机通信任务、系统监控任务。系统初始化任务负责上电时序控制先给FPGA上电并加载配置然后给ADS1299上电并配置寄存器最后启动FreeRTOS调度器。ADS1299的寄存器配置通过SPI完成配置参数包括采样率、增益、输入多路复用器设置等。FPGA数据读取任务通过FSMC从FPGA的FIFO里读数据读到的数据先存到STM32的SRAM里然后通过USB或以太网发给上位机。USB用的是STM32自带的USB OTG FS配置成CDC设备上位机不需要装驱动就能识别。以太网用的是STM32的MAC控制器外接PHY芯片跑TCP协议。上位机通信协议是我自己定义的简单来说就是上位机发命令STM32回数据。命令包括启动采集、停止采集、设置采样率、设置增益等。数据包格式和FPGA的帧格式一致上位机解析后直接显示波形。注意STM32的FSMC接口在和FPGA通信时要注意建立时间和保持时间的配置。我一开始没注意读出来的数据偶尔会错位后来把FSMC的时序参数调大了一档问题就解决了。5. 系统联调与常见问题排查实录5.1 噪声问题的排查思路噪声是脑电采集系统最常见的敌人。我遇到过的噪声问题大概分三类工频干扰、基线漂移、随机噪声。工频干扰表现为50Hz或60Hz的正弦波解决方法是做好屏蔽和接地同时在FPGA里加一个50Hz的陷波器。基线漂移表现为低频的大幅波动通常是电极接触不良或者被试者移动导致的解决方法是加一个高通滤波器截止频率设成0.1Hz。随机噪声的来源比较多可能是电源噪声、可能是ADC本身的噪声、也可能是FPGA的数字噪声耦合到了模拟部分。排查的时候我一般会先把ADS1299的输入短路看噪声水平。如果短路噪声还是很大那就是电源或ADC的问题。如果短路噪声正常接上电极后噪声变大那就是电极或走线的问题。5.2 SPI通信失败的调试方法SPI通信失败是新手最容易遇到的问题。症状通常是读出来的数据全是0或者全是0xFF或者数据错位。排查步骤是这样的先用示波器看SCLK、MOSI、MISO、CS四根线的波形确认时序是否正确。然后检查ADS1299的寄存器配置确认SPI时钟极性、相位、数据位宽是否匹配。最后检查片选信号确认同一时刻只有一片ADS1299被选中。我遇到过一次SPI通信失败查了半天发现是ADS1299的复位引脚没有正确拉高。ADS1299的复位引脚是低电平复位上电后必须拉高才能正常工作。这个细节在数据手册里写得很清楚但很容易被忽略。5.3 FPGA配置加载失败的解决方案ECP5的配置加载失败通常表现为FPGA不工作所有IO都是高阻态。排查的时候先看配置Flash的电源和时钟是否正常然后看FPGA的配置引脚是否连接正确。ECP5支持多种配置模式我用的是主SPI模式FPGA主动从Flash读取配置数据。配置引脚包括CFG0、CFG1、CFG2、PROGRAMN、INITN、DONE。上电后PROGRAMN要先拉低再拉高触发配置过程。配置成功后DONE引脚会拉高。如果配置失败INITN引脚会保持低电平。这时候可以用Lattice的Programmer工具通过JTAG接口直接下载配置看是否能成功。如果JTAG能成功但SPI不行那就是Flash的问题。如果JTAG也不行那就是FPGA本身的问题。5.4 常见问题速查表问题现象可能原因排查方法解决方案数据全是0SPI时序错误示波器看波形调整SPI时钟极性相位数据全是0xFF片选未拉低检查片选信号确认片选逻辑正确噪声大电源或接地问题短路输入看噪声优化电源和接地设计基线漂移电极接触不良检查电极阻抗重新贴电极或加高通滤波FPGA不工作配置加载失败检查DONE引脚重新加载配置或换FlashSTM32读数据错位FSMC时序不对调整时序参数增大建立保持时间6. 系统扩展与上位机软件的那些事6.1 模块化扩展的实际操作这套系统的模块化设计体现在两个层面硬件层面每块采集板管8个通道需要更多通道就插更多板子软件层面FPGA的逻辑和STM32的固件都支持动态配置通道数。硬件扩展很简单把新的采集板插到背板上就行。背板提供电源、时钟、SPI总线、片选信号。每块采集板有独立的ADS1299和输入连接器互不干扰。软件扩展需要改两个地方FPGA的通道数配置和STM32的数据包解析。FPGA里用一个参数控制激活的采集板数量STM32根据这个参数调整数据包的长度。上位机也要相应调整但因为我用的是统一的数据帧格式上位机只需要改一个通道数配置就行。6.2 上位机软件的功能与实现上位机我用的是Python加PyQt主要功能包括实时波形显示、数据存储、阻抗测量、滤波设置。实时波形显示用pyqtgraph这个库画图性能很好64通道同时刷新也不卡。数据存储用HDF5格式每通道一个数据集方便后续用Python做分析。阻抗测量是通过ADS1299的内部测试信号实现的往电极注入一个已知频率的正弦波测量响应幅度就能算出阻抗。滤波设置包括高通、低通、陷波三个滤波器参数可以在界面上实时调整。滤波在FPGA里做上位机只负责发命令。6.3 系统性能实测与优化建议实测下来这套系统的输入参考噪声在增益为24时大约是1.2μVpp共模抑制比大约-105dB通道间串扰小于-80dB。这个性能做常规脑电研究完全够用。优化建议方面如果你追求更低的噪声可以把ADS1299的增益设到24采样率降到250SPS这样噪声能降到1μVpp以下。如果你需要更高的采样率可以把SPI时钟提到16MHz但要注意PCB走线的信号完整性。功耗方面整套系统在64通道全开的情况下大约消耗1.5W其中ADS1299占了大头。如果做便携设备可以考虑用低功耗模式把采样率降到125SPS功耗能降到0.8W左右。提示ADS1299的功耗和采样率成正比如果做电池供电的便携设备采样率的选择要权衡性能和续航。7. 写在最后的一些个人体会这套系统从第一版到现在稳定运行前后改了五版硬件、三版FPGA逻辑、无数版STM32固件。最大的体会是脑电采集系统的性能瓶颈往往不在ADC本身而在电源、接地、屏蔽这些看似不起眼的地方。我见过太多人花大价钱买了好ADC结果因为电源没做好噪声大得根本没法用。另一个体会是FPGA在这类系统里的价值被低估了。很多人觉得FPGA难学宁愿用STM32硬扛所有任务。但实际做下来FPGA在实时性和并行处理上的优势是STM32没法比的。8片ADS1299同时采集每片1kSPSSTM32要不停地在SPI和USB之间切换CPU占用率很高。加了FPGA之后STM32只需要从FIFO里读预处理好的数据CPU占用率降到20%以下。最后说一个容易被忽略的细节电极。再好的采集系统如果电极没贴好信号质量也好不了。我一般用Ag/AgCl电极贴之前用磨砂膏轻轻打磨皮肤降低接触阻抗。电极阻抗控制在5kΩ以下信号质量就有保障了。这个经验看起来简单但实际做实验的时候很多问题都出在电极上。