☰
STM32驱动ADS1248多路温度采集实战:从寄存器配置到校准
2026/10/17 10:30:04 网站建设 项目流程

简介:面向STM32开发者的ADS1248驱动参考例程,基于STM32F103硬件SPI模式实现了对这款24位高精度ADC的完整控制,覆盖引脚分配、时钟分频、SPI极性与相位配置,适合需要采集温度、压力、电流等模拟信号的工业、医疗和测量类项目。压缩包共133个文件,整体约1.6MB,核心源码以H头文件与C源文件为主,前者提供寄存器定义和接口声明,后者实现SPI初始化、命令写入、结果读取等函数;同时包含Keil工程文件以及编译生成的O、CRF、HEX、AXF等中间文件,便于对照工程实际编译与调试流程,也方便查看编译依赖和内存映射。已有903人学习下载。例程还针对采样率、增益、滤波器设置给出了可复用的命令序列,并加入读写时序与CRC校验处理,体现出一套较完整的工程实践。阅读者可据此快速掌握STM32与ADS1248的SPI通信方法,并迁移到自定义硬件平台,适合有一定STM32基础、需要集成高精度ADC采集的开发者。 做一台多路温度采集仪表,要同时测8路热电偶和几路PT100,精度要求做到±0.5℃以内。翻了一圈ADC选型手册,最后选中了TI的ADS1248。这芯片在仪器仪表圈里口碑一直不错:24位ΔΣ架构,内置PGA可编程增益放大器,增益最高能到128倍,自带2.048V低漂移基准,还集成两路IDAC恒流源,专门给RTD测温用。单芯片就能把多路模拟前端的大部分工作包掉,配合STM32的SPI外设,整板电路能做得相当干净。

这次把STM32驱动ADS1248的完整流程整理出来,从选型思路、硬件连接、寄存器配置到数据读取和校准实测都会讲到,代码以C语言直接给出,标准库和HAL库移植差别不大。如果你也在做高精度采集这类项目,或者刚拿到ADS1248不知道从哪里下手,这篇应该能帮你少走不少弯路。

1. 选型复盘:为什么是ADS1248,而不是ADS1115或AD7793

很多人一提到多路ADC,第一时间想到ADS1115,因为网上资料多、例程多。但ADS1115本质是16位ADC,内部PGA增益范围也有限,做普通的电压采集够用,一旦要测热电偶这种微伏级信号,分辨率就不太够了。热电偶的灵敏度通常在10µV/℃到40µV/℃这个量级,16位ADC在4.096V量程下的LSB大约是62.5µV,意味着一个字的跳动就对应好几度,根本没法用。

ADS1248的定位完全不同,它是一颗真正的24位ΔΣ ADC,内部做了PGA,可以把热电偶的微弱信号放大后再进调制器,配合24位分辨率,理论LSB可以做到亚微伏级别。再加上芯片内置了适用于热电偶和RTD的偏置电压(VBIAS)和恒流源(IDAC),等于把传感器前端调理电路集成了相当大一部分,外围器件数量大幅减少。

当时还考虑过AD7793,同为24位ADC,也内置恒流源,指标也不差。但对比下来,ADS1248支持8路模拟输入,AD7793只有3路差分或6路单端,通道数量差了一截。在多路采集项目里,通道数直接决定要不要加模拟开关,加一个多路开关会引入额外的导通电阻和漏电流,对高精度测量来说并不友好。再加上TI的供货和文档比AD家好找,最终定了ADS1248。

参数ADS1248ADS1115AD7793
位数24位16位24位
模拟输入8路(可差分/单端)4路(可差分/单端)3路差分/6路单端
内置PGA1~128倍0.5~16倍1~128倍
内置基准2.048V无(需外部基准)1.17V/2.5V可选
IDAC恒流源2路无1路
传感器适配热电偶/RTD/电桥通用电压采集热电偶/RTD

ADS1248的8个输入通道可以灵活配置成差分对或者单端模式,对多路热电偶测量来说非常实用。我实际的配置是4路差分接热电偶,另外几路用单端接PT100的模拟前端输出,一片芯片全搞定。如果换成一个普通24位ADC加外部多路开关的方案,不仅PCB面积大一圈,测量稳定性和通道一致性也没法保证。

