☰
STM32+HX711拉力传感器高精度采集实战:从原理到标定滤波
2026/10/2 5:52:26 网站建设 项目流程

1. 项目概述与方案选型

1.1 拉力传感器数据采集,为什么绕不开HX711

做嵌入式的小伙伴应该都有这种感觉:采集电压、电流、温度这些信号还算顺手,但一到“称重”或者“拉力”这种需要高精度小信号采集的场景,就容易卡壳。原因很简单——拉力传感器的核心是一个惠斯通电桥,它输出的差分信号通常只有几毫伏(mV级),直接用STM32的内部ADC去采,分辨率根本不够看。

STM32的ADC通常是12位,参考电压如果是3.3V,那么最小分辨率大约是3.3V / 4096 ≈ 0.8mV。而一个常见的拉力传感器,在满量程时的输出信号可能只有10mV~20mV,也就是说满量程在整个ADC量程里连1%都占不到。这样算下来,你采出来的数值基本就是在噪声里折腾,根本没有实用价值。

这就是HX711存在的意义。它内部集成了一个低噪声的可编程增益放大器(PGA)和一个24位的高精度Σ-Δ型ADC。增益可以设置为32、64或128,配合24位的分辨率,能够把mV级的传感器信号放大到适合采样的范围,然后以数字方式输出,MCU只需要通过两根线(时钟SCK和数据DOUT)就能把数据读回来。整个链路简单、稳定、成本低,是市面上做称重、拉力、压力采集最成熟、资料最全的方案。

这篇内容我尽量用实战的方式来讲:从传感器原理、硬件接线、驱动代码,到标定、滤波和常见坑点,一次性梳理清楚。做完这个项目,你不仅能搞定拉力传感器,后续换压力传感器、称重模块,原理都是一模一样的。

1.2 系统组成与核心指标

这个项目的硬件组成非常精简,整条链路一共就四个部分:

模块型号/规格作用
主控MCUSTM32F103C8T6(或其他任意型号)负责时序控制、数据处理和通信输出
信号采集HX711模块(带放大和ADC)将传感器差分信号转为24位数字信号
传感器拉力传感器(推荐5kg~50kg量程)将拉力/重力转为电阻变化,输出差分电压
显示/上位机串口调试助手或OLED屏实时查看采集到的重量/拉力值

这里多说一句,HX711模块在淘宝上非常多,几块钱一块,板载了晶振和必要的去耦电容,一般还带了接线端子,用起来很方便。需要注意的是,市面上模块质量参差不齐,有的模块上的120Ω电阻精度不高,会导致零点漂移,这个后面排查问题会讲到。

2. 硬件连接与底层原理

2.1 拉力传感器的工作原理

拉力传感器本质上是一个“应变片桥”。传感器内部贴了四片应变电阻,组成一个全桥结构。当外力作用在传感器上时,其中两片电阻被拉伸、阻值变大,另外两片被压缩、阻值变小,于是电桥失去平衡,在输出端产生一个与外力成正比的差分电压。

举个例子,一个量程10kg的拉力传感器,灵敏度常见为1.0mV/V或2.0mV/V。如果激励电压是5V、灵敏度为2.0mV/V,那么满量程时输出差分电压是:

5V × 2.0mV/V = 10mV

这10mV在满量程(10kg拉力)时输出,对应到每个千克就是1mV的分辨率。这个信号如果直接进STM32的ADC,几乎没法用。所以HX711的放大功能在这里就非常关键,它的增益为128时,可以把10mV放大到1.28V,这个范围用24位ADC量化,分辨率远远够用。

信号调理的核心任务,就是把“微小差分电压”变成“可被稳定采样的数字量”。HX711本质上负责三件事:放大、模数转换、串行输出,MCU要做的只是按照时序把数字量读出来。

2.2 HX711引脚定义与接线方案

