☰
STC15内部SPI驱动ADS1256:24位ADC采集与寄存器调试
2026/10/26 17:20:18 网站建设 项目流程

简介:面向高精度电压监测、仪器仪表与嵌入式采集开发,提供基于STC15系列单片机内置SPI驱动ADS1256的参考工程,适合需要掌握24位Σ-Δ型ADC配置与调试的学习者和工程师,重点解决多通道通信、采样速率设置及低噪声读数问题。压缩包共20个文件,整体约9.96MB,包含c/h源码、Keil工程文件、hex烧录固件、PDF芯片手册,以及obj、lst、m51等编译中间文件,目录按源码、工程、文档和编译产物组织,便于直接编译验证并对照底层寄存器配置。已有1408人学习下载。资料围绕8路单端或差分输入、7.80MHz晶振与2.5~10sps采样设置展开,实测电压误差可控制在0.00001V内,可帮助读者梳理SPI时序、初始化流程、数据读取与误差排查思路,对通信异常、采样漂移和精度校准也有参考价值,并可用于课程设计、毕业设计及仪器类项目的前期验证。

1. 先别急着写寄存器:STC15 内部 SPI 接 ADS1256 的适用边界

手头一块 STC15 小板上已经跑着串口打印,旁边挂着一片 ADS1256 模块,想直接拿内部 SPI 把 24 位码值读上来,这个组合能不能成立?结论是能,但别把它当成“接上线就能出数”的模块:STC15 的 SPI 是主机模式、8 位收发,ADS1256 要的是模式 1、MSB 先行、SCLK 不超过 1.92 MHz,还要盯住 DRDY 的下降沿。适合已经能把 GPIO 点灯、串口收发的读者,用来做低速称重、热电偶、压力桥这类小信号采集;如果采样率要求高到几十 kSPS,或者现场干扰很大,先别急着上,后面几章的时序和排错会决定你能不能拿到稳定码值。

2. 硬件连线与 SPI 模式:把 SCLK 压到 1.92 MHz 以下

2.1 先看电源、基准和四条信号线

ADS1256 不是单电源随便喂的 ADC。模拟电源 AVDD 常见接 5 V,数字电源 DVDD 可以接 3.3 V 或 5 V,基准 VREF 常见 2.5 V;STC15 如果是 3.3 V 供电,SPI 的 MOSI、SCLK、CS 输出高电平大约 3.3 V,ADS1256 的 DVDD 也建议跟单片机同电压,避免数字输入高电平判据卡在边缘。模拟输入是差分输入,正负端都有共模范围,不能因为 PGA 设成 1 就把任意电压直接怼进去。实际连线时,SCLK、MOSI、MISO 三根线走短线,CS 单独一根 GPIO,DRDY 也接 GPIO,RESET 和 PDWN 建议上拉,不要悬空。

下面的引脚分配只是示例,STC15 不同型号的 SPI 引脚可能要通过P_SW1或类似寄存器切换到指定管脚,写代码前先对着具体型号的管脚表确认。CS 用软件片选,原因很直接:ADS1256 一次命令往往包含多个字节,硬件 SS 自动拉低容易在字节之间产生毛刺,软件控制 CS 更稳。

#include <STC15.H> // 下面四个引脚按实际板子改,不要照抄 sbit ADC_CS = P1^2; sbit ADC_DRDY = P1^3; sbit ADC_RESET = P1^4; sbit ADC_PDWN = P1^5; #define ADC_CS_LOW() (ADC_CS = 0) #define ADC_CS_HIGH() (ADC_CS = 1)

这段只做引脚声明和片选宏。ADC_CS低有效,ADC_DRDY是 ADS1256 的输出,单片机端配成输入;ADC_RESET和ADC_PDWN低有效,初始化时先给高电平。引脚号不是重点,重点是 CS 必须由软件控制,DRDY 必须能读到,RESET 和 PDWN 不能省。

