简介:这是一份面向嵌入式开发者的ADS1248芯片SPI驱动源码包,聚焦16位高精度ADC的底层通信实现,适合需要快速集成传感器采集、工业自动化或医疗测量功能的单片机项目。驱动采用C语言编写,包含ads1248.c与ads1248.h两个核心文件,分别实现SPI读写、PGA增益配置、数字滤波器设置以及转换启停等函数,并配套README说明文档,便于理解调用流程与引脚连接。压缩包仅3个文件、大小约4KB,轻量简洁,可直接复制到工程中使用。已有310人学习下载。对不熟悉ADS1248寄存器操作或SPI时序的开发者而言,这份驱动省去了查阅数据手册和调试底层通信的步骤,能显著缩短高精度模拟采集模块的开发周期。若配合手册使用,还可借此掌握多通道单端输入、可编程增益放大器及低功耗模式等典型配置方法。
1. ADS1248 驱动代码包背后的第一课:24 位精度不是靠接对线就有的
从网上下到一个叫 ADS1248-Drivers-master_spi_ads1248_ 的驱动包,第一步往往是高兴,第二步是翻车——很多人把里面的 .c 文件拷进工程、把 STM32CubeMX 里的 SPI 速度直接拉到 18M,读回来的数据却全在乱跳。ADS1248 是一颗 24 位 Δ-Σ ADC,标称精度看着美好,但能不能把 24 位分辨率变成真实测量精度,取决于驱动里的 SPI 时序、寄存器参数和硬件布局,跟引脚号没关系。这套驱动代码包正好可以作为入口,带你从“能读到数”走到“读得准”。适合正在做高精度数据采集、称重、温压测量的嵌入式工程师,也适合被 SPI 通信折腾到怀疑人生的新手。
2. ADS1248 为什么难驱动:24 位 Δ-Σ ADC 的硬件边界与 SPI 时序
ADS1248 不是拿出来就有高精度的芯片。它的精密性来自一整套模拟链路:输入多路选择器、可编程增益放大器(PGA)、24 位 Δ-Σ 调制器、数字滤波器,再加上片内温度传感器和参考电压源。任何一个环节用错,读回来的数据都会让你误以为芯片是坏的。
2.1 普通 16 位 ADC 和 ADS1248 差在哪:噪声、PGA 和内置参考
先明确一个事实:普通 16 位 SAR ADC 的真实有效位数往往只有 12 位左右,因为量化噪声和参考源噪声会吃掉低几位。ADS1248 能到 24 位,靠的是过采样和数字滤波,把带内噪声压得很低。但这不是免费的,数字滤波器需要时间,所以数据速率越低,噪声越小。5SPS 下面噪声远小于 2000SPS 下面,这是 Δ-Σ 结构本身的特性,不要拿 SAR 芯片“速率越高越准”的思路来套。
PGA 增益范围 1 到 128,对热电偶这种几十微伏到几十毫伏的小信号特别重要。没有 PGA,24 位分辨率只是数字上的花架子,小信号会被前端噪声盖掉。但 PGA 不是白放的:增益越大,输入阻抗越低、失调和温漂的影响越明显,对外部信号源阻抗也更敏感。用 128 倍增益去接一个 10kΩ 源阻抗的传感器,读数基本不可能稳定。
内置参考是 2.048V,温漂典型在 10ppm/°C 这个量级,很多工业场景够用。但对长期漂移要求高的称重仪表,我会选外部精密参考,因为内部参考的绝对值误差还能用软件校准,温漂却很难通过单点校准补回来。内部温度传感器也别当摆设,热电偶冷端补偿直接读它最省事。
2.2 SPI 通信协议里的模式 1 陷阱:CPOL/CPHA 与数据输出时序
这是驱动包最难搞的部分。ADS1248 的 SPI 要求模式 1,也就是 CPOL=0、CPHA=1:空闲时时钟为低,数据在第二个边沿(下降沿)被采样。常见误区是直接用 STM32CubeMX 默认的模式 0(CPOL=0、CPHA=0),结果命令发出去、数据读回来全是 0xFF 或者乱码。
看 SPI 时序图会清楚很多:模式 0 和模式 1 就差一个相位的采样沿,命令字相同,但 DOUT 上的数据会错开半个位周期。示波器抓 SCLK 和 DOUT 时,只要对着手册上的 SPI 时序图比对一次,就能发现是采样沿选错。很多下载到的驱动包默认按模式 0 写,是因为作者用某颗国产 MCU 的硬件 SPI 时发现模式 0 也“能跑”,实际是把 CPOL 和 CPHA 的组合理解反了。我的习惯是:任何一颗 SPI 芯片,第一件事就是查手册里的时序图,确认是模式几,再在 CubeMX 里选。
ADS1248 的命令分两类:一类是单字节命令,比如 RESET(0x06)、START(0x08)、WAKEUP(0x02);另一类是寄存器读写命令 RREG(0x20 + 地址)和 WREG(0x40 + 地址)。读寄存器时,第二个字节必须写“寄存器数量减 1”,这个字节太容易踩坑——只读一个寄存器写 0x00 没错,写 0x01 就会多读一个寄存器,后面的数据全错位。
2.3 软件片选、硬件片选和 DRDY:板级接法决定驱动复杂度
很多人喜欢用 STM32 的硬件 NSS 自动控制片选,这在只有一个从机的板子上问题不大,但 ADS1248 的 CS 要求在每笔 SPI 事务结束后拉高一段时间,硬件 NSS 在多从机共线时很难把时序做干净。我一般用 GPIO 软件片选,把 CS 当作普通推挽输出,读写前拉低、结束再拉高,干净利落。这个选择也会直接影响后续能不能和 W25Q64 这类 SPI Flash 共用总线。
DRDY 是数据就绪输出,转换完成后由高变低,读取数据后恢复高。它最好接 MCU 的外部中断引脚,下降沿触发;如果只是用 GPIO 查询,驱动里务必要加超时。否则初始化没配好、SPI 又卡住的时候,整个程序会死在 while 等 DRDY 的循环里。上电时序也要注意:模拟电源和数字电源要同时上电,RESET 引脚建议由 MCU 的 GPIO 控制,不要在 PCB 上直接悬空。
3. 把 Drivers 包里的思路移植到 STM32:从 CubeMX 建工程到连续读出 24 位数据
标题里那个 Drivers 目录结构,习惯上会是底层 SPI 加寄存器层再加应用层的三层结构。很多下载包把三个层次全塞进一个文件里,移植时看着省事,后面调参数就痛苦了。我更推荐自己写一套最小驱动,核心就四个函数:底层发一个字节、写寄存器、读寄存器、读转换数据。
3.1 最小系统与 CubeMX 配置:先以 1MHz SPI 把时序跑起来
第一步不是写代码,是先把 CubeMX 的 SPI 配置做对。使用 STM32F103 或 F407,任意外设 SPI 都行,关键是这三个参数:波特率预分频先选大一点,让 SPI 时钟在 1MHz 左右;时钟极性 CPOL 设为 Low;时钟相位 CPHA 设为 2 Edge,也就是模式 1。CS 和 DRDY 都配成 GPIO,CS 推挽输出,DRDY 输入,可选的 RESET 也配成 GPIO 推挽输出。
先跑低速的原因很简单:ADS1248 的 24 位数据输出不像 Flash 那么抗造,高速下 PCB 走线寄生电容会让沿变缓,初期排查时分不清是时序问题还是硬件问题。低速调通后,再把波特率升上去,比如先改到 4MHz,逐级验证。下面这段是把整个驱动和 HAL 耦合收口的底层函数,所有上层函数都只调它。
#define ADS1248_CS_LOW() HAL_GPIO_WritePin(ADS1248_CS_GPIO_Port, ADS1248_CS_Pin, GPIO_PIN_RESET) #define ADS1248_CS_HIGH() HAL_GPIO_WritePin(ADS1248_CS_GPIO_Port, ADS1248_CS_Pin, GPIO_PIN_SET) static uint8_t adc_spi_byte(uint8_t tx) { uint8_t rx = 0; while (HAL_SPI_GetState(&hspi2) != HAL_SPI_STATE_READY) {} if (HAL_SPI_TransmitReceive(&hspi2, &tx, &rx, 1, 100) != HAL_OK) { return 0xFF; } return rx; }这个函数同时发送一个字节并接收一个字节,SPI 全双工模式下必须这么写,因为读数据时也要给 SCLK 提供时钟。超时时间我习惯设 100ms,调试时如果频繁返回 0xFF,多半是模式没配对或时钟没起来。这个函数只依赖 hspi2,换 SPI 外设时只改这一处。
3.2 寄存器级驱动层:WREG/RREG 命令序列与初始化函数
寄存器层要按手册的命令字实现读写,重点是把“命令字节、数量字节、数据字节”的顺序写对。写寄存器时,发送 WREG 命令后跟数量字节,再跟数据;读寄存器时,发送 RREG 命令后跟数量字节,再读一个字节。每次事务都要拉低 CS 开始、拉高 CS 结束,中间不能停太久。
#define ADS1248_CMD_RESET 0x06 #define ADS1248_CMD_START 0x08 #define ADS1248_CMD_RREG 0x20 #define ADS1248_CMD_WREG 0x40 void ADS1248_Reset(void) { ADS1248_CS_LOW(); adc_spi_byte(ADS1248_CMD_RESET); ADS1248_CS_HIGH(); HAL_Delay(10); } void ADS1248_WriteReg(uint8_t addr, uint8_t value) { ADS1248_CS_LOW(); adc_spi_byte(ADS1248_CMD_WREG | (addr & 0x07)); adc_spi_byte(0x00); // 数量字节,写 1 个寄存器所以是 0x00 adc_spi_byte(value); ADS1248_CS_HIGH(); } uint8_t ADS1248_ReadReg(uint8_t addr) { uint8_t value = 0; ADS1248_CS_LOW(); adc_spi_byte(ADS1248_CMD_RREG | (addr & 0x07)); adc_spi_byte(0x00); value = adc_spi_byte(0x00); ADS1248_CS_HIGH(); return value; }数量字节是 8 位寄存器个数减 1,写一个寄存器必须是 0x00。总有人拿着别的芯片例程写成 0x01,结果 ADC 把后一个寄存器也覆盖掉。读回校验是很好的习惯:写完寄存器立刻读回来对比,能排除大部分 SPI 配置问题。
初始化函数按典型顺序做四件事:等电源稳定、复位、配置寄存器、启动转换。寄存器配置先用保守参数,后面再调:
void ADS1248_Init(void) { ADS1248_CS_HIGH(); HAL_Delay(50); // 等待电源和内部参考稳定 ADS1248_Reset(); ADS1248_WriteReg(0x00, 0x00); // MUX0:关闭断电检测,输入由MUX1决定 ADS1248_WriteReg(0x01, 0x00); // VBIAS:关闭所有偏置电压 ADS1248_WriteReg(0x02, 0x00); // MUX1:默认差分通道 AIN0/AIN1 ADS1248_WriteReg(0x03, 0x34); // SYS0:PGA=8, 数据速率 20SPS ADS1248_CS_LOW(); adc_spi_byte(ADS1248_CMD_START); ADS1248_CS_HIGH(); }0x34 是我常用的一个工程起点,高四位 0011 对应 PGA 增益 8 倍,低四位 0100 对应 20SPS。这个值在热电偶和大部分模拟小信号的场景下都够用,后续要改,只需要动 SYS0 这一个寄存器。MUX1 的 0x00 默认配置取决于你的板卡输入通道,别照抄完就以为选对通道了。
3.3 连续转换读取:DRDY 查询、24 个时钟与二进制补码拼解
数据读取是驱动里最关键的部分。ADS1248 在连续转换模式下,DRDY 会周期性地拉低表示新数据就绪。读取时要等 DRDY 变低,然后拉低 CS,连发 24 个时钟把三个字节从 DOUT 移出来。不需要发送读数据命令,直接给时钟就能拿到最新转换结果,这是 ADS1248 给硬件设计的一个方便点。
int ADS1248_WaitData(uint32_t timeout_ms) { uint32_t start = HAL_GetTick(); while (HAL_GPIO_ReadPin(ADS1248_DRDY_GPIO_Port, ADS1248_DRDY_Pin) != GPIO_PIN_RESET) { if (HAL_GetTick() - start > timeout_ms) { return -1; } } return 0; } int ADS1248_ReadData(int32_t *raw) { uint8_t buf[3]; if (ADS1248_WaitData(100) != 0) { return -1; } ADS1248_CS_LOW(); buf[0] = adc_spi_byte(0x00); buf[1] = adc_spi_byte(0x00); buf[2] = adc_spi_byte(0x00); ADS1248_CS_HIGH(); *raw = (int32_t)(((uint32_t)buf[0] << 16) | ((uint32_t)buf[1] << 8) | ((uint32_t)buf[2])); return 0; }这里最容易错的是符号扩展。buf[0] 最高位为 1 时,整个 24 位数是负数,直接拼成 int32_t 会得到一个大正数。ADS1248 输出的是二进制补码,所以我把三个字节先拼成 uint32_t,再强制转成 int32_t,用 C 语言的位模式转换直接解释成负数,逻辑简单也不会漏。电压换算公式我一般这样写:
float ADS1248_ToVoltage(int32_t raw, float vref, float pga) { return (float)raw * (2.0f * vref / pga) / 8388608.0f; }公式的含义是:满量程对应 ±VREF/PGA,24 位补码的满码范围是 ±2 的 23 次方,所以每个 LSB 对应 2VREF/(PGA×2^24)。如果你的项目要求更高精度,这里可以按手册的 LSB 定义再核对一次,不同版本手册表述略有差异。
4. ADS1248 的六个必调参数与寄存器配置:增益、速率、滤波器要一起看
驱动能读出数只是第一步,参数配不好,读出来的数没法用。ADS1248 的寄存器配置里最要命的不是命令字,而是增益、数据速率、滤波器和参考源之间的耦合关系。
4.1 数据速率与 PGA 增益联动:为什么“越高越好”在这里不成立
很多人上来就把数据速率设到最高,理由是“采样快一点,数据多点,求平均更准”。这在 Δ-Σ ADC 上是反向工程:数字滤波器带宽跟随数据速率变化,速率越高,通带越宽,带内噪声越多。更关键的是,PGA 增益越高,内部放大器可用带宽越小,高增益配高数据速率时,信号根本没建立完就被采样了。
工程上我一般按“信号带宽的 5 到 10 倍”选数据速率。热电偶信号本身只有几 Hz,20SPS 已经足够;称重传感器动态响应慢,10SPS 更合适,还能顺便把工频干扰压掉。增益的选择反过来:先用示波器看信号幅值,再用“目标满量程÷输入信号幅值”估算增益,别直接上 128。
4.2 SYS0 寄存器典型配置:三组测量场景的参数对照表
SYS0 寄存器是 ADC 配置的核心,高四位选 PGA 增益,低四位选数据速率。下面这三组配置是我在实际板卡上验证过的起始点,不是唯一答案,但足够新手下第一版固件:
| 测量场景 | 输入信号典型范围 | PGA | 数据速率 | 参考源选择 | 注意事项 |
|---|---|---|---|---|---|
| 热电偶测温 | ±10mV 级别 | 128 | 5SPS | 内部 2.048V | 开启内部温度传感器做冷端补偿 |
| 称重传感器 | ±10mV 到 ±20mV | 64 到 128 | 10SPS | 内部 2.048V | 传感器激励源要单独滤波 |
| 压力变送器 4-20mA | 1V 到 5V(250Ω 采样电阻) | 1 到 4 | 20SPS | 外部 2.5V 参考 | 输入信号幅值大,重点是共模电压 |
配好之后一定要读回 SYS0 确认。很多代码包初始化函数里写了一大串寄存器,实际 SPI 模式不对,数据根本没写进去。读回校验会立刻暴露问题。
4.3 内部参考和外部参考的取舍:2.048V、漂移和参考去耦
内部参考 2.048V 省事,但精度和温漂不是顶级。做批量产品时,每一片 ADS1248 的内部参考都存在个体差异,靠软件校准能拉回增益误差,却很难拉回温漂。外部参考一般选 2.5V 或 4.096V,前提是 ADC 模拟供电要足够高,比如 5V 供电、2.5V 参考就比 2.048V 更安全。
外部参考的 PCB 设计比选型更重要。参考输出端要放 10μF 钽电容并联 0.1μF 陶瓷电容,去耦电容离 REF 引脚越近越好。千万不要用普通电阻分压直接从电源取参考电压,ADC 内部开关电容采样会在参考引脚上抽取瞬态电流,分压网络的阻抗稍微高一点,参考电压就被调制出毛刺,读数低几位立刻开始跳。
4.4 输入通道与 MUX 的设置:单端、差分和伪差分
ADS1248 有 8 个输入引脚,可以配成差分或单端,但严格说都是伪差分:所有输入都以 AGND 为参考。所以共模电压必须在 GND+0.1V 到 AVDD-0.1V 这个范围内,超过了一点点,PGA 就会饱和。工程师常在这里翻车,以为只要信号差值是安全的就行,忽略了输入引脚对地的绝对电压。
MUX0 和 MUX1 两个寄存器配合决定输入方式。MUX0 还带一个断电检测位,如果软件里不小心把它设成 1,输入通道会被切到内部检测路径,读数完全错误。初始化前最好把这个寄存器读回来看一眼。
5. ADS1248 驱动调试避坑:五条高频翻车记录与排查路径
下面这五条问题,是我在十来个项目里见过最多的,每条都按现象、原因、解决的顺序写清楚。照着排查,大多能在半小时内定位。
5.1 读出全 0:先查 SPI 模式,别急着怀疑芯片
现象:写寄存器后读回来全是 0x00,转换数据也全是 0,片选和电源都正常。
原因:八成是 SPI 模式配置成模式 0 了,数据采样沿和 ADS1248 要求的模式 1 差半个时钟周期。也有少量情况是 SPI 时钟极性反了,DOUT 数据在错的沿被采到。硬件上 CS、SCLK、DIN、DOUT 四根线全部接反的可能性也有,但少见。
解决:先在 CubeMX 里把 Mode 改成 CPOL=Low、CPHA=2 Edge,重新生成代码。再用示波器或逻辑分析仪抓 SCLK 和 DOUT,对比手册里的 SPI 时序图。抓不到就写一个只读固定寄存器 0x00 的函数,用 1MHz 时钟反复读,一般几十秒就能确认。
5.2 DRDY 悬空导致程序卡死:超时机制和上拉接法
现象:程序运行到等待 DRDY 的循环里永远出不来,看门狗也不生效,整机假死。
原因:DRDY 引脚悬空,或者外部中断没配置成下降沿触发。ADS1248 的 DRDY 在每个转换周期都会拉低一次,如果 MCU 这边没接该引脚,驱动里 while 轮询状态永远为高,就会卡死。
解决:DRDY 必须接 MCU GPIO 或 EXTI。更稳妥的做法是驱动里 WaitData 函数带超时参数,超时后返回错误码并复位 SPI 外设。这个后悔药一定要提前吃,否则量产现场出问题,靠示波器很难立刻定位。
5.3 读数低几位乱跳:参考电压噪声和地环路才是黑匣子
现象:输入一个稳定的电压源,读数高几位很稳,低三位在上下跳动,换算成电压有几十微伏的摆动。
原因:24 位分辨率下,1 个 LSB 对应的电压是微伏级别,PCB 上的地环路噪声、参考电压纹波、开关电源的尖峰都会被 ADC 看到。普通万用表量不出来的噪声,在 24 位 ADC 面前被放得很大。
解决:先用内部参考对比外部参考,排除参考噪声。然后把模拟地和数字地在 ADC 引脚附近单点连接,模拟电源用 LDO 加 LC 滤波,参考去耦电容加大到 10μF。软件上开数字滤波器,或者做多次采样平均,是最后的救命手段,但治标不治本。
5.4 高增益下偶发大毛刺:输入阻抗与 MUX 切换后的沉降
现象:PGA 增益设到 128,输入悬空或接高阻传感器时,数据偶尔跳出一个明显偏大的尖峰,过几个采样点又恢复。
原因:PGA 输入阻抗有限,高增益下输入引脚对地的阻抗更低,外部信号源阻抗高时,采样电容建立不充分。MUX 切换通道后,前几个转换周期内部模拟链路还在沉降,数据当然不可靠。
解决:把传感器输出阻抗降到 1kΩ 以下,或在 ADC 前面加一级低噪声运放缓冲;软件上每次切换通道后丢弃前两个转换结果。驱动包里如果提供“丢弃 N 次”的配置,直接设成 2 到 4 次,毛刺基本消失。
5.5 上电后寄存器写不进去:POR 时间与复位命令时序
现象:初始化函数写了好几串寄存器,读回却全是默认值,像是 SPI 根本没通信。
原因:上电后芯片还在内部复位,MCU 这边立刻就开始写寄存器,命令被丢掉了。还有一种情况是板子上 ADS1248 和 W25Q64 挂同一根 SPI 总线,CS 控制没处理好,上一笔 Flash 事务结束后 CS 没拉高足够长,把 ADC 这边的 CS 时序污染了。
解决:初始化前先延时 50ms 以上,再主动发送 RESET 命令,延时 10ms,最后写寄存器并逐个读回校验。多从机共用 SPI 总线时,每笔事务结束后 CS 拉高至少几百纳秒,我用逻辑分析仪测过,很多翻车问题就是 CS 拉高时间不够。
6. 用电压源校准 ADS1248:从粗测到两点校准,把驱动变成可交付模块
驱动稳定后,最重要的一件事是用真实电压源验证精度,而不是拿万用表怼着电源看个大概。我会准备一个精度 0.05% 以上的可调电压源,先输入 0V,再输入参考电压的一半,分别记录原始码并换算成电压。
零输入时读数应该很接近 0,但不会刚好是 0,因为失调电压和 PGA 偏置电流会让输出码偏几个 LSB。半量程输入时读数与理论值之间的比例偏差,就是增益误差。粗测的判据是:换算电压误差在满量程的 0.1% 以内,说明驱动链路基本健康。如果差得远,先怀疑参考电压实际值,再怀疑输入信号源精度。
粗测通过后做两点校准,把增益误差和失调误差固化成软件参数,比在硬件上死磕精密电阻更划算。我会在驱动里定义一个校准结构体:
typedef struct { float k; // 增益补偿系数 float b; // 偏移量,单位 mV } ADS1248_Cal; float ADS1248_ApplyCal(int32_t raw, float vref, float pga, ADS1248_Cal *cal) { float mv = (float)raw * (2.0f * vref / pga) / 8388608.0f * 1000.0f; return mv * cal->k + cal->b; }校准流程是对标准电压源输入两个点,比如 0V 和 1V,记录原始码算出 mv,然后解二元一次方程求出 k 和 b。生产线上每块板跑一遍,把结果存进外部 Flash 或 MCU 的 Flash 参数区,产品一致性会好很多。很多高精度仪表厂都是这么做的,区别只在于校准点的数量和自动化的程度。
三个提升有效位数的小习惯也提一下:第一,PCB 布局时模拟输入走线越短越好,差分走线要平行且等长,远离 SCLK 和 DIN 这类跳变信号;第二,SPI 读取如果频率很高,可以用 DMA 加 DRDY 中断,让 CPU 在等待转换期间去做别的事,数据稳定读取反而更好;第三,每读完一个数据不要急着用,先做一次中值滤波或滑动平均,滤掉偶发毛刺后再进入控制链路。
我习惯把校准常数、SYS0 配置和输入通道选择全部写进一个头文件,每换一块新板子,先跑十分钟零输入稳定性测试,再决定固件是否定版。这套流程看着麻烦,但能把 24 位 ADC 的潜力真正挖出来。希望帮到你。
本文还有配套的精品资源,点击获取