资讯动态

STC15硬件SPI驱动ADS1256:8位单片机实现24位高精度ADC采集

发布时间:2026/10/26 23:38:03 来源:尧图企业网站定制
简介面向高精度电压监测与嵌入式测量开发这份工程包给出STC15系列单片机利用内置SPI与TI 24位Σ-Δ型ADS1256通信的完整实现适合电子测量、仪器仪表方向的开发者与学习者参考。资源共20个文件约9.96MB包含3个h头文件、2个c源文件、Keil工程配置uvproj/uvopt、可烧录hex及obj、lst、m51等编译中间文件并附ADS1256英文手册与STC15F2K60S2数据手册便于对照寄存器与硬件参数排查问题。内容围绕8路通道采样、单端与拆分输入、7.8MHz晶振配置展开采样速率可控制在2.510sps实际电压监测误差可减小到0.00001V以内。已有1408人学习下载可作为高精度监测仪器项目中SPI时序调试、初始化流程与数据处理思路的参考底稿。1. 一块 24 位 ADC 挂在 8 位单片机上难点从来不在 SPI前阵子帮人看一块称重板主控用的是 STC15采集端是 ADS1256。对方一开始图省事用 GPIO 软件模拟 SPI跑了半个月读数始终在最后几位跳得厉害30kSPS 连一半都跑不到。问题不在 ADS1256也不在称重传感器而是软件模拟的时序被定时器和串口中断撕得七零八落时钟高低电平宽度全在抖。后来改用 STC15W4K 的内部硬件 SPI四根线接上代码缩到两百行出头DRDY 一低就去取数反而不抖了。这套组合适合的场景其实很明确主控成本卡死、算力也就那样但前端非要微伏级分辨率——电子秤、热电偶测温、应变桥、电池内阻测试都属于这一类。它不追求采样率追求的是把那 24 位有效位真正兑现出来。2. 硬件与引脚先对齐STC15 内部 SPI 与 ADS1256 的时序契约在写代码之前有几件事必须先确定下来否则后面调试会非常痛苦。ADS1256 的 SPI 不是那种“接上就能通”的接口它有时序上限、有电源域要求还有一条容易被忽略的 CS 整帧拉低规则。2.1 电源域和四根线的接法先看供电。ADS1256 的模拟电源 AVDD 一般给 5V数字电源 DVDD 手册允许 2.7V 到 5.25V。STC15W4K 系列本身也是宽压2.5V 到 5.5V 都能跑。这两者的电源关系必须想清楚STC15 跑 5V那 ADS1256 的 DVDD 也给 5V两边电平自然匹配最省事STC15 跑 3.3VDVDD 也给 3.3V同样没问题千万不要出现 5V 的 STC15 去推 3.3V 供电的 DVDDMISO 上那 5V 会直接灌进 ADS1256 的输入保护二极管短期能通长期是玄学故障。推荐第一种。DVDD 用 5V 时ADS1256 的输入高电平门限是 0.7×DVDD 3.5VSTC15 的 5V 输出足够同时 STC15 的输入高门限约 0.7×VCCADS1256 输出的 5V 也够。接线我一般按下面这张表来ADS1256 引脚方向STC15 引脚说明CS输入P3.4普通 IO 做片选低有效SCLK输入P1.5硬件 SPI 时钟输出DIN输入P1.3MOSI主机输出DOUT输出P1.4MISO主机输入DRDY输出P3.2 / INT0数据就绪低有效可接外部中断RESET输入P3.6低电平复位PDWN输入P3.7低电平掉电正常工作时拉高还有两点容易漏。第一ADS1256 的 XTAL1/XTAL2 要接 7.68MHz 晶振这个时钟决定了它的采样速率精度和内部滤波器的陷波点用内部 RC 或者随便给个 8MHz 都会让 50Hz 抑制跑偏。第二CS 虽然只有一个从机时可以常拉低但我建议还是用 IO 控制因为 ADS1256 对“一帧命令”这个概念很敏感CS 在中途抬起会导致命令解析错位。2.2 SPCTL/SPSTAT/SPDAT三个寄存器定生死STC15 的 SPI 外设非常精简就三个寄存器控制寄存器 SPCTL、状态寄存器 SPSTAT、数据寄存器 SPDAT。另外还有 AUXR1 里的两位用来切换 SPI 引脚组因为在 STC15W4K 上 SPI 可以映射到 P1.2~P1.5、P2.4~P2.7 或者 P4.0~P4.3 三组。/* 直接给地址省得依赖具体型号的头文件 */ sfr SPSTAT 0xCD; sfr SPCTL 0xCE; sfr SPDAT 0xCF; sfr AUXR1 0xA2; sbit ADS_CS P3^4; sbit ADS_DRDY P3^5; /* 若接 INT0 则用 P3^2 */ sbit ADS_RST P3^6; sbit ADS_PDWN P3^7;SPCTL 的位定义要背下来因为这几位配错一个现象就是“完全读不到数据”没有任何提示void spi_init(void) { AUXR1 ~0x0C; /* 引脚组选择00 - SSP1.2 MOSIP1.3 MISOP1.4 SCLKP1.5 */ SPCTL 0xD4; /* SSIG1 忽略SS | SPEN1 使能 | DORD0 高位先发 MSTR1 主机 | CPOL0 空闲低 | CPHA1 第二边沿采样 SPR1:SPR0 00 - 4 分频 */ SPCTL | 0x02; /* 改成 10 - 16 分频24MHz 主频下 SCLK 1.5MHz */ SPSTAT 0xC0; /* SPIF 和 WCOL 都是写 1 清零 */ }几个参数单独说明一下。SSIG 必须置 1否则 SS 引脚P1.2一旦被外部拉低SPI 会强制切到从机模式这是很多人“程序跑着跑着 SPI 就不动了”的真正原因。MSTR 置 1 是主机模式。CPOL0、CPHA1 对应 SPI 模式 1正好是 ADS1256 要求的SCLK 空闲为低数据在下降沿移出、上升沿采样。DORD0 表示 MSB 先行ADS1256 的 24 位数据就是高位在前。SPSTAT 的两个标志位也值得说。SPIF 是传输完成标志WCOL 是写冲突标志——如果你在上一字节还没发完就往 SPDAT 里写新数据WCOL 会置位数据直接丢弃。这两个位都是写 1 清零写 0 无效这一点和很多 MCU 相反血泪经验。2.3 速率上限为什么 24MHz 主频只能跑 /16 分频ADS1256 手册给了一条硬约束SCLK 周期不能小于 4 个主时钟周期。7.68MHz 晶振下就是 520ns换算成频率大约 1.92MHz。这条线不能越。STC15 的分频档位是主频除以 4、8、16、32部分型号还能到 64 和 128。假设主频 24MHzSPR1:SPR0分频SCLK 频率能不能用00/46MHz超了读数会错位01/83MHz超了10/161.5MHz可以留了余量11/32750kHz可以偏保守所以 24MHz 主频下只能用 /16 或更慢。有人会问1.5MHz 会不会太慢算一下就知道了读一次 24 位数据是 4 个字节1 个命令 3 个数据32 个 SCLK在 1.5MHz 下约 21µs加上字节之间轮询 SPIF 的开销实测 30µs 左右。ADS1256 最高 30kSPS 对应 33µs 一个周期刚好卡在边上所以真要跑满 30kSPS得靠中断 精简代码这点后面会讲。提示STM32 之类带 FIFO 和 DMA 的 SPI 可以做到字节间零间隙STC15 没有每个字节都要靠软件轮询 SPIF。这反而降低了平均时钟密度对手册的时序约束是好事。3. 驱动骨架从字节收发到寄存器读写硬件对齐之后驱动其实是分层的最底下是一个字节收发中间是命令帧封装最上面是寄存器读写和初始化。层次分清楚出问题时才能一层一层往下压。3.1 ADS1256 的命令集与寄存器地图ADS1256 的寄存器不多常用的就四个记住这几个够用了地址名称关键位常用值0x00STATUS高 4 位是 IDbit2 ACAL 自动校准bit1 BUFEN 输入缓冲0x040x01MUX高 4 位 PSEL 正端低 4 位 NSEL 负端0x00 差分 / 0x08 单端0x02ADCONbit7~6 时钟输出bit5~4 传感器电流源bit2~0 PGA0x00PGA10x03DRATE数据速率0x82 100SPS命令层面真正用到的也就几条命令编码用途WAKEUP0x00从待机唤醒RDATA0x01读一次转换结果RDATAC0x02进入连续读模式SDATAC0x03退出连续读模式RREG0x10 | reg读寄存器WREG0x50 | reg写寄存器SELFCAL0xF0自校准SYNC0xFC同步切换通道后常用RESET0xFE软复位这里有个关键点上电或复位之后ADS1256 默认处于 RDATAC 模式也就是“SCLK 一来就吐数据”。在这个模式下写寄存器是无效的必须先发 SDATAC 退出。这一条不知道坑过多少人现象就是“寄存器写进去了读回来还是默认值”。3.2 字节级收发函数底层就一个函数收发合一。因为 SPI 是全双工发一个字节的同时就在收一个字节所以收数据时也要送一个哑字节去产生时钟。/** * 收发一个字节 * param dat 要发送的字节 * return 同时接收到的字节 */ unsigned char spi_xfer(unsigned char dat) { SPSTAT 0xC0; /* 清 SPIF / WCOL */ SPDAT dat; /* 写 SPDAT 即启动一次传输 */ while ((SPSTAT 0x80) 0); /* 等 SPIF 置位实际项目里要加超时 */ SPSTAT 0xC0; /* 再清一次防止 WCOL 残留 */ return SPDAT; /* 读 SPDAT 取回接收字节 */ }逻辑很直白写 SPDAT 启动时钟硬件自动发 8 个 SCLK发完置 SPIF读 SPDAT 拿到 MISO 上的数据。参数只有一个就是待发送字节返回值是同时收到的字节发送时可以忽略。要提醒的是那个while循环。如果 SPI 初始化没做对或者 SCLK 被外部短路这个循环会死等。我一般会加一个计数器超过几千次就报错返回否则现场调试时根本分不清是死机还是等数据。3.3 WREG / RREG 的封包格式ADS1256 的寄存器读写有个“数量字节”的设计命令字节后面跟一个字节表示“要读写几个寄存器数量减一”。只操作一个寄存器时这个字节就是 0x00。而且 CS 必须从命令头一直拉到数据尾中间不能抬。/* 写单个寄存器 */ void ads_wreg(unsigned char reg, unsigned char val) { ADS_CS 0; spi_xfer(0x50 | reg); /* WREG 命令 寄存器地址 */ spi_xfer(0x00); /* 只写 1 个数量减一 0 */ spi_xfer(val); /* 数据 */ ADS_CS 1; delay_us(2); /* 命令之间留一点间隔 */ } /* 读单个寄存器 */ unsigned char ads_rreg(unsigned char reg) { unsigned char v; ADS_CS 0; spi_xfer(0x10 | reg); /* RREG 命令 寄存器地址 */ spi_xfer(0x00); /* 只读 1 个 */ v spi_xfer(0xFF); /* 送 0xFF 只是为产生 8 个时钟 */ ADS_CS 1; delay_us(2); return v; } /* 无参数命令的通用封装 */ void ads_cmd(unsigned char cmd) { ADS_CS 0; spi_xfer(cmd); ADS_CS 1; delay_us(2); }三个函数参数都很简单reg是寄存器地址0x00 到 0x0Aval是要写入的值cmd是命令编码。spi_xfer(0xFF)里的 0xFF 没有任何含义纯粹是因为主机不发时钟就收不到数据送什么值都行习惯上写 0xFF。delay_us(2)这行别删。ADS1256 手册要求命令之间留 t6 量级的间隔7.68MHz 下不到 1µs但 C51 的函数调用开销本来就不确定留 2µs 更稳妥。省掉这行的后果是偶发性的寄存器写失败属于最难查的那类问题。4. 上电初始化到取数一条完整的采集链路把上面这些拼起来就是一条从复位到拿到电压值的完整链路。顺序错一步后面全是白忙。4.1 复位、PDWN 与 SDATAC 的顺序上电时序我踩过一次坑只做了软件 RESET 命令没拉硬件 RESET 引脚结果有一批板子冷启动时 DRDY 一直不低。原因是 ADS1256 内部振荡器起振需要时间软件复位命令在时钟还没稳的时候发过去芯片根本没收到。/** * 初始化返回 0 表示成功 */ unsigned char ads1256_init(void) { ADS_CS 1; ADS_PDWN 1; /* PDWN 高电平 正常工作 */ ADS_RST 0; delay_ms(10); ADS_RST 1; /* 硬件复位给内部振荡器留时间 */ delay_ms(20); ads_cmd(0xFE); /* 再补一条软件 RESET双保险 */ delay_ms(5); if (ads_wait_drdy(100)) /* 等复位完成DRDY 会拉低一次 */ return 1; ads_cmd(0x03); /* SDATAC退出连续读模式否则写寄存器无效 */ ads_wreg(0x00, 0x04); /* STATUS使能自动校准 ACAL */ ads_wreg(0x01, 0x00); /* MUXAIN0-AIN1 差分 */ ads_wreg(0x02, 0x00); /* ADCON关时钟输出关电流源PGA1 */ ads_wreg(0x03, 0x82); /* DRATE100SPS */ return 0; }ads_wait_drdy是个带超时的等待函数这里就不展开了逻辑就是循环判断 DRDY 引脚超时返回错误码。参数单位是毫秒100ms 对复位来说足够宽裕。顺序上硬件复位 → 软件复位 → 等 DRDY → SDATAC → 写寄存器一步都不能少。特别是 SDATAC 那一步我把它单独拎出来说它必须在写任何寄存器之前发而且只需要一次。4.2 SELFCAL 与 DRDY 的等待策略写完寄存器后要做一次自校准。ADS1256 的自校准会测量内部失调和增益误差把结果存到 OFC 和 FSC 寄存器里这个过程耗时跟数据速率有关100SPS 下大概几十毫秒2.5SPS 下能到一秒多。/* 接在上面的 init 后面 */ ads_cmd(0xF0); /* SELFCAL */ if (ads_wait_drdy(2000)) /* 校准完成后 DRDY 会拉低 */ return 2;这里的关键是等 DRDY 而不是死等固定时间。不同 DRATE 下校准时间差十倍以上用固定延时要么不够要么浪费。至于 DRDY 的等待方式100SPS 这种低速场景直接轮询就够了CPU 闲着也是闲着上了 1kSPS 以上就该考虑接 INT0 了。4.3 RDATA 读 24 位与符号扩展取数是整个驱动里最核心的一段。ADS1256 输出 24 位二进制补码范围是 -8388608 到 8388607。/** * 读一次原始码值返回 -8388608 ~ 8388607 */ long ads1256_read_raw(void) { long v; unsigned char b0, b1, b2; if (ads_wait_drdy(200)) /* DRDY 低电平说明有新数据 */ return 0; ADS_CS 0; spi_xfer(0x01); /* RDATA 命令 */ delay_us(2); /* 手册要求的 t6 间隔 */ b0 spi_xfer(0xFF); /* 高 8 位 */ b1 spi_xfer(0xFF); /* 中 8 位 */ b2 spi_xfer(0xFF); /* 低 8 位 */ ADS_CS 1; v ((long)b0 16) | ((long)b1 8) | b2; if (v 0x00800000L) /* 第 23 位是符号位 */ v | 0xFF000000L; /* 手工符号扩展到 32 位 */ return v; }几个细节。第一spi_xfer(0x01)之后必须等一小会儿再读数据手册里叫 t67.68MHz 下不到 1µs但 C51 的函数返回加上位操作差不多就过去了写个 2µs 更保险。第二三个数据字节必须连续读中间 CS 不能抬。第三符号扩展必须做否则负电压读数会被当成一千六百万多显示出来就是“电压突然飙到满量程”。关于第三点再多说一句。C51 里long是 32 位有符号把 24 位补码塞进去做算术运算是可以的但直接赋值v b016|b18|b2之后编译器不知道这是补码得手工做符号扩展。这是 24 位 ADC 驱动里最经典的一步。4.4 从原始码到电压值码值换算公式很简单差分输入下满量程是 ±VREF/PGA一个 LSB 等于2 × VREF / (PGA × 2^24)。VREF 取 2.5V、PGA1 时LSB ≈ 0.298µV。/* 浮点版适合显示层低频调用 */ float raw_to_uv(long raw) { return (float)raw * (5000000.0f / 16777216.0f); /* ±2.5V 对应 ±5,000,000 uV */ } /* 定点版高频循环里用(raw4)*3056 是 0.298 的近似 中间结果最大约 1.6e8不会撑爆 32 位 long */ long raw_to_uv_int(long raw) { return ((raw 4) * 305L) 6; }浮点版直观但 Keil C51 的 float 是软件模拟的一次乘除要几百微秒放在 100SPS 的采集循环里已经吃掉一半 CPU。定点版精度损失约 0.06%对绝大多数应用足够且只做一次乘法和两次移位几微秒就出来了。注意定点版的(raw4)会截掉低 4 位导致小信号时读数跳变为 0。如果测的是微伏级缓变信号还是老老实实用浮点但把它挪到显示/通信层采集中断里只搬原始码。5. 避坑排查本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