HX711模块对外引出的引脚一般有8个:E+、E-、A+、A-、B+、B-、SCK、DOUT,加上VCC和GND(不同模块引出形式略有差异)。

  • E+ / E-:给传感器桥路供电的激励电压输出
  • A+ / A-:通道A的差分输入,接传感器信号线
  • B+ / B-:通道B输入,一般用于接第二路传感器或者不用
  • SCK:时钟输入,由MCU控制
  • DOUT:数据输出,数据准备好后拉低,MCU读取

接线非常简单:

HX711引脚连接对象说明
VCC3.3V或5V电源模块支持2.6V~5.5V供电
GND电源地与STM32共地
SCKSTM32任意GPIO(输出)推荐PB0或PA0,便于测试
DOUTSTM32任意GPIO(输入)推荐PB1或PA1
E+传感器红线激励电压正
E-传感器黑线激励电压负
A+传感器绿线(或白线)信号正
A-传感器白线(或绿线)信号负

每家传感器厂家的线序颜色可能不一样,我手里的这个传感器是红黑绿白,对应E+、E-、A+、A-。如果你拿到的传感器颜色不同,建议先查一下厂家的规格书,或者用万用表量一下:激励端两根线之间的阻值一般较大(几百欧),信号端两根线之间阻值较小且接近,连接反了也能工作,只是读到的数值是负的,后面标定时可以软件取反。

2.3 供电与接线中容易踩的坑

HX711的供电电压直接决定了传感器激励电压,也会影响最终的稳定性。我实际测试下来,用3.3V供电还是5V供电,差别主要在传感器灵敏度上——激励电压越高,传感器满量程输出信号越大,信噪比越好。但HX711的数字部分和模拟部分对电源纹波有要求,电源不干净会导致读数漂移严重。

几个关键注意点:

  • 必须共地:HX711的GND和STM32的GND要连在一起,否则DOUT高电平都可能读不对。
  • 信号线尽量短:拉力传感器到HX711之间的连线我建议控制在30cm以内,如果实在要长,用屏蔽线。
  • 电源去耦:在HX711模块的VCC和GND之间并一个100uF的电解电容和0.1uF的陶瓷电容,可以有效改善电源纹波对采样的影响。很多现成模块板载了这些电容,但如果买的是散件自己焊,这个不能省。
  • 传感器E+和E-不要接反:接反了传感器虽然不会烧,但输出信号极性反了,读出来是负值,排查时容易误判为硬件故障。

3. 核心代码实现与解析

3.1 HX711读取时序解析

HX711的输出协议只有两根线,但时序细节很关键。简单来说:

  • 当DOUT从高电平变为低电平时,表示一次转换完成,数据已经准备好了。
  • MCU检测到DOUT拉低后,开始给SCK发送25个脉冲(或26个,取决于是否要通道/增益信息)。
  • 前24个脉冲的上升沿,HX711会在DOUT上依次输出24位数据,从最高位(D23)到最低位(D0)。
  • 第25个脉冲决定下一次转换的通道和增益:1个脉冲表示通道A、增益128;如果发送第26个脉冲,则表示通道A、增益64;如果发送第25~27个脉冲(共27个),则表示通道B、增益32。
  • 完成最后一个脉冲后,DOUT会在下一次转换完成前保持高电平。

数据格式是二进制补码,高位在前。实际使用时直接读24次,每次读DOUT的电平,然后拼成一个int型变量即可。

如果你跟我一样,第一次接触这个时序时觉得有点绕,可以把它想象成“MCU问一次,芯片答一次”的握手过程:DOUT拉低是芯片说“我准备好了”,SCK脉冲是MCU说“把数据交给我”。

3.2 HX711驱动代码(可以直接抄)

下面这段代码我基于标准库写的,用GPIO模拟时序,不依赖任何特殊外设,任何STM32型号都适用。核心就两个函数:HX711_Init()做引脚初始化,HX711_Read()读取一次24位原始值。

