简介:这份资源面向使用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 引脚 | 作用 |
|---|---|---|
| SCLK | P1.5 | SPI 时钟 |
| DIN | P1.6 | 主机输出 |
| DOUT | P1.7 | 主机输入 |
| CS | P2.0 | 片选,低有效 |
| DRDY | P2.1 | 数据就绪,低有效 |
| RESET | P2.2 | 复位,低有效 |
| AVDD | 5V | 模拟电源 |
| DVDD | 3.3V | 数字电源 |
这里有个血泪经验:ADS1256 的模拟电源和数字电源最好分开走线,模拟地和数字地单点共地。我一开始图省事全接在一起,读数噪声直接大了十几倍。另外 DRDY 一定要接,不要想着用延时替代,ADS1256 的转换时间跟 PGA 和采样率有关,固定延时根本靠不住。
2.2 STC15 内部 SPI 的配置寄存器怎么算
STC15 的 SPI 模块在寄存器里叫 SPCTL、SPSTAT、SPDAT。主机模式、时钟极性、时钟相位这几个位如果配错,ADS1256 根本不会响应。ADS1256 要求 SCLK 空闲为低,数据在上升沿采样,所以 CPOL=0、CPHA=1。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; // 主机模式, CPOL=0, CPHA=1, 时钟=SYSCLK/4 SPSTAT = 0xC0; // 清中断标志和写冲突标志 CS = 1; // 片选拉高,空闲状态 DRDY = 1; // 输入口,外部上拉 }SPCTL 的 0xD0 拆开看:bit7 是 SPEN 使能,bit6 是 SSIG 忽略 SS 引脚,bit4 是 MSTR 主机模式,bit3 是 CPOL=0,bit2 是 CPHA=1,低两位是时钟分频。这里分频设成 4,如果主频 24MHz,SCLK 就是 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 是 SPIF,0x40 是 WCOL 写冲突。如果 WCOL 置位说明你发得太快,上一次还没发完就写了新数据,这时候读出来的值不可信。我一般会在 while 里加超时计数,防止死等。
3. ADS1256 寄存器操作:命令、状态与通道切换
3.1 命令字节表:写寄存器、读寄存器、自校准
ADS1256 的操作靠命令字节区分,不是直接给寄存器地址。常用的几条命令我列在下面,这些值在数据手册里都有,但实际用的时候容易把写寄存器和读寄存器的位搞反。
| 命令 | 值 | 作用 |
|---|---|---|
| RDATA | 0x01 | 读转换数据 |
| RREG | 0x10 | 读寄存器,后面跟寄存器地址 |
| WREG | 0x50 | 写寄存器,后面跟寄存器地址 |
| SELFCAL | 0xF0 | 自校准 |
| SYNC | 0xFC | 同步 |
| WAKEUP | 0x00 | 唤醒 |
写寄存器的时候,命令字节是 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: PGA=1, 缓冲关 ADS1256_WriteReg(0x03, 0x82); // DRATE: 100SPS CS = 0; SPI_ReadWrite(0xF0); // SELFCAL 自校准 while (DRDY == 1); // 等待校准完成 CS = 1; }STATUS 寄存器的 0x01 表示 ACAL=1,每次转换前自动校准,省得手动发 SELFCAL。ADCON 的 0x00 是 PGA=1,如果你要测热电偶这种小信号,可以改成 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 设太大,二是模拟地和数字地没处理好。我一开始 PGA=64,短接时噪声就有几百个码。改成 PGA=1 后降到几十个码,再把模拟地和数字地单点共地,噪声降到个位数。如果还跳,可以在输入端加 RC 滤波,截止频率设成采样率的一半以下。
4.3 SPI 读出来的寄存器值全是 0xFF
现象:读 STATUS 寄存器返回 0xFF,写进去的值读不回来。原因多半是 SPI 模式配错,CPOL 和 CPHA 跟 ADS1256 不匹配。ADS1256 要求 CPOL=0、CPHA=1,对应 STC15 的 SPCTL 是 0xD0。如果你用的是 0xC0 或者 0xE0,读出来就是乱的。另外 CS 片选必须在整个命令序列期间保持低电平,不能发一个字节拉高一次,ADS1256 会认为命令中断。
4.4 连续读数据时偶尔丢一个点
现象:采集 100 个点,中间有几个点数值明显异常。原因是 DRDY 判断和 SPI 读取之间有竞争,如果你在 DRDY 刚变低的时候没及时读,下一次转换完成会覆盖掉旧数据。解决方法是在 DRDY 中断里置标志,主循环检测标志后再读,或者把采样率降下来,给 MCU 留足处理时间。我一般用 100SPS,STC15 在 24MHz 下完全跟得上。
4.5 自校准后读数反而偏了
现象:发完 SELFCAL,读数零点偏移变大。原因是自校准需要输入端接固定电平,通常是短接到地或者接参考电压。如果你在自校准的时候输入端还挂着传感器,校准出来的偏移就是错的。正确做法是上电时先把输入端短接,发 SELFCAL,等 DRDY 变低后再接传感器。或者用 ACAL=1 让每次转换自动校准,但这样会降低有效采样率。
5. 二次开发与精度验证:从能读到读得准
5.1 把读数换算成电压:参考电压和 PGA 的关系
ADS1256 的输出码值跟输入电压的关系是:Vin = (code / 8388607) * (Vref / PGA)。假设 Vref 是 2.5V,PGA=1,那么满量程是 ±2.5V,一个码对应 2.5 / 8388607 ≈ 0.298uV。如果你用内部参考,Vref 就是 2.5V,但内部参考的温漂比外部基准大,要求高的话建议外接 REF5025 之类的基准源。我一般会在代码里做一个换算函数,把 long 型的码值转成 uV 单位的整数,方便后续处理。
// 将 ADC 码值转换为微伏,Vref=2.5V, PGA=1 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 后不能立刻读数据,因为模拟输入需要建立时间。我实测下来,PGA=1 时切换通道后等 1ms 再读比较稳,PGA=64 时要等 5ms 以上。如果你用 ACAL=1,切换通道后会自动校准,时间更长。所以多通道采集时,采样率不能设太高,否则通道间会串扰。
5.3 用已知电压验证:拿一节干电池做基准
代码跑通后,别急着接传感器,先拿一节干电池验证。干电池的电压大概 1.5V,用万用表量一下实际值,然后看 ADS1256 读出来换算后的电压跟万用表差多少。如果差在 1mV 以内,说明参考电压和 PGA 配置没问题。如果差很多,先检查 Vref 是不是 2.5V,再检查 PGA 是不是设成了 1。我见过有人 PGA 设成 64,读 1.5V 直接饱和到满量程,还以为芯片坏了。
5.4 改造方向:加 DMA 或者换更快的 MCU
STC15 的 SPI 没有 DMA,每次读写都要 CPU 参与,100SPS 下没问题,但如果你想跑到 1kSPS 以上,CPU 占用率就上来了。这时候可以考虑换带 SPI DMA 的 MCU,或者用 STC15 的 SPI 中断来收发。另一个改造方向是加隔离,如果传感器在工业现场,ADS1256 和 STC15 之间最好加数字隔离器,否则地环路会引入很大的工频干扰。
从那以后我每次调 ADS1256,都强制先短接输入端跑一遍自校准,再用干电池验证零点,最后才接传感器。这个习惯帮我省了很多来回拆焊的时间。希望帮到你。
本文还有配套的精品资源,点击获取