ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ADS1299生物电采集实战:SPI通信、寄存器配置与STM32 DMA数据解析

ADS1299生物电采集实战:SPI通信、寄存器配置与STM32 DMA数据解析 1. 为什么ADS1299值得花时间啃下来如果你正在做脑电、心电或者生物电信号采集相关的硬件项目大概率绕不开ADS1299这颗芯片。它是TI推出的一款8通道、24位同步采样ADC专门为生物电测量场景设计内置了可编程增益放大器、右腿驱动电路、导联脱落检测、内部振荡器和基准电压源。换句话说一颗芯片就把前端模拟链路的大部分关键功能都包了。但很多人在拿到ADS1299评估板或者自己画完板子之后会卡在同一个地方SPI能通ID能读出来但采集到的数据要么全是噪声要么通道映射错乱要么数据跳变严重。问题往往不在芯片本身而在于寄存器配置的细节和数据解析的格式没有完全吃透。这篇文章面向的是已经具备基本嵌入式开发能力、正在用STM32或者类似MCU驱动ADS1299的工程师。我会从寄存器配置的底层逻辑讲起把SPI通信的时序要点、数据帧的解析方式、以及实际调试中容易踩的坑都摊开来说。不堆砌数据手册的原文而是讲清楚每个配置项背后的意图以及为什么这样配。2. ADS1299的SPI通信底层时序、模式与片选策略2.1 SPI模式选择与时钟极性的实际影响ADS1299的SPI接口支持标准四线制通信SCLK、DIN、DOUT、CS各一根。数据手册里明确写了它工作在SPI模式1CPOL0CPHA1也就是时钟空闲为低电平数据在第二个边沿采样。这个细节如果搞错读出来的寄存器值会整体偏移一位表现为读到的ID寄存器值不对或者配置写不进去。我在实际调试中遇到过一种情况用STM32的硬件SPI配置成模式0读ID能读到0x3E看起来是对的但写配置寄存器后回读发现值不对。后来用逻辑分析仪抓波形才发现模式0下数据在第一个边沿采样导致MOSI上的数据被移位的时机和芯片预期差半个周期。改成模式1之后一切正常。所以第一步确认你的SPI初始化代码里CPOL和CPHA的设置。以STM32 HAL库为例hspi1.Init.CLKPolarity SPI_POLARITY_LOW; hspi1.Init.CLKPhase SPI_PHASE_2EDGE; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_32;时钟频率方面ADS1299的SCLK最高支持到20MHz但在实际布线中如果走线较长或者没有做好阻抗匹配建议先从4MHz左右开始调试稳定后再逐步提高。我一般用STM32F103的SPI1APB2时钟72MHz预分频32得到2.25MHz对于大多数场景已经足够。2.2 硬件片选与软件片选的取舍ADS1299的CS片选信号有两种控制方式硬件片选和软件片选。硬件片选是MCU的SPI外设自动控制CS引脚每次传输时自动拉低和释放软件片选则是用普通GPIO手动控制。从实际使用来看我更推荐软件片选。原因有两个第一ADS1299的某些操作比如连续读取数据模式需要在一次CS拉低期间完成多个字节的传输硬件片选在每次字节传输后可能会释放CS导致通信中断第二软件片选让你对时序有完全的控制权方便在CS拉低和第一个SCLK之间插入必要的延时。具体做法是把CS配置为推挽输出的GPIO在每次SPI传输前手动拉低传输完成后拉高#define ADS1299_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define ADS1299_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) void ADS1299_WriteReg(uint8_t reg, uint8_t value) { ADS1299_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); HAL_SPI_Transmit(hspi1, value, 1, 100); ADS1299_CS_HIGH(); }注意CS拉低之后不要立刻发时钟建议插入至少4个SCLK周期的延时约1微秒让芯片有时间检测到片选有效。这个细节在数据手册的时序图里有标注但很容易被忽略。2.3 上电初始化序列的关键步骤ADS1299上电后不能直接开始配置寄存器需要遵循一个固定的初始化序列。这个序列如果顺序错了芯片可能不响应后续命令。完整的上电流程是这样的上电等待至少1秒让内部基准电压稳定拉低PWDN/RESET引脚至少2个CLK周期然后拉高发送SDATAC命令0x11退出连续读取数据模式等待至少4个SCLK周期发送RESET命令0x06或者通过硬件复位引脚复位等待18个SCLK周期发送SDATAC命令开始配置寄存器这里面第3步和第7步的SDATAC命令特别关键。ADS1299上电后默认处于连续读取数据模式RDATAC在这个模式下除了SDATAC之外的命令都不会被响应。很多人第一次调试时写寄存器没反应就是因为忘了先发SDATAC。3. 寄存器配置的深层逻辑从通道使能到基准电压3.1 通道寄存器CONFIG1到CONFIG4的配置思路ADS1299的寄存器分为几个大类全局配置寄存器CONFIG1、CONFIG2、CONFIG3、通道配置寄存器CH1SET到CH8SET、以及导联脱落检测相关寄存器。每个通道的配置寄存器结构相同包含增益设置、输入类型选择、通道电源开关等位域。以CHnSET寄存器为例它的8个bit是这样分配的Bit名称功能7PDn通道掉电控制0正常1掉电6:4GAINn增益设置0001, 0012, 0104, 0116, 1008, 10112, 110243SRB2是否将通道n的负输入连接到SRB22MUXn输入类型选择0正常电极输入1短路测试1:0-保留配置增益的时候要注意ADS1299的增益选项不是连续的2的幂次而是1、2、4、6、8、12、24。这个设计是为了适配不同幅度的生物电信号。比如脑电信号通常在10-100微伏需要较大增益心电信号在1-5毫伏增益可以小一些。我一般这样配置如果测脑电增益设为24GAIN110配合内部基准4.5V满量程输入范围是±4.5V/24≈±187.5mV分辨率达到24位足够捕捉微伏级信号。如果测心电增益设为6或8就够了。3.2 CONFIG3寄存器中基准电压与右腿驱动的配置CONFIG3寄存器控制着基准电压的启用和右腿驱动电路的配置这个寄存器的配置直接影响采集信号的共模抑制能力。关键位域包括Bit 7PD_REFBUF内部基准缓冲器使能必须设为1才能使用内部基准Bit 6保留必须设为1Bit 5VREF_4V基准电压选择02.4V14.0VBit 4RLD_MEAS右腿驱动测量使能Bit 3RLDREF_INT右腿驱动基准选择0外部1内部Bit 2PD_RLD右腿驱动使能Bit 1RLD_LOFF_SENS右腿驱动导联脱落检测Bit 0RLD_STAT右腿驱动状态实际配置时如果使用内部基准CONFIG3的值通常设为0xEC或0xE0。0xEC表示启用内部基准4.0V、启用右腿驱动、右腿驱动基准为内部。这个值是我在多个项目中验证过的稳定配置。右腿驱动的作用是抑制共模干扰。在生物电测量中人体相当于一个天线会引入50Hz工频干扰。右腿驱动电路通过反馈共模信号到人体抵消这个干扰。如果右腿驱动没配好采集到的信号上会叠加明显的工频噪声。3.3 导联脱落检测的配置与取舍ADS1299内置了导联脱落检测功能可以通过注入微小电流到电极上检测电极是否脱落。这个功能在可穿戴设备中很有用但在高精度采集场景下注入电流可能会引入额外噪声。导联脱落检测的配置涉及LOFF寄存器。关键参数包括电流大小6nA、24nA、6uA、24uA和比较器阈值。我一般建议在调试阶段先关闭导联脱落检测等信号链路调通后再开启这样可以排除干扰源。如果确实需要导联脱落检测建议选择6nA的电流档位这个电流足够检测电极阻抗变化又不会对信号造成明显干扰。配置代码如下// 关闭导联脱落检测 ADS1299_WriteReg(LOFF, 0x00); // 或者配置为6nA电流阈值默认 ADS1299_WriteReg(LOFF, 0x02);4. 数据帧解析从原始字节到电压值4.1 连续读取模式下的数据帧结构ADS1299在RDATAC模式下每个采样周期会输出一帧数据。数据帧的长度取决于通道数和是否启用某些功能。以8通道全开为例每帧包含3字节的状态字包含导联脱落状态、GPIO状态等8个通道各3字节的采样数据共24字节总计27字节每个通道的24位数据是二进制补码格式最高位是符号位。转换公式是电压值 (原始码值 × VREF / 增益) / 2^23其中VREF是基准电压2.4V或4.0V增益是通道配置的增益值。这里有个容易搞错的地方2^23而不是2^24。因为24位数据中最高位是符号位有效数值位是23位。所以满量程是2^23-1对应正满量程电压。4.2 数据对齐方式与符号扩展处理ADS1299输出的24位数据是大端格式也就是高位在前。在STM32这种小端MCU上解析时需要手动拼接int32_t ADS1299_ParseChannel(uint8_t *buf) { int32_t value 0; value ((int32_t)buf[0] 16) | ((int32_t)buf[1] 8) | buf[2]; // 符号扩展如果第23位是1说明是负数 if (value 0x00800000) { value | 0xFF000000; } return value; }符号扩展这一步非常关键。如果不做符号扩展负电压会被解析成很大的正数表现为信号波形整体上移失去负半周。我在第一次调试时就是忘了做符号扩展看到波形只有正半周以为是电极接触问题换了电极、换了导联线折腾了半天才发现是代码问题。4.3 状态字中的隐藏信息数据帧的前3个字节是状态字很多人直接跳过不解析其实这里面包含了不少有用的信息第1字节的bit 7-4导联脱落检测状态对应8个通道的正负端第1字节的bit 3-0GPIO引脚状态第2字节保留第3字节保留在调试导联连接时解析状态字可以快速判断哪个电极没贴好。比如状态字第1字节的高4位是0xF0说明通道1到通道4的正负端都检测到导联脱落。5. STM32 SPI DMA方案让数据采集不丢帧5.1 为什么需要DMAADS1299在8通道全开、采样率1kSPS时数据率是27字节×100027KB/s。如果每个字节都用中断或者轮询方式读取CPU会被频繁打断在高采样率下容易丢帧。用DMA方式SPI接收完一帧数据后自动触发DMA传输把数据搬到内存缓冲区CPU只需要在DMA传输完成中断里处理数据即可。这样CPU占用率可以降到很低留出时间做信号处理。5.2 CubeMX中的DMA配置要点在CubeMX里配置SPI1的DMA接收通道时有几个参数需要注意Mode选择Normal模式不要选Circular。因为ADS1299的数据帧是固定长度的每次接收完一帧后需要重新启动DMA。Data WidthPeripheral和Memory都设为Byte。Priority设为High保证数据接收不被其他DMA请求打断。配置完成后启动DMA接收的代码void ADS1299_StartDMARead(void) { ADS1299_CS_LOW(); HAL_SPI_Receive_DMA(hspi1, rxBuffer, 27); }在DMA传输完成回调函数里处理数据void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi hspi1) { ADS1299_CS_HIGH(); ADS1299_ProcessFrame(rxBuffer); ADS1299_StartDMARead(); // 启动下一帧 } }5.3 DRDY中断与DMA的配合ADS1299的DRDY引脚在每个采样周期结束时输出一个下降沿提示数据准备好。正确的做法是把DRDY接到STM32的外部中断引脚在中断服务函数里启动DMA接收。这里有个时序问题DRDY下降沿之后数据并不是立刻就可读的需要等待一小段时间数据手册里叫tDISC约4个SCLK周期。如果DRDY中断里立刻启动DMA可能会读到上一帧的数据。我的做法是在DRDY中断里设置一个标志位在主循环里检测标志位后再启动DMA这样虽然增加了一点延迟但保证了数据正确性。如果采样率很高可以在中断里加一个短延时void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin DRDY_Pin) { for (volatile int i 0; i 10; i); // 短延时 ADS1299_StartDMARead(); } }6. 实测中那些数据手册不会告诉你的坑6.1 基准电压建立时间与初始数据无效ADS1299上电后内部基准电压需要一定时间才能稳定。数据手册里写的典型值是1秒但在实际测试中如果环境温度较低可能需要更长时间。我遇到过一种情况上电后立刻开始采集前几百个采样点的数据全是满量程跳变大约500毫秒后才逐渐稳定。后来在初始化序列里加了1.5秒的延时问题解决。所以建议在配置完寄存器、启动采集之前至少等待1秒。如果对数据精度要求高等2秒更保险。6.2 通道间串扰与PCB布局的关系ADS1299的8个通道之间理论上是独立的但在实际PCB上如果模拟走线靠得太近通道间会出现串扰。表现为一个通道有信号输入时相邻通道也能看到微弱的同频信号。这个问题不能靠软件解决必须在PCB设计阶段注意。我的经验是模拟输入走线之间保持至少3倍线宽的间距最好在中间加一条地线隔离。另外ADS1299的模拟地和数字地要分开在芯片下方单点连接。6.3 高增益下的噪声抑制技巧当增益设为24时ADS1299的输入等效噪声会放大采集到的信号上会看到明显的基线漂移和高频噪声。除了前面提到的右腿驱动配置外还有几个软件层面的技巧第一在数据解析后加一个简单的滑动平均滤波。窗口大小取8到16个采样点对高频噪声有很好的抑制效果同时不会明显影响信号波形。第二如果采样率允许可以在ADS1299内部开启SINC滤波器。CONFIG1寄存器里的HR位和LP位控制滤波器的截止频率根据实际信号带宽选择合适的档位。第三电源纹波对高增益采集影响很大。如果条件允许给ADS1299的AVDD和DVDD分别加LC滤波L用磁珠C用10uF和0.1uF并联。6.4 SPI通信不生效的排查链路最后分享一个完整的排查思路当你发现SPI读写ADS1299不生效时按这个顺序检查用示波器或逻辑分析仪抓SCLK、MOSI、CS三根线的波形确认时序是否符合模式1读ID寄存器地址0x00正常应该返回0x3E。如果返回0x00或0xFF说明通信根本没通检查CS是否在传输期间保持低电平有没有被意外拉高确认SDATAC命令已经发送芯片不在RDATAC模式检查电源电压是否正常AVDD和DVDD是否都在规定范围内如果用的是自己画的板子检查ADS1299的VCAP1引脚电容是否焊接这个电容是内部稳压器的滤波电容缺失会导致芯片工作异常这个排查链路是我在多次调试中总结出来的基本上90%的SPI通信问题都能在前三步定位到。7. 从原始数据到可用信号一个完整的解析示例把前面所有内容串起来给一个完整的代码示例。假设8通道全开增益为24基准电压4.0V采样率1kSPS。#define VREF 4.0f #define GAIN 24.0f #define NUM_CHANNELS 8 typedef struct { int32_t raw[NUM_CHANNELS]; float voltage[NUM_CHANNELS]; } ADS1299_Frame; void ADS1299_ProcessFrame(uint8_t *buf) { ADS1299_Frame frame; // 跳过3字节状态字 uint8_t *dataPtr buf 3; for (int ch 0; ch NUM_CHANNELS; ch) { int32_t value ((int32_t)dataPtr[0] 16) | ((int32_t)dataPtr[1] 8) | dataPtr[2]; // 符号扩展 if (value 0x00800000) { value | 0xFF000000; } frame.raw[ch] value; frame.voltage[ch] (float)value * VREF / GAIN / 8388608.0f; dataPtr 3; } // 这里可以把frame送到后续处理 ADS1299_Output(frame); }这段代码里8388608就是2的23次方。计算出来的voltage单位是伏特乘以1000就是毫伏乘以1000000就是微伏。实际使用中我建议把原始码值和电压值都保留原始码值用于调试和对比电压值用于后续的信号处理和显示。8. 一些关于采样率和滤波器选择的经验ADS1299的采样率由CONFIG1寄存器的DR位控制支持从250SPS到16kSPS多个档位。选择采样率时需要考虑信号带宽和后续处理能力。对于脑电信号主要能量集中在0.5Hz到50Hz之间根据奈奎斯特采样定理采样率至少100Hz。但实际中为了留出滤波器过渡带通常选择250SPS或500SPS。我一般用500SPS配合内部的SINC滤波器可以很好地抑制高频噪声。对于心电信号带宽通常在0.05Hz到150Hz采样率选择500SPS或1kSPS比较合适。如果要做阻抗测量或者需要更高的时间分辨率可以选2kSPS或4kSPS但要注意数据量会成倍增加DMA缓冲区和后续处理算法都要相应调整。内部滤波器的选择在CONFIG1寄存器的HR位和LP位。HR1时启用高分辨率模式功耗稍高但噪声更低LP1时启用低功耗模式。在电池供电的场景下如果对噪声要求不是特别苛刻可以选低功耗模式。9. 写在最后调试ADS1299的心态建议ADS1299是一颗功能很强大的芯片但它的寄存器配置项多时序要求严格第一次调试时很容易感到挫败。我的建议是分阶段验证先确保SPI通信正常能正确读写ID寄存器然后配置单个通道用信号发生器输入一个已知幅度的正弦波验证数据解析链路最后再开启全部通道接入实际电极。每完成一个阶段用逻辑分析仪或者串口打印确认结果不要跳步。我见过太多人一上来就接电极测脑电结果数据不对根本不知道是配置问题、解析问题还是电极问题。另外数据手册要反复看。ADS1299的数据手册有100多页但真正核心的内容集中在寄存器描述和时序图那几十页。每次遇到问题回去翻手册都会有新的发现。最后如果你在调试过程中发现数据有周期性跳变先检查DRDY中断和DMA启动的时序十有八九是这里的问题。这个坑我踩过不止一次希望你能绕过去。
返回列表