电源部分我一般会这样做:AVDD 和 DVDD 各自放 10 μF 钽电容加 0.1 μF 瓷片电容,VREF 端放 10 μF 加 0.1 μF,模拟地和数字地在 ADC 芯片下方单点连接。ADS1256 的输入如果接桥式传感器,输入串 1 kΩ 电阻,差分端并 0.1 μF 电容,给共模干扰一条低阻通路。不要小看这些被动件,24 位 ADC 的低位码值能不能稳住,很多时候不是程序问题,而是基准和地线在“抖”。

2.2 SPI 模式 1 和分频表:别让 SCLK 超限

ADS1256 的 SPI 时序大致是:SCLK 空闲低电平,数据在 SCLK 下降沿更新,主机在上升沿采样。对应 SPI 模式 1,也就是 CPOL=0、CPHA=1。STC15 的内部 SPI 寄存器SPCTL里,CPOL和CPHA两位要按这个配;DORD=0表示 MSB 先行;MSTR=1表示主机;SSIG=1表示软件片选;SPEN=1使能 SPI。把这些位拼起来,常见的配置值是0xD6,其中低两位SPR1:SPR0=10,代表 16 分频。

分频不能拍脑袋。ADS1256 的 SCLK 上限按 1.92 MHz 留余量,STC15 主频如果是 24 MHz,4 分频是 6 MHz,8 分频是 3 MHz,都超了;16 分频是 1.5 MHz,可以用;32 分频是 0.75 MHz,更保守。如果主频是 11.0592 MHz,8 分频是 1.3824 MHz,也在范围内。下面这张表可以直接对照:

SPR1:SPR0分频24 MHz 下 SCLK11.0592 MHz 下 SCLK是否可用
0046 MHz2.7648 MHz超限
0183 MHz1.3824 MHz24 MHz 超限
10161.5 MHz0.6912 MHz可用
11320.75 MHz0.3456 MHz可用

我一般先用 16 分频跑通,等寄存器读写稳定、数据能连续出来,再考虑要不要提到 8 分频。ADS1256 不是 SPI Flash,读快了不会给你报错,它只会用错位的数据让你怀疑人生。

2.3 初始化和字节收发:清标志、等 SPIF、处理 WCOL

STC15 的 SPI 发送一个字节的流程是:先清SPSTAT的SPIF和WCOL,把数据写进SPDAT,等SPIF置位,再读SPDAT拿到同时收到的字节。注意SPIF和WCOL都是写 1 清零,不是写 0 清零。如果上一个字节还没发完就写SPDAT,WCOL会置位,后面的时序就乱了。下面这个收发函数加了超时保护,防止 SPI 没使能时死等。

#define MAIN_Fosc 24000000UL void spi_init(void) { // SSIG=1 软件片选;SPEN=1 使能;DORD=0 MSB 先行; // MSTR=1 主机;CPOL=0;CPHA=1;SPR=10 -> 16 分频 SPCTL = 0xD6; SPSTAT = 0xC0; // 写 1 清 SPIF/WCOL } unsigned char spi_xfer(unsigned char dat) { unsigned int guard = 0; SPSTAT = 0xC0; // 清两个标志 SPDAT = dat; // 启动一个字节 while (!(SPSTAT & 0x80)) { if (++guard > 20000) break; // 时钟没配好时不要死等 } if (SPSTAT & 0x40) { // WCOL 置位说明写得太快 SPSTAT = 0xC0; } SPSTAT = 0x80; // 清 SPIF return SPDAT; // 全双工,读回同时收到的字节 }

spi_init里除了SPCTL,还要确认 SPI 引脚切换寄存器有没有配。STC15 有些型号的 SPI 默认不在手边那三个脚上,需要把P_SW1的对应位切过去;这一步漏了,代码看起来在跑,SCLK 就是不出波形。spi_xfer的guard不是装饰,调试阶段最怕程序卡在while里,串口也不打印,看起来像单片机“死机”,其实是 SPI 时钟没起来。

2.4 上板先做通路测试,不要直接读 ADC 数据

