news 2026/10/19 9:39:30

STM32驱动ADS1220高精度ADC:从寄存器配置到多通道低功耗采集

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32驱动ADS1220高精度ADC:从寄存器配置到多通道低功耗采集

简介:这份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 引脚说明
SCLKSPI_SCK时钟,模式 CPOL=0 CPHA=1
DINSPI_MOSI主机输出
DOUT/DRDYSPI_MISO数据输出(复用 DRDY 功能)
CSGPIO 输出片选,低有效
DRDYGPIO 输入数据就绪,低有效

注意 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 驱动,都强制先写一个最小验证程序:只读一个通道、只用一个已知电压、打印原始码值和换算值,确认这条链路完全通了,再去动多通道和低功耗。这个习惯帮我省下了大量「以为是硬件问题、其实是驱动配置」的排查时间。希望这份拆解能帮到你,拿到代码后先跑通最小链路,再按自己的传感器改配置,稳扎稳打比什么都快。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 9:38:50

无感FOC零速低速段怎么控?脉振高频注入法原理与实战

第一次给板子写完无感FOC固件&#xff0c;我按下启动键&#xff0c;电机在零速附近“嗡”了一下&#xff0c;然后开始不紧不慢地反向爬行&#xff0c;电流波形像心电图一样乱跳——那一刻我就知道&#xff0c;转子位置估计在零点附近栽了跟头。做永磁同步电机&#xff08;PMSM&…

作者头像 李华
网站建设 2026/10/19 9:37:42

iFlow CLI hook实战:Windows下打造个性化任务完成通知

开头最近我一直在用一个叫 iFlow 的任务管理工具&#xff0c;日常把它挂在命令行里&#xff0c;随手抓任务、写待办。用得多了就开始不满足于它默认那套提醒方式&#xff1a;任务完成的时候&#xff0c;屏幕上顶多弹个系统通知&#xff0c;消息内容固定&#xff0c;也没有任务详…

作者头像 李华
网站建设 2026/10/19 9:37:25

环境管理方案:Shell脚本与Docker Compose如何选择与配合

这两年我经手维护的项目里&#xff0c;环境管理方案出现了很明显的分化&#xff1a;老一点的仓库还靠一串 Shell 脚本打天下&#xff0c;新一点的项目基本都切到了 Docker Compose。两种方案我都在实际项目里深度用过&#xff0c;也分别踩过不少坑。今天不站队说谁一定更好&…

作者头像 李华
网站建设 2026/10/19 9:37:05

PmrNet:门控卷积与掩膜引导的手写字消除网络

做OCR的朋友可能都有那种体验&#xff1a;一张质量还不错的扫描件&#xff0c;被红笔黑笔批注得密密麻麻&#xff0c;扔给识别引擎&#xff0c;出来的文本先被笔迹干扰。手写笔迹和印刷体叠在一起&#xff0c;传统的阈值滤波根本没法分离。这篇是我的手写字消除系列第二篇&…

作者头像 李华
网站建设 2026/10/19 9:37:04

Windows开启CPU虚拟化与关闭Hyper-V完整指南

在虚拟化这条路上&#xff0c;我见过太多用户在 BIOS 里把“启用虚拟化”的开关来回拨&#xff0c;重启进系统后虚拟机软件还是不给面子。要么弹“此主机支持 Intel VT-x&#xff0c;但该功能处于禁用状态”&#xff0c;要么提示“VT-x is not available”&#xff0c;明明设置…

作者头像 李华
网站建设 2026/10/19 9:37:02

手写字图片修复:PmrNet掩膜细化网络的设计与训练

手写字图片修复这事&#xff0c;我断断续续折腾了大半年。起因很朴素&#xff1a;手里有一堆扫描的老笔记、试卷截图&#xff0c;上面的手写批注比正文还抢眼。用PS仿制图章一点点抠&#xff0c;效率低到让人怀疑人生&#xff0c;而且修完总能看到明显的涂抹痕迹。后来转念一想…

作者头像 李华