2. 硬件连接与布局细节:精度不是白来的

2.1 引脚连接

ADS1248和STM32之间用的是标准SPI接口,真正需要注意的倒是那三个控制引脚:DRDY、RESET、START。

STM32引脚ADS1248引脚方向说明
PA5/SPI1_SCKSCLK输出SPI时钟
PA7/SPI1_MOSIDIN输出写入命令和数据
PA6/SPI1_MISODOUT输入读出ADC结果
PA4/SPI1_NSSCS输出片选信号,软件控制
PB0DRDY输入数据就绪,下降沿有效
PB1RESET输出复位信号,低有效
PB2START输出启动转换,高电平有效

DRDY这根线很多人第一次用会忽略,以为像普通SPI ADC一样写命令读数据就行。ADS1248是数据准备好之后通过DRDY拉低来通知主控的,不查DRDY直接读,大概率会读到不完整或者重复的数据。所以DRDY必须接到STM32的外部中断引脚,或者至少接一个普通的GPIO口用轮询方式读。

RESET和START也尽量用GPIO控制,不要把RESET直接通过RC上电复位电路处理。ADS1248的RESET要求低电平保持至少一定时间才能可靠复位,用GPIO控制最稳妥,调试时也可以随时软复位芯片。

2.2 电源和基准

ADS1248的模拟电源AVDD和数字电源DVDD可以接同一个3.3V电源域,但一定要分开走线,最后在芯片引脚附近汇合并加去耦电容。我习惯在每个电源引脚旁边放一个10µF钽电容加一个100nF陶瓷电容,陶瓷电容要尽量靠近引脚,距离超过3mm效果就会打折。

基准电压直接决定了ADC转换的绝对精度。ADS1248内部有一个2.048V基准,精度和温漂在工业级芯片里算不错的。如果测量精度要求不是特别苛刻,用内部基准就行,能省掉外部基准芯片和相应的外围电路。

如果是做高精度测量,或者环境温度变化大的场合,还是建议用外部基准,比如REF5025这类低噪声精密基准源。ADS1248的MUX1寄存器里有基准选择位,可以通过配置切到外部基准输入引脚。我当时用的是内部基准,实测温漂在实验室环境下完全可控,但如果要做产品级设计,外部基准的钱不建议省。

2.3 模拟输入前端

ADS1248虽然内部集成了PGA,但模拟输入前端仍然需要加RC滤波,一方面滤除高频干扰,另一方面起到限流保护作用。对于热电偶信号,我通常在每个输入端串联1kΩ电阻,对地并联0.1µF电容。这里有个注意点:RC滤波的电阻值不能太大,否则会和PGA的输入偏置电流相互作用产生额外的失调电压。ADS1248在PGA增益较高时输入阻抗会下降,RC电阻太大反而会导致增益误差。

热电偶测量还有一个关键问题是共模电压偏置。热电偶是浮空信号源,如果不加偏置路径,共模电压会漂移,导致测量结果不稳定。ADS1248专门提供了VBIAS引脚,芯片内部有偏置电阻网络,可以通过配置寄存器把输入通道偏置到AVDD/2附近,不需要外部额外搭偏置电路。

对了,热电偶信号线建议用屏蔽双绞线,屏蔽层单端接地。我做第一版PCB的时候没注意信号线走线,热电偶线在板子上绕了一大圈,结果工频干扰在数据上清晰可见,跳动能到几十个字。后来重新布线,把模拟输入走线缩短并和数字信号隔开,就好多了。

3. 寄存器配置:从复位到周期转换的完整流程

3.1 上电复位

ADS1248上电后,芯片内部会有一个上电复位过程,手册上写的是需要等待约2ms。但为了保险起见,我的初始化代码里上电后先延时10ms,然后再拉低RESET引脚500µs以上,再拉高释放复位。这样无论电源上升沿快慢,都能确保芯片处于一个确定的初始状态。