在接 ADS1256 之前,可以先把 MOSI 和 MISO 短接,调用spi_xfer(0x55),看返回值是不是0x55。这一步能确认 STC15 的 SPI 主机模式、分频、MSB 顺序和标志位清理都正常。如果回环都不对,后面读 ADS1256 的寄存器一定是玄学。回环通过后,再把 CS 接回 ADC,用读寄存器的方式做第二次冒烟测试:读STATUS寄存器,看能不能返回初始化时写进去的值。这个顺序看起来慢,实际上比对着示波器猜“是模式错了还是线接反了”快得多。

3. 寄存器配置与初始化:从 RESET 到 RDATAC 的一条龙代码

3.1 命令集和寄存器地址:先把 WREG、RREG 的格式记死

ADS1256 的 SPI 不是“地址加数据”那种简单协议,它先发命令字节,再根据命令决定后面跟几个字节。常用命令有:RESET=0xFE、SDATAC=0x0F、RDATAC=0x03、RDATA=0x01、RREG=0x10、WREG=0x50、SELFCAL=0xF0。写寄存器用WREG | 寄存器地址,第二个字节是“写几个寄存器减一”,然后才是数据;读寄存器用RREG | 寄存器地址,第二个字节也是“读几个减一”,之后读回数据。写一个寄存器时,第二个字节就是0x00,不是0x01,这里翻车的人不少。

寄存器地址先记住四个最常用的:STATUS=0x00、MUX=0x01、ADCON=0x02、DRATE=0x03。STATUS里可以控制输入缓冲,MUX选差分正负端,ADCON选 PGA 和传感器检测,DRATE选数据率。初始化时不要一上来就写一堆寄存器,先发SDATAC停止连续读取,再写配置,最后发SELFCAL自校准,等校准完成再进RDATAC。顺序错了,寄存器可能写不进去,读回的数据也会是旧配置。

#define CMD_RDATA 0x01 #define CMD_RDATAC 0x03 #define CMD_SDATAC 0x0F #define CMD_RREG 0x10 #define CMD_WREG 0x50 #define CMD_SELFCAL 0xF0 #define CMD_RESET 0xFE #define REG_STATUS 0x00 #define REG_MUX 0x01 #define REG_ADCON 0x02 #define REG_DRATE 0x03

这些宏只是把命令和地址起了名字。真正写代码时,建议把ads1256_write_reg和ads1256_read_reg单独封装,不要在每个地方手拼命令字节,否则查问题时很难确认到底是命令错了还是数据错了。

3.2 写寄存器和读寄存器:CS 宽度与 t6 延时

写单个寄存器的流程是:CS 拉低,发WREG | reg,发0x00表示写一个,发数据,CS 拉高。读单个寄存器的流程是:CS 拉低,发RREG | reg,发0x00表示读一个,等一小段时间让 ADC 把数据放到 MISO 上,再发一个哑字节把数据换回来,CS 拉高。这个“等一小段时间”就是手册里的 t6,不同数据率下数值不同,调试阶段用 10 μs 到 50 μs 保守延时通常够用。

