简介:这份ADS1220驱动代码面向使用STM32F407进行嵌入式开发的工程师与学习者,针对24位高精度ADC在传感器接口、医疗设备及精密测量等场景下的驱动实现问题,提供可直接参考的底层代码方案。资源包共2个文件,包含1个ads1220.h头文件与1个ads1220.c源文件,压缩包约4KB,头文件用于声明寄存器定义与对外接口,源文件则实现SPI通信、命令发送与数据读取等核心逻辑。代码覆盖初始化配置、工作模式与增益设定、24位转换结果读取及中断服务等环节,并涉及STM32的SPI主模式配置、GPIO引脚管理与错误处理思路,便于读者理解ADS1220与微控制器之间的完整通信流程。目前已有510人学习下载,适合需要快速搭建高精度采集链路、对照调试SPI时序或进行驱动定制的开发者参考借鉴。
1. 从一块称重板说起:为什么我要把 ADS1220 驱动单独拆出来讲
去年帮一个做配料设备的团队调一块称重板,硬件同事拍胸脯说前端电路没问题,结果 STM32 读回来的 ADC 值跳得跟心电图一样,小数点后两位根本稳不住。换了几版滤波参数都没用,最后定位到根子上——ADS1220 的寄存器配置压根没配对,增益、数据速率、基准源全在默认状态跑。这件事之后我就养成了一个习惯:凡是 24 位 ΔΣ 型 ADC,驱动层必须自己从头捋一遍,不能拿来主义。
ADS1220 是 TI 的一颗 24 位低功耗 ΔΣ ADC,四路差分/单端输入可选,内置 PGA 和基准,SPI 接口,在称重、测温、压力变送这些场景里出镜率很高。这份驱动代码就是围绕 STM32 平台,把 ADS1220 的初始化、寄存器读写、单次/连续转换、数据读取和状态判断这一整套流程封装成可复用的 C 模块。适合正在做高精度采集、又不想在时序和寄存器上反复翻车的人。下面我按「这项目能干什么 → 怎么跑起来 → 怎么改」的顺序,把这份驱动拆开讲。
2. 驱动整体架构:SPI 时序、寄存器映射与状态机怎么分层
2.1 目录结构与模块划分
这份驱动不是一个大 .c 文件糊到底,而是按职责切成了几层。常见做法是下面这种结构,我拿到源码后第一件事就是看目录,基本能判断作者有没有分层意识:
ads1220_driver/ ├── inc/ │ ├── ads1220.h // 对外接口、寄存器地址宏、命令字定义 │ └── ads1220_port.h // 平台移植层:SPI 收发、CS/DRDY 引脚操作 ├── src/ │ ├── ads1220.c // 核心逻辑:初始化、配置、读写、转换 │ └── ads1220_port.c // 移植层实现(STM32 HAL 版) └── example/ └── main_example.c // 单次转换 + 连续转换的调用示例ads1220.h里定义的是命令字和寄存器地址,比如ADS1220_CMD_RESET、ADS1220_REG_CONFIG0这类宏;ads1220.c只依赖ads1220_port.h暴露的几个函数,不直接碰 HAL 库。这样分层的好处是,你换到 GD32 或者别的 MCU,只改 port 层就行,核心逻辑一行不动。我见过太多驱动把HAL_SPI_Transmit直接写进业务函数里,移植时改到怀疑人生。
2.2 寄存器映射与命令字
ADS1220 的操作靠命令字驱动,不是直接读写寄存器地址。它有两类操作:一类是命令(如 RESET、START、POWERDOWN),一类是寄存器读写(RREG/WREG)。驱动里通常这样定义:
/* ads1220.h —— 命令字与寄存器地址 */ #define ADS1220_CMD_RESET 0x06 // 复位,恢复上电默认值 #define ADS1220_CMD_START 0x08 // 启动转换 #define ADS1220_CMD_POWERDOWN 0x02 // 进入掉电模式 #define ADS1220_CMD_RDATA 0x10 // 读取转换数据 #define ADS1220_CMD_RREG 0x20 // 读寄存器,低4位为起始地址 #define ADS1220_CMD_WREG 0x40 // 写寄存器,低4位为起始地址 /* 寄存器地址(共4个配置寄存器) */ #define ADS1220_REG_CONFIG0 0x00 #define ADS1220_REG_CONFIG1 0x01 #define ADS1220_REG_CONFIG2 0x02 #define ADS1220_REG_CONFIG3 0x03这里有个容易忽略的点:RREG/WREG 命令的低 4 位是寄存器起始地址,高 4 位是命令码,第 4 位(0x08)表示是否连续读写多个寄存器。所以写 CONFIG0 到 CONFIG3 时,命令字是0x40 | 0x03(从地址 0 开始连写 4 个),而不是分四次写。参数说明上,ADS1220_CMD_WREG本身是基址,实际发送时要和地址、数量做位或运算,这个在ads1220.c的写寄存器函数里体现。
2.3 状态机与 DRDY 信号处理
ADS1220 转换完成会拉低 DRDY 引脚,驱动要围绕这个信号做状态判断。核心逻辑是一个轻量状态机:空闲 → 启动转换 → 等待 DRDY → 读数据 → 回到空闲。等待环节有两种写法,轮询和中断。轮询简单但占 CPU,中断省资源但要处理引脚中断优先级。驱动里一般把等待封装成带超时的函数:
/* ads1220.c —— 等待转换完成,带超时保护 */ static int ads1220_wait_drdy(uint32_t timeout_ms) { uint32_t start = HAL_GetTick(); /* DRDY 低电平有效,等待其拉低 */ while (ads1220_port_read_drdy() != 0) { if ((HAL_GetTick() - start) > timeout_ms) { return -1; // 超时,返回错误码 } } return 0; }timeout_ms这个参数很关键。ADS1220 在低速档(比如 20 SPS)下一次转换要 50ms,如果你超时设成 10ms,必然误判失败。我一般按「数据速率倒数 × 3」来给超时,留足余量。ads1220_port_read_drdy()是移植层函数,STM32 上就是读一个 GPIO 电平,注意 DRDY 是低有效,别读反了。
3. 跑起来:STM32 上的初始化、配置与数据读取全流程
3.1 硬件连接与 SPI 参数确认
先把线接对,这是所有问题的前提。ADS1220 和 STM32 之间是标准 4 线 SPI,加上 DRDY:
| ADS1220 引脚 | STM32 引脚 | 说明 |
|---|---|---|
| SCLK | SPI_SCK | 时钟,模式 CPOL=0 CPHA=1 |
| DIN | SPI_MOSI | 主机输出 |
| DOUT/DRDY | SPI_MISO | 数据输出(复用 DRDY 功能) |
| CS | GPIO 输出 | 片选,低有效 |
| DRDY | GPIO 输入 | 数据就绪,低有效 |
注意 ADS1220 的 SPI 模式是 CPOL=0、CPHA=1,也就是空闲低电平、第二个边沿采样。这个和很多传感器默认的 Mode 0 不一样,配错了读出来全是 0xFF 或者 0x00。我一般会在ads1220_port.c里把 SPI 初始化参数写死并加注释,避免后面有人手滑改掉。
3.2 初始化与寄存器配置
初始化流程是:复位 → 写配置寄存器 → 启动转换。复位后芯片回到默认状态,然后按你的传感器需求配 CONFIG0~3。以称重场景为例,增益 128、数据速率 20 SPS、使用内部基准:
/* ads1220.c —— 初始化并配置寄存器 */ int ads1220_init(void) { uint8_t cfg[4]; ads1220_port_spi_init(); // 初始化 SPI 外设 ads1220_port_cs_high(); // 片选拉高 ads1220_send_cmd(ADS1220_CMD_RESET); // 软复位 HAL_Delay(1); // 等待复位完成 /* CONFIG0: 增益128, 单次转换模式 */ cfg[0] = (0x04 << 1); // PGA增益位,具体编码查手册 /* CONFIG1: 20SPS, 正常模式 */ cfg[1] = (0x00 << 5); /* CONFIG2: 内部基准, 50/60Hz抑制 */ cfg[2] = (0x01 << 6); /* CONFIG3: 关闭IDAC, 使能DRDY */ cfg[3] = 0x00; return ads1220_write_regs(ADS1220_REG_CONFIG0, cfg, 4); }cfg数组的每一位都对应手册里的字段,这里用移位和或运算拼出来,比直接写十六进制可读性好。ads1220_write_regs内部会拼出0x40 | 0x03这样的命令字,然后连续发 4 个字节。参数上,增益编码不是线性的,128 倍对应的是0x04,具体要对着手册的增益表来,写错了增益就是错的,数据自然不对。
3.3 单次转换与连续转换的调用差异
单次转换适合低功耗场景,每次要数据就启动一次;连续转换适合高速采集。驱动里通常提供两个接口:
/* 单次转换:启动 → 等待 → 读数据 */ int ads1220_read_single(int32_t *value) { ads1220_send_cmd(ADS1220_CMD_START); if (ads1220_wait_drdy(200) != 0) { return -1; } return ads1220_read_data(value); } /* 连续转换:先启动,之后循环读 */ int ads1220_start_continuous(void) { return ads1220_send_cmd(ADS1220_CMD_START); }ads1220_read_data里发ADS1220_CMD_RDATA,然后读 3 个字节拼成 24 位有符号数。这里有个符号扩展的坑:24 位数据读进来是uint32_t,要判断最高位(bit23)是否为 1,是的话高 8 位补 0xFF,否则补 0。不做这一步,负电压采出来会变成巨大的正数。我见过有人直接强转int32_t,结果负值全错。
3.4 数据校验与异常处理
读回来的数据不能直接用,至少要做两件事:一是检查 DRDY 是否真的就绪,二是对连续多次读取做合理性判断。驱动里可以加一个简单的滑动判断,如果连续几次读到的值都是 0x7FFFFF 或 0x800000,基本可以判定前端饱和或断线。异常处理不用太复杂,返回错误码让上层决定重试还是报警就行。常见做法是在ads1220_read_data里加一个范围检查,超出量程就置一个标志位。
4. 避坑与排查:那些让我熬夜的 ADS1220 驱动问题
4.1 读出来全是 0 或 0xFF
现象是 SPI 能通,但数据寄存器读回来恒定不变。原因通常是 SPI 模式配错,或者 CS 时序不对。ADS1220 要求 CS 在命令字节之间保持低电平,如果你每个字节都拉高再拉低,命令会被截断。解决方法是把 CS 控制放在命令发送函数的首尾,中间不要动。另外确认 CPHA=1,这个错配的概率最高。
4.2 数据跳变严重、小数点后两位不稳
现象是数值能用但噪声大。原因多半是基准源和滤波配置不匹配。如果用内部基准,CONFIG2 里的基准选择位要设对;如果数据速率设太高(比如 1000 SPS),而前端又没有抗混叠滤波,噪声自然大。解决方法是把数据速率降到 20 SPS 或 45 SPS,同时打开 50/60Hz 抑制,再配合软件做几次平均。我一般会先用低速档确认数据稳定,再逐步提速。
4.3 DRDY 一直不拉低
现象是等待超时,读不到数据。原因可能是没发 START 命令,或者芯片处于掉电模式没唤醒。ADS1220 上电默认是掉电状态,必须先发 START 或者写配置寄存器才会开始转换。另一个可能是 DRDY 引脚配置成了推挽输出而不是输入,读电平永远是高。解决方法是检查ads1220_port_read_drdy的 GPIO 初始化,确认是输入模式且带上拉。
4.4 负电压采集成正数
现象是差分输入负值时读回来是个大正数。原因就是前面说的 24 位符号扩展没做。解决方法是读回 3 字节后,判断 bit23,手动做符号扩展。这个坑很隐蔽,因为正电压下一切正常,只有负值才暴露。
4.5 多片 ADS1220 共用 SPI 时互相干扰
现象是两片芯片数据串扰。原因是 CS 控制不严,或者 SPI 时钟太快导致建立时间不够。解决方法是每片独立 CS,操作前确保其他片选拉高,同时把 SPI 时钟降到 1MHz 以下试试。ADS1220 的 SCLK 最高支持到 4MHz 左右,但长走线下建议留余量。
5. 二次开发:把驱动改成多通道轮询与低功耗采集
5.1 多通道轮询的改造思路
默认驱动一般只跑一个通道。要改成四通道轮询,核心是每次转换前改 CONFIG0 的输入多路复用位(MUX)。改造点集中在ads1220_init和读取函数之间,加一个通道切换函数:
/* 切换输入通道,ch: 0~3 对应 AIN0~AIN3 */ int ads1220_set_channel(uint8_t ch) { uint8_t cfg0; if (ads1220_read_regs(ADS1220_REG_CONFIG0, &cfg0, 1) != 0) { return -1; } cfg0 = (cfg0 & 0x0F) | ((ch & 0x0F) << 4); // MUX位在高4位 return ads1220_write_regs(ADS1220_REG_CONFIG0, &cfg0, 1); }注意改完 MUX 后要重新发 START,并且等第一个转换周期结束再读,因为切换通道后第一次转换可能是旧通道的残留。我一般会丢弃切换后的第一帧数据,从第二帧开始用。这个「丢一帧」的习惯,是我调多通道时踩坑踩出来的。
5.2 低功耗采集的配置调整
电池供电场景下,连续转换很费电。改造方向是:用单次转换模式,采集完立刻发 POWERDOWN 命令,让 MCU 和 ADC 都进休眠,定时器唤醒后再启动。驱动里加一对ads1220_sleep和ads1220_wakeup就行。参数上,把数据速率降到 20 SPS,转换时间约 50ms,其余时间全休眠,平均电流能压到几十微安级别。验证方法是串一个电流表看平均功耗,别只看手册的典型值。
5.3 验证改造是否成功的三个检查点
改完之后怎么确认没改坏?我固定走三步:第一,用已知电压源输入,看读回来的原始码值换算成电压后误差是否在允许范围;第二,切换通道时确认数据跟着通道变,不串;第三,连续跑一小时,看有没有偶发超时或数据卡死。第三步最容易被跳过,但恰恰是暴露时序问题的地方。
5.4 一个让我长记性的习惯
上面这些改造点里,通道切换丢帧和符号扩展这两件事,我都在真实项目里翻过车。从那以后,我每次拿到新的 ADC 驱动,都强制先写一个最小验证程序:只读一个通道、只用一个已知电压、打印原始码值和换算值,确认这条链路完全通了,再去动多通道和低功耗。这个习惯帮我省下了大量「以为是硬件问题、其实是驱动配置」的排查时间。希望这份拆解能帮到你,拿到代码后先跑通最小链路,再按自己的传感器改配置,稳扎稳打比什么都快。
本文还有配套的精品资源,点击获取