// hx711.h #ifndef __HX711_H #define __HX711_H #include "stm32f10x.h" // 引脚配置,根据实际接线修改 #define HX711_SCK_PORT GPIOB #define HX711_SCK_PIN GPIO_Pin_0 #define HX711_SCK_CLK_ENABLE() RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE) #define HX711_DOUT_PORT GPIOB #define HX711_DOUT_PIN GPIO_Pin_1 #define HX711_DOUT_CLK_ENABLE() RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE) #define HX711_SCK_L() GPIO_ResetBits(HX711_SCK_PORT, HX711_SCK_PIN) #define HX711_SCK_H() GPIO_SetBits(HX711_SCK_PORT, HX711_SCK_PIN) #define HX711_DOUT_READ() GPIO_ReadInputDataBit(HX711_DOUT_PORT, HX711_DOUT_PIN) void HX711_Init(void); long HX711_Read(void); #endif
// hx711.c #include "hx711.h" void HX711_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; // 打开GPIO时钟 HX711_SCK_CLK_ENABLE(); HX711_DOUT_CLK_ENABLE(); // SCK配置为推挽输出 GPIO_InitStructure.GPIO_Pin = HX711_SCK_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(HX711_SCK_PORT, &GPIO_InitStructure); // DOUT配置为上拉输入(注意,有些模块DOUT是开漏输出,上拉必须加) GPIO_InitStructure.GPIO_Pin = HX711_DOUT_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(HX711_DOUT_PORT, &GPIO_InitStructure); // 初始SCK为低 HX711_SCK_L(); } long HX711_Read(void) { long val = 0; unsigned char i; // 等待DOUT拉低,表示数据就绪 // 如果DOUT一直为高,则说明芯片异常或接线错误 while (HX711_DOUT_READ()); // 读24位数据,从最高位开始 for (i = 0; i < 24; i++) { HX711_SCK_H(); delay_us(1); // 脉冲高电平持续时间 val = val << 1; if (HX711_DOUT_READ()) { val++; } HX711_SCK_L(); delay_us(1); } // 第25个脉冲:通道A,增益128 // 如果你想用增益64,这里改成发26个脉冲 HX711_SCK_H(); delay_us(1); HX711_SCK_L(); delay_us(1); return val; }

这里有几个细节要留意:

  • delay_us(1)至少要有1微秒,HX711对时序的要求不是特别严格,但脉冲宽度太小会误判。在72MHz主频下,这个延时函数用定时器或者DWT实现都行,千万别用空循环随便delay,不同优化等级下差异很大。
  • val是long类型,因为24位数据超出16位范围。如果你用的是int,高字节会被截断。
  • 第一次上电后,DOUT拉低的时间可能比平时晚一些,这是因为HX711上电后需要一段时间做自校准。如果你在主循环里第一次读数据发现数值异常大,正常,等一下再读就好了。

3.3 串口输出和主循环示例

光能读原始值还不够,调试时要把数据显示出来。我用串口1做数据输出,9600波特率足够,当然你用115200也没问题。

// main.c 片段 #include "stm32f10x.h" #include "hx711.h" #include "usart.h" int main(void) { long val; // 配置系统时钟72MHz SystemInit(); // 初始化串口和HX711 USART1_Init(9600); HX711_Init(); printf("HX711 Test Start...\r\n"); while (1) { val = HX711_Read(); printf("RAW:%ld\r\n", val); delay_ms(200); } }

运行上电后,串口应该能看到RAW值的连续输出。如果串口没有任何输出,先检查串口配置,别怀疑HX711。如果RAW值一直是8388608左右(也就是0x800000,24位补码的最小负数),大概率是DOUT一直没拉低,芯片没工作,检查一下VCC和GND。

如果RAW值在某个固定值附近波动,说明你的传感器和HX711已经正常工作,接下来就可以做标定了。

4. 标定与数据滤波

