基于STM32与HX711的智能体重秤设计:应变电桥信号链到软件校准
1. 为什么这个方案选型合理STM32、应变电桥与HX711的组合逻辑做智能体重秤之前最容易被问到的就是为什么非要STM3251单片机不行吗用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寸OLEDI2C接口只需要SCL和SDA两根线接PA6和PA7。因为I2C地址可以硬件跳线后面想挂传感器、时钟模块都不冲突。预留的两个扩展口分别是串口PA9/PA10和I2CPB6/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。原因是传感器弹性体在拧外壳螺丝时预紧力发生了变化相当于传感器在没有负载时就承受了一个力。所以结构件的装配顺序很有讲究先固定传感器到秤底板再放上秤面四个角的螺丝要交替逐步拧紧不要先拧紧一个角再拧其他角避免应力集中。装配完成后重新做一次全流程标定而不是沿用裸板状态的参数。做这个项目最有意思的部分其实是把一路从选型、画图、写代码、仿真到真实排障走完后回头看每一个当初觉得“无所谓”的选择最后都在某些看不见的地方决定了系统的稳定性。如果你也在复现这个智能体重秤建议按照硬件、驱动、算法、结构四条线分别推进每一条线验证通过后再整合不要一把梭全焊好再上电——那样出了问题很难定位。