1. 为什么这个方案选型合理:STM32、应变电桥与HX711的组合逻辑
做智能体重秤之前,最容易被问到的就是:为什么非要STM32?51单片机不行吗?用Arduino不是更快吗?这个问题其实挺关键,决定你后面整个项目是顺畅还是返工。
先说结论:智能体重秤这种项目,核心难点根本不在主控芯片跑不跑得动,而在于传感器信号的质量。体重秤的传感器输出是一个只有几毫伏到二十毫伏级别的微弱差分信号,普通单片机自带的12位ADC根本识别不了这么小的变化。如果强行用51单片机配合运放放大电路,你得自己搭仪表放大器、调零电路、温度补偿,光这套模拟前端设计就够折腾好几个星期,还不一定稳定。
STM32F103C8T6这个芯片在这个项目里的优势恰好不是“更强”,而是“综合成本可控”。它主频72MHz,算力完全溢出,但更重要的是GPIO资源、定时器、I2C、SPI、串口这些外设齐整,后续想扩展蓝牙模块、OLED显示屏、心率传感器、USB通信,几乎不用换平台。另一个实际原因是开发环境成熟——STM32CubeMX生成初始化代码、标准外设库资料丰富、调试器便宜,遇到问题搜索到的案例也多。对于想把项目搞完而不是在学校里耗一学期的场景,这很重要。
再聊聊传感器选型。市面上能测重力的方案其实不少:
| 方案类型 | 精度 | 成本 | 复杂程度 | 适合场景 |
|---|---|---|---|---|
| 电阻应变式压力传感器 | 高 | 中 | 低 | 绝大多数电子秤、体重秤 |
| 电容式压力传感器 | 中 | 高 | 高 | 触摸感知、特殊工业场景 |
| 电磁力平衡式 | 极高 | 极高 | 极高 | 精密天平 |
| 压力电阻膜 | 低 | 低 | 低 | 粗略人机交互 |
智能体重秤既要求一定精度(至少±0.1kg级别),又要求成本可控,电阻应变式传感器是绝对的主流选择。这类传感器内部是惠斯通电桥结构——四个电阻应变片贴在弹性体上,人踩上去弹性体形变,应变片的阻值发生变化,电桥失去平衡,输出端就产生了一个与重量成正比的微小电压差。
问题来了,这个电压差有多么微小?常见的100kg量程传感器,满量程输出大约在1mV/V到2mV/V之间。如果传感器激励电压是5V,满量程输出也就5mV到10mV,也就是说1kg的重量变化只对应大约0.05mV的电压变化。普通单片机ADC的参考电压通常是3.3V,用它来测0.05mV的电压变化,等于用一把一米的尺子去量头发丝直径。
所以HX711在这里的核心价值在于:它内部集成了低噪声可编程增益放大器(PGA)和24位Σ-Δ型ADC,可以以64或128倍增益放大微弱信号,并把模拟信号转换成24位的数字输出。这是和传感器最匹配的搭档,用HX711省掉的模拟前端电路,远比贵出的那几块钱芯片成本值钱。
还有个容易被忽略的选型细节:称重传感器的量程怎么定。体重秤一般选100kg到150kg的量程就行,不建议选200kg以上的。传感器实际工作在量程的10%-80%区间精度最优,量程选太大,日常50kg的人踩上去只在满量程的25%附近,小信号的相对误差会变大。
2. 原理图上的关键信号链:从传感器毫伏信号到MCU串行数据
画原理图不是把芯片引脚连上就行,关键是理解信号是怎么一步步走过来的。这个项目的信号链是:人的重力 → 弹性体形变 → 应变片阻值变化 → 电桥输出电压差 → HX711放大+AD转换 → 串行时钟送出 → STM32读取 → 算法处理 → 显示屏显示。
2.1 应变式传感器的接口接法
传感器模块引出的线,典型是四根:红色接E+(激励正)、黑色接E-(激励负)、绿色接S+(信号正)、白色接S-(信号负),也有些模块用蓝线替代白线。很多新手在这里犯迷糊,把传感器直接往单片机上接,当然不行,必须先接到HX711模块的对应端子。
HX711模块上的A通道差分输入分别标着A+和A-,接传感器的S+和S-,E+和E-则接到HX711模块的VCC和GND使传感器获得供电。这里有个细节要特别说:HX711的AVDD引脚会输出一个稳压激励电压给传感器。当模块供电电压VCC为5V时,AVDD内部稳压出来大约2.7V到3.3V,实际值取决于电路设计。传感器在较低激励电压下满量程输出会同比变小,所以软件标定时不要沿用别人的参数,必须自己对当前硬件做校准,这一点后面专门讲。
2.2 HX711的工作模式与引脚配置
HX711的PD_SCK和DOUT这两个引脚是串行通信线:DOUT是数据输出,PD_SCK是时钟输入。有意思的是,这枚芯片和单片机通信只用两根线,而且时序非常特殊。每读完一次转换结果需要拉24个高电平脉冲,数据按高位先出的顺序在DOUT引脚上逐位送出。第25个脉冲用于切换增益和通道,芯片内部是通过脉冲计数来设定工作模式的:
- 为下一次转换选择A通道、128倍增益:第25个脉冲时PD_SCK为高电平后停止脉冲
- 为下一次转换选择B通道、32倍增益:第25个脉冲后PD_SCK继续拉高再拉低
- 为下一次转换选择A通道、64倍增益:第26个脉冲后PD_SCK继续拉高再拉低
体重秤基本上只用A通道128倍增益就够了。以前有人为了省资源把增益切到64,实际上128倍增益下对应满量程差分输入大约±20mV,对体重秤信号来说更合适,噪声表现也更稳。
还有一个需要留意的引脚:RATE。它决定内部振荡器的采样速率,RATE接低电平时输出速率10SPS(每秒10次),接高电平时80SPS。体重秤选10SPS就够了,低采样速率本身就是天然的均值滤波,数据更稳。
2.3 电源、去耦和复位电路的原则
STM32最小系统部分没有太多花活:8MHz晶振配上两个20pF负载电容,复位脚接10kΩ上拉电阻加0.1uF电容到地,BOOT0接地进入常规启动模式。但电源部分值得多写几句。
HX711的数字电源和模拟电源虽然引脚多,实际供电都是同一个5V进来,区别在于去耦策略。每个电源引脚旁边都配一个0.1uF陶瓷电容紧贴着引脚放置去耦,AVDD附近再加一个10uF钽电容做低频储能滤波。传感器线缆如果用屏蔽线,屏蔽层必须在传感器端单点接地,不能两端都接,否则地环路反而会引入更多噪声。
数字地和模拟地在PCB上最好单点连接,不要让大电流的地电流穿过模拟信号区域。HX711模块成品通常已经把这些问题处理得不错,直接用模块时要注意模块的VCC电源要干净——从USB取5V时,USB的5V纹波可能达到几十毫伏,这种纹波对MCU没影响,但对HX711的参考电压就是干扰。稳妥做法是加一个AMS1117-3.3给MCU供电,5V经过磁珠或电感再给HX711模块。
2.4 显示屏和扩展接口的预留
显示部分我选用的是0.96寸OLED,I2C接口,只需要SCL和SDA两根线,接PA6和PA7。因为I2C地址可以硬件跳线,后面想挂传感器、时钟模块都不冲突。预留的两个扩展口分别是串口(PA9/PA10)和I2C(PB6/PB7),串口用来打印调试信息特别有用。
提示:第一版原理图建议把所有外设都引出排针,调试时不要焊死。尤其是HX711的DOUT和PD_SCK,预留测试点,方便示波器探头抓时序波形。
3. 软件里的重量真相:校准、滤波与单位换算的完整闭环
硬件只是骨架,软件才是体重秤有没有“灵性”的地方。这里的核心问题有三个:怎样把HX711的原始读数变成可靠的重量值?怎样处理读数抖动?怎样让标定后的数值在断电重启后不丢失?
3.1 HX711读取代码的质量决定一切
网上流传的一些读取HX711代码其实写得比较粗糙——比如直接死等DOUT拉低,没有超时保护。一旦传感器断路或者HX711通信异常,程序就卡死在读取函数里,看门狗都救不回来。合理的代码框架应该是这样:
// 返回0表示读取成功,-1表示超时 int HX711_Read(int32_t *value) { uint32_t data = 0; uint16_t timeout = 0; // 等待DOUT拉低,表示转换结果已准备好 while (HAL_GPIO_ReadPin(DOUT_GPIO_Port, DOUT_Pin) != GPIO_PIN_RESET) { if (++timeout > 100000) return -1; // 超时保护 } // 循环24次,读取24位数据 for (int i = 0; i < 24; i++) { HAL_GPIO_WritePin(PD_SCK_GPIO_Port, PD_SCK_Pin, GPIO_PIN_SET); data = data << 1; if (HAL_GPIO_ReadPin(DOUT_GPIO_Port, DOUT_Pin) == GPIO_PIN_SET) { data++; } HAL_GPIO_WritePin(PD_SCK_GPIO_Port, PD_SCK_Pin, GPIO_PIN_RESET); } // 第25个脉冲,回到A通道128倍增益 HAL_GPIO_WritePin(PD_SCK_GPIO_Port, PD_SCK_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(PD_SCK_GPIO_Port, PD_SCK_Pin, GPIO_PIN_RESET); // 24位有符号数,符号扩展 if (data & 0x800000) { data |= 0xFF000000; } *value = (int32_t)data; return 0; }这段代码有一个细节值得说:读取完成后必须发送第25个脉冲,否则HX711不会切换回A通道128倍增益模式,下一次读取可能会乱套。很多人把这段代码删掉,结果跑着跑着数据就跳变了,实际上就是这个原因。另外第1个0到第24个1的循环里,时钟脉冲必须要有足够的延时让DOUT端稳定——STM32主频72MHz时GPIO翻转太快,理论上加上3个空指令延时更保险。
3.2 两点标定:为什么单点校准不够用
很多人以为放个标准砝码,记录一下读数,然后把比例算出来就行了。这种做法在传感器特性完全线性的时候没问题,但实际应变片电桥存在零点偏移(空秤时输出不是零)和非线性偏差。更合理的方法是两点标定:
设传感器在空载时的原始读数为零店采样值zero,在已知重量W_std下的原始读数为raw_std,则线性模型为:
[ weight = \frac{raw_current - zero}{raw_std - zero} \times W_std ]
也就是说,标定过程实际上要做两步:先空秤采集一组数据得到zero,再放一个已知重量的标准物得到raw_std,然后算出比例系数。如果只做单点校准而不扣除零点,那么空秤时LCD会显示一个不归零的重量,而且这个偏移会随着温度飘移变得不可预测。
实操中,zero这个值不能采一次就完事。秤台可能会被人不小心压过,或者长时间放置后弹性体微变,零点会慢慢漂。我的代码里是这样处理的:每次开机执行一次零点自动校正,前提是秤台上没有重物。怎么判断有没有重物?采集连续20次读数,如果波动绝对值小于一个阈值,就认为秤是空载的,然后取平均作为零点。
3.3 平滑滤波的取舍:既要稳又要快
直接用单一读数显示重量时,你会看到末位数字像跑马灯一样跳。普通数字滤波其实做法很多,滑动平均、中值滤波、一阶低通都可以。但对体重秤来说有个矛盾体:滤波太强,读数稳定了但响应变慢,人站上去要等两三秒才显示;滤波太弱,数字跳得让人心烦。
我最后采用的是8点滑动平均,在每次HX711完成转换后取最近8个有效原始值的平均值,再代入标定公式换算。8个采样点对应10SPS采样率下大约是0.8秒的响应窗口,实际体感是站上去约1秒后读数基本稳定,之后几位小数几乎纹丝不动。
如果还想进一步压抖动,可以引入一个简单的中值+平均两步法:先取窗口内5个值的中间3个再平均,这种方法对抗偶发毛刺更有效,但代码复杂度略高。体重秤应用又不是精密天平,滑动平均在稳定性和响应速度上已经处于甜点区。
还有一个非常容易被忽略的问题:滤波窗口里的数据必须是“连续有效的”。我前面提到传感器断路超时返回-1的情况,如果代码里没有剔除无效值,这个-1会被当作正常数据参与滑动平均,导致显示瞬间跳到异常数值。处理办法是:读取返回-1时直接丢弃,不加入队列,用一个标记位记录通信故障次数,连续故障超过5次就在屏幕提示传感器异常。
3.4 校准参数的掉电保存
比例系数、零点值、单位模式(kg/lb)这些数据如果每次开机都重新标定,用起来会疯掉的。程序必须把它们存储到STM32内部Flash的最后一个扇区,模拟EEPROM效果。
在STM32F103C8T6里,Flash容量64KB,最后一个扇区地址是0x0800FC00,大小1KB。写入Flash前必须先擦除整个扇区,这一点和EEPROM不同,特别容易踩坑。每次改校准值时不能直接写,应该先把值缓存在RAM里,等标定流程结束统一写一次。频繁擦写Flash会损耗寿命,但这枚芯片的Flash擦写寿命大约1万次,正常用户一年也标定不了几次,完全够用。
读取参数时的校验可以这样设计:在存储区头部放一个固定魔数(比如0xA5A5),写参数时写入,读取时先检查魔数是否匹配,不匹配说明是首次上电或数据被破坏,就回出厂默认值并自动进入标定模式。这个方法比什么校验和都简单可靠。
4. 仿真怎么补短板:没有真实传感器也能验证代码逻辑
很多人在做这个项目时被“仿真”两个字劝退——Proteus里没有HX711模型,更没有应变片传感器模型,仿真图根本搭不出来。这个认识其实一半对一半错。硬件电路确实没法百分之百仿真,但核心逻辑绝对可以先跑通。
我的做法是做一个“模拟HX711层”。在STM32CubeMX生成的工程里,把HX711的读取函数封装在单独一个文件里,对外暴露统一的接口。然后在PC上的仿真工程里,用这个接口的一个桩实现替代真实读取:桩实现内部用随机数发生器生成一个基准值,并叠加一个正比于“模拟重量输入”的偏移量,同时故意叠加一些高频噪声,模拟真实传感器信号。
这样写的好处是,主程序里所有和重量换算、滤波、标定、显示有关的代码都不变,在仿真环境里调通之后,移植到真实硬件只需要替换底层的读取函数。这一层抽象设计是在实际开发中非常推荐的做法,哪怕不做仿真,也建议代码从第一天就分层写——底层驱动、业务逻辑、UI显示,三者不要混在一起。
仿真还有一个作用:验证标定算法的边界行为。我在仿真里故意输入了异常值,比如传感器读数为满量程、传感器突然断线、连续读数上下剧烈跳变,观察系统的反应。真实硬件只能模拟部分场景,而仿真可以快速把各种边界情况和时序问题提前暴露出来。例如HX711的25个时钟脉冲时序逻辑,完全可以在仿真里用虚拟示波器观察PD_SCK和DOUT的波形,确认每一拍的数据位是否对齐。
如果你的仿真电路里确实想放一个“传感器等效模型”,可以用一个电位器分压网络模拟电桥输出:电位器中间抽头输出0到3.3V的电压,经两个大电阻分压得到毫伏级差分电压输给HX711。严格说这种等效模型并不准确,因为HX711输入是差分信号且共模电压范围有约束,但用来验证整条数据链路走通还是够用的。
5. 实测复现最容易翻车的五个细节与检查方法
项目开发到了真正焊板子、烧代码、踩上去看显示的阶段,会遇到一堆芯片手册和教程里不会写的问题。我把实际调试中碰到的几类典型问题整理了出来,给要复现这个项目的读者当个快速排障清单。
5.1 传感器线序搞错导致读数为负或跳变
四线传感器的红黑绿白如果接错(尤其是绿白信号线接反),HX711读出来可能是负数或者读数严重非线性。检查方法很简单:用手轻轻压传感器,观察读数的绝对值是否随压力增大而增大。若压下去读数变小或者往反方向跑,基本可以断定信号极性接反,对调S+和S-即可。
5.2 供电纹波让HX711读数周期性波动
USB供电时读数总是有规律地上下波动,大约每几十毫秒一次,拔掉适配器改用电池供电就稳定了。这种情况大多数是5V供电纹波引起的。示波器测量5V测试点能看到几十毫伏的工频纹波叠加噪声。解决办法是电源走线时尽量让大电流路径避开模拟区域,给HX711模块的VCC脚加一个LC滤波器(磁珠+10uF钽电容),不要共用USB的5V直接从杜邦线飞过去。
5.3 零点自动校正启动条件没判断好
逻辑上这个坑很隐蔽:如果上电时秤台上已经站了人,开机自动校准会把当前读数当作零点,导致显示重量偏小。后来我把自动校正逻辑改成开机后先延时2秒,然后连续采集20次,要求这20次中的最大值和最小值之差小于20个原始计数单位(大约相当于0.03kg),才认为确实是空秤,否则跳过自动校正直接用上次存储的零点。这个逻辑在仿真里测过、真机上验证过,基本杜绝了误标定。
5.4 标定现场没有标准砝码怎么办
很多人标定时找不到标准砝码。我用的是桶装水:5L桶装水约5kg(实际精度取决于每次装的水量),或者用超市买的包装米、袋装面粉,包装袋上印着净含量。标定时重物要放在秤面正中央,左右对称位置多次测量取平均,因为偏心加载会引入误差。一个小技巧:标定的标准重量尽量接近日常使用的重量区间,比如做家用体重秤,标定用10kg到20kg的物体比用2kg的物体更合理,这样日常量程内线性度更好。
5.5 壳体装配后的“重量”问题
最后分享一个我踩过的坑:搞定了所有电路,装上亚克力外壳和四个支脚后,发现空秤读数漂了将近0.2kg。原因是传感器弹性体在拧外壳螺丝时预紧力发生了变化,相当于传感器在没有负载时就承受了一个力。所以结构件的装配顺序很有讲究:先固定传感器到秤底板,再放上秤面,四个角的螺丝要交替逐步拧紧,不要先拧紧一个角再拧其他角,避免应力集中。装配完成后,重新做一次全流程标定,而不是沿用裸板状态的参数。
做这个项目最有意思的部分,其实是把一路从选型、画图、写代码、仿真到真实排障走完后,回头看每一个当初觉得“无所谓”的选择,最后都在某些看不见的地方决定了系统的稳定性。如果你也在复现这个智能体重秤,建议按照硬件、驱动、算法、结构四条线分别推进,每一条线验证通过后再整合,不要一把梭全焊好再上电——那样出了问题很难定位。