4.1 为什么必须做标定

HX711读出来的是原始ADC码值,不是一个直接的“克数”或者“牛顿数”。要得到有物理意义的拉力值,必须建立“原始码值”和“真实拉力”之间的映射关系。

映射关系本身是线性的:拉力值 = (原始值 - 零点值) / 斜率。

这里有两个关键参数:

  • 零点值(Offset):传感器不受力时HX711输出的原始值。这个值不为零很正常,因为电桥本身就有一定的初始不平衡,HX711也有失调电压。
  • 斜率(Scale):每单位拉力对应的码值变化量。斜率跟传感器的灵敏度、HX711的增益、供电电压都有关系。

理论上,如果你知道传感器的灵敏度(比如2.0mV/V)、激励电压(5V)和HX711增益(128),可以算出一个理论斜率。但实际焊接、安装应力、传感器个体差异都会让理论值不准确,所以最可靠的办法就是用已知重量的砝码做两点标定。

4.2 两点标定法实操步骤

标定不需要什么高级设备,几个已知重量的砝码就行,我用的是一套50g~500g的砝码。

操作步骤:

  1. 记录零点值:传感器空载,连续读20次,取平均值作为offset_value。
  2. 加载已知重量:挂上一个已知重量的砝码,比如200g,等读数稳定后记录平均值value_200g。
  3. 计算斜率:scale = (value_200g - offset_value) / 200,这个值表示“每克对应多少码值”。

这里有个更稳妥的做法:你可以分别挂100g和500g两个砝码,取两个点做差值:

scale = (value_500g - value_100g) / 400

这个方法的好处是,即使零点偏移有误差,两个已知重量之间的斜率跟零点无关,计算更准确。

得到offset_value和scale后,任意时刻的拉力值就是:

float weight = (float)(val - offset_value) / scale;

我把标定参数直接写死在代码里,实际项目如果你希望更灵活,可以把这两个参数存在EEPROM或者Flash里,上电时读取,不用每次重新标定。

4.3 数据的稳定性与滤波策略

24位ADC听着很厉害,但实际用起来你会发现,原始数据的最后几位一直在跳。这是正常现象——传感器本身的机械振动、电源噪声、电磁干扰都会反映在ADC结果上。

如果不做处理,你会在串口上看到类似这种输出:

RAW: 5234812 RAW: 5234901 RAW: 5234767 RAW: 5235123 RAW: 5234900

最大值和最小值之间差了三百多,换算成重量变化可能有几克。这在称重场景里是不能接受的。

我常用的滤波方案有三种,从简单到复杂排序:

  • 滑动平均滤波:维护一个长度为N的队列,每次读取新值就放入队列,输出队列平均值。N一般取5~20,N越大越平滑,但响应越慢。称重场景N取10左右比较合适。
  • 中值滤波:连续读N次(N取奇数,如5),排序后取中间值。这个对脉冲干扰(比如突然的机械冲击)效果很好。
  • 一阶低通滤波:out = out_old + alpha * (new - out_old),alpha取0.1~0.5。这个对高频噪声平滑效果好,但会引入相位滞后。

实际项目里我经常把中值滤波和一阶低通组合使用:先做一次5点中值,再去掉毛刺,然后一阶低通平滑。代码很好写,这里给一个滑动平均的参考:

#define FILTER_SIZE 10 long filter_buf[FILTER_SIZE]; unsigned char filter_index = 0; long filter_sum = 0; char filter_cnt = 0; long HX711_Read_Filtered(void) { long raw = HX711_Read(); if (filter_cnt < FILTER_SIZE) { filter_buf[filter_cnt++] = raw; filter_sum += raw; return raw; } else { filter_sum -= filter_buf[filter_index]; filter_buf[filter_index] = raw; filter_sum += raw; filter_index = (filter_index + 1) % FILTER_SIZE; return filter_sum / FILTER_SIZE; } }

