简介这份资源面向使用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读出来的数据要么全是0要么全是0xFFFFFF要么跳得跟心电图一样。问题往往不在芯片本身而在于ADS1256的时序要求和STC15的SPI外设之间存在几个容易被忽略的细节。这份资源就是围绕“STC15硬件SPI驱动ADS1256”这个具体场景展开的包含完整的驱动代码、初始化流程和寄存器配置。它适合已经熟悉STC15基本开发、需要把高精度ADC集成进项目的开发者也适合想搞明白“SPI驱动ADC到底要注意什么”的嵌入式新手。下面我按实际调试顺序把这份代码拆开讲一遍。2. 先搞清楚ADS1256的SPI时序和STC15的硬件SPI能不能对上2.1 ADS1256的SPI模式不是标准Mode 0那么简单ADS1256的SPI接口在数据手册里写的是“SPI兼容”但它有几个特殊之处。首先它的SCLK空闲电平可以是高也可以是低取决于你怎么配置。但关键在于ADS1256在SCLK的下降沿输出数据对MCU来说是采样在上升沿锁存数据对MCU来说是输出。这意味着如果STC15作为主机它的SPI模式必须配置成CPOL0、CPHA1也就是Mode 1。很多人在STC15的SPI配置里习惯性选Mode 0结果就是读回来的数据整体偏移一位或者完全乱码。STC15的SPI寄存器配置里CPOL和CPHA分别对应SPCTL寄存器的CPOL和CPHA位。下面这段是初始化代码// STC15 SPI主机初始化用于驱动ADS1256 // 注意ADS1256要求CPOL0, CPHA1即SPI Mode 1 void SPI_Init(void) { SPDAT 0; // 清空SPI数据寄存器 SPSTAT 0xC0; // 清除SPIF和WCOL标志 SPCTL 0xD0; // 1101 0000: SSIG1(忽略SS), SPEN1, DORD0(MSB先出), // MSTR1(主机), CPOL0, CPHA1, SPR00(最快) // 如果发现数据不稳定可以把SPR改成01或10降低时钟频率 }这里SPCTL的值0xD0拆开看bit7 SSIG1表示忽略SS引脚用普通IO去控制ADS1256的CSbit6 SPEN1使能SPIbit5 DORD0表示MSB先出ADS1256也是MSB先出这个必须一致bit4 MSTR1设为主机bit3 CPOL0bit2 CPHA1合起来就是Mode 1bit1~0 SPR00对应STC15系统时钟的4分频。如果STC15跑在24MHzSPI时钟就是6MHzADS1256的最高SCLK频率是1.92MHz在AVDD5V时所以这里其实超频了。实际调试时我一般会把SPR设成10或11把时钟降到1.5MHz以下。2.2 片选和DRDY信号别用硬件SS用普通IOSTC15的硬件SS引脚在SSIG1时会被忽略但ADS1256需要两个控制信号CS和DRDY。CS可以一直拉低如果总线上只有这一个从机但DRDY必须接到一个外部中断引脚或者用轮询方式检测。DRDY是ADS1256的数据就绪信号低电平有效每次转换完成后它会拉低直到你读走数据或者发下一个命令。常见做法是把DRDY接到STC15的INT0或INT1引脚用外部中断下降沿触发。但如果你不想用中断也可以在读数据前轮询DRDY引脚等它变低再读。下面这段是轮询方式的读数据函数// 等待DRDY变低然后读取24位ADC数据 // 返回值24位有符号数扩展成32位返回 int32_t ADS1256_ReadData(void) { uint32_t timeout 0; int32_t result 0; ADS1256_CS 0; // 拉低片选 while (ADS1256_DRDY 1) { // 等待DRDY变低 timeout; if (timeout 100000) { // 超时保护防止死等 ADS1256_CS 1; return 0x80000000; // 返回一个明显的错误值 } } // 发送RDATA命令读取最新转换结果 SPI_WriteByte(ADS1256_CMD_RDATA); // ADS1256的RDATA命令后需要延时约6.5ust6这里用空循环 Delay_us(7); // 连续读3个字节MSB在前 result SPI_ReadByte() 16; result | SPI_ReadByte() 8; result | SPI_ReadByte(); // 24位有符号数如果最高位是1需要符号扩展 if (result 0x00800000) { result | 0xFF000000; } ADS1256_CS 1; return result; }这段代码里有两个关键点一是RDATA命令发出后必须延时至少6.5微秒t6参数否则读回来的数据可能是上一次的或者无效的二是24位数据读回来后要做符号扩展因为ADS1256输出的是二进制补码格式。很多人在这一步忘了符号扩展结果负电压读出来变成很大的正数。2.3 寄存器配置增益、速率、通道怎么设ADS1256内部有11个寄存器常用的有STATUS、MUX、ADCON、DRATE、IO。上电后必须先配置这些寄存器否则芯片处于默认状态可能不转换或者转换速率不对。下面是一个典型的初始化序列// ADS1256寄存器初始化 void ADS1256_Init(void) { ADS1256_CS 0; // 发送复位命令让芯片回到已知状态 SPI_WriteByte(ADS1256_CMD_RESET); Delay_ms(10); // 复位后需要延时 // 配置STATUS寄存器自动校准关闭使用缓冲器 ADS1256_WriteReg(ADS1256_REG_STATUS, 0x02); // 配置MUX寄存器选择通道0为正输入通道1为负输入 ADS1256_WriteReg(ADS1256_REG_MUX, 0x01); // 配置ADCON寄存器PGA1(增益1)关闭传感器检测 ADS1256_WriteReg(ADS1256_REG_ADCON, 0x00); // 配置DRATE寄存器设置为1000SPS具体值查数据手册 ADS1256_WriteReg(ADS1256_REG_DRATE, 0x82); // 配置IO寄存器关闭所有GPIO输出 ADS1256_WriteReg(ADS1256_REG_IO, 0x00); // 发送自校准命令 SPI_WriteByte(ADS1256_CMD_SELFCAL); while (ADS1256_DRDY 1); // 等待校准完成 ADS1256_CS 1; }写寄存器的时候要注意ADS1256的寄存器写操作是“命令寄存器地址数据”的格式而且寄存器地址要加上写标志0x50。读寄存器则是“命令寄存器地址读标志0x10数据”。这些细节在数据手册里都有但很容易看漏。我一般会把读写寄存器的函数单独封装避免每次都要查手册。3. 完整驱动代码拆解从SPI字节收发到连续采样3.1 SPI字节收发STC15的硬件SPI怎么用STC15的硬件SPI收发共用一个寄存器SPDAT。发送时把数据写入SPDAT然后等待SPIF标志置位再从SPDAT读出接收到的数据。下面这两个函数是驱动的基础// SPI发送并接收一个字节 // 参数dat - 要发送的字节 // 返回接收到的字节 uint8_t SPI_WriteByte(uint8_t dat) { SPSTAT 0xC0; // 清除SPIF和WCOL标志 SPDAT dat; // 写入数据启动传输 while (!(SPSTAT 0x80)); // 等待SPIF置位表示传输完成 return SPDAT; // 读取接收到的数据 } // 只读一个字节发送0xFF作为哑元 uint8_t SPI_ReadByte(void) { return SPI_WriteByte(0xFF); }这里有个坑STC15的SPI在传输完成后SPIF标志不会自动清除必须手动写1清除。如果你在循环里连续调用SPI_WriteByte不清标志的话第二次就会卡死在while循环里。另外WCOL标志表示写冲突如果上一次数据还没发完就写SPDATWCOL会置位数据会丢失。所以每次传输前清标志是个好习惯。3.2 命令和寄存器的读写封装ADS1256的命令分两类一类是控制命令如RDATA、RESET、SELFCAL另一类是寄存器读写。寄存器读写需要先发命令字节再发地址然后才是数据。下面是对应的封装// 写ADS1256寄存器 // 参数reg - 寄存器地址0x00~0x0A // value - 要写入的值 void ADS1256_WriteReg(uint8_t reg, uint8_t value) { SPI_WriteByte(ADS1256_CMD_WREG | reg); // 写寄存器命令地址 SPI_WriteByte(0x00); // 只写1个寄存器 SPI_WriteByte(value); // 写入数据 } // 读ADS1256寄存器 // 参数reg - 寄存器地址 // 返回寄存器当前值 uint8_t ADS1256_ReadReg(uint8_t reg) { SPI_WriteByte(ADS1256_CMD_RREG | reg); // 读寄存器命令地址 SPI_WriteByte(0x00); // 只读1个寄存器 return SPI_WriteByte(0xFF); // 发送哑元读取数据 }注意WREG命令后面跟的第二个字节是“要写的寄存器数量减1”。比如只写1个寄存器这个字节就是0x00写2个就是0x01。很多人在这里直接写0x01结果多写了一个寄存器把后面的配置覆盖了。读寄存器同理第二个字节是“要读的数量减1”。3.3 连续采样和数据处理单次采样只能看到瞬时值实际项目里通常需要连续采样做滤波或者计算有效值。下面是一个连续采样的示例每100毫秒读一次数据并通过串口输出// 连续采样并输出 void ADS1256_ContinuousSample(void) { int32_t adcValue; float voltage; while (1) { adcValue ADS1256_ReadData(); // ADS1256在PGA1时的满量程是±5V对应±0x7FFFFF // 转换为电压值单位V voltage (float)adcValue / 8388607.0f * 5.0f; // 通过串口输出假设串口已初始化 printf(ADC: %ld, Voltage: %.6f V\r\n, adcValue, voltage); Delay_ms(100); } }这里的转换系数8388607是2的23次方减1对应正满量程。如果PGA设成2、4、8等满量程会相应缩小转换系数也要改。比如PGA2时满量程是±2.5V系数就变成2.5/8388607。这个细节在数据手册的“输入范围”章节有详细说明但很多人直接抄代码换了增益之后忘了改系数结果电压算出来差了一倍。4. 避坑与常见问题调试ADS1256时最容易翻车的几个地方4.1 读回来全是0xFFFFFF或0x000000现象上电后读数据不管输入电压怎么变结果始终是满量程或者零。 原因最常见的是SPI模式不对。如果CPHA设成0Mode 0ADS1256会在错误的边沿采样读回来的数据全是1或者全是0。另一个可能是DRDY信号没接对程序一直在读旧数据。 解决用示波器看SCLK和DIN的波形确认数据在SCLK的上升沿被ADS1256锁存。如果没有示波器就把SPI时钟降到最低SPR11然后换CPHA1再试。4.2 数据跳动很大最后几位不稳定现象输入一个稳定的电压读回来的数值在几十个LSB范围内跳动。 原因ADS1256的噪声性能和PCB布局、电源质量关系很大。如果AVDD和DVDD没有分开滤波或者模拟地和数字地混在一起噪声会直接耦合进ADC。 解决在AVDD和AGND之间并一个10uF钽电容和一个0.1uF陶瓷电容DVDD也同样处理。模拟地和数字地单点连接连接点选在ADC芯片下方。另外DRATE设得太高也会增加噪声可以先降到100SPS测试。4.3 自校准后数据反而更差现象执行SELFCAL命令后读回来的数据偏移更大或者噪声更严重。 原因自校准需要输入引脚接一个稳定的共模电压如果输入悬空或者接的是高阻抗信号源校准结果会不准确。 解决校准前把MUX寄存器设成短路输入比如MUX0x08正负输入都接内部短接等校准完成后再切回正常通道。或者干脆不用自校准用外部基准电压手动校准。4.4 多通道切换后第一组数据不可信现象从通道0切换到通道1读回来的第一个值总是偏差很大。 原因ADS1256内部的多路开关切换后需要一定的建立时间如果切换后立刻读数据采样保持电容还没充到新的电压。 解决切换通道后丢弃前2~3个转换结果或者发SYNC命令后等DRDY再读。我一般会在通道切换后加一个10ms的延时然后连续读3次取后2次的平均值。4.5 STC15的SPI时钟太快导致通信失败现象SPI初始化后读寄存器读回来的值全是0xFF或者0x00。 原因STC15的SPI时钟由系统时钟分频得到如果系统时钟是24MHz甚至30MHzSPI时钟可能超过ADS1256的1.92MHz上限。 解决把SPCTL的SPR位设成1016分频或1132分频。以24MHz系统时钟为例16分频是1.5MHz32分频是0.75MHz都在安全范围内。如果还不行就检查STC15的时钟配置确认没有开倍频。5. 二次开发怎么改从单通道到多通道轮询的改造思路这份代码目前是单通道采样实际项目里经常需要轮询多个通道。改造的核心在于MUX寄存器的切换和采样时序的调整。下面是我一般会用的多通道轮询框架// 多通道轮询采样 // 参数channels - 通道数组每个元素是MUX寄存器的值 // numChannels - 通道数量 // results - 存储结果的数组 void ADS1256_ScanChannels(uint8_t *channels, uint8_t numChannels, int32_t *results) { uint8_t i; for (i 0; i numChannels; i) { ADS1256_CS 0; // 切换通道 ADS1256_WriteReg(ADS1256_REG_MUX, channels[i]); // 发送SYNC命令让ADC重新同步 SPI_WriteByte(ADS1256_CMD_SYNC); // 等待DRDY变低表示新通道的数据准备好 while (ADS1256_DRDY 1); // 读数据 results[i] ADS1256_ReadData(); ADS1256_CS 1; // 通道间加一点延时让输入建立 Delay_ms(5); } }这个框架里SYNC命令的作用是让ADS1256立即开始一次新的转换而不是等内部定时器。如果不发SYNC切换MUX后要等一个完整的转换周期才能读到新通道的数据对于低速率的配置比如10SPS来说就是100毫秒的等待。加了SYNC之后转换立即开始DRDY很快就能变低。另一个改造方向是加入软件滤波。ADS1256本身有硬件滤波但在工频干扰严重的场合软件上再做一层滑动平均或者中值滤波会更稳。我一般会开一个长度为8的环形缓冲区每次读到的数据放进去输出时取中值。这样既能抑制突发噪声又不会引入太大的滞后。如果你用的是STC15F2K60S2或者STC15W4K系列还可以把SPI传输改成中断方式避免轮询DRDY时阻塞主循环。不过对于大多数采样率不高的场景轮询已经够用了。改中断的话要注意SPI中断里不能做太耗时的操作读数据最好还是放在主循环里中断只负责置一个标志位。最后说一个我踩过的坑STC15的SPI在主机模式下如果SSIG1SS引脚可以当普通IO用。但如果你不小心把SS引脚配置成了输出低电平而SSIG又忘了设成1SPI会一直认为有从机选中导致传输异常。从那以后我每次初始化SPI都会先把SSIG位确认一遍再检查SS引脚的IO模式。希望这些能帮到你少走几个弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?