STM32驱动ADS1248多路温度采集实战:从寄存器配置到校准 简介面向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/电桥通用电压采集热电偶/RTDADS1248的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); // PGA32, 采样率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也就是CPOL0、CPHA1。这意味着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对应CPOL0SPI_PHASE_2EDGE对应CPHA1。这个配置如果错了读回来的数据会全部偏移一位表现为数值完全不对。我之前给另一个项目写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总线这时候可以用RDATAC0x14连续读模式。在连续模式下芯片每次转换完成都会自动在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做片选。每片独立初始化、独立配置、独立读取代码结构上做好封装扩展起来非常顺手。本文还有配套的精品资源点击获取