1. 为什么ADS1255/1256值得单独写一篇避坑指南
ADS1255和ADS1256这两颗芯片,在搞高精度数据采集的圈子里算是老面孔了。24位无噪声分辨率、最高30kSPS的采样率、内置PGA和输入缓冲器,做电子秤、压力变送器、热电偶采集、微弱信号检测这类项目基本绕不开它。但凡是真正拿STM32硬件SPI去驱动过这两颗芯片的人,大概率都经历过下面这些场景:Cube MX里配置看起来没问题,代码烧进去读出来全是0xFFFFFF或者0x000000;偶尔能读到数据但跳动得厉害,换几个寄存器配置又好了,过一会儿又不行;DRDY中断死活进不去,示波器一打发现SCLK根本没波形。
这些问题的根源,往往不在ADS1256本身,而在于STM32的SPI外设和ADS1256的时序要求之间存在几处非常容易踩的“错位”。我前后用STM32F103、F407、G431这几款芯片驱动过ADS1255/1256,从最开始的软件模拟SPI到后来全部切到硬件SPI+DMA,中间踩的坑足够写一篇长文。这篇内容就把整个链路拆开讲:从Cube MX的SPI参数怎么算、GPIO怎么配、DRDY怎么接,到命令时序怎么发、数据怎么读、校准怎么做,最后把常见故障整理成速查表。适合正在做STM32+ADS1256项目、被时序或者数据稳定性卡住的开发者,也适合刚接触高精度ADC、想搞清楚“硬件SPI到底比软件模拟强在哪”的朋友。
2. 先搞清楚ADS1255和ADS1256的差异,别拿错手册
2.1 两者核心区别与选型判断
很多人拿到项目直接搜“ADS1256程序”,结果芯片焊的是ADS1255,寄存器地址对不上,调半天以为是时序问题。这两颗芯片引脚兼容、寄存器映射基本一致,但输入通道数完全不同:
| 特性 | ADS1255 | ADS1256 |
|---|---|---|
| 模拟输入通道 | 2路差分(或1路差分+1路单端) | 8路差分(或4路差分+8路单端) |
| 输入多路复用器 | 2通道 | 8通道 |
| 封装 | SSOP-20 | SSOP-28 |
| 寄存器地址 | 完全一致 | 完全一致 |
| SPI时序 | 完全一致 | 完全一致 |
关键点在于:ADS1255的MUX寄存器只有低2位有效,如果你按ADS1256的写法往高通道号写,读回来可能正常但实际选中的通道不对。我建议在代码里用一个宏区分:
#define ADS1255_CH_MAX 1 #define ADS1256_CH_MAX 7初始化时根据实际芯片做一次通道扫描校验,读一下MUX寄存器回读值,确认写入和读出一致再往下走。这一步花不了两分钟,但能省掉后面几小时的困惑。
2.2 为什么必须用硬件SPI而不是软件模拟
ADS1256的SPI时序有几个硬性要求:SCLK空闲电平、数据在SCLK下降沿还是上升沿采样、命令之间的延时。软件模拟SPI在低速下(比如10kHz)确实能跑通,但一旦你想跑到芯片标称的30kSPS,软件模拟的GPIO翻转速度根本跟不上,而且中断一多时序就乱。
硬件SPI的优势在于:时钟由外设硬件产生,抖动小;配合DMA可以做到“发完命令自动读数据”,CPU只需要处理DRDY中断。实测在STM32F103上,硬件SPI跑1.8MHz,配合DMA读取,连续采样30kSPS时CPU占用率不到5%。软件模拟SPI同样速率下CPU基本被占满,稍微加点其他任务就丢数据。
注意:ADS1256的SCLK最高频率是1.92MHz(AVDD=5V时),不是随便跑多快都行。Cube MX里分频算出来的实际频率必须小于这个值,后面会讲怎么算。
3. Cube MX配置:SPI参数和GPIO的每一个细节
3.1 SPI时钟频率的计算过程
STM32F103的APB2总线默认72MHz,SPI1挂在APB2上。Cube MX里SPI的波特率预分频器(BaudRatePrescaler)可选2、4、8、16、32、64、128、256。实际SCLK频率 = APB2时钟 / 预分频值。
要满足ADS1256的1.92MHz上限,同时尽量跑快:
- 72MHz / 64 = 1.125MHz,安全但偏慢
- 72MHz / 32 = 2.25MHz,超了,不行
- 72MHz / 64 = 1.125MHz,这是F103上比较稳妥的选择
如果你用的是STM32F407,APB2是84MHz,84/64=1.3125MHz,也可以。G431的APB2是170MHz,170/128=1.328MHz。
我一般会在代码里加一个编译期检查:
#if (SPI_BAUDRATE_PRESCALER == SPI_BAUDRATEPRESCALER_32) #error "SCLK可能超过ADS1256上限,请检查APB时钟" #endif这样换芯片型号时不会因为忘了改分频而翻车。
3.2 SPI模式选择:CPOL和CPHA到底怎么设
ADS1256的数据手册里写得很清楚:SCLK空闲为低电平,数据在SCLK的上升沿被ADS1256采样(即STM32在下降沿改变数据),ADS1256在下降沿输出数据(STM32在上升沿采样)。对应到STM32的SPI模式:
- CPOL = 0(空闲低)
- CPHA = 1(第二个边沿采样)
也就是SPI模式1。Cube MX里选“CPOL Low, CPHA 2Edge”或者直接选Mode 1。
这里有个坑:很多人看ADS1256手册里的时序图,看到DIN在SCLK上升沿被采样,就选了CPHA=0,结果命令发出去芯片没反应。原因是STM32作为主机,CPHA=0时在第一个边沿(上升沿)改变数据,而ADS1256也在上升沿采样,两边同时动作,建立时间不够。选CPHA=1,STM32在下降沿改变数据,ADS1256在上升沿采样,中间有半个时钟周期的建立时间,稳。
3.3 数据宽度和位序
ADS1256的SPI是8位数据宽度,MSB First。Cube MX里:
- Data Size: 8 Bits
- First Bit: MSB First
不要选16位,虽然STM32支持,但ADS1256的命令和寄存器都是8位结构,16位反而要拆。
3.4 NSS片选:硬件片选还是软件片选
Cube MX里NSS有两个选项:Hardware NSS和Software NSS。我强烈建议选Software NSS,也就是把NSS引脚配成普通GPIO输出,手动拉低拉高。
原因:ADS1256的片选不是标准SPI从机的“每字节自动片选”逻辑。它的通信帧是“拉低CS → 发命令 → 读数据 → 拉高CS”,中间可能有多个字节,而且命令之间需要延时。硬件NSS在每字节传输后可能自动拉高,导致帧被切断。软件NSS可以精确控制CS的拉低和拉高时机。
具体配置:在Cube MX的GPIO里,把SPI的NSS引脚(比如PA4)设为GPIO_Output,初始电平High。代码里手动控制:
#define ADS_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define ADS_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)3.5 DRDY引脚配置
DRDY是ADS1256的数据就绪信号,低电平有效。它有两个作用:一是告诉MCU“转换完成,可以读数据了”,二是作为命令响应的标志。
配置要点:
- 设为GPIO_Input,上拉(ADS1256的DRDY是开漏输出,需要上拉)
- 如果要用中断方式,设为EXTI下降沿触发
- 如果要用轮询方式,直接读电平
我一般用EXTI中断,因为轮询会浪费CPU时间。Cube MX里把DRDY对应的引脚(比如PB0)设为GPIO_EXTI0,触发方式选“Falling Edge”。
注意:DRDY在ADS1256上电后、还没发SYNC/WAKEUP命令之前,可能一直是高电平。不要一上电就等DRDY下降沿,会死等。正确的顺序是:初始化寄存器 → 发SELFCAL命令 → 等DRDY变低 → 再开始读数据。
3.6 DMA配置(可选但推荐)
如果你要连续高速采样,建议给SPI的RX配DMA。Cube MX里在SPI1的DMA Settings里加一条:
- DMA Request: SPI1_RX
- Channel: DMA1 Channel2(F103)
- Priority: High
- Mode: Normal(不是Circular,因为每次读数据长度固定)
TX一般不需要DMA,因为命令字节很少,用阻塞发送就行。但如果你要连续写多个寄存器,也可以加TX DMA。
4. 寄存器配置与命令时序:最容易翻车的环节
4.1 ADS1256的寄存器地图
ADS1256有11个寄存器,地址从0x00到0x0A:
| 地址 | 寄存器 | 关键位 | 典型值 |
|---|---|---|---|
| 0x00 | STATUS | ORDER, ACAL, BUFEN, DRDY | 0x30 |
| 0x01 | MUX | PSEL[3:0], NSEL[3:0] | 0x01 |
| 0x02 | ADCON | CLK[1:0], SDCS[1:0], PGA[2:0] | 0x20 |
| 0x03 | DRATE | DR[7:0] | 0xF0 |
| 0x04 | IO | DIR, D0-D3 | 0x00 |
| 0x05 | OFC0 | OFC[7:0] | 0x00 |
| 0x06 | OFC1 | OFC[15:8] | 0x00 |
| 0x07 | OFC2 | OFC[23:16] | 0x00 |
| 0x08 | FSC0 | FSC[7:0] | 0x00 |
| 0x09 | FSC1 | FSC[15:8] | 0x00 |
| 0x0A | FSC2 | FSC[23:16] | 0x00 |
初始化时我一般这样配:
- STATUS = 0x30:关闭自动校准,开启输入缓冲,DRDY只反映数据就绪
- MUX = 0x01:AIN0为正输入,AIN1为负输入(差分)
- ADCON = 0x20:时钟输出关闭,PGA=1(增益1),传感器检测关闭
- DRATE = 0xF0:30kSPS(具体值查手册表格)
4.2 命令字节的发送顺序
ADS1256的命令分两类:一类是单字节命令(如SYNC、WAKEUP、RDATA),一类是带参数的寄存器读写命令。
写寄存器的标准流程:
- 拉低CS
- 发WREG命令:0x50 | (寄存器地址)
- 发一个字节:要写的寄存器数量 - 1
- 发数据字节
- 拉高CS
- 延时至少4个tCLKIN(约0.5us)
读寄存器的流程:
- 拉低CS
- 发RREG命令:0x10 | (寄存器地址)
- 发一个字节:要读的寄存器数量 - 1
- 读数据字节
- 拉高CS
这里有个大坑:WREG和RREG命令后的“数量-1”字节,很多人忘了发或者发错。比如你要写MUX寄存器(地址0x01),命令是0x51,然后发0x00(表示写1个寄存器),再发数据。如果你只发了0x51就直接发数据,ADS1256会把数据当成“数量-1”字节,实际写入的寄存器地址就错了。
4.3 命令之间的延时要求
ADS1256手册里明确写了几个延时:
- CS拉高后到下一次CS拉低:至少4个tCLKIN
- 发SYNC命令后到发WAKEUP命令:至少4个tCLKIN
- 发WAKEUP命令后到DRDY变低:取决于DRATE设置
tCLKIN是ADS1256的时钟周期。如果用外部7.68MHz晶振,tCLKIN≈130ns,4个就是520ns。我一般在CS拉高后加1us延时,保险。
#define ADS_DELAY() HAL_Delay_us(1)如果没有微秒延时函数,可以用DWT或者简单的for循环:
void HAL_Delay_us(uint32_t us) { uint32_t count = us * (SystemCoreClock / 1000000) / 4; while(count--); }4.4 自校准和偏移校准的触发时机
ADS1256支持自校准(SELFCAL)、偏移校准(SELFOCAL)和增益校准(SELFGCAL)。上电后建议做一次SELFCAL,之后如果环境温度变化大,可以定期做SELFOCAL。
发SELFCAL命令后,DRDY会变高,校准完成后变低。校准时间取决于DRATE设置,30kSPS下大概几十毫秒。不要在校准期间读数据,读出来是无效的。
ADS_CS_LOW(); SPI_WriteByte(0xF0); // SELFCAL命令 ADS_CS_HIGH(); ADS_DELAY(); while(HAL_GPIO_ReadPin(DRDY_GPIO, DRDY_PIN) == GPIO_PIN_SET); // 等校准完成5. 数据读取与DMA实战:从单次读到连续采样
5.1 单次读数据的标准流程
读一次转换结果的流程:
- 等DRDY变低
- 拉低CS
- 发RDATA命令:0x01
- 延时至少4个tCLKIN(约0.5us)
- 读3个字节(24位数据,MSB先出)
- 拉高CS
- 拼接成32位有符号数
int32_t ADS1256_ReadData(void) { uint8_t buf[3]; int32_t val = 0; while(HAL_GPIO_ReadPin(DRDY_GPIO, DRDY_PIN) == GPIO_PIN_SET); ADS_CS_LOW(); SPI_WriteByte(0x01); // RDATA ADS_DELAY(); SPI_ReadBytes(buf, 3); ADS_CS_HIGH(); ADS_DELAY(); val = ((int32_t)buf[0] << 16) | ((int32_t)buf[1] << 8) | buf[2]; if (val & 0x800000) { val |= 0xFF000000; // 符号扩展 } return val; }注意第4步的延时:RDATA命令发出后,ADS1256需要一点时间把数据放到MISO上。如果你紧接着就读,可能读到旧数据或者0。这个延时很多人会忽略,导致读出来全是0。
5.2 用DMA实现连续高速采样
单次读的CPU开销主要在等DRDY和SPI传输上。30kSPS时每33us就要读一次,用阻塞方式CPU基本没时间干别的。用DMA可以这样:
- DRDY接EXTI中断
- 中断里拉低CS,发RDATA命令(阻塞发送1字节)
- 启动SPI RX DMA,读3字节
- DMA传输完成中断里拉高CS,处理数据
Cube MX里配置SPI1_RX的DMA,然后在代码里:
uint8_t dma_buf[3]; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == DRDY_PIN) { ADS_CS_LOW(); SPI_WriteByte(0x01); ADS_DELAY(); HAL_SPI_Receive_DMA(&hspi1, dma_buf, 3); } } void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { ADS_CS_HIGH(); int32_t val = ((int32_t)dma_buf[0] << 16) | ((int32_t)dma_buf[1] << 8) | dma_buf[2]; if (val & 0x800000) val |= 0xFF000000; // 存入环形缓冲区 }注意:DMA传输完成中断里不要做太多事情,把数据存到缓冲区就退出。数据处理放到主循环里。
5.3 数据拼接与符号扩展的细节
ADS1256输出24位二进制补码。拼接时要注意:
- 先读到的字节是MSB
- 24位数据的最高位是符号位
- 拼成32位后要做符号扩展
我见过有人直接用uint32_t接收,然后除以2^23,结果负电压读出来是巨大的正数。符号扩展这一步不能省。
5.4 实际采样率与DRATE寄存器的对应关系
DRATE寄存器的值和实际采样率不是线性的,手册里有一张表。常用的几个:
| DRATE值 | 采样率(SPS) | 说明 |
|---|---|---|
| 0xF0 | 30,000 | 最快 |
| 0xE0 | 15,000 | |
| 0xD0 | 7,500 | |
| 0xC0 | 3,750 | |
| 0x82 | 100 | 常用 |
| 0x63 | 10 | 高精度 |
注意:采样率越高,噪声越大。做电子秤一般用10SPS或100SPS,做振动采集才用30kSPS。
6. 常见问题与排查技巧实录
6.1 读出来全是0xFFFFFF或0x000000
这是最常见的问题,排查顺序:
- 检查SPI模式:CPOL=0, CPHA=1,确认Cube MX里选对了
- 检查CS时序:CS是否在发命令前拉低、读完后拉高
- 检查RDATA后的延时:命令发出后至少等0.5us再读
- 检查DRDY:是否真的等到了DRDY变低
- 检查电源和基准:AVDD=5V,DVDD=3.3V,VREF=2.5V,缺一个都不行
我遇到过一次,排查了半天发现是VREF没接,ADS1256输出一直是满量程。
6.2 数据跳动大、不稳定
可能原因:
- PGA设置过大:增益越高噪声越大,小信号才需要高增益
- 输入缓冲未开启:STATUS寄存器的BUFEN位要置1
- 基准噪声:VREF的旁路电容不够,建议并0.1uF和10uF
- 地线处理:模拟地和数字地单点连接,不要形成地环路
- 采样率过高:降低DRATE试试
6.3 DRDY中断不触发
- 检查DRDY引脚是否上拉
- 检查EXTI中断是否使能
- 检查NVIC里EXTI中断优先级是否配置
- 检查ADS1256是否真的在转换(发SYNC+WAKEUP后才会开始)
6.4 SPI通信完全无响应
用示波器看SCLK、MOSI、CS三个信号:
- SCLK有没有波形?没有的话检查SPI外设是否使能
- MOSI有没有数据?没有的话检查发送函数
- CS有没有拉低?没有的话检查GPIO配置
如果三个都有但ADS1256没反应,检查ADS1256的复位引脚(RESET)是否已经拉高,以及晶振是否起振。
6.5 常见问题速查表
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 读数全0或全1 | SPI模式错、CS时序错、RDATA延时不够 | 逐项检查 |
| 数据跳动大 | PGA过大、基准噪声、地线问题 | 降增益、加滤波、改地线 |
| DRDY不触发 | 未发WAKEUP、引脚未上拉、中断未使能 | 检查命令和GPIO |
| 通信无响应 | 复位未释放、晶振未起振、电源缺失 | 查硬件 |
| 采样率不对 | DRATE值写错 | 查手册表格 |
| 负电压读成正数 | 符号扩展未做 | 补符号扩展 |
7. 几个我踩过的坑和实操心得
第一个坑是Cube MX里SPI的NSS引脚。我一开始选了Hardware NSS,结果每次传输完NSS自动拉高,ADS1256的通信帧被切断,读出来全是乱码。后来改成Software NSS,手动控制CS,问题解决。这个坑在ST的官方例程里不会写,因为官方例程大多用软件模拟SPI。
第二个坑是RDATA命令后的延时。手册里写了“t6延时”,但很多人看时序图觉得命令发完数据就出来了。实际上ADS1256需要一点时间把转换结果加载到输出寄存器。我实测在1.125MHz SCLK下,不加延时读出来有50%概率是错的。加1us延时后稳定。
第三个坑是DRDY的上电初始状态。ADS1256上电后DRDY可能是高也可能是低,取决于之前的转换状态。如果你一上电就等DRDY下降沿,可能永远等不到。正确的做法是先发SYNC和WAKEUP,强制启动一次转换,然后再等DRDY。
第四个坑是DMA和SPI中断的优先级。如果你同时用了SPI的DMA和DRDY的EXTI,要确保EXTI的优先级高于DMA传输完成中断。否则DMA还没传完,下一次DRDY就来了,CS被提前拉高,数据错位。我一般设EXTI优先级为1,DMA为2。
第五个坑是基准电压的旁路电容。ADS1256的VREF引脚对噪声非常敏感。我一开始只并了一个0.1uF,数据跳动有几十个LSB。后来加了10uF钽电容,跳动降到几个LSB。如果做高精度测量,建议用低噪声基准源,比如REF5025。
最后分享一个调试技巧:如果怀疑SPI时序有问题,先把SCLK降到最低(比如72MHz/256=281kHz),用示波器看波形。确认命令和数据都对得上之后,再逐步提高频率。不要一上来就跑最高速,出了问题很难定位。
这个方案后续还可以扩展:比如加一个环形缓冲区做连续采集,配合FFT做频谱分析;或者用ADS1256的IO口控制多路模拟开关,扩展通道数。如果要做隔离采集,可以在SPI线上加数字隔离器,注意隔离后的电源要独立。