☰
STM32读写AD7190:24位ADC称重例程的寄存器配置与标定全解析
2026/10/28 16:02:05 网站建设 项目流程

简介:面向STM32单片机开发者,压缩包内提供了基于AD7190的24位称重模块读写与TFT液晶显示完整例程。源码采用HAL库编写,涵盖系统时钟配置、串口调试、3.5寸液晶初始化、重量采集均值滤波、按键清零与砝码校准等关键流程,可直接移植到项目或用于学习高精度ADC与称重方案设计。包内共667个文件,以C源码、头文件、启动汇编文件、链接脚本及工程配置文件为主,另含Hex固件、说明文档和效果图,压缩包整体7.46MB,便于快速查看与二次开发。目前已有1148人学习下载,适合需要参考24位ADC驱动、称重系统软硬件联调或液晶显示例程的电子工程师与嵌入式学习者。

1. 从标题拆解这套 AD7190 称重例程:STM32 读写 24 位 ADC 到底在做什么

做称重项目的工程师,十有八九在“最后几克”上翻过车:传感器接好了,程序却读不回稳定码值。AD7190 是工业称重里很常见的一颗 24 位 Σ-Δ ADC,STM32 通过 SPI 寄存器读写,把称重传感器输出的毫伏级差分信号变成数字码值,再换算成重量显示在液晶屏上。标题里这套例程 DEMO 源码,价值不在某个函数库有多花哨,而在于把“模拟前端 → 数字滤波 → 标定换算 → 屏显示”这条链路完整串起来了。适合手里有 STM32 开发板和 AD7190 模块、想搭高精度称重、又不想从零啃数据手册的人。下面按原理、寄存器、显示和排错的顺序,把常见做法和踩过坑讲清楚。

2. AD7190 为什么能做称重核心:24 位 Σ-Δ 的精度机制与选型理由

2.1 为什么称重场景要用 24 位 Σ-Δ,而不是片内 12 位 ADC

STM32 片内 ADC 通常是 12 位,对 0~3.3V 满量程分成 4096 格。称重传感器输出是毫伏级差分信号,就算按灵敏度 2mV/V、激励电压 5V 来算,满量程也只有 10mV 左右。把 10mV 分成 4096 格,每格大约 2.4µV,听起来好像够用,但实际电路里随便一点电源纹波、接触热电势、PCB 漏电流都会超过这个量,测回来的数据根本没有实用意义。这就是为什么称重仪表里几乎清一色用带 PGA 的高分辨率 Σ-Δ ADC,而不是直接拿 MCU 片内 ADC 去采。

Σ-Δ 架构的工作方式有点反直觉:它不追求单次采样有多准,而是用远高于目标信号带宽的采样率反复采样输入,再在数字域做噪声整形和低通滤波。换句话说,它用时间换精度。AD7190 内部有数字滤波器,输出速率设得越低,滤波越深,等效有效位数越高。把数据速率从 1000Hz 降到 10Hz,噪声指标会明显改善,这就是为什么例程里的寄存器配置会优先照顾低速率。

这里要纠正一个常见误读:24 位是输出寄存器宽度,不是有效精度。很多人拿到 AD7190 后发现低位码字一直在跳,就觉得芯片坏了,其实是把“物理分辨率”和“有效分辨率”搞混了。24 位 ADC 的名义分辨率是 2^24,但受噪声影响,真正可信的有效位数通常远低于 24。学习 AD7190 的过程里,你会慢慢理解“速率-增益-滤波”三者怎么组合,这比单纯用 STM32 的片内 ADC 切换通道扫多路传感器要深入得多——它逼你把信号链和时序管理放到台面上来。

2.2 称重传感器桥路与 AD7190 的信号链匹配

称重模块的“模拟前端”一般是一组应变片组成的惠斯通电桥。传感器受力时电桥失去平衡,输出一个和应变近似线性的差分电压。这个电压很小:常见称重传感器的灵敏度在 1~3mV/V,用 5V 激励,满量程差分输出也就 5~15mV。