注意,滤波会导致数值变化变慢,如果你测试时轻轻碰了一下传感器,可能要几百毫秒后才能看到稳定输出,这很正常。追求实时性的场景要适当减小窗口。

5. 完整示例:基于HX711拉力采集的上位机显示

5.1 CubeMX配置要点

如果你用的是STM32CubeMX生成工程,配置非常简单:

  • 时钟:外部8MHz晶振,系统时钟设为72MHz(STM32F103)。
  • USART1:异步模式,波特率115200,8位数据位,无校验,1位停止位。
  • GPIO:PB0设为GPIO_Output,PB1设为GPIO_Input。
  • 工程设置:生成独立的hx711.c/h文件和usart.c/h文件。

CubeMX生成的GPIO初始化里,PB0默认是推挽输出,PB1需要改一下,把GPIO mode从Output改成Input,再选Pull-up。

5.2 完整功能代码

这一版代码我在前面的基础上加入了标定参数、滑动平均滤波和串口格式化输出,可以直接在STM32F103开发板上跑:

// main.c #include "stm32f10x.h" #include "hx711.h" #include "usart.h" #include <stdio.h> // 标定参数,根据你自己的传感器和模块修改 #define OFFSET_VALUE 5234800L #define SCALE_VALUE 22.34f // 单位:码值/克 #define FILTER_SIZE 8 static long filter_buf[FILTER_SIZE]; static unsigned char filter_index = 0; static long filter_sum = 0; static unsigned char filter_cnt = 0; long read_filtered(void) { long raw = HX711_Read(); if (filter_cnt < FILTER_SIZE) { filter_buf[filter_cnt++] = raw; filter_sum += raw; return raw; } else { filter_sum -= filter_buf[filter_index]; filter_buf[filter_index] = raw; filter_sum += raw; filter_index = (filter_index + 1) % FILTER_SIZE; return filter_sum / FILTER_SIZE; } } int main(void) { long raw; float weight; SystemInit(); USART1_Init(115200); HX711_Init(); printf("HX711 Demo\r\n"); while (1) { raw = read_filtered(); weight = (float)(raw - OFFSET_VALUE) / SCALE_VALUE; if (weight < 0) weight = 0; printf("RAW: %ld WEIGHT: %.2f g\r\n", raw, weight); delay_ms(100); } }

5.3 实测数据与结果分析

我在测试台上用100g、200g、500g的标准砝码做了一组验证,滤波窗口取8次,串口输出的数据如下:

加载重量理论输出(g)实测输出(g)误差
0g(空载)00.000
100g100.0099.87-0.13
200g200.00200.16+0.16
500g500.00499.92-0.08

这个测试结果是理想环境(桌面平稳、无风、线束固定)下的表现。误差主要来源有三个:一是砝码本身存在公差,二是传感器的非线性特性,三是滤波算法的平均效果。整体来说,1%以内的误差对于DIY项目完全够用。

如果做的是精密称重设备,建议做多点标定,在使用的量程范围内取5~10个点做线性插值,可以进一步减小非线性误差。

6. 常见问题与排查技巧

6.1 问题速查表

我在这个项目上踩过不少坑,网上也看了大量提问,把典型的几个问题整理成表格,方便你直接对号入座:

现象可能原因解决办法
读数一直为0x800000左右(巨大的负数)DOUT一直为高,芯片未工作或接线错误检查VCC和GND电压,确认SCK/DOUT没有接反
读数固定为某个值,不受力时不变传感器信号线没接对用万用表量传感器输出,确认A+/A-接到了正确的信号线
读数来回跳,波动幅度达几百电源纹波大、信号线过长、滤波不够给模块加去耦电容,缩短传感器线,提高滤波窗口
读数逐渐漂移,放几分钟后变了传感器预热未稳定、温度变化让系统通电预热5分钟再标定,标定和使用在同一温度环境
重量值符号反了传感器正负极接反交换A+和A-接线,或者软件中取反
放下砝码后数值需要很久才稳定滤波窗口过大或机械振动减小滤波窗口,检查传感器安装是否紧固

