简介:面向STM32F103单片机学习者和电子秤方向开发者,这份实验例程完整实现了基于AD7192高精度ADC芯片的电子秤方案,采用标准库编写,通过SPI接口连接传感器模拟前端,并将称重结果经USART发送至串口调试助手显示。资源共80个文件,以C源码和头文件为主(35个.h、33个.c),另含启动文件、Keil工程文件与链接脚本,可直接打开编译烧录;代码按ADC驱动、按键、SysTick、USART等模块划分,便于理解高精度采样、数据滤波和串口交互的完整流程。包体仅293KB,轻量易携带。目前已有315人学习下载。配套readme说明文档可帮助快速上手,适合入门AD7192配置、电子秤标定与误差补偿的读者;也可作为课程设计或毕业设计的基础工程,在其上扩展液晶显示、蓝牙上传等功能。
1. 电子秤精度瓶颈:24 位 Sigma-Delta 才是关键
把 STM32F103 的 12 位 ADC 直接接称重传感器,分辨率远远不够。按 3.3V / 4096 计算,LSB 约为 0.8mV,而常见桥式称重传感器满量程输出只有 10mV 上下(以 2mV/V、5V 激励计算),12 位 ADC 的有效位数根本不足以分辨 0.1g 量级的重量变化。AD7192 走的是另一条路:24 位 Sigma-Delta 架构,内置可编程增益放大器(PGA,1~128 倍)和数字滤波器,把量化噪声整形到高频后由滤波器切掉,再用 SPI 把结果送进 STM32F103。本实验例程的主题就是用标准外设库在 STM32F103 上驱动 AD7192,完成称重采样、滤波、标定和串口显示。整个过程拆成硬件接线、SPI 寄存器配置、数据读取与标定、串口输出、精度调试五步,每一段都有能直接抄进工程里的代码。
2. AD7192 称重电路原理与 STM32F103 引脚分配
2.1 桥式传感器输出特性与 AD7192 的 PGA
电子秤最常用的传感器是电阻应变片组成的惠斯通电桥。电桥有 4 根线的(E+、E-、S+、S-)和 6 根线的(多了两根反馈线),原理都一样:加载后桥臂电阻失衡,S+ / S- 之间产生差模电压。这个电压的幅度和激励电压成正比,典型规格是 2mV/V,也就是如果激励电压是 5V,满量程差模输出只有 10mV。要把 3000g 量程的分辨率做到 0.1g,ADC 需要分辨到 0.33uV,这对逐次逼近型 ADC 来说非常吃力,但 24 位 sigma-delta 芯片就容易得多。
AD7192 内部带着一个增益可调的模拟前端,增益范围 1、8、16、32、64、128。对 2mV/V 的传感器,例程里直接设到 128 倍增益,把 10mV 放大到 1.28V,接近 2.5V 基准电压的一半,有效位数利用最充分;如果换成灵敏度更高的传感器(比如 3mV/V),就把 PGA 降到 64。要注意的是,增益越大,输入等效噪声越明显,后面要配合 10SPS 左右的低输出速率来压噪声,这也是电子秤程序里最常见的参数组合方式。
2.2 最小系统的引脚规划:SPI、PDWN、SYNC
网上搜 STM32F103 最小系统的引脚分配,最常见的是把 AD7192 接到 SPI1:PA5 做 SCLK,PA6 做 MISO,PA7 做 MOSI,PA4 做片选 CS。如果 PA4 被别的外设占了,也可以换成 PA15 或 PB12,只要把 GPIO 初始化改掉即可。除了四根 SPI 线,还有两根控制线:PDWN(掉电/复位,低电平有效)和 SYNC(滤波同步控制,一般悬空或接高电平)。PDWN 引脚在做硬件复位时要用,软件初始化时也可以靠 SPI 连续写 0xFF 来完成串口接口复位,这个后面再讲。
一个 STM32F103 引脚规划的常见坑是:PA11 / PA12 默认复用为 USB,如果板子同时接了 USB 座和 SPI2 的 MISO/MOSI,初始化顺序不对可能互相干扰,排查时先看 GPIO 配置里是否把 PA11/PA12 点成了外设复用。以下接线表按最小系统最常用的配置列出来,直接照连即可。
| 信号 | STM32F103 引脚 | 说明 |
|---|---|---|
| SPI1_SCK | PA5 | SPI 时钟,建议空载上拉到 VCC |
| SPI1_MISO | PA6 | AD7192 DOUT,数据输出 |
| SPI1_MOSI | PA7 | AD7192 DIN,数据输入 |
| SPI1_CS | PA4 | AD7192 CS,软件手动控制 |
| AD7192_PDWN | PB0 | 上电后拉高,复位时拉低 10us |
| AD7192_SYNC | PB1 | 滤波同步,悬空或接高电平 |
| VREF | 外部 2.5V 基准 | 不用 MCU 的 3.3V 直接供电 |
2.3 基准电压与激励电源的选择
AD7192 的测量精度直接取决于基准电压的稳定性,例程的做法是在 VREF 引脚接一个 2.5V 高精度基准芯片,而不是直接把 STM32F103 的 3.3V 拿过来当 VREF。原因很简单:3.3V 电源上有数字开关噪声,而且随着负载变化会有毫伏级的波动,折算到 24 位分辨率里就是几十个 LSB 的闪烁。激励电压 E+ 则根据传感器规格选择,多数 3kg 传感器用 5V 激励,可以用一个 LDO 单独供电,避免和 MCU 电源共用后相互串扰。
AD7192 还有一对输入缓冲选项。开启缓冲后,输入阻抗大幅提高,可以直接接高阻抗传感器输出,不必担心影响电桥平衡;但缓冲开启后输入信号范围会比电源轨略小。对于称重应用,开启输入缓冲是默认选择,用 5V 激励时 S+ / S- 的共模大约 2.5V,AVDD 用 5V 供电,余量完全够用。供电部分注意 AVDD 和 DVDD 要分别用磁珠或小电阻隔开,数字地和模拟地在 PCB 上单点汇合,这个细节对 AD7192 的噪声影响比很多参数调整都明显。
3. 标准库下 SPI 通信与 AD7192 寄存器配置
3.1 新建标准库工程时的 SPI 初始化要点
用标准外设库新建工程时,SPI 初始化要保证三件事:RCC 时钟开启、GPIO 模式设为复用推挽或浮空输入、SPI 分频算准。AD7192 最高 SPI 时钟可以到几 MHz,但为了布线和电源噪声的容错,例程用 APB2 72MHz 除以 64,得到约 1.125MHz,这个速度对 10SPS 的称重采样完全够用。标准库的 SPI 初始化结构体写法如下:
SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_SPI1, ENABLE); // PA5 SCK, PA7 MOSI,配置为复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // PA6 MISO,配置为浮空输入,方向由 AD7192 驱动 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // PA4 CS,推挽输出,手动控制片选 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_4); // CS 默认拉高 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; // 空闲时钟为高 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // 第二个边沿采样 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE);这段代码的逻辑是:先把 SCK 和 MOSI 配成复用推挽,让 SPI 外设控制这两个引脚;MISO 是 AD7192 的输出引脚,STM32 侧应配成浮空输入;CS 用普通 GPIO 手动控制,方便在每次通信前拉低、结束后拉高。CPOL=High 和 CPHA=2Edge 是 AD7192 数据手册要求的 SPI 模式,第一次调不通时优先检查这里,很多例程用的是 STM32 默认的低电平空闲模式,会导致 AD7192 完全无响应。标准库下 NSS 配置成软件模式后,硬件 NSS 引脚不再参与控制,片选逻辑交给 PA4 的手动拉高拉低。
提示:PA4 的 GPIO 初始化放在 SPI_Cmd 使能之前,否则先开外设再拉低片选,可能出现一帧多余的时钟脉冲。
3.2 通信寄存器结构与底层读写函数
AD7192 的所有操作都通过通信寄存器发起,这是一个 8 位寄存器。写入通信寄存器时,最高位(位 7)表示下一次操作方向:1 表示读,0 表示写;位 5 到位 3 选择目标寄存器;位 2 表示是否为连续写模式;低两位保留。向配置寄存器写入数据时,指令字节是 0x10;要从数据寄存器读取 24 位数据,指令字节是 0x40(读 DATA 寄存器加最高位)。这个分层设计让 8 位 SPI 总线可以访问多个 24 位寄存器。
底层读写函数可以封装成下面这样,后面的初始化、采样和校准都只用这两个函数:
static void AD7192_Write(uint8_t reg, const uint8_t *pData, uint8_t len) { CS_LOW(); SPI_SendByte(reg); // 写通信寄存器,选目标寄存器 while (len--) { SPI_SendByte(*pData++); } CS_HIGH(); } static void AD7192_Read(uint8_t reg, uint8_t *pData, uint8_t len) { CS_LOW(); SPI_SendByte(reg | 0x40); // 最高位置 1,表示读操作 while (len--) { *pData++ = SPI_ReadByte(); } CS_HIGH(); }这里的 SPI_SendByte 和 SPI_ReadByte 是标准库 SPI_I2S_SendData / SPI_I2S_ReceiveData 加等待标志位的封装。读操作时,寄存器地址必须或上 0x40,否则 AD7192 会把后续数据当成写操作接收。CS 的低电平必须覆盖整个读或写过程,不能在两个字节之间拉高 CS,否则通信中断,寄存器指针会回到通信寄存器,下次操作必须重新发送读/写指令。标准库例程里常犯的一个错误是每次发送完一字节就判断一次 SPI_I2S_GetFlagStatus,导致 CS 超过 10us 拉高但数据传输还没完成,调试现象就是读出来全是 0xFF 或 0x00。
3.3 模式寄存器与配置寄存器的参数设计
模式寄存器(写入口 0x10)和配置寄存器(写入口 0x20)是 AD7192 上电初始化时最先要写的地方。模式寄存器控制转换模式、时钟源和输出速率;配置寄存器控制输入通道、PGA 增益、输入缓冲和双极性/单极性模式。电子秤例程的标准做法是连续转换模式,时钟用内部 4.92MHz,这样不需要额外接晶振。输出速率由滤波器字 FS 决定,FS 越大输出速率越低,噪声越低。
void AD7192_Init(void) { uint8_t mode[3], conf[3]; uint32_t mode_reg, conf_reg; // 先做一次串行接口复位:连续写入 6 个 0xFF CS_LOW(); for (uint8_t i = 0; i < 6; i++) SPI_SendByte(0xFF); CS_HIGH(); // 模式寄存器:连续转换模式,10SPS 左右 // 位 23-21 MD=000,位 19-18 CLK=10(内部时钟) // 位 10-0 FS = 240,配合内部 4.92MHz 得到接近 10SPS mode_reg = (0 << 21) | (2 << 18) | 240; // 配置寄存器:AIN1-AIN2 差分,PGA=128,开启输入缓冲,双极性 // 增益编码 101 对应 32 倍,111 对应 128 倍 conf_reg = (1 << 19) | (7 << 1) | (0 << 0); mode[0] = (mode_reg >> 16) & 0xFF; mode[1] = (mode_reg >> 8) & 0xFF; mode[2] = mode_reg & 0xFF; AD7192_Write(0x10, mode, 3); conf[0] = (conf_reg >> 16) & 0xFF; conf[1] = (conf_reg >> 8) & 0xFF; conf[2] = conf_reg & 0xFF; AD7192_Write(0x20, conf, 3); }模式寄存器里 FS=240 这个值按内部 4.92MHz 主频计算,输出速率约 10SPS,正好落在 50Hz 工频干扰衰减比较好的区间。如果你的环境是 60Hz 供电,把 FS 调小到 200 左右。配置寄存器里 PGA 增益编码要查对应对照,不同批次的 AD7192 对增益位定义一致,但很多例程直接把整个配置字写成十六进制类似 0x1B08,移植时只看注释不看公式,结果换了传感器量程不知道怎么改。建议把所有配置分解成移位表达式,这样每个位的作用一目了然,换传感器时只改增益和通道两个字段即可。
4. 数据读取、滤波与称重标定
4.1 连续转换模式下读取 24 位数据
配置完成后,AD7192 会自动开始连续转换。每次转换完成后,DOUT 引脚会拉低,MCU 可以通过查询 DOUT 电平判断数据是否就绪。拉低 CS、发送 0x40 读取数据寄存器,连续读 3 个字节得到一个 24 位补码值,这就是当前采样点的原始码值。电子秤例程里不需要中断,主循环轮询 DOUT 即可,10SPS 的速率下 CPU 占用可以忽略。如果开启了 DAT_STA 位,读到的 4 个字节里最后一个字节是状态寄存器,里面有通道号、超范围标志和数据就绪标志,调试时非常有用。
uint32_t AD7192_ReadData(void) { uint8_t buf[3]; uint32_t raw; // 等待 DOUT 拉低,表示数据已就绪 while (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_10) != 0); AD7192_Read(0x00, buf, 3); // 0x00 对应数据寄存器 raw = ((uint32_t)buf[0] << 16) | ((uint32_t)buf[1] << 8) | buf[2]; // 24 位补码转有符号数 if (raw & 0x800000) { raw |= 0xFF000000; } return (int32_t)raw; }这里参考接线里把 AD7192 的 DOUT 接到了 PC10,用查询方式判断数据就绪。24 位补码转有符号数的逻辑是:最高位为 1 表示负数,扩展到 32 位后再转成 int32_t 才有正确的负数含义。电子秤正常情况下加到传感器上的力是压力,原始码总是正值,但空载时因失调电压可能是负值,所以必须正确处理负数的扩展,否则去皮后重量会出现跳变。
4.2 滑动平均滤波与中位值滤波的组合
AD7192 的数字滤波器本身已经做了 Sinc 低通,但在 10SPS 下仍然能看到 1~2 个 LSB 的随机抖动。振动环境下这几个 LSB 折算到重量可能就有 0.2g~0.5g,所以例程在软件里加一级滑动平均。滑动平均对周期性振动抑制效果好,但对脉冲干扰(比如短暂触碰秤体)响应迟钝,所以更好的做法是“中位值 + 滑动平均”两级滤波:先取最近 5 次采样排中值,再对中值序列做 10 点滑动平均。这段代码很直白,但参数选择有讲究。
#define FILTER_QUEUE_SIZE 10 static int32_t filter_queue[FILTER_QUEUE_SIZE]; static uint8_t filter_index = 0; static int32_t Median5(int32_t a, int32_t b, int32_t c, int32_t d, int32_t e) { int32_t t; // 简单插入排序,数据量小没必要上快排 int32_t arr[5] = {a, b, c, d, e}; for (int i = 1; i < 5; i++) { for (int j = i; j > 0 && arr[j] < arr[j-1]; j--) { t = arr[j]; arr[j] = arr[j-1]; arr[j-1] = t; } } return arr[2]; } int32_t AD7192_GetFilteredValue(void) { int32_t raw[5], median, sum = 0; for (int i = 0; i < 5; i++) { raw[i] = AD7192_ReadData(); HAL_Delay(2); } median = Median5(raw[0], raw[1], raw[2], raw[3], raw[4]); filter_queue[filter_index] = median; filter_index = (filter_index + 1) % FILTER_QUEUE_SIZE; for (int i = 0; i < FILTER_QUEUE_SIZE; i++) { sum += filter_queue[i]; } return sum / FILTER_QUEUE_SIZE; }注意这段代码写成了标准库风格,但用了一个比较少见的设计:每个滤波周期内取 5 次原始采样做中位值,再把中位值放入 10 点滑动队列。这样等效采样率其实被拉低了,实际最终输出大概每 100ms 更新一次,对应上一个 10SPS 配置是合理的。如果希望响应更快,把 Median5 的循环次数改成 3,滑动平均窗口改成 4,更新速率可以翻倍,但噪声会略增。这个参数组合要在“稳定显示”和“快速响应”之间做取舍,电子秤一般以稳定优先。
4.3 标定的两个环节:去皮与增益系数
电子秤标定分成两个环节:空载时记录零点偏移,加载已知砝码时计算增益系数。零点偏移会因为温度漂移和机械形变缓慢变化,所以例程提供一个按键触发的去皮功能。增益系数的计算方法是:先读空载滤波值作为 zero_code,再放上标准砝码读 load_code,用两者差值除以砝码重量得到每克的码值,也就是 scale_factor。之后任意时刻的重量等于当前码值减去 zero_code,再除以 scale_factor。
typedef struct { int32_t zero_code; // 空载零点码值 float scale_factor; // 码值/克 float weight_gram; // 计算结果 } Scale_Handle; void Scale_Tare(Scale_Handle *h) { h->zero_code = AD7192_GetFilteredValue(); // 空载采样 } void Scale_Calibrate(Scale_Handle *h, float known_weight_gram) { int32_t load_code = AD7192_GetFilteredValue(); float delta = (float)(load_code - h->zero_code); h->scale_factor = delta / known_weight_gram; // 单位:码值每克 } void Scale_Update(Scale_Handle *h) { int32_t cur = AD7192_GetFilteredValue(); h->weight_gram = (float)(cur - h->zero_code) / h->scale_factor; }这套定义里 scale_factor 的符号隐含了方向:压力增加时码值增大,所以是正值。如果传感器接反,加载后码值变小,计算出来的重量为负数,这时只需交换 S+ 和 S- 两根线。这里没有引入任何浮点以外的重量换算,电子秤分辨率 0.1g 时直接用 float 足够,误差远小于传感器本身的非线性。要注意的是 Calibrate 函数里的 known_weight_gram 必须用标定砝码的实际重量,不要用显示值回代,否则会累积误差。
4.4 量程保护与超限判断
当负载超过传感器量程时,AD7192 的输出码值会接近满幅值并可能翻转。数据手册里有一个超范围状态位,开启 DAT_STA 模式后可以从状态寄存器读取。例程的做法比较简单:把滤波后的码值和上次保存的满量程码值比较,如果码值超过满量程对应值的 105%,就认为超载,显示区输出提示并停止重量更新。这个判断放在 Scale_Update 里做,避免超载时显示一个错误的满量程数值。
5. 串口输出与串口调试
5.1 标准库下 USART1 初始化与 printf 重定向
串口显示的两种常见做法是:直接调 sprintf 生成字符串后循环发送,或者用 fputc 重定向 printf 只维护一个库函数的指针。电子秤例程里用第二种,因为格式化输出频繁,printf 重定向后代码可读性好很多。USART1 在 PA9(TX)和 PA10(RX),初始化时注意使能 AFIO 时钟,否则引脚复用不生效。以下初始化代码取 115200 波特率、8 位数据、1 位停止位、无校验,这是串口调试助手的默认配置之一。
void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // TX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // RX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_RX | USART_Mode_TX; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); } int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }fputc 的重定向逻辑是每个字符发送前等待发送数据寄存器为空。115200 波特率下串一个字符约 87us,一秒输出 10 次重量、每次 20 字节左右,整个 printf 处理占用 CPU 的时间很短,不会干扰 10SPS 的 AD7192 采样。如果调试时发现 printf 没有输出,先在 while(1) 循环里每隔一段延时发送固定字符,排除波特率问题后再检查 fputc 有没有正确链接到库的底层。
5.2 串口 1 和串口 3 的使用差异
例程默认用串口 1,因为 PA9/PA10 是最常见的 TTL 引脚,USB 转 TTL 模块和串口调试助手默认接这对引脚。但项目中有时需要把两个串口都打开:串口 1 用来看调试日志,串口 3 接上位机或者蓝牙模块。串口 3 在 PB10(TX)和 PB11(RX),初始化时时钟使能的是 RCC_APB1Periph_USART3,注意这是 APB1 总线,而不是 APB2。APB2 时钟是 72MHz,APB1 时钟默认是 36MHz,如果用一个通用的 UART 初始化函数却不区分总线时钟,波特率会翻倍出现乱码。
STM32F103 标准库下串口 1 和串口 3 的差异,主要体现在时钟源、中断通道号和重映射三个方面:
| 串口 | TX/RX 默认引脚 | 挂载总线 | 中断通道 | 重映射说明 |
|---|---|---|---|---|
| USART1 | PA9 / PA10 | APB2 | USART1_IRQn | 可映射到 PB6/PB7 |
| USART2 | PA2 / PA3 | APB1 | USART2_IRQn | 可映射到 PD5/PD6 |
| USART3 | PB10 / PB11 | APB1 | USART3_IRQn | 可映射到 PC10/PC11 或 PD8/PD9 |
串口 1 和串口 3 的 BaudRate 计算都用到总线时钟,标准库里 USART_Init 会根据 RCC 配置自动取值,但如果你手动改过 RCC_PCLK1Config,必须确保初始化顺序是时钟配置在先、串口初始化在后。另一个容易踩的坑是串口 3 如果重映射到了 PC10/PC11,恰好和前面 AD7192 的 DOUT 查询引脚冲突,这时候只能另选 GPIO 来读取 DOUT,或改用串口 1。
5.3 串口调试助手与数据帧设计
电子秤例程输出建议按固定帧格式发送,方便上位机解析,也方便串口调试助手直接按文本显示:
void Scale_Report(Scale_Handle *h) { char buffer[48]; // 例程输出:WEIGHT,123.4,g sprintf(buffer, "WEIGHT,%.1f,g\r\n", h->weight_gram); printf("%s", buffer); }如果只显示重量数值,用printf("%.1f\r\n", h->weight_gram);即可。串口调试助手里要注意两项设置:波特率和“发送新行”勾选。很多调试失败不是程序问题,而是串口调试助手里的 DTR/RTS 默认电平把 STM32F103 复位了,打开串口一瞬间芯片连续重启,上位机看到的是一直重复的启动日志。CH340 这类 USB 转串口驱动在 Windows 下还有缓存缓冲,如果数据量大,要关闭“缓存”选项或改用 TTL 电平直接连接。
6. 提高称重精度与常见故障排查
6.1 使能 CHOP 斩波来抑制失调漂移
AD7192 支持斩波稳定模式(CHOP 置 1),该模式下器件会周期性地交换输入,把运放的失调和低频漂移调制到高频后再滤除。电子秤这种低速高精度应用,CHOP 开启后零点漂移改善明显,代价是最高的输出速率会降低,但 10SPS 档位下几乎不受影响。例程里在配置寄存器中把 CHOP 位置 1 即可。如果输出速率被要求提升到 100SPS 以上,CHOP 效果会减弱,此时应回到关闭斩波的模式并依赖外部校准来消除失调。
验证斩波是否生效的方法是:上电后连续记录 30 秒空载码值,统计最大最小值之差。未开斩波时如果差值为 15 个 LSB,开后应该能压到 5~8 个 LSB。如果不能明显降低,检查传感器供电是否稳定,以及 S+ / S- 的屏蔽层是否单端接地。
6.2 三类常见问题:AD7192 无响应、重量跳变、串口乱码
AD7192 无响应,优先检查 SPI 模式。上电后从状态寄存器读回设备 ID(0x00 读入口是 0x40,ID 寄存器地址是 0x03),读到 0xE 或 0x3 说明通信正常。如果读回全是 0xFF,说明 DOUT 一直被拉高,这时把 SCLK 空闲电平改为高、数据在第二个边沿采样再试。如果读回全是 0x00,检查 MISO 引脚是否被配置成推挽输出,标准库下浮空输入才是正确方向。
重量跳变,一是看传感器附近有没有振动源,二是把采样速率降到 10SPS 以下再观察。如果跳变量级是正负几十个 LSB,先用短路夹把 S+ / S- 短路,看码值是否稳定;稳定则问题在传感器机械部分,不稳定则问题在模拟前端或电源。串口乱码则先统一波特率,再确认串口 3 的时钟挂在 APB1 上,最后用示波器看 TX 引脚的空闲电平和起始位的宽度是否符合 115200 协议。
6.3 用原始码值代替重量调试
排查结束时,一个值得保留的习惯是把重量显示和原始码值分开调试。先在串口输出原始滤波码值确认系统稳定,再切到重量显示做标定。每次改完滤波器参数或换传感器,都先看原始码值是否稳定到预期的噪声范围内,再谈重量精度。这样能把“模拟前端问题”和“标定问题”彻底分开,调秤的效率会高很多。AD7192 这颗芯片本身噪声足够低,真正限制精度的往往在这条链路之外:传感器供电纹波、地平面布置、桌面振动,以及你给串口调试助手选的换行符。
本文还有配套的精品资源,点击获取