
做过嵌入式开发的人应该都遇到过这种需求把一个物理量拉过来变成长度、重量或者形变让单片机读进来。拉力传感器加HX711这个组合算是低成本方案里绕不开的一条路。标题说5分钟搞定其实不是口号——把模块接好、烧上一份能读原始值的代码真的用不了几分钟。但如果你想要的是稳定到能用的数据而不是屏幕上一串飘来飘去的数字那后面还有很多值得仔细看的内容。这篇文章面向的是那些手里已经有STM32开发板或者正在为毕业设计、小项目选型的同学。我尽量按自己调试时走过的路径来讲——先搞懂HX711在这里扮演什么角色再动手接线然后给完整代码最后才是标定和排错。你会发现读原始值确实很快但把读数变成可信的重量值才是真正的核心工作。1. 先搞懂HX711在整条链路里到底干了什么很多人直接把HX711当成一个“ADC模块”来用这个理解不算错但会漏掉很多关键细节。HX711不是普通ADC它的输入接口、增益放大器、供电方式都是专门为桥式传感器设计的。换句话说它比通用ADC更“挑食”但正因为挑食才能在称重这类场景里做到又好又省。1.1 拉力传感器的本质一个会变形的电桥拉力传感器也叫称重传感器、测力传感器内部通常是一个惠斯通电桥。电桥的四个臂由应变片组成当外部拉力作用在传感器上时应变片发生形变、电阻值跟着变化电桥的输出电压随之改变。这个电压变化量非常小满量程时往往只有几毫伏。拿常见的1kg量程传感器来说满量程输出可能只有1mV/V意思是5V激励电压下满量程也只有5mV左右的电压差。STM32内部ADC的量程是0~3.3V分辨率一般是12位用来采这个毫伏级信号就好比拿一把米尺去量头发丝的粗细——根本不够看。这时候HX711的价值就出来了。它内部集成了一个24位的德尔塔-西格玛ADC分辨率远高于STM32内置ADC。24位是什么概念理想情况下它能分辨出满量程的1/16777216。再加上片内集成的可编程增益放大器把传感器输出的微弱信号先放大128倍再转换这就把“微伏级变化”变成了“可读的数字量”。1.2 HX711的三大隐藏优势第一是差分输入。传感器电桥输出的正负两端接到HX711的A和A-它能直接读取两者之差对共模噪声有很强的抑制能力。第二是内部稳压。很多HX711模块上带了一个稳压芯片给传感器提供干净稳定的激励电压这样电桥输出不会因为供电波动而漂移。第三是通信协议简单。HX711和MCU之间只需要两根线——时钟线SCK和数据线DOUT我见过不少项目选它一个很重要的原因就是省IO。无论你是接在STM32F103C8T6这种小板上还是ESP32上两根线加上电源地就够用。1.3 芯片手册里最值得先看的三个参数打开HX711的数据手册别被几十页的英文吓到你只需要先关注三个地方数据输出速率常见的是10HzRATE引脚接低电平和80HzRATE引脚接高电平。称重场景用10Hz就够了80Hz通常用于动态测量但噪声也会更大。通道和增益HX711有A、B两个输入通道A通道增益可选128或64B通道固定为32。拉力传感器接A通道、用128倍增益这是最常用的配置。输入范围HX711的差分输入范围是±20mV增益128时超出这个范围ADC会饱和。传感器选型时要估算满量程输出别让信号超范围。2. 元器件的选择和接线颜色线序先分清这节是很多人卡住的地方。你以为接线就是把线插上实际上拉力传感器上的四根线颜色不同厂家的定义可能不同这一点比代码更坑。2.1 传感器和模块怎么搭配拉力传感器一般长这样一个金属外壳上面有四根引线通常是红、黑、白、绿也有红黑黄蓝等组合。四根线分别对应电桥的激励正、激励负、信号正、信号负。HX711模块上的对应关系是传感器线缆常见颜色HX711模块丝印功能说明激励正红E给电桥供电的正极激励负黑E-给电桥供电的负极信号正白A差分信号正输入信号负绿A-差分信号负输入但这只是最常见的约定。有些传感器的信号线是白和蓝有些是黄和蓝。稳妥的办法是用万用表测电阻传感器静止时E和E-之间的电阻约等于电桥的总电阻通常在几百欧姆到一千欧姆A和A-之间同理。如果测得某一对线之间电阻只有几欧姆那可能是输出短路线要重新判断。2.2 完整的接线表以STM32F103开发板和常见的红黑HX711模块为例接线如下HX711引脚STM32引脚备注VCC5V或3.3V推荐5V模块自带稳压给传感器更稳的激励GNDGND共地必须连接SCKPB0时钟线由STM32控制DOUTPB1数据线HX711输出需要特别注意的是如果开发板的IO是3.3V电平而模块的DOUT输出也是3.3V兼容的那可以直接连。如果模块是5V供电的老版本DOUT高电平接近5V建议加一个分压电阻或者电平转换否则长期使用可能损坏MCU引脚。现在的绝大多数模块都做了电平转换或者兼容设计但我仍然建议你拿万用表测一下DOUT高电平的电压值保险起见。2.3 模块版本差异有没有稳压差别很大市面上的HX711模块主要有两种一种板载了三端稳压芯片通常是7550-1或类似型号会多出几个引脚板上能看到明显的稳压管和电容另一种是纯粹的HX711最小系统板上面只有HX711芯片、晶振、几个去耦电容。后者便宜但对外部供电质量要求更高。如果你用的是纯最小系统板建议用锂电池或者线性稳压电源给传感器供电不要直接用开关电源。开关电源的纹波会直接窜进电桥表现为读数的随机跳动。我自己的建议是新手选带稳压的模块多花一两块钱能少排查好多奇怪问题。五块钱的模块和十几块钱的模块在稳定性上的差异比你想象的更大。3. 读取HX711的完整代码从电平时序到稳定数据接线完成之后就是关键的代码部分。HX711不像SPI或I2C那样有标准库它的时序完全靠GPIO模拟但只要理解了时序逻辑写起来非常简单。3.1 时序逻辑为什么是24个脉冲加1个额外脉冲HX711的工作时序可以这样理解它每次转换完成后会把DOUT引脚从高电平拉低告诉MCU“数据准备好了”。MCU看到DOUT拉低就开始在SCK引脚上输出脉冲。每来一个脉冲的高电平HX711就通过DOUT输出一位数据一共24位。这24位是从最高位开始的二进制补码表示本次转换结果。第25个脉冲的作用是让HX711回到下一次转换的状态。不同的通道和增益配置需要的脉冲总数不同。A通道增益128是25个脉冲A通道增益64是27个脉冲B通道增益32是26个脉冲。我们最常用的A通道128增益所以读到24位数据后再补发一个脉冲让芯片按当前配置进行下一轮转换。用生活化类比的话这就像去取快递DOUT拉低相当于收到“快递到了”的短信24个脉冲相当于按顺序一件件取走包裹第25个脉冲相当于告诉快递柜“我要继续等下一个快递”。3.2 完整C代码HAL库实现含符号扩展和均值滤波下面这份代码我实测过很多次可以直接用。它包含了最核心的读取函数以及一个简单的滑动平均滤波函数。如果你的工程里用的是标准库只需要把HAL_GPIO_ReadPin和HAL_GPIO_WritePin换成对应的GPIO_ReadInputDataBit和GPIO_SetBits/GPIO_ResetBits即可。#include main.h #include stdint.h // 引脚定义根据你自己的接线修改 #define HX711_SCK_PORT GPIOB #define HX711_SCK_PIN GPIO_PIN_0 #define HX711_DOUT_PORT GPIOB #define HX711_DOUT_PIN GPIO_PIN_1 // 简化写操作宏 #define HX711_SCK_HIGH() HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_SET) #define HX711_SCK_LOW() HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET) #define HX711_DOUT_READ() HAL_GPIO_ReadPin(HX711_DOUT_PORT, HX711_DOUT_PIN) // 微秒延时这里依赖DWT计数器精度高且不占用定时器 // 如果不想用DWT可以用简单的空循环替代72MHz主频下 __NOP() 约14ns void DelayUs_DWT(uint32_t us) { // 初始化DWT的代码建议放在main函数里只执行一次 // CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; // DWT-CYCCNT 0; // DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; uint32_t start DWT-CYCCNT; uint32_t ticks us * (SystemCoreClock / 1000000); while ((DWT-CYCCNT - start) ticks); } // 读取HX711原始值返回24位有符号数 int32_t HX711_ReadRaw(void) { uint32_t value 0; // 等待DOUT拉低表示数据准备好 // 如果一直等不到大概率是接线错误或传感器激励异常 while (HX711_DOUT_READ() GPIO_PIN_SET); // 读取24位数据先读最高位 for (uint8_t i 0; i 24; i) { HX711_SCK_HIGH(); DelayUs_DWT(1); value 1; if (HX711_DOUT_READ() GPIO_PIN_SET) { value; } HX711_SCK_LOW(); DelayUs_DWT(1); } // 第25个脉冲回到A通道、128增益开始下一次转换 HX711_SCK_HIGH(); DelayUs_DWT(1); HX711_SCK_LOW(); DelayUs_DWT(1); // 24位数据是补码格式最高位为1表示负数需要做符号扩展 if (value 0x800000) { value | 0xFF000000; } return (int32_t)value; } // 滑动平均滤波连续采样N次取平均 #define FILTER_SIZE 8 int32_t HX711_GetFilteredValue(void) { static uint32_t buffer[FILTER_SIZE]; static uint8_t index 0; uint64_t sum 0; buffer[index] HX711_ReadRaw(); if (index FILTER_SIZE) { index 0; } for (uint8_t i 0; i FILTER_SIZE; i) { sum buffer[i]; } return (int32_t)(sum / FILTER_SIZE); }这个代码有一个细节DOUT等待用了一个while死循环。正常情况下HX711转换完成需要几百毫秒如果程序卡死在这里通常意味着SCK或DOUT接线反了或者HX711没有正确上电。排查时可以先单独测一下DOUT引脚电平看看是不是有正常波动。3.3 采样速率与数据稳定性分析HX711默认输出速率是10Hz意味着每秒只有10个有效数据。如果你不加滤波直接拿这10个数去看会看到明显的跳动尤其在传感器没有负载或者负载很小时。这不是模块坏了而是噪声和量化误差的表现。滑动平均滤波能显著改善数据稳定性。滤波窗口的大小不是越大越好。FILTER_SIZE设为8意味着需要0.8秒才能输出一次平滑后的结果适合静态称重。如果你要做动态拉力测试比如拉伸过程中的实时力值变化滤波窗口要相应缩小或者改用10Hz原始数据做软件上的轻量处理。这是个权衡要平滑还是要实时性取决于你的应用场景。4. 从ADC原始值到克级重量标定是绕不开的一步很多新手把代码跑通之后看到屏幕上有个数字就以为大功告成了。但那个数字其实是ADC原始值和实际的拉力没有直接的对应关系。要让数字变成克数必须做标定。4.1 为什么“减去空载读数”并不等于去皮去皮是称重仪表上最常用的功能放上容器后按一下去皮显示器归零。很多人把这个逻辑简单实现为“当前读数减去空载读数”这在小范围内勉强可行但在全量程上并不准确。原因在于传感器和HX711的传递特性虽然接近线性但未必能保证过原点。理想电桥在没有负载时输出为0但实际应变片的初始电阻不可能完全相等加上HX711的失调电压空载时ADC读数往往是一个不小的正数或负数。如果直接用“结果 当前值 - 空载值”然后乘一个固定系数会导致零点附近的数据严重不准。正确的做法是把“零点”和“斜率”都纳入标定公式结果 k * 原始值 b。其中k和b就是通过两点标定求出来的。4.2 两点标定的实操流程找一个已知质量的砝码如果没有砝码可以用已知重量的纯净水水瓶、手机等作为替代品但精度要求高的场合还是建议买标准砝码花不了多少钱。具体步骤如下系统上电预热几分钟让传感器温度稳定。空载状态下读取滤波后的ADC值记为d1此时真实重量m1 0。放上一个已知重量m2的标准砝码读取滤波后的ADC值d2。用同样的方法再取一个更大重量m3的点测到d3。理论上两个点就能确定直线方程。为提高精度我建议三点测量后做最小二乘拟合能把个别测量误差平均掉。简单两点标定在小量程也够用但如果你追求的是全量程稳定三点或更多点会更靠谱。4.3 斜坡与截距的代码落地两点标定计算如下斜率 k (m2 - m1) / (d2 - d1)截距 b m1 - k * d1最终重量 k * 当前ADC值 b。对标定代码的落实我习惯这样写typedef struct { float slope; // 斜率单位g/ADC码 float offset; // 截距单位g } CalibParam; CalibParam calib {0.0f, 0.0f}; // 标定计算传入两个点的ADC值和对应重量 CalibParam HX711_Calibrate(int32_t d1, float m1, int32_t d2, float m2) { CalibParam p; p.slope (m2 - m1) / (float)(d2 - d1); p.offset m1 - p.slope * (float)d1; return p; } // 将ADC值换算为重量 float HX711_GetWeight(CalibParam *p) { int32_t raw HX711_GetFilteredValue(); return p-slope * (float)raw p-offset; }需要注意的一点是标定数据和实际使用时的环境温度、机械安装状态强相关。传感器被重新安装、换了一根加长杆、或者环境温度变化超过十几度最好重新标定。这个不是玄学是应变片本身的特性决定的。5. 实际调试中踩过的坑不看这章你大概率会白调一晚上这一节写的是我自己真实踩过的坑。每一个问题都对应一次深夜排查的经历希望对你有直接帮助。5.1 数据跳变先查供电别急着加滤波如果你发现数据不停跳变而且跳变量大得离谱首先怀疑对象应该是供电而不是软件滤波参数。HX711模块对电源纹波非常敏感。用USB供电或者劣质开关电源供电时即使加了滤波读数也可能不稳定。我遇到过好几次数据跳变范围高达±50个ADC码换了线性稳压供电后跳变立刻降到±3以内。排查方法是观察空载读数如果空载时采集多组数据最大值和最小值之差超过几十甚至上百优先检查供电。另一个常见问题是传感器受到周围振动影响比如桌面在震动、风扇引起共振这类干扰和电气噪声的表现不一样——电气噪声是随机跳振动是周期性规律波动。5.2 DOUT一直不拉低的几种原因程序卡在while (HX711_DOUT_READ() GPIO_PIN_SET)出不来是提问频率最高的问题。DOUT一直为高意味着HX711认为数据还没准备好或者根本没有工作。我归纳过几个常见原因按出现概率排序供电不足或没共地HX711需要3.3V或5V供电而且必须和STM32共地。很多人只接了SCK和DOUT忘记接GND造成电平参考不一致时序完全乱掉。SCK和DOUT接反了HX711不会因为你接反就冒烟但时序就彻底对不上了。传感器线序接错E、E-、A、A-接错电桥供电不对模块内部可能处于饱和状态DOUT也就不会正常输出。代码里的引脚初始化配置不对DOUT引脚必须配置为浮空输入或上拉输入SCK配置为推挽输出。如果DOUT被配置成了复用功能或模拟输入读到的电平就不对。排查时建议先写一个最简单的测试代码初始化后轮询读取DOUT的电平在OLED或者串口上打印出来。你会发现正常工作时DOUT会周期性拉低大概每100ms一次符合10Hz采样率。如果一直没有拉低硬件问题的可能性就很大了。5.3 传感器的温漂和蠕变传感器用久了或者环境温度变化大会出现“温漂”同样重量的物体早上和下午读出来的数据不一样。这属于应变片传感器的物理特性不是代码能完全消除的。常见的缓解手段包括传感器不要紧贴发热源模块板子不要贴近MOS管等发热元件必要时在算法里加温度补偿。但对于绝大多数室温环境下的项目这个影响可以接受。“蠕变”是另一个容易忽略的问题当你放上重物后读数在一段时间内会缓慢漂移几秒后才能稳定下来。这不是滤波不够而是应变片在长期受力下自身发生了微小形变。解决方法是采样前做一次短时间的“等待稳定”比如放上物体后延时2到3秒再开始读取数据或者在软件里做一个变化率判断当读数变化小于阈值时认为稳定。5.4 带电插拔和静电问题嵌入式项目里最常见的“莫名其妙”故障往往是带电插拔传感器线和外设导致引脚损伤。HX711模块本身是低压器件但传感器线缆从外部引入如果带电拔插瞬间的感应电动势可能损坏模块IO或传感器内部应变片。我自己的习惯是改接线前一定断电哪怕只是断一下电源开关。另外模块背面如果有裸露的焊盘建议在固定在金属机箱上时做好绝缘隔离不然机箱静电通过GND窜进来数据不稳定是小事芯片挂掉才是最头疼的。6. 一点扩展思路这块方案能怎么继续玩下去读完原始值、标定出重量值说明你已经完成了这个模块从0到1的一步。但我估计你不会就此停止。最后分享几个我实际用过的扩展方向。6.1 多通道和大称量范围的替代方案HX711有两个通道但A通道被传感器占用时B通道虽然可以接第二个传感器却只有32倍增益而且不能独立标定通道增益限制很大。我试过用两个HX711模块各接一个传感器用同一个STM32控制分别读取再软件合成总重量。这样做的优点是灵活缺点是模块数量翻倍、占用IO翻倍、两路数据的时间基准不一致。如果你需要测量更大范围的拉力比如几十公斤以上可以考虑量程更大的S型拉力传感器搭配HX711仍然适用。前提是传感器满量程输出不要超过HX711的差分输入范围。满量程输出可以用这个公式估算传感器灵敏度(mV/V) × 激励电压(V)。若灵敏度为2mV/V、激励电压5V满量程输出为10mV在HX711增益128的±20mV范围内余量很充裕。6.2 低功耗场景下的PowerDown用法HX711手册里提到当SCK保持高电平超过60微秒时芯片进入PowerDown模式电流降到约0.1微安。这非常合适做电池供电的便携称重设备。唤醒方式是把SCK拉低。我在一个低功耗项目里试过这样的流程单片机进入休眠前把SCK拉高并保持一段时间让HX711断电需要称重时先唤醒单片机再把SCK拉低等待DOUT拉低表示数据准备完成后读取数据读取结束后再次让HX711进入PowerDown。实测下来休眠时的省电效果非常明显。需要注意的是PowerDown后HX711会丢失内部状态醒来后第一次读取的数据建议丢弃或者多读几次再使用。另外一个实用小技巧如果你想让HX711的输出速率从10Hz变成80Hz把模块上的RATE引脚接到VCC即可。但我用过之后发现80Hz模式下噪声明显增大静态称重场景反而得不偿失所以最终还是用回10Hz。最后说一句实在话——HX711和拉力传感器这套方案看起来很“老派”没有花哨的协议没有复杂的驱动但正因为这样它才特别适合用来理解“传感器→模拟前端→数字处理→标定”这一整条信号链。把这套逻辑弄通了以后换任何传感器、任何ADC你都会发现思路是相通的。