所以 AD7190 前端必须带可编程增益放大器 PGA,增益从 1 到 128 可编程。接称重传感器时我习惯先把增益设到 64 或 128,相当于把桥路输出放大后再进 ADC,让毫伏级信号占满码值范围。但增益不是越大越好:增益越高,允许的差分输入范围越小,超出范围会直接饱和,读数固定死在满码。例程里如果配的是 128 倍增益,记住它对应的满量程电压窗口大概只有几十毫伏,校准前务必确认传感器空载输出电压落在这个窗口内。

AD7190 有两路差分输入 AIN1± 和 AIN2±。称重接一路,另一路可以留作备用或接温度传感器。用差分输入而不是单端,是为了把共模噪声抵消掉:传感器激励线和信号线在长距离走线时,共模干扰同时进入正负输入端,差分对会在芯片内部做减法,这一招在工业现场特别关键。传感器屏蔽线的接地位置也影响这条路,接到模拟地而不是数字地,是很多初次布线的人交过学费才记住的。

2.3 AD7190 的寄存器访问模型:SPI 命令字节是唯一入口

AD7190 的 SPI 接口是一个三角环式的寄存器模型——发命令、访问目标寄存器,顺序不能乱。例程里第一步通常就是定义寄存器地址和通信位掩码:

#define AD7190_REG_STAT 0x00 // 状态寄存器 #define AD7190_REG_MODE 0x01 // 模式寄存器 #define AD7190_REG_CONF 0x02 // 配置寄存器 #define AD7190_REG_DATA 0x03 // 数据寄存器 #define AD7190_REG_ID 0x04 // 芯片 ID 寄存器 #define AD7190_COMM_WEN 0x80 // 写使能位,最高位必须为 1 #define AD7190_COMM_READ 0x40 // 读操作标志

访问任何一个寄存器之前,都要先发一个通信命令字节:最高位 R/W 指定读还是写,最低几位是目标寄存器地址。命令字节不带 WEN 标记,芯片会直接忽略。这个“先发命令再传数据”的习惯,和普通 SPI 外设不太一样,忘记这一层是新手最常见的报错来源。所有配置最终都要落到 MODE 和 CONF 两个 24 位寄存器上,MODE 管输出速率、转换模式、校准使能,CONF 管通道选择、PGA 增益、单双极性。

SPI 极性的选择也在这里定:AD7190 支持的模式和大多数 SPI 设备一致,但我在项目里固定用时钟空闲为高、第二个边沿采样。如果同一路 SPI 上还挂了液晶屏,两边 SPI 模式必须一致,否则 LCD 正常时 ADC 就读不对,这是一个看似低级、实际很容易被忽略的坑。

2.4 为什么是 AD7190 而不是 HX711

提到称重芯片,很多人第一反应是 HX711。它便宜、集成度高、外围简单,做电子秤确实够用。但 HX711 的串行接口更像“边沿握手”,要靠 MCU 不断地发脉冲读数据,时序窗口很短,多路通道扩展和回读配置都不方便。AD7190 是完全的 SPI 寄存器操作,可以随时读寄存器确认配置状态,有芯片 ID、有状态寄存器、有完整校准命令,数据速率范围也更宽。如果你做的称重设备要联网、要对不同量程做动态切换、要把校准参数存进 MCU,AD7190 这个方向比 HX711 更值得投入。当然,成本也明显高些,项目预算敏感时,HX711 依然是可用的备选,但这不是一篇水文里能解决的问题。

3. 用 STM32 把 AD7190 读通:SPI 接线、寄存器初始化与校准流程

3.1 硬件接线:不要只连四根线

例程源码里最先能抄的是初始化函数,但初始化之前,硬件上有几个固定习惯。AD7190 的电源引脚要放一组 10µF 电解电容并联 100nF 陶瓷电容去耦,位置尽量靠近芯片供电脚。基准电压 REFIN+ 和 REFIN- 不能浮空,我一般直接外接基准芯片,把 2.5V 或 5V 基准送到 REFIN+,这样温漂可控。DOUT/RDY、SCLK、DIN、CS 四根线接 STM32 的 SPI1,CS 用软件控制更灵活。AD7190 的 DOUT 兼作 RDY,数据准备好之后会拉低,它是开漏输出,一般需要一个 10kΩ 上拉电阻。

