1. 项目概述称重方案的选型与整体思路手上有一套某开发者的STM32智能体重秤开源资料包含完整代码、原理图和Proteus仿真工程我从拿到手到复现跑通前后也花了一天多时间。这个项目适合刚学完单片机基础、想做一个完整外设项目的同学练手也适合工程师快速参考一套成熟的称重方案。先说这个项目解决了什么问题。体重秤的核心需求有两个一是把人体重量变成单片机可读的数字量二是用尽量少的成本把精度做到家用级±50g左右就够用了。整个系统链路其实不复杂称重传感器输出微小差分电压经过专用ADC芯片放大并转换为数字量STM32读取后滤波、校准、换算成重量再送屏幕显示。难的点全在细节里——传感器桥路的接线、ADC时序、校准算法、抗干扰处理每一步都有坑。整体硬件选型是典型的入门级低成本方案主控用STM32F103C8T6传感器用四应变片桥式称重传感器量程5kg到10kg都可以ADC用HX711显示用0.96寸I2C接口的OLED供电用USB的5V经过板载LDO降到3.3V。这套方案最大的优势是料好买、例程多、调试手段丰富Proteus里也有对应的仿真模型可以先在电脑上把逻辑跑通再焊板子。软件上代码用标准外设库Standard Peripheral Library写的没有上复杂的RTOS就是一个主循环加定时器中断的裸机程序。项目拿到了我重新梳理了一下代码结构发现作者把HX711驱动单独抽成了一个文件读取、校准、滤波三个功能分开这一点很值得学习——外设驱动和应用逻辑分离后面想换成其他传感器或加蓝牙模块改动量会小很多。2. 硬件电路设计从传感器桥路到主控连接2.1 四应变片电桥的工作原理体重秤传感器内部的结构值得先讲清楚因为很多人第一次接传感器看到红黑绿白四根线一脸懵。这四根线分别对应电桥的电源正、电源负、信号正、信号负。传感器弹性体上贴了四片电阻应变片不受力时四个电阻相等电桥平衡输出为零受力后两片被拉伸、两片被压缩桥路失去平衡差分输出一个与受力成比例的微小电压。以常见的5kg传感器为例灵敏度参数一般是1mV/V也就是说桥路供电电压每1V满量程输出1mV。如果给桥路供5V满量程输出也只有5mV这个信号直接给STM32的内部ADC是没法用的——内部ADC的参考电压一般是3.3V5mV的信号进去连一个LSB都覆盖不了完全采不出有效变化。这就是为什么需要HX711。这颗芯片把可编程增益放大器PGA增益可配置为32、64、128倍和高精度24位ADC集成在一起专门干称重这行。信号进HX711先放大再采样配合内部24位分辨率5mV的满量程信号实际能采到非常可观的变化量。项目里配置的是A通道、128倍增益算下来分辨率大约能到几克的级别做家用体重秤绰绰有余。2.2 HX711的外围电路与通信时序HX711的外围电路极简外围只需要在供电脚加去耦电容靠近芯片放一个0.1uF的陶瓷电容电源入口再放一个10uF电解电容做储能。AVDD给桥路供电HX711内部有稳压输出一般接一个10uF电容稳定电压。项目原理图里可以看到传感器电源直接接在HX711的AVDD引脚上而不是接STM32的3.3V这个细节很关键——HX711会让激励电压和ADC参考电压同步变化电源纹波的影响会被抵消测量稳定性明显好于外部单独供电。通信接口是两根线DOUT数据输出和SCK时钟输入。HX711的时序比较特殊SCK每来一个脉冲就读出一个位连续读完24位数据后DOUT会从低电平跳回高电平表示一次读取完成。有意思的是SCK的脉冲个数还兼了通道选择功能25个脉冲切换到B通道而读A通道固定就是24个脉冲。项目代码里对时序的处理很严谨DOUT为低时才启动读取同时加了超时退出机制防止DOUT一直不拉低导致程序卡死。// HX711读取核心函数读取一次24位ADC原始值 uint32_t HX711_Read(void) { uint32_t value 0; uint8_t i; while (HX711_DOUT_PIN 1); // DOUT为低表示数据就绪 for (i 0; i 24; i) { HX711_SCK_PIN 1; value value 1; HX711_SCK_PIN 0; if (HX711_DOUT_PIN 1) value; } // 第25个脉冲进入A通道下一轮转换 HX711_SCK_PIN 1; value ^ 0x800000; // 转为有符号数 HX711_SCK_PIN 0; return value; }代码里倒数第二行的value ^ 0x800000很关键HX711输出的24位数据是二进制补码形式的最高位是符号位直接当无符号数用会导致零点和负重量换算出错。取反最高位相当于把补码转成直观的有符号数这样后面做去皮和负数判断就顺了。2.3 显示与按键电路的连接细节显示部分用的是0.96寸OLEDSSD1306控制芯片I2C接口地址一般默认0x78或0x7A7位地址就是0x3C或0x3D。在STM32上可以用硬件I2C也可以用GPIO模拟I2C项目里用的是GPIO模拟好处是移植方便不用处理硬件I2C各种时序上的怪问题。OLED的SDA和SCL各接一个4.7k上拉电阻到3.3V这一点在原理图上也画得很清楚很多初学者会漏掉上拉导致I2C通信不稳定。另外OLED的VCC必须接3.3V接5V虽然暂时能亮但时间长了容易损坏屏幕。按键部分项目里做了一个“去皮”按键用于把当前重量清零。去皮键的处理用了软件消抖检测到按下后延时20ms再确认一次避免机械抖动造成的误触发。这个按键连接的是PA0引脚上拉输入模式按下接地。供电方案也是照搬了作者的设计USB 5V进来经过一颗3.3V LDO给STM32和OLED供电HX711的桥路电源取自AVDD而数字电源和模拟电源之间用磁珠做了隔离。磁珠在低频下等效于一小段导线在高频下呈现高阻能有效隔断数字电路的高频噪声串进模拟部分。如果手头没有磁珠用10欧姆电阻也能凑合效果差一点但能用。注意传感器四根线如果接反读到的重量会是负数。识别方法很简单上电后用手按压传感器看HX711读数方向——显示数值变大说明接线正确变小或变负就把信号正负对调。3. 软件核心逻辑滤波、校准与显示驱动3.1 主循环框架与状态管理软件整体结构清晰初始化部分做了四件事时钟配置、GPIO初始化、OLED初始化、HX711上电等待稳定。等待稳定这一步很多人会忽略HX711上电后内部电容充电、基准电压建立都需要时间直接开始读数据大概率前几十次都是乱的。项目里延时了500ms才开始校零实测下来确实比上电就读要稳定很多。主循环的思路很朴素反复读ADC、滤波、换算重量、刷新显示、扫描按键。没有用操作系统占用率也低完全够用。伪代码如下int main(void) { SystemInit(); OLED_Init(); HX711_Init(); Delay_ms(500); // 等待HX711电压稳定 uint32_t offset HX711_GetAverage(10); // 空载零点 float slope Calibrate_GetSlope(); // 从EEPROM读取标定斜率 while (1) { uint32_t raw HX711_GetAverage(8); // 滤波后的原始值 int32_t weight (int32_t)((raw - offset) * slope); // 换算成克 if (weight 0) weight 0; if (KEY_Dealt_Scan() KEY_PRESSED) // 去皮 { offset HX711_GetAverage(10); } OLED_ShowWeight(weight); Delay_ms(100); } }这个循环里最需要注意的是(raw - offset)的数据类型问题。offset是校零时的平均原始值raw是当前读数正常情况下raw比offset大但放上重物后如果raw小于offset结果是负数再乘以斜率就得到负数重量界面上就会显示负值。项目里做了一个下限钳位weight 0时直接置0这个处理比加绝对值更符合实际场景。3.2 滑动平均滤波的实现与参数选择称重传感器最让人头疼的问题就是读数抖动。人体站立时肌肉微颤、呼吸起伏体重的瞬时读数能跳几十克。项目用的是滑动平均滤波维护一个固定长度的缓冲区每次读入新值就丢掉最旧的值取缓冲区平均值。相比简单算术平均滑动平均不需要每次都重新读N次响应也更快。关键参数是滤波窗口大小。窗口太小比如3次基本上滤不掉抖动窗口太大比如30次读数变化会变得迟钝人站上去要好几秒才稳定下来。我在实测时把窗口从4调到16逐个试发现8次比较合适——读数稳定时间大约1秒静态误差能压到±10g左右人站在上面大概2秒后就不再来回跳了。项目代码里默认是用8次平均这个参数建议不要随便改大否则体验会很拖沓。HX711的采样率也要配合滤波一起看。HX711的DOUT输出速率有10SPS和80SPS两档代码里用的是默认的10SPS。10SPS对应的单次转换时间是100ms配合8次滑动平均意味着显示数值大约每0.8秒完成一次完整更新这个节奏对于体重秤来说是合理的。如果做厨房秤需要快速出值可以切换到80SPS但噪声会明显增大需要更大的滤波窗口来补偿。3.3 体重的两点校准法斜率与零点校准是称重项目里最核心的算法也是最容易出错的地方。标准做法是两点校准空载时记录零点平均值offset放上已知重量的标准砝码比如5kg记录满量程平均值两点连成直线斜率就是重量与ADC数值的比例关系。代码里把校准结果分两个参数存储offset存零点slope存“每克对应的ADC值”的倒数形式。实际计算公式是重量 (raw - offset) / slope其中slope (满量程ADC值 - 零点ADC值) / 砝码重量。很多人在这一步会犯一个单位错误ADC值是整数砝码重量是克两个一除得到的是“每克多少ADC码”直接用这个值乘以后续的(raw - offset)结果是对的。但如果把公式记成“每ADC码多少克”单位换算就会多一步除法。项目代码里明确用“每克ADC码”的方式存储计算时做一次除法效率高而且不易搞混。// 拟合斜率根据空载和满量程两点计算每克对应的ADC值 float HX711_Calibrate(uint32_t offset, uint32_t fullRaw, float fullWeight_g) { return (float)(fullRaw - offset) / fullWeight_g; } // 重量换算 float HX711_GetWeight(uint32_t raw, uint32_t offset, float slope) { return (float)(raw - offset) / slope; }需要特别强调的是斜率必须做掉电保存。项目里在EEPROM模拟块里存了校准结果上电后直接读取不需要每次重新校准。如果偷懒把斜率写成固定常量换一个传感器或换一个砝码读数就全错。我试过把5kg传感器换成10kg传感器没重新校准之前的读数差了接近一倍就是斜率完全不对导致的。3.4 OLED显示驱动与功耗优化方向显示部分用常见的SSD1306驱动库底层函数主要是I2C写命令和写数据。项目里的显示界面分三行第一行显示实时重量第二行显示校准状态第三行显示按键操作提示。大字显示用了取模软件生成的12x24字体把重量数字放大实际观看效果很直观。完整的字库太大项目里只做了数字0到9和少量标点的字模这也够用。关于低功耗作者在代码里留了一个未完成的优化点连续30秒无按键且重量变化小于阈值时关闭OLED显示让STM32进入STOP模式通过外部按键中断唤醒。这个思路对电池供电的体重秤很有意义因为OLED是系统里最耗电的部件屏幕常亮时整机电流能到20mA以上关屏休眠后可以降到几十微安。如果后面想做成纽扣电池方案可以在此基础上用RTC定时唤醒做自动开关机。4. 仿真环境搭建Proteus模型的实操细节4.1 元件选型与电路连接要点这套开源资料里附带的Proteus仿真工程是一个很实用的调试工具。没有现成硬件的时候先用仿真把逻辑跑通可以省很多来回焊板子的时间。打开工程可以看到仿真电路里主控用的是STM32F103C8T6模型OLED换成了Proteus自带的图形LCD模块——因为Proteus的SSD1306 OLED模型不算太稳定作者直接用了更可靠的虚拟终端加字符LCD代替这个方法值得借鉴。仿真验证的是逻辑显示方式可以根据模型情况灵活替换。HX711在Proteus元件库里没有直接对应的模型项目里用的是替代方案用一个仪表放大器模型加ADC模块来模拟HX711的放大和转换功能传感器部分则用两个电阻分压网络来模拟桥路输出。这种替代方案在仿真精度上肯定比不上实物但对于走通主循环逻辑、验证滤波算法和校准流程已经足够了。4.2 用电阻网络模拟桥路差分输出的技巧仿真里模拟传感器最实用的方法是用电位器滑动变阻器分压产生一个可调的直流电压接入ADC模块的输入端。具体做法是把电位器两端接在模拟电源5V和地之间中间抽头接ADC通道调节电位器就相当于改变传感器受力。这样调节起来很直观仿真运行时用鼠标拖动电位器滑块就能看到显示重量跟着变化比直接改代码里的ADC变量要真实得多。不过要注意有些Proteus版本的电位器模型默认比例是固定的运行时鼠标拖动不生效需要打开属性面板手动修改数值。遇到这种情况不用怀疑电路错了把电位器的属性改成“交互式调节”就行了。我在复现时还发现另一个问题就是电位器模拟的电压范围和真实传感器输出差距很大——传感器满量程只有几毫伏而电位器一下就给到几伏如果不做缩放程序里的校准时标定结果会对不上。这个问题的解决方案是在仿真里不用绝对数值只验证相对变化到了实物阶段重新校准一次即可。4.3 仿真烧录与联调时的几个反直觉点仿真里烧录程序用的是Proteus菜单加载hex文件的方式不是Keil直接在线仿真联调。实际操作中我摸索出的方便路径是先在Keil里编译生成hex文件再在Proteus里加载这样所有外设行为都由仿真模型决定程序只能被动响应和真实硬件环境更接近。Keil的Debugger直接连Proteus虽然也能用但需要配置AGDI驱动版本不匹配时容易出幺蛾子不推荐新手折腾。另一个反直觉的点是仿真里芯片引脚的电平行为STM32模型在程序未运行时所有引脚是高阻态OLED和HX711模型在这种状态下可能显示异常容易误判硬件连接错误。正确做法是先加载hex文件并运行仿真再观察电路行为。这一条排除了我复现时大量“不通电”的假象。5. 实测问题与排查方案实录5.1 读数漂移问题热机与电源去耦第一次把实物焊好上电最常遇到的现象是读数一直在缓慢变化放上砝码后数字会先冲高再慢慢回落怎么等也不彻底稳定。这个问题的根源有两个一是传感器弹性体和应变片没有达到热平衡上电后内部温度还在缓慢变化导致零点漂移二是电源纹波和地噪声叠加到了ADC输入端。项目代码里留了一个“预热”注释建议上电后等待1到2分钟再校准实测下来这个建议非常实用。电源去耦方面我踩过一次坑最开始省掉了HX711供电引脚旁的0.1uF电容结果读数在几十克范围内来回跳怎么增大滤波窗口都压不住。补上电容后问题立刻消失。这类微小电容的作用不是能量存储而是给高频噪声提供一个低阻抗回流路径对高精度ADC来说是刚需省不得。5.2 线性度不足两点校准的误差边界两点校准在5kg量程内中间段的误差表现往往能接受大约±20g但校准范围覆盖越到两端越容易出现偏差。这是因为应变片传感器的桥路输出并不是严格线性弹性体的形变在小受力时接近线性接近满量程时会逐渐偏离。第一次测试时我用2kg砝码校准结果称5kg时差了100多克就是这个原因。解决办法是分段线性化在0到满量程之间取三五个校准点每段分别计算斜率实时称量时先判断当前落在哪个区间再用对应斜率。这个功能我在移植时自己加进去了代码增加不到五十行但精度能提升不少。如果只是家用场景也可以把传感器量程选大一点、只用中间段比如买10kg传感器只称0到6kg范围线性度会好很多。5.3 去皮与负数显示的处理细节去皮功能逻辑上很简单按下按键就把当前值设为新的零点。但实际使用中有两个细节需要处理。一是去皮后如果移走重物实际重量变为负数程序必须把显示钳位到0而不是显示负的偏移值项目代码里的判断已经处理了这个。二是去皮只改变零点不改变斜率如果去皮时传感器上还压着重物这个重物的重量就不会再被计入后续显示逻辑上是对的但容易让人误解——比如传感器上放了碗再按去皮之后加水称的就是水的重量这正是厨房秤的需求。按键消抖这里也出现过问题。项目用的延时消抖在裸机主循环里实现简单但按键作为外部中断触发时中断服务函数里不能做延时否则会阻塞主循环。后来我把按键扫描挪到主循环里用标志位加延时判断中断里只负责置标志问题就解决了。5.4 问题排查速查表现象可能原因排查与解决读数一直跳滤波后仍很大HX711供电缺去耦电容在VCC和GND之间补0.1uF电容检查地线是否单点连接放上重物读数反而变小传感器信号线接反对调传感器信号正负两根线按压传感器确认数值方向读数总是偏大或偏小校准斜率不对重新校准确认砝码重量准确不要沿用旧斜率仿真中重量为0不变电位器分压值未变检查电位器属性是否可交互调节或手动改分压比例OLED显示乱码I2C地址不匹配确认屏幕地址是0x3C还是0x3D代码里对应修改按键没反应消抖延时阻塞或引脚配置错误确认GPIO为输入上拉模式按键另一端接GND5.5 换传感器时的重校准注意事项换传感器是整个项目里最容易出“灵异问题”的环节。看似同样是5kg量程不同批次传感器的灵敏度可能差5%以上零点也不一样。最稳妥的操作流程是换上新传感器后先不校准读取空载值确认数值在合理范围内且方向正确然后放上标准砝码做全量程两点校准最后用另一个已知重量的物品比如一瓶500ml水验证一下误差。另外换传感器后建议把之前的斜率清掉避免程序上电读取旧参数导致读数离谱。项目代码里EEPROM存储区有版本号字段改动了这个字段的值之后上电检测到版本不匹配就会强制重新校准这个设计很巧妙也推荐大家在自己的项目里保留——省去很多“明明换了传感器读数还是错的”争论。6. 写在最后一些复现过程中的个人经验把整套资料从PDF原理图到实物跑通我最深的体会是这个项目的“坑”更多在硬件和调试方法上而不是代码本身。HX711这颗芯片虽然外围简单但它对电源和布线的敏感程度远超很多入门教程的描述这也是为什么仿真里跑得好好的逻辑一到实物就需要反复调滤波和校准参数。另外校准这个环节建议大家在程序里做成“按键触发重新校准”的模式不要每次烧录都去改代码里的常量。我第一次就是图省事把斜率写死结果传感器轻微位移或者温度变化之后读数就开始偏离排查了半天才意识到是校准基准已经变了。如果你想在这个基础上继续扩展可以优先考虑往两个方向走一是加蓝牙或Wi-Fi模块把体重数据同步到手机这种是健康类设备最常见的需求二是把传感器换成多点支撑结构用三四个传感器做分布式称重适合做体脂秤或者物流称重设备。无论是哪个方向现有的代码框架都能给你一个不错的起点HX711驱动和校准模块都可以直接复用。