STC15内部SPI驱动ADS1256:24位ADC稳定采集实战 简介这份资源面向使用STC15系列单片机进行高精度数据采集的开发者与电子竞赛选手核心是用STC15内置SPI外设驱动TI的24位Σ-Δ型ADS1256模数转换芯片解决高分辨率电压监测中采样精度不足的问题。ADS1256支持单端与拆分输入、8路通道采样配合7.80MHz晶振并将采样速率控制在2.5~10sps时实测电压误差可压至0.00001V以内适合精密仪器、传感器信号采集等场景。压缩包共20个文件约9.96MB包含c与h源码、uvproj/uvopt工程文件、hex烧录固件、lst与m51编译中间文件以及STC15F2K60S2和ADS1256英文手册两份PDF便于对照寄存器说明理解驱动逻辑。目前已有1406人学习下载可作为SPI时序配置、通道切换与高精度采样调试的完整参考工程。1. 用STC15内部SPI把ADS1256跑起来从丢数据到稳定读数的踩坑实录如果你手头有一块 STC15 系列的单片机又刚好需要采集微伏级的直流信号那 ADS1256 这颗 24 位 ADC 大概率会进入你的选型清单。问题在于很多人第一次把它俩接在一起时读数要么纹丝不动要么跳得亲妈都不认识。我最初也以为是自己焊接虚焊查了两天才发现根子在 STC15 内部 SPI 的时序配置和 ADS1256 的 DRDY 握手逻辑没对上。这个项目要解决的就是让 STC15 通过片内 SPI 主机模式稳定驱动 ADS1256 完成单端或差分通道的 24 位数据采集适合做电子秤、压力变送器、热电偶采集这类对分辨率和低噪声有要求的场景。下面把我从接线、配置到读数校准的完整过程拆开讲代码可以直接抄。2. 硬件链路与SPI模式先把物理层和时序对齐2.1 接线不是随便连ADS1256 最小系统引脚分配ADS1256 是 SPI 从机但它比普通 SPI 器件多了一根 DRDY 和一根 RESET这两根线如果处理不好后面读出来的全是 0xFFFFFF 或者 0x000000。我用的最小系统连接方式如下STC15 这边选的是 P1.5/P1.6/P1.7 做 SPI 三线片选和 DRDY 用普通 IO。ADS1256 引脚STC15 引脚作用SCLKP1.5SPI 时钟DINP1.6主机输出DOUTP1.7主机输入CSP2.0片选低有效DRDYP2.1数据就绪低有效RESETP2.2复位低有效AVDD5V模拟电源DVDD3.3V数字电源这里有个血泪经验ADS1256 的模拟电源和数字电源最好分开走线模拟地和数字地单点共地。我一开始图省事全接在一起读数噪声直接大了十几倍。另外 DRDY 一定要接不要想着用延时替代ADS1256 的转换时间跟 PGA 和采样率有关固定延时根本靠不住。2.2 STC15 内部 SPI 的配置寄存器怎么算STC15 的 SPI 模块在寄存器里叫 SPCTL、SPSTAT、SPDAT。主机模式、时钟极性、时钟相位这几个位如果配错ADS1256 根本不会响应。ADS1256 要求 SCLK 空闲为低数据在上升沿采样所以 CPOL0、CPHA1。STC15 的 SPCTL 里 CPOL 和 CPHA 的定义跟标准 SPI 略有差异我直接给出实测能用的配置值。// STC15 SPI 主机初始化时钟约 1MHz void SPI_Init(void) { P1M1 ~0xE0; // P1.5/P1.6/P1.7 设为推挽输出 P1M0 | 0xE0; P2M1 ~0x07; // P2.0/P2.1/P2.2 推挽 P2M0 | 0x07; SPCTL 0xD0; // 主机模式, CPOL0, CPHA1, 时钟SYSCLK/4 SPSTAT 0xC0; // 清中断标志和写冲突标志 CS 1; // 片选拉高空闲状态 DRDY 1; // 输入口外部上拉 }SPCTL 的 0xD0 拆开看bit7 是 SPEN 使能bit6 是 SSIG 忽略 SS 引脚bit4 是 MSTR 主机模式bit3 是 CPOL0bit2 是 CPHA1低两位是时钟分频。这里分频设成 4如果主频 24MHzSCLK 就是 6MHz但 ADS1256 在 3.3V 下最高 SCLK 约 1.8MHz所以实际我后面又把分频改成了 16保证时序余量。很多人读不出数据就是 SCLK 太快ADS1256 还没准备好就被采走了。2.3 字节收发函数别用库自己写更可控STC15 的 SPI 收发就一个寄存器 SPDAT写进去启动发送读出来是接收数据。但要注意 SPSTAT 的 SPIF 标志发完必须等它置位再读否则读到的可能是上一次的残留值。// SPI 收发一个字节返回接收到的数据 unsigned char SPI_ReadWrite(unsigned char dat) { SPSTAT 0xC0; // 清标志 SPDAT dat; // 写入数据启动传输 while (!(SPSTAT 0x80)); // 等待 SPIF 置位 return SPDAT; // 读取接收数据 }这个函数是后面所有 ADS1256 寄存器操作的基础。注意 SPSTAT 的 0x80 是 SPIF0x40 是 WCOL 写冲突。如果 WCOL 置位说明你发得太快上一次还没发完就写了新数据这时候读出来的值不可信。我一般会在 while 里加超时计数防止死等。3. ADS1256 寄存器操作命令、状态与通道切换3.1 命令字节表写寄存器、读寄存器、自校准ADS1256 的操作靠命令字节区分不是直接给寄存器地址。常用的几条命令我列在下面这些值在数据手册里都有但实际用的时候容易把写寄存器和读寄存器的位搞反。命令值作用RDATA0x01读转换数据RREG0x10读寄存器后面跟寄存器地址WREG0x50写寄存器后面跟寄存器地址SELFCAL0xF0自校准SYNC0xFC同步WAKEUP0x00唤醒写寄存器的时候命令字节是 0x50 | 寄存器地址然后跟一个字节表示要写几个寄存器减一。比如写 STATUS 寄存器就是 0x50 | 0x00再跟 0x00 表示写一个。读寄存器类似0x10 | 地址再跟个数减一。3.2 寄存器初始化PGA、采样率、通道怎么设ADS1256 上电后必须配置 STATUS、ADCON、DRATE 这几个寄存器否则它不会按你想要的增益和速率工作。我一般用单端输入PGA 设 1采样率设 100SPS这样噪声和速度比较平衡。// 写 ADS1256 寄存器 void ADS1256_WriteReg(unsigned char reg, unsigned char dat) { CS 0; SPI_ReadWrite(0x50 | reg); // WREG 命令 SPI_ReadWrite(0x00); // 写一个寄存器 SPI_ReadWrite(dat); // 写入数据 CS 1; } // 初始化 ADS1256 void ADS1256_Init(void) { CS 0; SPI_ReadWrite(0xFE); // 复位命令实际用 RESET 引脚也行 CS 1; Delay_ms(10); ADS1256_WriteReg(0x00, 0x01); // STATUS: 高位在前自校准使能 ADS1256_WriteReg(0x01, 0x00); // MUX: 通道0单端 ADS1256_WriteReg(0x02, 0x00); // ADCON: PGA1, 缓冲关 ADS1256_WriteReg(0x03, 0x82); // DRATE: 100SPS CS 0; SPI_ReadWrite(0xF0); // SELFCAL 自校准 while (DRDY 1); // 等待校准完成 CS 1; }STATUS 寄存器的 0x01 表示 ACAL1每次转换前自动校准省得手动发 SELFCAL。ADCON 的 0x00 是 PGA1如果你要测热电偶这种小信号可以改成 0x03 把 PGA 设成 64但要注意输入共模范围会变窄。DRATE 的 0x82 对应 100SPS这个值在 3.3V 下比较稳再快噪声就上来了。3.3 读一次转换数据DRDY 下降沿才是唯一信号读数据不能靠延时必须等 DRDY 变低。DRDY 低电平表示新数据已经准备好这时候发 RDATA 命令然后连续读三个字节拼成 24 位有符号数。// 读取一次 24 位转换结果 long ADS1256_ReadData(void) { long val 0; unsigned char buf[3]; while (DRDY 1); // 等待数据就绪 CS 0; SPI_ReadWrite(0x01); // RDATA 命令 buf[0] SPI_ReadWrite(0xFF); buf[1] SPI_ReadWrite(0xFF); buf[2] SPI_ReadWrite(0xFF); CS 1; val ((long)buf[0] 16) | ((long)buf[1] 8) | buf[2]; if (val 0x800000) // 负数补码处理 val | 0xFF000000; return val; }这里有个细节发 RDATA 之后第一个字节是最高位最后是最低位。拼的时候一定要按顺序我见过有人把 buf[0] 和 buf[2] 搞反结果读数完全不对。另外 0x800000 是符号位判断24 位有符号数的范围是 -8388608 到 8388607超过这个范围说明数据异常。4. 避坑与排查DRDY 不响应、读数跳变、SPI 死锁4.1 DRDY 一直高电平程序卡在 while 里现象上电后 DRDY 始终是高ADS1256_ReadData 里的 while 死循环出不来。原因通常是复位没成功或者自校准没完成。ADS1256 的 RESET 引脚需要至少 4 个时钟周期的低电平我一开始只拉低了 1us芯片根本没复位。解决方法是把 RESET 拉低后延时 10ms 再拉高然后再等 DRDY 变低。如果还是不行检查 AVDD 和 DVDD 是否都在正常范围模拟电源低于 4.5V 时 ADS1256 不工作。4.2 读数在 0 附近跳几十个码现象输入短接时读数不是稳定的 0而是在 -50 到 50 之间跳。原因有两个一是 PGA 设太大二是模拟地和数字地没处理好。我一开始 PGA64短接时噪声就有几百个码。改成 PGA1 后降到几十个码再把模拟地和数字地单点共地噪声降到个位数。如果还跳可以在输入端加 RC 滤波截止频率设成采样率的一半以下。4.3 SPI 读出来的寄存器值全是 0xFF现象读 STATUS 寄存器返回 0xFF写进去的值读不回来。原因多半是 SPI 模式配错CPOL 和 CPHA 跟 ADS1256 不匹配。ADS1256 要求 CPOL0、CPHA1对应 STC15 的 SPCTL 是 0xD0。如果你用的是 0xC0 或者 0xE0读出来就是乱的。另外 CS 片选必须在整个命令序列期间保持低电平不能发一个字节拉高一次ADS1256 会认为命令中断。4.4 连续读数据时偶尔丢一个点现象采集 100 个点中间有几个点数值明显异常。原因是 DRDY 判断和 SPI 读取之间有竞争如果你在 DRDY 刚变低的时候没及时读下一次转换完成会覆盖掉旧数据。解决方法是在 DRDY 中断里置标志主循环检测标志后再读或者把采样率降下来给 MCU 留足处理时间。我一般用 100SPSSTC15 在 24MHz 下完全跟得上。4.5 自校准后读数反而偏了现象发完 SELFCAL读数零点偏移变大。原因是自校准需要输入端接固定电平通常是短接到地或者接参考电压。如果你在自校准的时候输入端还挂着传感器校准出来的偏移就是错的。正确做法是上电时先把输入端短接发 SELFCAL等 DRDY 变低后再接传感器。或者用 ACAL1 让每次转换自动校准但这样会降低有效采样率。5. 二次开发与精度验证从能读到读得准5.1 把读数换算成电压参考电压和 PGA 的关系ADS1256 的输出码值跟输入电压的关系是Vin (code / 8388607) * (Vref / PGA)。假设 Vref 是 2.5VPGA1那么满量程是 ±2.5V一个码对应 2.5 / 8388607 ≈ 0.298uV。如果你用内部参考Vref 就是 2.5V但内部参考的温漂比外部基准大要求高的话建议外接 REF5025 之类的基准源。我一般会在代码里做一个换算函数把 long 型的码值转成 uV 单位的整数方便后续处理。// 将 ADC 码值转换为微伏Vref2.5V, PGA1 long ADS1256_ToMicroVolt(long code) { // 2.5V / 8388607 ≈ 0.298uV/code return (long)((float)code * 0.298f); }这个换算里用的是浮点STC15 没有硬件浮点如果嫌慢可以改成定点运算把 0.298 放大 1000 倍变成 298最后结果除以 1000。注意 code 是 24 位有符号数负数换算出来也是负的别用无符号类型接。5.2 多通道轮询切换 MUX 后要等多久ADS1256 有 8 个单端通道切换通道就是写 MUX 寄存器。但写完 MUX 后不能立刻读数据因为模拟输入需要建立时间。我实测下来PGA1 时切换通道后等 1ms 再读比较稳PGA64 时要等 5ms 以上。如果你用 ACAL1切换通道后会自动校准时间更长。所以多通道采集时采样率不能设太高否则通道间会串扰。5.3 用已知电压验证拿一节干电池做基准代码跑通后别急着接传感器先拿一节干电池验证。干电池的电压大概 1.5V用万用表量一下实际值然后看 ADS1256 读出来换算后的电压跟万用表差多少。如果差在 1mV 以内说明参考电压和 PGA 配置没问题。如果差很多先检查 Vref 是不是 2.5V再检查 PGA 是不是设成了 1。我见过有人 PGA 设成 64读 1.5V 直接饱和到满量程还以为芯片坏了。5.4 改造方向加 DMA 或者换更快的 MCUSTC15 的 SPI 没有 DMA每次读写都要 CPU 参与100SPS 下没问题但如果你想跑到 1kSPS 以上CPU 占用率就上来了。这时候可以考虑换带 SPI DMA 的 MCU或者用 STC15 的 SPI 中断来收发。另一个改造方向是加隔离如果传感器在工业现场ADS1256 和 STC15 之间最好加数字隔离器否则地环路会引入很大的工频干扰。从那以后我每次调 ADS1256都强制先短接输入端跑一遍自校准再用干电池验证零点最后才接传感器。这个习惯帮我省了很多来回拆焊的时间。希望帮到你。本文还有配套的精品资源点击获取