STM32 引脚AD7190 引脚说明
PA5 (SPI1_SCK)SCLKSPI 时钟
PA6 (SPI1_MISO)DOUT/RDY数据输出兼就绪
PA7 (SPI1_MOSI)DIN数据输入
PA4 (GPIO)CS片选,软件控制
3.3V / 5VAVDD / DVDD按模块要求供电
GNDAGND / DGND单点共地

很多称重模块是 5V 模拟电源,STM32 是 3.3V 电平,SPI 口不建议直接长时间跨压驱动。最省事的办法是用支持 5V 容忍的引脚,或者加一级电平转换。我在自己板子上习惯加转换芯片,因为称重模块的 DIN 和 SCLK 线可能走线较长,电平裕量不够会导致偶发写寄存器失败,这种问题排查起来特别耗时间。如果你习惯用 VSCode 搭 STM32 开发环境,这边提醒一句:CubeMX 生成的 SPI 句柄名要和例程里的 hspi1 保持一致,否则移植到一半会发现 HAL 层函数对不上。

3.2 复位与初始化:先确认 SPI 链路再写配置

AD7190 手册要求复位序列最少 40 个 SCLK 周期。例程里一般用循环翻转时钟来实现,比直接发字节更可控。复位完成后 DOUT 会变高,然后可以读一次 ID 寄存器,确认 SPI 通信链路是通的,再继续配置。顺序不能反,否则后面写什么寄存器都像石沉大海。

