1. 称重项目为什么值得用 STM32+HX711 这套组合来做
称重看起来简单,无非是把物体放上去读个数,但真要做到稳定、准确、不跳变,里面的门道比想象中多。我前后做过好几个称重相关的项目,从最简单的电子秤到配料系统的称重反馈,踩过的坑基本都集中在三个地方:传感器信号太微弱、ADC 采样噪声大、显示刷新和采样节奏打架。STM32 加 HX711 加 OLED 这套组合,恰好能用较低的成本把这三个问题都处理得比较干净,所以它成了很多人做毕业设计、DIY 电子秤、小型配料装置的首选方案。
先把这套东西的角色分工说清楚。HX711 是一颗专为高精度电子秤设计的 24 位模数转换芯片,它内部集成了稳压电源、片内时钟振荡器,还有专门针对桥式传感器(也就是我们常说的电阻应变片称重传感器)的差分输入通道。它的两个通道 A 和 B,通道 A 可以设置 128 倍或 64 倍增益,通道 B 固定 32 倍增益,用来接一些辅助信号。STM32 在这里扮演的是大脑,负责按 HX711 的时序把数据读出来、做滤波和标定运算、驱动 OLED 显示,顺便还能扩展按键、串口上报、报警输出这些功能。OLED 则是人机交互的窗口,把重量数值、单位、状态实时呈现出来。
为什么不用 STM32 自带的 ADC 直接接传感器?这是新手最容易问的问题。原因在于称重传感器输出的差分电压非常小,满量程通常只有几毫伏到几十毫伏,STM32 内置的 12 位 ADC 分辨率和噪声水平根本不够看,直接采出来的数会跳得你怀疑人生。HX711 的 24 位分辨率配合内部低噪声放大器,能把有效精度做到十几万分之一甚至更高,这才是称重场景真正需要的东西。所以这套组合不是随便凑的,而是各司其职。
这套方案适合谁?如果你正在做基于 STM32 的毕业设计、想做一个厨房电子秤、想做配料称重、想给鱼缸或者宠物喂食器加称重反馈,那这套组合基本可以直接抄作业。它不需要你懂太深的模拟电路,只要把 HX711 的时序搞明白,剩下的都是软件活。下面我会从硬件连接、时序驱动、标定算法、OLED 显示、滤波优化几个角度,把整个项目拆开讲透,尽量把那些文档里不会写的经验都倒出来。
2. HX711 的硬件连接与那些容易接错的引脚
2.1 传感器和 HX711 之间的四线接法
称重传感器一般引出四根线,颜色通常是红、黑、白、绿,但不同厂家的配色并不统一,这一点必须先确认。标准接法是:红线和黑线是传感器的激励电源正负,白线和绿线是差分信号输出。接到 HX711 上时,红黑接 E+ 和 E-,白绿接 A+ 和 A-。这里有个非常关键的细节:白绿两根线如果接反了,读数会反向,也就是压上去重量变成负数。很多人第一次调试发现数值是负的,第一反应是程序写错了,其实只是信号线接反了,对调一下就好。
传感器到 HX711 的连线尽量短,如果实在要延长,建议用屏蔽线,并且屏蔽层单端接地。我见过有人用几十厘米的杜邦线随便一插,结果读数一直在抖,换成短一点的线立刻稳定。称重传感器的信号是毫伏级的,任何一点干扰都会被放大,所以走线这件事不能偷懒。
2.2 HX711 和 STM32 之间的引脚分配
HX711 和单片机之间只需要两根信号线:SCK(有时标为 PD_SCK)和 DOUT(有时标为 DT)。这两根线是 HX711 自己定义的串行时序,不是标准 I2C 也不是标准 SPI,所以不能用硬件外设直接驱动,必须用普通 GPIO 软件模拟时序。这一点是新手最容易误解的地方,看到两根线就以为是 I2C,结果怎么调都不通。
我一般这样分配引脚:把 DOUT 接到 STM32 的一个输入引脚,SCK 接到一个输出引脚。以 STM32F103C8T6 为例,可以选 PA0 做 DOUT、PA1 做 SCK,这两个引脚不冲突,配置起来也方便。DOUT 必须配置成输入模式,SCK 配置成推挽输出。如果你用的是 HAL 库,初始化的时候把这两个引脚分别设成 GPIO_MODE_INPUT 和 GPIO_MODE_OUTPUT_PP,速度设成低速就够了,因为 HX711 的时序并不快。
注意:HX711 的供电电压要和 STM32 的 IO 电平匹配。HX711 常见的工作电压是 2.7V 到 5V,如果你用 5V 给 HX711 供电,而 STM32 是 3.3V 系统,DOUT 输出的高电平可能接近 5V,长期直接接会损伤 STM32 引脚。稳妥的做法是 HX711 也用 3.3V 供电,或者加电平转换。实测 3.3V 供电时 HX711 完全能正常工作,精度也够用。
2.3 电源和去耦电容的处理
HX711 的供电质量直接影响读数稳定性。模块上一般已经带了滤波电容,但如果你是自己画的板子,AVDD 和 DVDD 附近一定要各放一个 0.1uF 的去耦电容,电源入口再并一个 10uF 的电解电容。传感器激励电源 E+ 和 E- 之间的电压稳定性同样重要,如果电源纹波大,读数就会跟着波动。
我遇到过一个很典型的案例:有人用 USB 供电给整个系统,电机一启动读数就乱跳。后来在 HX711 电源入口加了一个 LC 滤波,问题立刻缓解。所以如果你的项目里还有电机、继电器这类负载,一定要把称重部分的电源和它们隔离开,至少加个磁珠或者电感。
3. 把 HX711 的时序吃透:24 位数据到底怎么读出来
3.1 HX711 的通信时序本质
HX711 的通信协议说穿了很简单,但时序细节必须抠准。它的工作流程是这样的:当 DOUT 为高电平时,表示数据还没准备好,这时候你不能去读;当 DOUT 变成低电平,说明一次转换完成,数据可以读了。这时候你给 SCK 发 25 到 27 个时钟脉冲,前 24 个脉冲把 24 位数据一位一位地移出来,第 25 个脉冲用来选择下一次转换的通道和增益。
具体来说,第 25 个脉冲的数量决定下一次读的是哪个通道、什么增益:发 25 个脉冲选通道 A 增益 128,发 26 个脉冲选通道 B 增益 32,发 27 个脉冲选通道 A 增益 64。这个设计很巧妙,用脉冲个数来切换配置,省了额外的控制线。但如果你数错了脉冲,下一次读出来的数据就会来自错误的通道,表现就是数值突然变得很奇怪。
3.2 用 GPIO 模拟时序的代码实现
下面这段是基于 HAL 库的读取函数,逻辑很直白,你可以直接拿去改:
long HX711_Read(void) { unsigned long value = 0; unsigned char i; // 等待 DOUT 变低,表示数据就绪 while (HAL_GPIO_ReadPin(HX711_DOUT_PORT, HX711_DOUT_PIN) == GPIO_PIN_SET); for (i = 0; i < 24; i++) { HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_SET); value = value << 1; HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET); if (HAL_GPIO_ReadPin(HX711_DOUT_PORT, HX711_DOUT_PIN) == GPIO_PIN_SET) { value++; } } // 第 25 个脉冲,选择通道 A 增益 128 HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET); // 补码转换:24 位有符号数 if (value & 0x800000) { value |= 0xFF000000; } return (long)value; }这段代码里有两个地方值得展开说。第一,等待 DOUT 变低的那句 while 循环是阻塞的,如果 HX711 没接好或者坏了,程序会卡死在这里。实际项目里我会加一个超时计数,超过一定次数就返回错误,避免整个系统被一个传感器拖死。第二,24 位数据是补码形式,最高位是符号位,所以读完之后要判断第 23 位,如果是 1 就要把高 8 位补成 1,转成 32 位有符号数才能正确表示负值。
3.3 采样率 10Hz 还是 80Hz 的选择
HX711 有两个采样率版本,通过模块上的一个引脚(通常标为 RATE)来选择。拉低是 10Hz,拉高是 80Hz。这个选择不是随便定的,它直接决定了你的系统响应速度和噪声水平。
10Hz 模式下,芯片内部做了更多的平均,噪声更低,读数更稳,适合静态称重,比如电子秤、配料称。80Hz 模式下响应快,但噪声明显增大,适合需要快速跟踪重量变化的场景,比如动态检重、流量称重。我个人的经验是:如果你只是做一个静态电子秤,老老实实用 10Hz,读数稳定性会好很多,滤波压力也小。如果你发现 10Hz 下响应太慢,比如放上物体要等一秒才稳定,那再考虑 80Hz,同时把软件滤波加强。
提示:切换采样率之后,标定参数可能需要重新做,因为不同采样率下的噪声特性和零点漂移不完全一样。我一般会在确定采样率之后再开始标定,避免返工。
4. 从原始 ADC 值到真实重量:标定和去皮怎么做才靠谱
4.1 为什么要标定,标定的数学本质是什么
HX711 读出来的是一个 24 位的原始数值,它和真实重量之间是线性关系,但比例系数和零点你并不知道。标定就是把这个线性关系确定下来。数学上很简单:重量 = (原始值 - 零点值) / 比例系数。零点值是空载时的读数,比例系数是每单位重量对应的原始值变化量。
标定分两步。第一步去皮:空载时读若干个值取平均,作为零点。第二步放砝码:放一个已知重量的物体,读出稳定后的原始值,用 (当前值 - 零点值) / 砝码重量 算出比例系数。这个系数就是你的标定参数,存到 Flash 里,下次开机直接调用。
4.2 标定实操中的几个关键细节
砝码的选择有讲究。尽量选接近你常用量程的砝码,比如你平时称 1kg 左右的东西,就用 1kg 砝码标定,不要用 100g 去标一个 5kg 的量程,那样误差会被放大。如果没有标准砝码,可以用已知重量的物品代替,比如一袋标注净含量的米,但精度会差一些。
读数要等稳定了再取。HX711 上电后需要一点时间稳定,放上砝码后也要等读数不再漂移。我的做法是连续读 20 个值,去掉最大最小各几个,剩下的取平均,这样得到的标定值比较可靠。标定时环境温度也会影响结果,应变片对温度敏感,如果标定和使用的温差很大,零点会漂。要求高的场合需要做温度补偿,普通项目里至少保证标定和使用在相近温度下进行。
4.3 去皮功能的实现和常见误区
去皮功能看起来简单,但有个坑很多人踩:去皮之后如果零点漂移了,重量会慢慢偏。原因是去皮时记录的零点是一个瞬时值,而 HX711 的零点会随温度和时间缓慢变化。解决办法是定期自动去皮,或者在检测到空载且读数稳定时自动更新零点。
我一般会实现一个"自动零点跟踪"逻辑:当读数在零点附近一个小范围内波动,并且持续一段时间没有明显重量变化时,就认为当前是空载,慢慢把零点往当前值靠。这样既能消除漂移,又不会在称重过程中误更新。这个逻辑在电子秤里是标配,自己写项目时加上会让体验好很多。
5. OLED 显示:让重量数值稳定又好看地呈现出来
5.1 OLED 选型和 I2C 地址的那些事
0.96 寸 OLED 是最常见的选择,SSD1306 驱动芯片,四针 I2C 接口,接线简单。但这里有个高频问题:0.9 寸和 0.96 寸的 OLED 对 I2C 的兼容性不完全一样,有些 0.9 寸屏的 I2C 时序要求更严格,用标准库的软件 I2C 可能点不亮。如果你遇到 OLED 不亮,先检查地址,SSD1306 的 I2C 地址通常是 0x78 或 0x7A(7 位地址是 0x3C 或 0x3D),有些模块背面有电阻可以改地址。
还有一个常见现象是花屏。花屏的原因很多,电源不稳、I2C 速率太高、初始化序列不对都可能。我遇到最多的是电源问题,OLED 在刷新时电流会有波动,如果和 HX711 共用一条电源线且没有足够去耦,就会互相干扰。解决办法是在 OLED 的 VCC 和 GND 之间并一个 0.1uF 电容,尽量靠近屏幕引脚。
5.2 显示刷新的节奏控制
称重项目里,OLED 刷新和 HX711 采样如果处理不好会互相拖累。HX711 在 10Hz 模式下每 100ms 出一次数据,OLED 全屏刷新一次 I2C 传输大概需要几十毫秒。如果你在主循环里既读 HX711 又刷 OLED,很容易出现读数节奏被打乱的情况。
我的做法是把采样和显示解耦:用一个定时器或者系统滴答定时器,每 100ms 触发一次 HX711 读取,读到的值放进一个缓冲区做滤波;OLED 则每 200ms 到 300ms 刷新一次,只更新变化的数字区域,不做全屏刷新。这样既保证了采样节奏稳定,又不会因为显示拖慢系统。SSD1306 支持页地址模式,可以只更新某几页,刷新数字区域用这个模式效率很高。
5.3 显示内容和单位处理
显示内容上,我建议至少包含这几项:当前重量数值、单位(g 或 kg)、状态提示(稳定/不稳定)。数值的位数要根据量程定,比如 5kg 量程、1g 精度,就显示到小数点后一位或者整数克。单位切换可以在超过 1000g 时自动切到 kg,这样读起来更直观。
数值的稳定性显示是个加分项。当连续几次读数变化小于某个阈值时,显示"稳定",否则显示"波动"。这个判断逻辑很简单,但用户体验提升明显,尤其是配料场景,操作者需要知道什么时候读数可信。
6. 滤波和抗干扰:让读数不跳变的实战经验
6.1 为什么原始读数会跳
HX711 虽然精度高,但原始读数一定会有波动,这是正常的。波动来源有几个:传感器本身的噪声、电源纹波、机械振动、温度变化。你不可能把所有噪声都从硬件上消除,所以软件滤波是必须的。
很多人第一反应是用一个很大的平均值滤波,读 100 次取平均。这样做确实稳,但响应会变得很慢,放上物体要好几秒才显示正确值。滤波的本质是在稳定性和响应速度之间找平衡,没有免费午餐。
6.2 几种实用的滤波方案对比
| 滤波方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 算术平均 | N 次采样取平均 | 实现简单,抑噪明显 | 响应慢,N 越大越慢 | 静态称重 |
| 滑动平均 | 维护一个窗口,每次替换最旧值 | 响应比算术平均快 | 需要缓冲区 | 一般称重 |
| 中值滤波 | 取 N 次采样的中间值 | 抗脉冲干扰强 | 对连续噪声效果一般 | 有突发干扰的场合 |
| 一阶低通 | 新值按系数加权 | 计算量小,响应可调 | 参数需要调 | 实时性要求高 |
| 复合滤波 | 中值+滑动平均组合 | 兼顾抗脉冲和平滑 | 实现稍复杂 | 推荐方案 |
我实际项目里最常用的是中值加滑动平均的复合滤波:先连续采 5 个值,去掉最大最小,剩下 3 个取平均,再把这个结果放进一个长度为 5 的滑动窗口做二次平均。这样既有抗脉冲能力,又能平滑连续噪声,响应速度也还能接受。实测在 10Hz 采样率下,放上物体大约 300ms 到 500ms 就能稳定显示。
6.3 硬件层面的抗干扰补充
软件滤波再强,也架不住硬件层面的干扰。几个我踩过坑之后总结的硬件要点:传感器信号线远离电源线和电机线,如果必须交叉,尽量垂直交叉而不是平行走线;HX711 的模拟地和数字地分开走,最后单点汇合;整个系统如果有金属外壳,外壳接地。这些看起来是老生常谈,但在称重这种微弱信号场景里,每一条都能实实在在减少跳变。
还有一个容易被忽略的点:机械安装。传感器如果安装不牢固,或者受力方向不对,读数会随位置变化。称重传感器有明确的受力方向,安装时一定要按照它的受力轴线加载,不要有侧向力。我见过有人把传感器随便用螺丝拧在亚克力板上,结果不同位置放同一个物体读数都不一样,这就是机械安装没做好。
7. 项目整合:主循环怎么组织才不卡顿
7.1 用定时器驱动采样,主循环只管显示和逻辑
把整个项目串起来的时候,最容易犯的错误是把所有事情都塞进主循环顺序执行。读 HX711 要等 DOUT,刷 OLED 要等 I2C,按键要消抖,如果都顺序做,任何一个环节慢了都会拖累其他环节。
我的组织方式是:用一个定时器中断每 100ms 置一个标志位,主循环检测到标志位就去读一次 HX711 并做滤波;OLED 刷新用另一个计数器,每 3 次采样刷新一次;按键扫描放在主循环里,用状态机做消抖。这样各个任务的节奏是独立的,互不阻塞。如果你用的是 FreeRTOS,那就更简单,采样、显示、按键各开一个任务,用队列传递数据,优先级合理安排就行。
7.2 标定参数的存储和读取
标定参数要存到 STM32 的 Flash 里,掉电不丢。STM32F103 的 Flash 擦写需要注意,写之前要先擦除整个页,不能直接覆盖写。我一般会在 Flash 的最后一页划一块区域专门存参数,存的时候加一个校验和,读的时候校验一下,如果校验失败就用默认值,避免参数损坏导致系统异常。
参数结构体大概长这样:零点值、比例系数、校验和。存的时候把结构体按字节写入,读的时候按字节读出再还原。注意 Flash 写入的地址要按半字对齐,STM32F1 的 Flash 编程单位是半字(16 位),这一点在写代码时要留意。
7.3 串口上报和扩展功能
称重数据除了显示,很多时候还需要上报给上位机或者传给其他模块。用串口按固定格式发送是最简单的,比如每 200ms 发一帧"重量:1234g\r\n"。如果你要做更复杂的系统,比如配料控制,可以在重量达到设定值时触发继电器或者蜂鸣器,这些扩展都不难,核心还是把称重这部分做稳。
我在一个配料项目里做过这样的逻辑:设定目标重量,实时显示当前重量和差值,达到目标值 95% 时慢速加料,达到 100% 时停止。这里称重的响应速度和稳定性直接决定了配料精度,所以前面讲的滤波和采样率选择在这个场景里格外重要。
8. 调试过程中最常遇到的几个问题及排查思路
8.1 读数一直是 0 或者一直是最大值
这种情况通常是通信没通。排查顺序:先确认 DOUT 有没有在变化,如果 DOUT 一直高电平,说明 HX711 没有完成转换,检查供电和传感器接线;如果 DOUT 有变化但读出来全是 0 或全 1,检查 SCK 时序,特别是脉冲数量和顺序。用逻辑分析仪或者示波器看一眼 SCK 和 DOUT 的波形,问题一目了然。没有仪器的话,可以在代码里把原始值打印出来看变化趋势。
8.2 读数跳动厉害
先排除硬件:传感器接线是否牢固、电源是否干净、有没有电机等干扰源。硬件没问题再调软件:加大滤波窗口、降低采样率到 10Hz、检查标定是否合理。如果跳动是周期性的,可能是电源纹波或者机械振动,需要从根源上解决。
8.3 数值线性度差,小重量准大重量不准
这通常是标定问题。检查标定砝码是否接近常用量程,标定时读数是否稳定。如果传感器本身质量差,线性度就是不行,软件没法完全弥补。另外注意传感器的量程选择,用一个大量程传感器称小重量,精度天然就差,这是物理限制。
8.4 OLED 显示正常但数值不更新
检查采样标志位有没有被正确置位,滤波缓冲区有没有溢出,显示函数有没有被调用。这种问题多半是任务调度逻辑写错了,用调试器单步跟一下就能找到。
9. 我在这个项目里积累的几条实在经验
做称重项目这些年,有几个体会是文档里不会写但特别有用的。第一,先把硬件调通再写复杂软件。我见过太多人一上来就写一大堆滤波和显示代码,结果硬件接线是错的,调了半天以为是软件问题。正确的顺序是先用最简单的代码把原始值读出来,确认能随重量变化,再往上加功能。
第二,标定比滤波更重要。很多人把精力全花在滤波上,却忽略了标定的准确性。实际上一个准确的标定能让你的读数又稳又准,而滤波只是锦上添花。标定时多花十分钟,后面省心很多。
第三,留出调试接口。项目里一定要留串口打印原始值和中间变量的能力,调试阶段全靠它。等产品定型了再把调试代码去掉或者用宏关掉。
第四,注意温度影响。如果你的项目在不同温度环境下使用,零点漂移是绕不开的。简单的做法是开机时自动去皮,复杂一点可以加温度传感器做补偿。普通室内使用的项目,开机预热几分钟再标定,基本就够用了。
这套 STM32+HX711+OLED 的方案,硬件成本低,软件难度适中,只要把时序、标定、滤波这三块吃透,做出来的称重系统精度和稳定性都能满足大部分需求。后面如果你想扩展成无线传输、多路称重、或者接入更复杂的控制系统,这套基础框架都能直接往上搭。