简介:基于STM32与HX711的压力传感器电子秤设计资源,适合单片机课程设计、电子称重项目入门及STM32外设应用学习,也适合需要快速搭建称重采集方案的初开发者。方案明确划分最小系统、数据采集、人机交互、系统电源、时钟与语音报数六大模块,使用高精度24位AD芯片HX711完成称重传感器信号放大与模数转换,配合1602液晶显示、4×4矩阵键盘、DS1302时钟和SC1010B语音播报电路,实现0~5kg称重、超量程报警、时钟设置与语音报数等功能,称重误差可控制在±0.005kg内。压缩包共182个文件,含C/H源码、Keil工程文件、hex/axf固件、sch原理图、PDF及DOC说明文档,以及编译生成的中间文件,包体约5.08MB,便于直接查看程序结构、对照电路调试或烧录验证。已有3605人学习,适合希望从模块级理解压力传感采集、液晶交互和语音播报综合开发的读者,也可作为课程报告和项目改造的参考基础。 做称重相关的项目,大家绕不开的一个组合就是STM32加HX711。市面上资料很多,但大多是“能读出一个数”就结束的Demo,真正能直接拿去做成一个准的、稳的、标定过的小量程电子秤的资料反而少。我最近用STM32F103C8T6配合5kg量程的应变式称重传感器重新整理了一套完整流程,从选型到接线,从时序分析到标定滤波,连踩的坑一起记录在这篇里。如果你正打算做电子秤、智能货架、邮包称重或者任何一个需要“测了多少克”的毕设或产品原型,这篇文章应该能帮你少走不少弯路。
需要先说清楚一个概念:标题里写的“压力传感器”,在STM32+HX711这个生态下,绝大多数时候指的其实是应变式称重传感器。它测的是力/重量,原理是被测物压在弹性体上产生微小形变,贴在弹性体上的电阻应变片阻值跟着变化。HX711则是专门为这种桥式传感器设计的24位高精度ADC,把那一丁点电阻变化引起的电压差放大、量化成数字信号给单片机读。搞清楚这层关系,后面所有接线、代码、标定就都好理解了。
1. 5kg传感器与HX711:这个黄金组合到底强在哪
1.1 称重传感器的输出信号为什么非要专用ADC
传感器本身不输出“克”这个单位,它输出的是一个差分电压。5kg量程的传感器在5V激励下,满量程输出大约在1mV/V到2mV/V之间,也就是说满量程时信号变化只有5mV到10mV。这个信号太小了,普通单片机自带的12位ADC直接去采样,分辨率根本不够看:12位ADC在3.3V参考电压下的LSB约等于0.8mV,满量程10mV的信号换算下来只能分出12个台阶,别说称克了,连“有没有放东西”都分不出来。
所以需要HX711这种外部ADC来干这件事。HX711内部带了可编程增益放大器,通道A可以设置128倍或64倍增益,内置稳压电路还可以直接给传感器桥路供电。5mV输入被放大128倍变成640mV,再用24位ADC去量化,这样分辨率就上去了。实际做一个量程5kg的秤,稳定读出1g的变化是很常规的,如果结构做得好,0.1g级别的分辨率也能摸到。
1.2 为什么市面上大家都选HX711而不是其他ADC
HX711能火起来,核心原因就是一个“省”字:便宜,几块钱一片;外围电路简单到离谱,一个芯片加四个电容就能工作;单片机只需要两根普通GPIO就能读出数据。对比同定位的ADS1232,性能其实差不多,但价格贵不少。MCP3561性能更强、通道更多、采样率可调范围大,但引脚复杂、寄存器配置多,做一个小量程称重项目属于杀鸡用牛刀。
当然HX711也有它自己的短板:数据率只有10Hz和80Hz两档,不适合高速动态称重;只有一个真正的差分通道(通道A),通道B增益固定32倍,没法像MCP3561那样同时挂多路高精度桥式传感器。所以如果只是做一个静态称重、智能货架压力检测或者小量程电子秤,HX711非常合适;要是需要动态抓峰值或者同时测多路传感器,再往上换方案。
1.3 5kg量程的选择逻辑
量程选5kg而不是1kg或者10kg,是根据实际被测物决定的。称重传感器的选型原则是让日常使用重量落在量程的20%到80%之间,这样线性度和重复性最好。比如做厨房秤,日常称量几百克到两三千克,5kg量程就很从容;做快递秤,经常称超过5kg的包裹,那就得选10kg以上,否则长期超载会加快弹性体疲劳,产生永久变形,精度断崖式下降。
另外要注意,传感器标称的安全过载通常只有量程的120%到150%,超过这个数值应变片会直接损坏。5kg量程的传感器硬放一个8kg的哑铃上去,读数可能先飙到满量程然后直接归零——那是应变片已经拉坏了。结构设计时一定要留机械限位,不要让被测物直接冲击到传感器上。
2. 硬件接线与电源布局:读数稳不稳,九成看这里
2.1 传感器四根线怎么认
市面上一两百克的“5kg称重传感器模块”大多数是半桥结构的,引出四根线:红线接E+,黑线接E-,白线接A+(也叫S+),绿线接A-(也叫S-)。红线黑线是给传感器内部的应变桥供电的,白线绿线是输出差分信号的,直接进HX711的A通道就行。
这里一定要小心:不同厂家的线色定义不完全统一,有的传感器绿线和白线定义反了,有的模块把信号线标成S+和S-,还有直接把HX711模块集成在一起的(红色PCB那种),那些更简单,直接找VCC、GND、DOUT、PD_SCK四个针脚就能用。最坑的情况是买到全桥四线但颜色不按套路来的传感器,插上去读数要么不动要么乱跳。我的习惯是拿到传感器先不接MCU,直接用万用表量一下红黑两端的电阻,再量白绿两端电阻,半桥传感器这两组阻值通常都在1kΩ左右,量不出来就说明线没找对。
2.2 供电方案:3.3V还是5V
HX711的数据手册上AVDD的推荐范围大概在2.6V到5.5V,但实践下来,想让传感器有比较好的灵敏度,还是用5V给AVDD供电更稳。因为传感器的灵敏度数值本身是基于5V激励标定的,激励电压低了,输出信号幅度也等比下降,后面ADC量程的利用率就会变差。
STM32F103C8T6的VCC是3.3V,但HX711模块上一般自带稳压和电平转换,可以直接用单片机的3.3V给模块的VCC供电,模块内部升压到5V给传感器。这么做有个好处是DOUT输出的高电平接近VCC,而VCC是3.3V,刚好能被STM32识别为高电平,不用额外做电平转换。如果模块上标注VCC必须接5V,那就单独给模块供5V,DOUT输出5V电平对STM32来说也能识别是高电平(STM32的GPIO输入耐压是容忍5V的,但严格说还是看具体型号数据手册),只是不太推荐长期这么干。
2.3 接地和退耦:来自高频噪声的教训
第一版硬件我图省事,把HX711模块和STM32用杜邦线直接一插就上电了。结果读数在空载状态下漂了±50个码值,换算成重量接近±2g,非常难看。后来排查了半天,发现问题出在两个地方。
一是给HX711模块供电的线太细太长,线上压降和电感效应导致AVDD不稳。二是我在给传感器桥路供电的5V和地之间没有做退耦,单片机翻转GPIO产生的电流尖峰顺着电源线窜到了HX711的模拟电源上。解决办法也很土,在HX711模块的VCC和GND之间就近并一个100uF电解电容加一个0.1uF瓷片电容,在传感器供电引脚附近也并一个0.1uF电容,然后再把STM32的GND和HX711的GND在最短路径上单点汇合。改完之后空载漂移直接压到±3个码值以内,效果立竿见影。
连接方式上,能用PCB板就尽量用PCB板,实在要飞线,至少保证传感器线用双绞线或者屏蔽线,长度控制在20cm以内,千万不要和电机驱动器、继电器这些大电流线路绑在同一个线槽里。
3. STM32驱动HX711:时序原理与代码实现
3.1 读数据前先搞懂它那个别扭的时序
HX711和单片机之间是纯GPIO握手协议,只有两根线:PD_SCK和DOUT。机制是这样的:
- 芯片完成一次AD转换后,DOUT会从高电平拉低,告诉单片机“数据准备好了,赶紧来拿”。
- 单片机看到DOUT为低,开始给PD_SCK发送脉冲。每来一个高电平脉冲,DOUT上就移出一位数据,一共发24个脉冲就能把24位数据全部移出来。数据是MSB先出。
- 移完24位数据之后,再多发1到3个脉冲,用来配置下一次转换的通道和增益。多发1个脉冲=通道A,增益128;多发2个脉冲=通道B,增益32;多发3个脉冲=通道A,增益64。
- 第25个脉冲结束后,DOUT重新变高,芯片开始下一次转换。
所以常规读一次数据的流程就是:等待DOUT拉低,然后给24个时钟脉冲读取数据,再补1个脉冲切回通道A增益128,完事。整个过程中MCU必须严格按时序操作,尤其是移位的24个脉冲,中间不能有太长的停顿,否则DOUT上的数据位可能已经移位过去了,读出来的数值就是乱的。
3.2 基于HAL库的驱动代码
我平时主要用HAL库,下面的代码可以直接搬到STM32CubeMX生成的工程里。GPIO分配上,我习惯把PD_SCK接到PA0,DOUT接到PA1,这两个引脚不需要任何复用功能,就是最普通的GPIO。
// hx711.h #ifndef __HX711_H #define __HX711_H #include "main.h" #define HX711_SCK_PORT GPIOA #define HX711_SCK_PIN GPIO_PIN_0 #define HX711_DOUT_PORT GPIOA #define HX711_DOUT_PIN GPIO_PIN_1 void HX711_Init(void); int32_t HX711_ReadRaw(void); void HX711_Tare(uint8_t times); float HX711_GetWeight(float k, uint8_t avg_cnt); #endif// hx711.c #include "hx711.h" static int32_t offset_value = 0; void HX711_Init(void) { GPIO_InitTypeDef gpio_init = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); // PD_SCK 配置为推挽输出,默认低电平 gpio_init.Pin = HX711_SCK_PIN; gpio_init.Mode = GPIO_MODE_OUTPUT_PP; gpio_init.Pull = GPIO_PULLUP; gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(HX711_SCK_PORT, &gpio_init); HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET); // DOUT 配置为上拉输入 gpio_init.Pin = HX711_DOUT_PIN; gpio_init.Mode = GPIO_MODE_INPUT; gpio_init.Pull = GPIO_PULLUP; HAL_GPIO_Init(HX711_DOUT_PORT, &gpio_init); } int32_t HX711_ReadRaw(void) { uint32_t data = 0; uint32_t timeout = 0; // 等待 DOUT 拉低,表示数据转换完成 while (HAL_GPIO_ReadPin(HX711_DOUT_PORT, HX711_DOUT_PIN) == GPIO_PIN_SET) { if (++timeout > 1000000) { return 0x800000; // 超时返回一个中间值,避免主循环卡死 } } // 读 24 位数据,MSB 在前 for (int i = 0; i < 24; i++) { HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_SET); data = (data << 1) | HAL_GPIO_ReadPin(HX711_DOUT_PORT, HX711_DOUT_PIN); HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET); } // 第 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 (data & 0x800000) { data |= 0xFF000000; } return (int32_t)data; } void HX711_Tare(uint8_t times) { int64_t sum = 0; for (uint8_t i = 0; i < times; i++) { sum += HX711_ReadRaw(); HAL_Delay(20); } offset_value = (int32_t)(sum / times); } float HX711_GetWeight(float k, uint8_t avg_cnt) { int64_t sum = 0; for (uint8_t i = 0; i < avg_cnt; i++) { sum += HX711_ReadRaw(); } int32_t value = (int32_t)(sum / avg_cnt); return (float)(value - offset_value) / k; }3.3 代码里容易被忽略的细节
上面这段代码有几个点值得单独拿出来说。
超时机制绝对不能省。我见过不少人在等待DOUT拉低时直接写死循环,一旦传感器没接好或者供电异常,整个单片机就卡在那里不走了。加个超时计数虽然简单,但关键时刻能救命,至少主程序还能继续跑别的逻辑,方便你做错误提示。
读取数据的循环里,第25个脉冲很容易被漏掉。如果漏了,芯片会一直保持上一次的增益配置不切换,这次没事,但下次再读的时候数据可能是在错误通道上采的。所以这个脉冲一定要保留,哪怕你确定永远只用通道A增益128。
移位读数据的顺序也要注意,HX711是高位先出,所以每读到一个bit,先让data左移一位再填当前位。如果反过来从低位开始拼,最后算出来的重量会完全对不上。
还有一个不算bug但容易被坑到的点:HX711的原始读数当场转换一次并不需要等很久,但如果你在10Hz速率下连续读得太快,DOUT可能还没来得及拉低,你等DOUT低电平就会等到下一次转换周期。这会导致实际读取频率被拖到10Hz左右,属于正常现象,不是程序错误。如果项目需要20Hz以上的更新率,就得把HX711的RATE引脚接到高电平,切到80Hz档。
4. 标定与滤波:从一堆乱码到精确的“克”
4.1 滤波:为什么原始数据会抖,以及怎么压住它
拿HX711直接读出来的原始码值,数值是稳定的,但不代表它是准的。空载时读8000000附近的一个数,放上重物后会明显往上走。轻微抖动来自传感器本身的热噪声、机械平台的微小振动、电源纹波,短时间内表现为读数在几个码值之间来回跳。
处理噪声的方式最常用的是滑动平均。比如连续读N次求平均,N取5到15之间。太小的窗口滤波效果差,太大的窗口响应变慢。5kg传感器配10Hz采样率,我一般取N=5或者N=10,这样实时性和平滑性比较均衡。
如果发现个别读数突然跳得特别离谱,比如本来100个码值突然跳成500多,大概率是毛刺,这时候单纯滑动平均会被拉偏。可以先用中值滤波把毛刺踢掉,再做滑动平均。“先中值后平均”的组合在小量程称重里非常实用。不过中值滤波需要缓存多个值做排序,代码量会多一点,如果只是做普通产品,滑动平均够用了。
4.2 标定的完整实操流程
“读数”和“重量”之间是线性关系,但具体斜率是多少,不同传感器、不同供电电压之间会有差异,必须先标定。我的流程是这样的:
第一步,空载去皮。接通电源后让系统空转一两分钟,等读数稳定下来,调用HX711_Tare(10),把当前空载平均值记录下来,作为零点offset_value。
第二步,放上标准砝码。比如放一个已知质量500g的砝码,连续读几十次取平均,得到value_500。那么缩放系数K就是:
K = (value_500 - offset_value) / 500.0f;这里的K单位是“码值/克”,它代表着每克重量对应多少个码值。
第三步,把这个K和offset_value存到Flash或者EEPROM里,下次上电直接读取,不用每次开机都重新标定。如果用的是STM32F103C8T6,可以直接借用内部Flash最后一个页来存,只是要注意擦写寿命,别频繁写入。
实际使用中,我建议至少用三个重量点做一下线性度校验:比如100g、500g、2000g。把小量程段的偏差摸清楚,如果发现100g附近的误差偏大,可以考虑做两点校准或者查表修正。5kg量程的传感器在量程中段(2kg到3kg)表现最好,低端量程因为信号微弱,误差占比会明显变大,这是应变式传感器的通病。
4.3 测出来的重量为什么还会缓慢漂移
标定完之后不是一劳永逸。传感器自身有温度漂移,表现在开机半小时内空载读数会缓慢变化,夏天和冬天的差异也肉眼可见。应对办法有两种。
一种是被动等稳定:上电后程序延时几分钟再开始称重,适合那些不追求开机速度的场合。另一种是主动做软件补偿:开机后每隔一段时间自动记一次零点,但在“秤盘上有东西”的状态下不要更新零点,否则会把重物重量吃掉。最简单的方式是开机稳定后强制Tare一次,然后每隔5分钟检查一次当前读数在零点附近很小的范围内时,才允许更新零点。
如果做的是长期运行的产品,建议把“非砝码归零”做成按键操作,类似电子秤面板上的去皮功能,让用户在有需要时手动归零,比自动归零更可靠。
5. 实战踩坑记录:读数不对时,从哪几个方向查
5.1 读数为全满量程或者完全不动
最常见的第一个坑就是上电后HX711读出来的数据要么是0x7FFFFF附近的最大正值,要么是0x800000附近的最小负值,而且怎么放东西都不变。
这个现象九成是传感器接线问题。全满量程最大正数,通常意味着A+和A-之间看到的电压差超过了ADC的测量范围,可能是白绿两根信号线的接线顺序反了,也可能是传感器激励线红黑没有形成正常供电。先用万用表确认红黑之间有正常的桥路电阻,再确认红黑两端确实有5V电压,最后把白线和绿线对调试试。
如果DOUT一直高,等了半天也不拉低,那就是芯片压根没进入转换状态。原因通常是供电问题,HX711的VCC没有供上电,或者模块上的AVDD没有正确接传感器。我把HX711模块的供电引脚直接接到传感器的VCC上,结果传感器被人为拔掉,模块就一下没电,DOUT僵死在高电平,现象一模一样。
5.2 读数有变化,但和重量完全对不上关系
第二种典型情况是传感器确实有反应,放上去的东西越重读数就越大,但数字完全不在预期范围内。这时候先算一下你的K值,如果K异常大或者异常小,比如放500g砝码才变了50个码值,那说明放大倍数不对。
检查一下是不是通道A配置成了增益64而不是128。如果你在读取时第25个脉冲发了3个而不是1个,芯片就会切到增益64,同样的传感器输出码值会直接减半。再检查HX711模块上的RATE引脚电平,80Hz档的噪声会明显比10Hz档大,但不会影响放大倍数。
还有一个我看过好多人在问的问题:STM32的ADC参考电压引脚的电容失效或者焊接不良,导致3.3V辅助电源不稳,会间接影响模块的数字电平判断。这种问题出现概率不高,但排查起来特别隐蔽,一般只会在主控做低功耗休眠时出现。如果发现读数在单片机低功耗唤醒后会突然乱跳,优先怀疑电源时序不够干净。
5.3 烧录调试时连不上目标芯片
最后顺带提一个和称重无关,但在开发期特别常见的环节:写好了代码准备烧录,结果Keil报错“Error: No STM32 Target Found! If your product embeds debug authentication, please...”。这个报错看着很吓人,其实大多数情况不是芯片坏了,而是BOOT0引脚和复位时序的问题,或者调试器引脚被复用成普通GPIO了。
HX711只用到PA0和PA1,不会占用SWDIO和SWCLK,所以这个报错更多是谁把PA13/PA14配置成别的功能了,或者是芯片在睡眠模式下复位连接不上。处理方式是按住复位键,点击下载的瞬间松开复位键,多试几次就能连上。真连不上,把BOOT0拉高强制进系统存储器模式,再重新烧录,基本都能救回来。调试这一步卡住的时候,先冷静检查调试器连接和BOOT引脚,别一上来就怀疑芯片烧了。
5.4 称重结果的重复性不好
标定好了、滤波也做了,但同一个物体放上去拿下来再放上去,读数来回差了十几克,这种问题通常不在电路,而在机械结构。
检查传感器安装的四个固定螺丝是否受力均匀,托盘是否居中放置,以及传感器的弹性体周围是不是有什么东西碰到它。我最开始做的一个版本,传感器下面垫的亚克力板不平,拧紧后传感器已经处于预变形状态,导致零点和灵敏度都不稳定。后来换成了一块厚铝板,四个角用同一规格螺丝,用扭矩螺丝刀统一锁到一样的力矩,问题直接消失。
另外,物体放到秤盘上的位置也会影响读数。5kg的半桥传感器通常只用一个支撑点,但如果托盘做得太大太软,重物放在托盘边缘会让力矩作用到传感器上,读出来的重量和实际值差一大截。解决方式是让托盘尽量小一点、硬一点,并且把重心控制在传感器的受力点上。如果你的应用是非接触式的,比如快递柜掉包裹进去自动称重,还要考虑下落的冲击力峰值会不会让传感器瞬间过载,这时候软件要做一个短时间内的最大值滤波或者延迟稳定判断,别把冲击瞬间的尖峰当成真实重量。
整套流程跑下来之后,5kg量程的电子秤做到±1g以内的重复性是很轻松的。如果你打算在这个基础上继续深入,可以考虑加一个OLED屏幕做一个独立的手持称重设备,或者通过串口把重量数据上传到上位机做记录分析,还可以试试把HX711替换成两个传感器并联做更大秤面的平台秤。方向上可以延伸的东西很多,但核心的选型、接线、时序、标定、滤波这套基本功,就是这个组合里最值得下功夫的部分。
本文还有配套的精品资源,点击获取