void AD7190_Reset(void) { // 手册要求:CS 拉低后,SCLK 连续翻转至少 40 个周期 AD7190_CS_LOW(); for (int i = 0; i < 40; i++) { AD7190_SCK_HIGH(); AD7190_SCK_LOW(); } AD7190_CS_HIGH(); // 复位后延迟 1ms,让内部状态机稳定 HAL_Delay(1); } uint8_t AD7190_ReadID(void) { uint8_t cmd = AD7190_COMM_WEN | AD7190_COMM_READ | AD7190_REG_ID; uint8_t rx = 0; AD7190_CS_LOW(); // 发命令字节的同时,读回 ID 寄存器内容 HAL_SPI_TransmitReceive(&hspi1, &cmd, &rx, 1, 100); AD7190_CS_HIGH(); return rx; }

逻辑说明:复位循环里的高低电平翻转必须发生在 CS 拉低之后。如果 CS 先拉高再翻转时钟,复位信号根本送不进去。ID 寄存器读取用了 TransmitReceive 一次性完成,命令字节由 MOSI 发出,返回数据从 MISO 读回,同一根 SPI 总线上收发同时发生,这就是 SPI 全双工的特点。AD7190 的 ID 寄存器一般返回非 0xFF 的固定值,具体数值以你手上这颗芯片的数据手册为准。如果读到 0xFF,先别怀疑芯片,检查 SPI 极性是不是配反了。

3.3 读取转换结果:轮询 DOUT 还是连续读数据寄存器

AD7190 在读数据时,CS 必须保持低电平覆盖整个传输过程。我的读取函数一般这样写:

uint32_t AD7190_ReadData(void) { uint8_t cmd = AD7190_COMM_WEN | AD7190_COMM_READ | AD7190_REG_DATA; uint8_t buf[3] = {0, 0, 0}; AD7190_CS_LOW(); // 第一步发命令字节,后续三个字节接收 24 位转换结果 HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); HAL_SPI_Receive(&hspi1, buf, 3, 100); AD7190_CS_HIGH(); return ((uint32_t)buf[0] << 16) | ((uint32_t)buf[1] << 8) | buf[2]; }

逻辑说明:这里分两步做,因为命令和数据是“先写后读”的关系。有些工程师习惯用 TransmitReceive 一次发 4 字节,也能跑通,但要注意命令字节之后紧跟的三个字节必须是接收方向,不能把发送缓冲里的垃圾数据混进来。读回来的 24 位数据是偏移二进制格式,这个后面换算称重值时再处理。

读取时机同样重要:单次转换模式下,每次读之前要确认 DOUT 已经拉低,表示新数据准备好了。连续转换模式下,DOUT 在转换结束后拉低,读走数据后自动拉高。如果你设定 10Hz 输出速率,转换周期是 100ms,程序在 1ms 内反复查询 DOUT,会发现自己看到的永远是同一个电平,这不是芯片卡死,而是你比芯片快太多。

3.4 校准流程:内部零点和系统校准的取舍

AD7190 校准命令藏在 MODE 寄存器里。做称重最少要做一次内部零刻度校准,把芯片自身的失调电压消掉。更完整的是系统校准:传感器空载做零点,放上标准砝码做满量程。

void AD7190_Calibrate(void) { // 第一遍:内部零点校准,传感器空载 AD7190_WriteMode(AD7190_MODE_INT_ZERO_CAL); // 等待校准完成,轮询 MODE 寄存器中的校准状态标志位 while (AD7190_ReadMode() & AD7190_MODE_CAL_BUSY) ; // 第二遍:系统满量程校准,传感器上放好标准砝码 AD7190_WriteMode(AD7190_MODE_SYS_FULL_CAL); while (AD7190_ReadMode() & AD7190_MODE_CAL_BUSY) ; }

逻辑说明:这个流程里最关键的是“等待”而不是“发送”。校准状态位在 MODE 寄存器里,校准完成会自动清零。while 循环就是在等这个位翻转。实际使用中,我很少用系统满量程校准,原因很现实:标准砝码摆放位置稍微偏一点,传感器受力就不均匀,校准出来的增益系数反而不准。更稳的做法是只用内部零点校准消掉失调,然后靠软件标定拟合斜率。

参数说明:无论是哪种校准,传感器在开始校准后必须保持稳定,不能有人碰秤台、不能有风压波动。我遇到过一次现场事故:启动校准后有人靠了一下桌子,整个零点偏了 0.5g。所以例程里如果开放了在线校准功能,建议加一道“传感器稳定判定”——连续读几次码值,差值小于阈值才允许开始校准。

4. 带液晶显示的称重例程怎么改:刷新时序、码值换算与滤波设计

4.1 液晶屏和 AD7190 共用 SPI 总线的时序冲突

很多 DEMO 把 AD7190 和液晶屏挂在同一个 SPI 接口上:一组 SPI1,ADC 占一个片选,液晶占另一个片选。理论上分时操作就行,实际踩坑不少。LCD 初始化或刷屏时,SPI 波特率往往要拉到 10MHz 以上,而 AD7190 的 SPI 时钟不能配太高,否则 MISO 上的数据采样时会吃掉沿抖动的边。两边共用一个外设时,最稳妥的做法不是取一个折中的波特率,而是每次切换片选时重新设置 SPI1 的预分频。

片选切换顺序也有讲究:先拉高 ADC 的 CS,等一个短延迟,再拉低液晶的 CS。反过来容易出现瞬间两个设备同时被选中的毛刺。如果嫌频繁重配 SPI 影响性能,我一般直接给液晶分配另一个 SPI 外设,逻辑上省心很多。例程里通常只写了一种调法,你拿到后如果发现液晶显示正常、称重读数偶发错位,大概率就是两边的波特率和时序没有完全分开。

4.2 从 24 位码值到克数:偏移二进制与标定系数

AD7190 在双极性模式下输出的是偏移二进制码,0x800000 附近对应零点。直接把读回来的 0x000000 到 0xFFFFFF 当无符号数处理,无法正确区分正负重量,零点也容易被算偏。换算函数应该先把码值扩展成有符号数:

typedef struct { float zeroCode; // 空载时的平均码值 float slope; // 每克对应的码值变化量 } CalibParam; float AD7190_ToWeight(uint32_t rawCode, const CalibParam *cal) { // 24 位偏移二进制码转有符号:第 23 位是符号位 int32_t signedCode = (rawCode & 0x800000) ? (int32_t)(rawCode | 0xFF000000) : (int32_t)rawCode; float delta = (float)signedCode - cal->zeroCode; return delta / cal->slope; }

逻辑说明:rawCode 是 0~0xFFFFFF 的 24 位值。如果最高位置 1,说明是负数,把高 8 位补 1 扩展成 32 位有符号数;否则直接符号扩展。这就是 C 语言里小技巧的活学活用。换算之后再加一个“死区判断”,比如 -0.2g 到 +0.2g 之间直接显示 0.0,否则空载时屏幕上数字一直跳,用户观感很差。

标定方法:先让传感器空载,连续读若干次码值取平均,记为 zeroCode。再放一个已知重量的砝码,比如 500g,记录当前码值,slope 就等于 500 除以码值变化量。这个 slope 最好在满量程的 50% 附近标定,因为秤的线性误差通常在两端更大。例程如果只给了一个粗略换算公式,实际装机前必须用你自己的传感器重新标定,否则显示出来的克数只能算参考值。

4.3 滤波和刷新频率:让读数不要“肉眼可见地跳”

称重显示最怕的不是慢,而是跳。8 次滑动平均是很稳妥的起步做法:

#define AD7190_FILTER_LEN 8 uint32_t AD7190_ReadAverage(void) { uint32_t sum = 0; for (int i = 0; i < AD7190_FILTER_LEN; i++) { sum += AD7190_ReadData(); } return sum / AD7190_FILTER_LEN; }

代码很简单,背后的时序关系要清楚:8 次平均会占用 8 个 AD7190 转换周期。若输出速率是 10Hz,完成一次平均要 800ms,刷新率大约 1Hz。静态秤台场景够用,但如果是动态称重或要求读数快速稳定的场景,这个响应速度就会拖后腿。不要只改滤波窗口长度,而是先提高 MODE 寄存器里的数据速率,再把窗口缩短。比如 50Hz 速率配合 4 次平均,响应大概 80ms,比原来的方案灵敏得多。

液晶屏刷新同样要配合这个节奏。我一般让显示代码每 500ms 刷一次,而不是每次读回码值都刷屏。有些 LCD 控制器整屏刷写会有明显闪烁,这时可以做成区域刷新:只更新重量数值所在的字符区域。例程里如果直接把字符串格式化后整屏 clear 再显示,速度快不起来是正常的,改成先计算显示位置再局部画数字,会顺滑很多。

5. 避坑:AD7190 称重模块最常见的 5 个翻车现场与排查顺序

5.1 读到的码值乱跳,零点都不稳

现象:传感器空载,连续读回来几十个数,码值跳动几百上千个字,显示重量忽正忽负。

原因:最常见不是芯片坏,而是增益和速率不匹配。PGA 开到 128 倍的同时输出速率又配到几百赫兹,噪声必然大。其次是基准电压纹波太大,或者传感器屏蔽线没接好,桥路输出像天线一样把 50Hz 工频干扰收进来。

解决:先把数据速率降到 4.7Hz 或 10Hz 档,重新读数;把传感器屏蔽层接到模拟地而不是数字地;再不行把 PGA 从 128 降到 64。三步下来码值跳动一般能压到几十个字以内。如果还是压不住,就去看板子的 AGND 和 DGND 是不是单点相连,布线时模拟地走线太细也会有影响。

5.2 校准之后称重不准,放 100g 显示 98g

现象:空载显示 0.0,放上标定砝码后显示偏差 1%~2%,换一个位置放,偏差还不同。

原因:标定时的受力点跟实际称重不一致。传感器输出对施力点很敏感,同一块秤板,砝码放左角和放右角应变分布不同,单点标定自然有偏差。另一个常见问题是系统满量程校准时,传感器还没完全“压稳”,弹簧特性还在振荡,增益系数本身就偏了。

解决:改用软件两点或三点标定。空载取零点,放 50% 满量程砝码取第二点,再放 90% 砝码取第三点。每个点都用滑动平均后的读数记录,砝码位置统一放在秤台中心。标定范围要覆盖实际使用量程,不能拿 1kg 砝码标定却拿去称 20kg 的料,那超范围的部分基本没法看。

5.3 读回的数据永远是 0xFFFFFF 或 0x000000

现象:写寄存器像没写一样,读数据寄存器全 1 或全 0,改配置没有任何反应。

原因:第一次发命令字节没带 WEN 写使能位,通信寄存器直接忽略。或者 SPI 空闲极性配反,MOSI 上的数据按错误的时钟沿采样,寄存器收到的都是错位数据。还有一种情况是 CS 片选在 HAL 函数之间被意外拉高,导致一次传输被拆成两段。

解决:用软件控制 CS,传输全程保持低电平。检查命令字节最高位是否为 1。然后用 MISO 回读一次 ID 寄存器,如果读到非预期值,把 SPI 在模式 0 和模式 3 之间各试一次,通常很快能定位是时序问题。排除顺序是:命令字节 → CS 时序 → SPI 极性 → 硬件连线,按这个顺序查,比盲目改寄存器配置高效很多。

5.4 液晶刷新时,称重值偶发跳变

现象:液晶一刷屏,重量瞬间跳 0.5g,不刷屏时读数正常。

原因:片选切换的瞬间,MISO 线上残留着液晶驱动的电平。AD7190 和液晶共用 SPI 总线时,如果上一个设备还在释放总线,下一个设备就启动传输,残留位被当成数据读进去了。

解决:固定片选切换顺序:先拉高当前设备的 CS,延迟几十微秒,再拉低目标设备的 CS。用同一个 SPI 外设时,两边波特率和 SPI 模式必须一致。如果分开两个 SPI 外设,要注意电源地必须共地,否则电位差会通过 MISO 线产生灌电流。另外把 AD7190 的 DOUT 上拉电阻从 10kΩ 改成 4.7kΩ,有时候也能把读边末端的尖刺压下去。

5.5 冷启动第一次读数异常,后面正常

现象:每次上电第一次读数跳几克,第二次开始才正常,换一片 AD7190 也一样。

原因:电源上电瞬间,AVDD 和基准电压还没建立完成,芯片内部复位也没有结束。第一次读操作落进了未就绪的时间窗,读到的自然是复位后的默认值或中间态。

解决:上电后先延迟 100ms,再执行复位序列,然后读 ID 确认链路,正式读数前丢弃前 2~3 个转换结果。这套流程写成固定的初始化顺序,以后换任何板子都按“上电等待 → 复位 → ID 验证 → 丢前三次读数 → 正常采集”来跑。这类玄学问题不像硬件故障那么难查,但最容易被忽视,因为问题只在冷启动瞬间出现一次。

6. 进阶:让 AD7190 读数更稳的土办法和长期验证习惯

前面把链路搭通之后,真正决定称重体验的是滤波和标定这两件小事。我习惯的做法是三层滤波:第一层在 ADC 输出速率层面,把速率降到能接受的响应范围;第二层在码值层面,用滑动平均去掉随机噪声;第三层在显示层面,加死区和轻量的变化率限制,防止单次异常值直接跳到屏幕上。三层各司其职,比单靠一层猛滤波有效得多。

标定方面,我个人不太信任一次性的两点标定就交付。称重设备的误差会随温度变化,应变片的温漂和 AD7190 基准的温漂会叠加。条件允许的话,我会在 20°C 和 40°C 下各做一次零点记录,算出一个零点温漂系数存进 Flash。使用时按当前温度补偿零点,能让大称台在温差明显的环境里也维持稳定。这不是数据手册里的规定,是用了几年后觉得值得做的事。每个项目我都会保留一个长期验证习惯:上电后先预热传感器 10 分钟,再执行一次内部零点校准,然后才开始计量。计量过程中每 30 分钟记录一次空载码值,观察零点漂移是否在允许范围。这套流程不花多少时间,但能挡掉不少“用了三个月后突然不准”的售后问题。AD7190 是一颗值得投入时间摸透的芯片,把寄存器、滤波、标定这几件事做扎实,称重项目就不会再被“最后几克”卡住。希望帮到你。

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

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

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

立即咨询