6.2 标定偏移的曲线修正技巧

两点标定在量程范围内基本够用,但如果你要更大范围(比如0~50kg),两点线性模型可能会在两端出现稍大偏差。我自己测试时发现,有些传感器在载荷超过80%量程后,输出会开始出现轻微饱和,这时候线性模型就不好使了。

解决办法是做一个分部标定:

  • 取0、25%、50%、75%、100%量程五个点,记录对应的原始值。
  • 在代码里存一张查找表,任意值落在两个标定点之间时做线性插值。

这样可以把误差从1%~2%压缩到0.3%以内,代价是标定工作量和代码复杂度增加。我做工业级拉力测试设备时用的是这种方法。

6.3 上电读数异常的注意事项

还有一个容易被忽略的点:HX711上电后第一次读数往往不准。因为芯片上电后需要完成一次自校准,这个过程在DOUT第一次拉低时会触发。如果你在主循环里第一次就读取数据,经常会出现一个很大的异常值。

我的建议是上电后先启动延时(至少100ms以上),再连续读取几次并丢弃前几次的读数,等系统稳定后再输出数据。这个“预热”步骤在产品化时非常重要,不然客户一上电就看到一个错误数值,体验很差。

7. 扩展与总结心得

7.1 这个方案还能往哪些方向扩展

拉力传感器采集本身是一个基础能力,加上STM32的处理和通信能力后,你可以往很多方向扩展:

  • PLC或上位机通信:把串口输出改成RS485或CAN,接工业触摸屏,做一个实时的拉力监控面板。
  • 无线传输:加一个ESP8266模块,把拉力值通过Wi-Fi上传到手机App或者服务器,远程查看。
  • 多通道采集:HX711一个芯片只能接一路传感器,如果需要测多点拉力,可以多个HX711并联,每个芯片用独立的DOUT引脚,SCK可以共用。
  • 控制联动:采集到的拉力值超过设定阈值就触发继电器动作,可以做成简单的张力控制器或者过载保护器。
  • 数据记录:用SD卡模块定时把拉力值写入日志文件,事后回放分析。

我在做某个产品原型时,就用这个方案搭配OLED屏做了一个手持式的拉力计,实测操作体验还行,除了精度上比专业设备差一些,功能完全够用。

7.2 项目复盘:哪一步最重要

这个项目整体难度不大,但有几个环节最影响成败:

第一是接线和硬件稳定性。很多朋友一上来就写代码,结果数据完全不对,最后排查半天发现是DOUT没接对或者没共地。我建议先花5分钟用万用表确认所有接线的通断和电压,再开始写代码。

第二是标定。不要跳过这一步,不要用理论值硬算。拿真实砝码做两点标定,比任何计算公式都靠谱。

第三是滤波策略。一味加滤波窗口会把系统反应搞得很迟钝,要根据实际场景找到合适的窗口大小。我一般先不加滤波调试,看原始数据的噪声水平,然后再决定滤波策略。

7.3 给新手的最后几点建议

  • HX711模块买市场上销量高的成熟款就好,不用追求贵。
  • 传感器量程选择上,实际使用量程最好控制在标称量程的50%~80%之间,既保证精度又留出安全余量。
  • 做产品时,标定参数一定要存储,不要每次上电都让用户重新标定。
  • 如果发现读数不稳定,先怀疑电源,再怀疑传感器安装,最后再怀疑芯片。

这个项目做到最后,你会发现自己对“小信号采集”的整个链路有了完整的认识:传感器的物理原理、模拟信号的放大与数字化、数字信号的时序读取、数据的软件算法处理。这套知识在以后做其他传感器项目时都能复用,这也是我这个项目最有价值的部分。

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

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

立即咨询