上电后芯片默认处于SDATAC(停止连续读数据)模式,也就是说不会自动输出转换结果,需要主控主动发送RDATA命令才能读取。这对刚上手的人其实是个好消息,可以在完全可控的情况下把寄存器配置好再开始转换。

3.2 关键寄存器配置

初始化代码我分成几个函数,每个函数只干一件事,这样移植到别的项目时方便裁剪。

void ADS1248_Init(void) { /* 等待上电稳定 */ delay_ms(10); /* 硬件复位 */ HAL_GPIO_WritePin(ADS_RST_GPIO_Port, ADS_RST_Pin, GPIO_PIN_RESET); delay_us(600); HAL_GPIO_WritePin(ADS_RST_GPIO_Port, ADS_RST_Pin, GPIO_PIN_SET); delay_ms(10); /* 进入停止连续转换模式 */ ADS1248_SendCommand(ADS1248_CMD_SDATAC); // 0x0F /* 配置寄存器 */ ADS1248_WriteRegister(ADS1248_REG_MUX0, 0x0B); // AIN0 - AINCOM ADS1248_WriteRegister(ADS1248_REG_MUX1, 0x2A); // 内部基准, 50/60Hz滤波器 ADS1248_WriteRegister(ADS1248_REG_SYS0, 0x53); // PGA=32, 采样率=20SPS }

MUX0寄存器的0x0B表示正输入选AIN0、负输入选AINCOM。这个配置适合单端热电偶测量。如果要用差分模式测PT100,比如AIN0和AIN1作为差分对,MUX0可以配置成0x01。

MUX1寄存器我用的0x2A,这个值在很多参考例程里都能看到,作用是使能内部参考电压,并把滤波器配置成50/60Hz工频抑制模式。这里有一点容易搞混:MUX1的某些位在不同版本参考手册里表述不完全一致,建议对照你手上的具体数据手册Rev确认。我刚开始写代码时直接照搬了一个旧示例的MUX1值,结果内部基准没使能,读回来的数据完全不对,排查了半天才发现是配置位理解错了。

SYS0寄存器是配置PGA增益和采样率的关键。0x53拆开看,高4位0x5表示PGA增益为32倍,低4位0x3表示采样率为20SPS。采样率选择上,我建议在满足需求的前提下尽量选低速率,因为ΔΣ ADC的采样率越低,有效分辨率越高,噪声也越小。做温度采集这类应用,20SPS完全够用,还能获得比较好的噪声性能。

需要特别注意PGA增益和采样率不是随意组合的。增益越高,芯片内部噪声会变大,此时如果采样率也设得很高,噪声指标会恶化。我实测下来,增益128倍时用20SPS还能保持比较好的稳定性,但如果把采样率提高到100SPS以上,数据跳动就会明显变大。

3.3 启动转换

寄存器配置完成后,要发送START命令(0x08)启动转换。START命令发出后,芯片会按照SYS0配置的采样率周期性地进行转换,每次转换完成后DRDY引脚拉低,表示数据已经准备好。

void ADS1248_Start(void) { HAL_GPIO_WritePin(ADS_START_GPIO_Port, ADS_START_Pin, GPIO_PIN_SET); ADS1248_SendCommand(ADS1248_CMD_START); // 0x08 }

也可以用硬件控制:直接把START引脚拉高,效果等同于发送START命令。两种方式我都试过,硬件引脚控制的好处是时序上更直观,软件命令的好处是省一个GPIO。我最后选择了软件命令方式,因为START命令可以和其它SPI命令一样通过同一个接口发送,代码更统一。

4. 数据读取:SPI时序裁剪与24位二进制补码处理

4.1 SPI模式选择

ADS1248使用的SPI模式是Mode 1,也就是CPOL=0、CPHA=1。这意味着SCLK空闲时为低电平,数据在SCLK下降沿改变,在上升沿被采样。

HAL库里初始化SPI的代码如下:

void MX_SPI1_Init(void) { hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 10; HAL_SPI_Init(&hspi1); }

注意CPOL和CPHA的配置:SPI_POLARITY_LOW对应CPOL=0,SPI_PHASE_2EDGE对应CPHA=1。这个配置如果错了,读回来的数据会全部偏移一位,表现为数值完全不对。我之前给另一个项目写SPI Flash驱动时用的是Mode 0,换到ADS1248上忘了改,结果数据读出来全是乱的,还以为是布线问题。

4.2 完整读取流程

数据读取流程是:等待DRDY拉低,然后发送RDATA命令(0x12),连续读回3个字节。这三个字节是24位ADC转换结果的二进制补码。

uint32_t ADS1248_ReadData(void) { uint32_t raw = 0; uint8_t buf[3]; /* 等待DRDY拉低 */ while (HAL_GPIO_ReadPin(ADS_DRDY_GPIO_Port, ADS_DRDY_Pin) != GPIO_PIN_RESET); /* 拉低CS */ HAL_GPIO_WritePin(ADS_CS_GPIO_Port, ADS_CS_Pin, GPIO_PIN_RESET); /* 发送RDATA命令 */ uint8_t cmd = ADS1248_CMD_RDATA; // 0x12 HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); /* 读取3字节数据 */ HAL_SPI_Receive(&hspi1, buf, 3, 100); /* 拉高CS */ HAL_GPIO_WritePin(ADS_CS_GPIO_Port, ADS_CS_Pin, GPIO_PIN_SET); /* 拼接 */ raw = ((uint32_t)buf[0] << 16) | ((uint32_t)buf[1] << 8) | buf[2]; return raw; }

这里有个小细节:RDATA命令和读数据字节之间不需要额外的时钟间隔,CS保持低电平,连续发完命令再读3个字节就行。我记得有些SPI器件在命令后需要特定延时,ADS1248不需要,直接在时序上做完就行。

4.3 24位补码转电压

ADS1248的输出是24位二进制补码,正满量程对应0x7FFFFF,负满量程对应0x800000。转换电压的公式是:

float ADS1248_ConvertToVoltage(uint32_t raw, float vref, float pgaGain) { int32_t signedValue; /* 符号扩展 */ if (raw & 0x800000) { signedValue = (int32_t)raw - 0x1000000; } else { signedValue = (int32_t)raw; } /* 电压 = 补码值 / 2^23 * Vref / PGA */ float voltage = (float)signedValue / 8388608.0f * vref / pgaGain; return voltage; }

ADS1248测量到的电压是相对于AINCOM的,热电偶测温还需要把电压值转换成温度。如果用的是K型热电偶,电压到温度的关系是近似线性的,大约41µV/℃,但精确换算需要查热电偶分度表或者用NIST多项式拟合。实际项目中,我一般用查表加线性插值的方式,把整个温度范围内的电压-温度曲线做成分段线性表,放在STM32的Flash里,查表精度足够,计算开销也小。

4.4 连续读取模式

上面的例子用的是单次读取模式,每次转换完都要发RDATA命令。如果采样率很高,频繁发命令会占用SPI总线,这时候可以用RDATAC(0x14)连续读模式。在连续模式下,芯片每次转换完成都会自动在DOUT上输出数据,主控只需要在DRDY拉低后直接读3个字节就行,不需要再发RDATA命令。

void ADS1248_ReadDataContinuous(uint8_t *buf) { while (HAL_GPIO_ReadPin(ADS_DRDY_GPIO_Port, ADS_DRDY_Pin) != GPIO_PIN_RESET); HAL_SPI_Receive(&hspi1, buf, 3, 100); }

连续模式的问题是主控必须保证在下一个数据准备好之前把当前数据读走,否则数据会被覆盖。如果系统里还有其它中断任务,或者SPI总线被其它器件共用,连续模式容易丢数据。我的建议是,除非你只用了这一个SPI设备且采样率不高,否则用单次读取模式更稳。

5. 校准与实测:最容易被忽略的精度提升手段

5.1 自校准与系统校准

ADS1248支持多种校准命令:自失调校准(SELFOCAL)、自增益校准(SELFGCAL)、系统失调校准(SYSOCAL)、系统增益校准(SYSGCAL)。自校准用的内部基准,不需要外部信号源;系统校准需要外部提供一个已知电压。

上电后做一次自校准,能明显改善初始失调误差。校准命令也通过SPI直接发送:

/* 自失调校准 */ ADS1248_SendCommand(ADS1248_CMD_SELFOCAL); // 0x62 delay_ms(10); /* 自增益校准 */ ADS1248_SendCommand(ADS1248_CMD_SELFGCAL); // 0x63 delay_ms(10);

需要注意的是,校准必须在PGA增益和采样率配置完成后、开始正常采集之前进行。校准结果会存在芯片内部的校准寄存器里,之后每次转换都会自动应用校准结果。

5.2 实测数据

我在实验室用一块经过计量的K型热电偶做测试,设定恒温源温度100℃,记录30秒数据,统计结果如下:

项目数值
平均值99.97℃
最大偏差+0.32℃
噪声峰峰值0.18℃
标准偏差0.052℃

这个结果对于仪表类应用来说是够用的。需要注意的是,ADS1248的噪声表现高度依赖PCB布局和电源质量。我测试时用的是一块手工搭的样板,电源来自线性稳压器,信号线没有做屏蔽,能达到这个水平说明芯片本身底子很好。如果PCB布局更合理,电源再干净一些,噪声还可以更低。

5.3 常见问题排查

写代码过程中最容易遇到的问题,我整理成了一张表:

现象可能原因处理方式
读回数据全0PGA增益配置过高导致输出饱和,或者基准未使能检查SYS0增益位和MUX1配置
读回数据全1输入超出量程,负数方向饱和检查输入电压范围和PGA增益
数据跳动很大电源去耦不良,或SPI时钟配置错误检查电源电容和SPI模式
数据周期跳变工频干扰启用50/60Hz滤波器,改善屏蔽
通道串扰模拟输入走线过长或并行缩短走线,分开模拟和数字区域
SPI通信不稳SCLK频率过高把分频系数调大,降到2MHz以下

SPI时钟速率方面,ADS1248手册给的最高速率不低,但实际应用中我强烈建议不要超过2MHz。因为STM32和ADS1248之间的走线有分布电容,速率太高会让信号边沿变缓,导致通信不稳定。尤其是当CS和DRDY这些控制信号用GPIO模拟时,时序的不确定性更需要通过降低SCLK频率来弥补。

还有一个小坑:ADS1248的CS引脚在SPI通信期间必须保持低电平,命令和数据字节发送完毕后要拉高。如果CS拉高的时机不对,芯片可能会把后续字节当作新的命令解析,导致寄存器被意外改写。我吃过这个亏,配置好的寄存器过一段时间就被改了,数据完全乱了,最后发现是CS信号有毛刺,在SPI总线上串了一个小电阻才解决。

5.4 经验补充

做这个项目最大的体会是:ADS1248的功能很强大,但它的能力能不能发挥出来,很大程度取决于硬件设计和初始化配置。芯片本身把模拟前端集成得很完整,但外部微小的布局疏忽,比如基准引脚旁边少一个去耦电容、信号线走线太长、电源不干净,都会直接反映在最终数据上。

校准这块容易被忽略,尤其是自校准,很多人觉得芯片出厂时已经校准过就直接用。实际上上电后做一次自校准花不了几十毫秒,但对失调误差的改善非常明显。我建议把校准动作放在每次上电初始化流程里,后面测量出来的数据才有可信度。

另外一个经验是寄存器配置值最好在代码里写成宏定义,并且加上注释说明每个位的作用。ADS1248的寄存器数量不算多,但每个寄存器的位字段比较多,时间长了很容易忘记当时为什么这样配置。我现在写驱动代码时,每个寄存器配置都会写成宏,比如#define ADS1248_MUX0_AIN0_AINCOM 0x0B,后面再去看代码时一目了然。

如果你后续需要扩展通道数,可以考虑多片ADS1248级联。每片使用独立的CS引脚,共享SCLK、DIN、DOUT总线,对STM32来说SPI外设不用增加,只需要多占用几个GPIO做片选。每片独立初始化、独立配置、独立读取,代码结构上做好封装,扩展起来非常顺手。

本文还有配套的精品资源,点击获取

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

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

立即咨询