简介:这份资源面向使用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模式必须配置成CPOL=0、CPHA=1,也就是Mode 1。很多人在STC15的SPI配置里习惯性选Mode 0,结果就是读回来的数据整体偏移一位,或者完全乱码。STC15的SPI寄存器配置里,CPOL和CPHA分别对应SPCTL寄存器的CPOL和CPHA位。下面这段是初始化代码:// STC15 SPI主机初始化,用于驱动ADS1256 // 注意:ADS1256要求CPOL=0, CPHA=1,即SPI Mode 1 void SPI_Init(void) { SPDAT = 0; // 清空SPI数据寄存器 SPSTAT = 0xC0; // 清除SPIF和WCOL标志 SPCTL = 0xD0; // 1101 0000: SSIG=1(忽略SS), SPEN=1, DORD=0(MSB先出), // MSTR=1(主机), CPOL=0, CPHA=1, SPR=00(最快) // 如果发现数据不稳定,可以把SPR改成01或10降低时钟频率 }这里SPCTL的值0xD0拆开看:bit7 SSIG=1表示忽略SS引脚,用普通IO去控制ADS1256的CS;bit6 SPEN=1使能SPI;bit5 DORD=0表示MSB先出,ADS1256也是MSB先出,这个必须一致;bit4 MSTR=1设为主机;bit3 CPOL=0,bit2 CPHA=1,合起来就是Mode 1;bit1~0 SPR=00,对应STC15系统时钟的4分频。如果STC15跑在24MHz,SPI时钟就是6MHz,ADS1256的最高SCLK频率是1.92MHz(在AVDD=5V时),所以这里其实超频了。实际调试时我一般会把SPR设成10或11,把时钟降到1.5MHz以下。2.2 片选和DRDY信号:别用硬件SS,用普通IOSTC15的硬件SS引脚在SSIG=1时会被忽略,但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.5us(t6),这里用空循环 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寄存器:PGA=1(增益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标志表示写冲突,如果上一次数据还没发完就写SPDAT,WCOL会置位,数据会丢失。所以每次传输前清标志是个好习惯。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在PGA=1时的满量程是±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等,满量程会相应缩小,转换系数也要改。比如PGA=2时满量程是±2.5V,系数就变成2.5/8388607。这个细节在数据手册的“输入范围”章节有详细说明,但很多人直接抄代码,换了增益之后忘了改系数,结果电压算出来差了一倍。4. 避坑与常见问题:调试ADS1256时最容易翻车的几个地方4.1 读回来全是0xFFFFFF或0x000000现象:上电后读数据,不管输入电压怎么变,结果始终是满量程或者零。 原因:最常见的是SPI模式不对。如果CPHA设成0(Mode 0),ADS1256会在错误的边沿采样,读回来的数据全是1或者全是0。另一个可能是DRDY信号没接对,程序一直在读旧数据。 解决:用示波器看SCLK和DIN的波形,确认数据在SCLK的上升沿被ADS1256锁存。如果没有示波器,就把SPI时钟降到最低(SPR=11),然后换CPHA=1再试。4.2 数据跳动很大,最后几位不稳定现象:输入一个稳定的电压,读回来的数值在几十个LSB范围内跳动。 原因:ADS1256的噪声性能和PCB布局、电源质量关系很大。如果AVDD和DVDD没有分开滤波,或者模拟地和数字地混在一起,噪声会直接耦合进ADC。 解决:在AVDD和AGND之间并一个10uF钽电容和一个0.1uF陶瓷电容,DVDD也同样处理。模拟地和数字地单点连接,连接点选在ADC芯片下方。另外,DRATE设得太高也会增加噪声,可以先降到100SPS测试。4.3 自校准后数据反而更差现象:执行SELFCAL命令后,读回来的数据偏移更大或者噪声更严重。 原因:自校准需要输入引脚接一个稳定的共模电压,如果输入悬空或者接的是高阻抗信号源,校准结果会不准确。 解决:校准前把MUX寄存器设成短路输入(比如MUX=0x08,正负输入都接内部短接),等校准完成后再切回正常通道。或者干脆不用自校准,用外部基准电压手动校准。4.4 多通道切换后第一组数据不可信现象:从通道0切换到通道1,读回来的第一个值总是偏差很大。 原因:ADS1256内部的多路开关切换后需要一定的建立时间,如果切换后立刻读数据,采样保持电容还没充到新的电压。 解决:切换通道后丢弃前2~3个转换结果,或者发SYNC命令后等DRDY再读。我一般会在通道切换后加一个10ms的延时,然后连续读3次,取后2次的平均值。4.5 STC15的SPI时钟太快导致通信失败现象:SPI初始化后读寄存器,读回来的值全是0xFF或者0x00。 原因:STC15的SPI时钟由系统时钟分频得到,如果系统时钟是24MHz甚至30MHz,SPI时钟可能超过ADS1256的1.92MHz上限。 解决:把SPCTL的SPR位设成10(16分频)或11(32分频)。以24MHz系统时钟为例,16分频是1.5MHz,32分频是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在主机模式下,如果SSIG=1,SS引脚可以当普通IO用。但如果你不小心把SS引脚配置成了输出低电平,而SSIG又忘了设成1,SPI会一直认为有从机选中,导致传输异常。从那以后我每次初始化SPI,都会先把SSIG位确认一遍,再检查SS引脚的IO模式。希望这些能帮到你,少走几个弯路。本文还有配套的精品资源,点击获取
简介:这份资源面向使用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模式必须配置成CPOL=0、CPHA=1,也就是Mode 1。很多人在STC15的SPI配置里习惯性选Mode 0,结果就是读回来的数据整体偏移一位,或者完全乱码。STC15的SPI寄存器配置里,CPOL和CPHA分别对应SPCTL寄存器的CPOL和CPHA位。下面这段是初始化代码:// STC15 SPI主机初始化,用于驱动ADS1256 // 注意:ADS1256要求CPOL=0, CPHA=1,即SPI Mode 1 void SPI_Init(void) { SPDAT = 0; // 清空SPI数据寄存器 SPSTAT = 0xC0; // 清除SPIF和WCOL标志 SPCTL = 0xD0; // 1101 0000: SSIG=1(忽略SS), SPEN=1, DORD=0(MSB先出), // MSTR=1(主机), CPOL=0, CPHA=1, SPR=00(最快) // 如果发现数据不稳定,可以把SPR改成01或10降低时钟频率 }这里SPCTL的值0xD0拆开看:bit7 SSIG=1表示忽略SS引脚,用普通IO去控制ADS1256的CS;bit6 SPEN=1使能SPI;bit5 DORD=0表示MSB先出,ADS1256也是MSB先出,这个必须一致;bit4 MSTR=1设为主机;bit3 CPOL=0,bit2 CPHA=1,合起来就是Mode 1;bit1~0 SPR=00,对应STC15系统时钟的4分频。如果STC15跑在24MHz,SPI时钟就是6MHz,ADS1256的最高SCLK频率是1.92MHz(在AVDD=5V时),所以这里其实超频了。实际调试时我一般会把SPR设成10或11,把时钟降到1.5MHz以下。2.2 片选和DRDY信号:别用硬件SS,用普通IOSTC15的硬件SS引脚在SSIG=1时会被忽略,但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.5us(t6),这里用空循环 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寄存器:PGA=1(增益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标志表示写冲突,如果上一次数据还没发完就写SPDAT,WCOL会置位,数据会丢失。所以每次传输前清标志是个好习惯。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在PGA=1时的满量程是±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等,满量程会相应缩小,转换系数也要改。比如PGA=2时满量程是±2.5V,系数就变成2.5/8388607。这个细节在数据手册的“输入范围”章节有详细说明,但很多人直接抄代码,换了增益之后忘了改系数,结果电压算出来差了一倍。4. 避坑与常见问题:调试ADS1256时最容易翻车的几个地方4.1 读回来全是0xFFFFFF或0x000000现象:上电后读数据,不管输入电压怎么变,结果始终是满量程或者零。 原因:最常见的是SPI模式不对。如果CPHA设成0(Mode 0),ADS1256会在错误的边沿采样,读回来的数据全是1或者全是0。另一个可能是DRDY信号没接对,程序一直在读旧数据。 解决:用示波器看SCLK和DIN的波形,确认数据在SCLK的上升沿被ADS1256锁存。如果没有示波器,就把SPI时钟降到最低(SPR=11),然后换CPHA=1再试。4.2 数据跳动很大,最后几位不稳定现象:输入一个稳定的电压,读回来的数值在几十个LSB范围内跳动。 原因:ADS1256的噪声性能和PCB布局、电源质量关系很大。如果AVDD和DVDD没有分开滤波,或者模拟地和数字地混在一起,噪声会直接耦合进ADC。 解决:在AVDD和AGND之间并一个10uF钽电容和一个0.1uF陶瓷电容,DVDD也同样处理。模拟地和数字地单点连接,连接点选在ADC芯片下方。另外,DRATE设得太高也会增加噪声,可以先降到100SPS测试。4.3 自校准后数据反而更差现象:执行SELFCAL命令后,读回来的数据偏移更大或者噪声更严重。 原因:自校准需要输入引脚接一个稳定的共模电压,如果输入悬空或者接的是高阻抗信号源,校准结果会不准确。 解决:校准前把MUX寄存器设成短路输入(比如MUX=0x08,正负输入都接内部短接),等校准完成后再切回正常通道。或者干脆不用自校准,用外部基准电压手动校准。4.4 多通道切换后第一组数据不可信现象:从通道0切换到通道1,读回来的第一个值总是偏差很大。 原因:ADS1256内部的多路开关切换后需要一定的建立时间,如果切换后立刻读数据,采样保持电容还没充到新的电压。 解决:切换通道后丢弃前2~3个转换结果,或者发SYNC命令后等DRDY再读。我一般会在通道切换后加一个10ms的延时,然后连续读3次,取后2次的平均值。4.5 STC15的SPI时钟太快导致通信失败现象:SPI初始化后读寄存器,读回来的值全是0xFF或者0x00。 原因:STC15的SPI时钟由系统时钟分频得到,如果系统时钟是24MHz甚至30MHz,SPI时钟可能超过ADS1256的1.92MHz上限。 解决:把SPCTL的SPR位设成10(16分频)或11(32分频)。以24MHz系统时钟为例,16分频是1.5MHz,32分频是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在主机模式下,如果SSIG=1,SS引脚可以当普通IO用。但如果你不小心把SS引脚配置成了输出低电平,而SSIG又忘了设成1,SPI会一直认为有从机选中,导致传输异常。从那以后我每次初始化SPI,都会先把SSIG位确认一遍,再检查SS引脚的IO模式。希望这些能帮到你,少走几个弯路。本文还有配套的精品资源,点击获取