void ads1256_write_reg(unsigned char reg, unsigned char val) { ADC_CS_LOW(); spi_xfer(CMD_WREG | reg); spi_xfer(0x00); // 写 1 个寄存器,count-1 spi_xfer(val); ADC_CS_HIGH(); delay_us(10); } unsigned char ads1256_read_reg(unsigned char reg) { unsigned char v; ADC_CS_LOW(); spi_xfer(CMD_RREG | reg); spi_xfer(0x00); // 读 1 个寄存器,count-1 delay_us(20); // 等 t6,保守一点 v = spi_xfer(0xFF); // 哑字节换回数据 ADC_CS_HIGH(); delay_us(10); return v; }

这两个函数是后面所有配置的基础。ads1256_write_reg里的0x00不能改成0x01,否则会多写一个寄存器,把后面的配置打乱。ads1256_read_reg里的0xFF只是给 SPI 提供时钟,写什么都行,但必须发一个字节才能把从机数据换回来。如果读回的值一直是0x00或0xFF,先别怀疑寄存器地址,先确认 CS 有没有拉低、SCLK 有没有 8 个脉冲、MISO 有没有接对。

3.3 初始化序列:复位、停连续读、写配置、自校准

初始化我一般分四步:硬件复位、软件复位、停连续读、写配置。硬件复位是给RESET低脉冲,至少低 10 ms,再拉高等 100 ms;软件复位是发RESET命令,等 100 ms;然后发SDATAC,确保芯片不在连续读取模式;最后写STATUS、MUX、ADCON、DRATE。写完配置发SELFCAL,等校准完成,再发RDATAC进入连续读取。校准时间跟数据率有关,保守一点等 300 ms,不要刚发完命令就读数据。

void ads1256_init(void) { ADC_CS_HIGH(); ADC_PDWN_HIGH(); ADC_RESET_HIGH(); delay_ms(10); ADC_RESET_LOW(); delay_ms(10); ADC_RESET_HIGH(); delay_ms(100); ADC_CS_LOW(); spi_xfer(CMD_RESET); ADC_CS_HIGH(); delay_ms(100); ADC_CS_LOW(); spi_xfer(CMD_SDATAC); ADC_CS_HIGH(); delay_ms(5); ads1256_write_reg(REG_STATUS, 0x04); // 开输入缓冲,按需改 ads1256_write_reg(REG_MUX, 0x10); // 正端 AIN0,负端 AIN1 ads1256_write_reg(REG_ADCON, 0x00); // PGA=1,关传感器检测 ads1256_write_reg(REG_DRATE, 0x60); // 100 SPS,按表查 ADC_CS_LOW(); spi_xfer(CMD_SELFCAL); ADC_CS_HIGH(); delay_ms(300); // 等自校准完成 ADC_CS_LOW(); spi_xfer(CMD_RDATAC); ADC_CS_HIGH(); delay_ms(5); }

STATUS=0x04是开模拟输入缓冲,输入阻抗高的信号源可以开,但要注意缓冲会带来输入偏置电流和共模范围变化;如果输入是低阻桥式传感器,也可以写0x00关掉。MUX=0x10表示正端 AIN0、负端 AIN1,低 4 位是正端,高 4 位是负端;如果实际接线反了,读数会反号,不是芯片坏了。ADCON=0x00是 PGA=1、关闭传感器检测,先从小增益开始,别一上来就 PGA=64,输入稍有过冲就饱和。DRATE=0x60对应 100 SPS,具体数据率表以手头手册为准,但 0x60 这个值在常见 ADS1256 驱动里就是 100 SPS。

3.4 连续读取、24 位补码和电压换算

进入RDATAC后,ADS1256 每次转换完成会把DRDY拉低,主机在DRDY低电平期间读 3 个字节,读完DRDY会变高,下一次转换完成再变低。所以读数据的函数先while (ADC_DRDY);等低,再 CS 拉低,连续发三个0xFF换回三个字节。不要在这三个字节之间拉高 CS,也不要在DRDY还没低的时候读,否则读到的是旧数据或者移位错位的数据。

long ads1256_read_rdatac(void) { long code = 0; unsigned char b0, b1, b2; while (ADC_DRDY); // 等 DRDY 变低 ADC_CS_LOW(); b0 = spi_xfer(0xFF); b1 = spi_xfer(0xFF); b2 = spi_xfer(0xFF); ADC_CS_HIGH(); code = ((long)b0 << 16) | ((long)b1 << 8) | b2; if (code & 0x800000L) { // 24 位补码转 32 位有符号 code |= 0xFF000000L; } return code; } float ads1256_to_voltage(long code, unsigned char pga) { // VREF=2.5V,满量程 ±VREF/PGA,LSB=VREF/(PGA*2^23) return (float)code * 2.5f / (8388608.0f * (float <p> <a href="https://download.csdn.net/download/a1329303396/7719903" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询