☰
基于STM32的智能体重秤DIY:从传感器信号链到滤波标定全解析
2026/10/12 3:03:29 网站建设 项目流程

1. 从一个厨房秤的痛点说起:为什么要自己做一个智能体重秤

去年帮朋友搬家,收拾厨房时翻出来三四个不同时期买的电子秤,有厨房秤、有体重秤、还有个体脂秤。挨个装电池试了一遍,发现一个共同问题:要么只能显示个数字,要么连个手机App传数据还得先注册账号、绑定设备、忍受广告弹窗。更离谱的是有个秤,每次开机都要重新校准,站上去三次能给出三个不同的数。

这事儿让我动了心思——体重秤这东西,核心需求其实特别朴素:称得准、看得清、能记录。市面上几十块钱的成品完全能满足前两条,但第三条"能记录"往往被做成了各种花哨的App生态,反而把简单的事情搞复杂了。于是就有了这个基于STM32的智能体重秤项目,从原理图到代码再到仿真,全部自己动手走一遍。

这个项目适合谁看?如果你正在学STM32,想找一个有完整闭环、能出实物、还能讲清楚原理的练手项目,那这个秤比点灯、跑马灯有意思得多。它涉及模拟信号采集、ADC采样、数字滤波、OLED显示、按键交互、数据存储这几个嵌入式开发的核心模块,麻雀虽小五脏俱全。如果你只是想买个秤用,那直接去电商平台下单更省事——但如果你想搞明白"一个秤到底是怎么把重量变成数字的",往下看就对了。

整个系统的核心链路是这样的:压力传感器把重量转换成微弱电压信号,仪表放大器把信号放大到STM32能识别的范围,ADC采样后经过滤波和标定计算得出重量值,最后通过OLED显示出来,同时支持按键去皮、单位切换、数据存储。听起来不复杂,但每个环节都有坑,后面我会把踩过的坑一个个拆开讲。

2. 硬件选型:为什么是STM32F103而不是别的

2.1 主控芯片的取舍逻辑

选STM32F103C8T6作为主控,理由很实在:价格便宜、资料多、性能够用。这颗芯片是Cortex-M3内核,72MHz主频,64KB Flash,20KB RAM,对于体重秤这种应用来说性能绰绰有余。有人可能会问,用8位机行不行?比如STC89C52之类的。理论上可以,但实际做下来会发现两个问题:一是ADC精度和稳定性不如STM32,二是后续想加蓝牙、加存储、加复杂滤波算法时,8位机的资源会捉襟见肘。

另一个选择是STM32F030这类更低成本的M0芯片。我实际对比过,F030的ADC在低速采样时表现也不错,但它的开发资料和社区支持相比F103差了一截。对于新手来说,遇到问题能搜到答案比省那几块钱重要得多。所以最终定下来用F103C8T6,最小系统板十几块钱就能买到,烧录用ST-Link或者串口都行。

2.2 传感器方案:应变片还是压力传感器

体重秤的核心是传感器。市面上常见的方案有两种:电阻应变式传感器和电容式压力传感器。电容式的精度高但电路复杂,需要专门的电容检测芯片;电阻应变式便宜、电路简单,缺点是输出信号微弱,需要配合仪表放大器使用。

我选的是人体秤专用的电阻应变片传感器,量程50kg,输出灵敏度1mV/V左右。什么意思呢?假设激励电压是5V,满量程时输出信号只有5mV。而STM32的ADC参考电压是3.3V,12位分辨率下最小能分辨0.8mV。也就是说,如果不放大,满量程信号只占ADC量程的0.15%,根本没法用。所以仪表放大器是必须的。

这里有个细节:传感器有四个应变片组成惠斯通电桥,输出差分信号。常见的放大方案是用三运放搭建仪表放大器,或者直接用集成芯片如INA125、HX711。HX711是专门为电子秤设计的24位ADC,集成度很高,但它的缺点是采样率低、需要额外的通信协议,而且用HX711的话STM32就只负责显示和逻辑,ADC部分被模块替代了,学习价值打折扣。所以我选择用分立仪表放大器+STM32内置ADC的方案,虽然电路复杂一点,但能完整理解信号链的每一个环节。

2.3 显示与交互:OLED还是LCD

显示部分我用了0.96寸的OLED屏,I2C接口,分辨率128x64。选它的理由很简单:自发光、对比度高、视角广、驱动简单。LCD需要背光,在体重秤这种可能放在光线较暗环境下的设备上,OLED的显示效果更舒服。而且OLED的I2C接口只需要两根线,节省IO口。

按键方面用了三个轻触开关:去皮/清零、单位切换、模式切换。没有用触摸按键是因为成本考虑,而且机械按键的手感反馈更明确,用户按下去知道有没有触发。

电源部分,我用了3.7V锂电池+升压模块的方案。传感器和仪表放大器需要稳定的5V供电,STM32和OLED用3.3V。所以需要一颗升压芯片把锂电池电压升到5V,再用LDO降到3.3V。这里要注意模拟电源和数字电源的隔离,否则数字电路的噪声会串到模拟部分,导致读数跳动。

3. 原理图设计:信号链路上的每一个细节

3.1 传感器接口与激励电路

传感器的四个引脚分别是:激励正、激励负、信号正、信号负。激励电压我用的是5V,由升压模块提供。这里有个关键点:激励电压的稳定性直接决定测量精度。如果5V波动10mV,输出信号就会波动2uV,虽然看起来很小,但经过1000倍放大后就变成2mV,在ADC上就是好几个LSB的跳动。

所以我在激励电路上加了RC滤波和稳压二极管。具体做法是在5V输出端并联一个100uF的电解电容和一个0.1uF的陶瓷电容,再串一个10欧姆的电阻到传感器的激励正端,激励负端直接接地。这样做的目的是滤除电源纹波,同时限制流过传感器的电流。传感器内阻通常是1kΩ左右,5V激励下电流约5mA,在10欧姆电阻上压降只有50mV,对激励电压影响很小。

信号输出端直接接到仪表放大器的差分输入端。这里要注意走线要尽量短且对称,因为信号幅度只有毫伏级别,任何不对称的走线都会引入共模干扰。

3.2 仪表放大器电路的计算与选型

仪表放大器我用的是AD620,这是一颗经典的低成本仪表放大器,增益范围1到10000,只需要一个外部电阻就能设定增益。增益公式是:

G = 1 + (49.4kΩ / Rg)

我需要把5mV的满量程信号放大到接近3.3V,所以增益大约需要660倍。代入公式:

Rg = 49.4kΩ / (G - 1) = 49.4kΩ / 659 ≈ 75Ω

实际选用了75Ω的1%精度金属膜电阻。这里有个坑:AD620的增益精度取决于Rg的精度,如果用5%的碳膜电阻,增益误差可能达到几个百分点,称重结果就会偏差几百克。所以Rg一定要用高精度电阻。

AD620的REF引脚我接了一个由两个10kΩ电阻分压得到的1.65V参考电压,这样输出信号就以1.65V为中心摆动,正好落在STM32的ADC量程中间。如果REF接地,输出只能向上摆动,浪费了一半量程。

3.3 ADC采样与参考电压

STM32F103的ADC是12位的,参考电压我用的是外部3.3V基准,而不是直接用VDDA。因为VDDA会随着负载变化有轻微波动,而专用基准芯片如REF3033能提供稳定的3.3V输出,温漂只有几十ppm/°C。虽然多花了几块钱,但换来的稳定性是值得的。

ADC输入端加了一个RC低通滤波器,R=1kΩ,C=0.1uF,截止频率约1.6kHz。这个滤波器的作用是滤除高频噪声,同时限制输入电流保护ADC引脚。注意ADC输入阻抗和这个RC滤波器的匹配,STM32的ADC采样时间要设置得足够长,让采样电容充分充电。

3.4 电源树与地线处理

整个系统的电源树是这样的:锂电池3.7V → 升压到5V → 一路给传感器和AD620,另一路经LDO降到3.3V给STM32和OLED。模拟地和数字地要分开走线,最后在电源入口处单点连接。这个做法能有效避免数字电路的开关噪声通过地线串到模拟部分。

我在PCB布局时把AD620和传感器接口放在板子的一侧,STM32和OLED放在另一侧,中间用地线隔开。虽然是个小项目,但良好的布局习惯从一开始就要养成,否则调试时会莫名其妙地出现读数跳动。

4. 软件架构:从ADC原始值到稳定重量的完整算法

4.1 系统初始化与任务调度

软件部分我没有用RTOS,因为任务不复杂,用前后台架构就够了。主循环里轮询按键和更新显示,ADC采样用定时器触发,在中断里读取数据。这样能保证采样间隔严格均匀,对后续的数字滤波很重要。

初始化顺序有讲究:先初始化时钟和电源,再初始化外设,最后初始化传感器。因为传感器需要预热时间,AD620上电后需要几毫秒稳定,如果初始化太快就读数,第一次的值会偏。我在初始化后加了一个500ms的延时,等所有电路稳定后再开始采样。

4.2 ADC采样与数字滤波

ADC采样我用的是定时器触发+DMA传输的方式。定时器每10ms触发一次ADC转换,转换完成后DMA自动把数据搬到内存数组里。这样CPU不用干预采样过程,只需要在需要的时候读取数组即可。

采样得到的是原始ADC值,范围0到4095。但直接读出来的值跳动很大,因为传感器信号里混杂了各种噪声。我用了滑动平均滤波+中值滤波的组合:先连续采样16次,去掉最大和最小的各2个值,剩下的12个取平均。这样既能抑制随机噪声,又能剔除突发的脉冲干扰。

实测下来,不加滤波时ADC值跳动范围大约±20个LSB,经过滤波后能稳定在±2个LSB以内。换算成重量,50kg量程下1个LSB约等于12g,±2个LSB就是±24g的波动,对于体重秤来说完全可以接受。

4.3 标定算法:两点校准与线性拟合

传感器和放大器的实际增益不可能和理论计算完全一致,所以必须标定。我用的是两点校准法:先放一个已知重量的物体(比如5kg的哑铃),记录ADC值;再放另一个已知重量(比如20kg),记录ADC值。然后用这两组数据拟合一条直线:

重量 = (ADC值 - 零点ADC值) × 斜率

斜率 = (20kg - 5kg) / (ADC20 - ADC5)

零点ADC值是在空载时测得的。这里有个细节:空载值会随温度漂移,所以每次开机时都要重新采集零点。我在开机流程里加了"请保持空载"的提示,等零点稳定后再进入称重模式。

标定参数存在STM32的Flash里,掉电不丢失。这样出厂前标定一次,用户拿到手就能直接用。如果想更精确,可以用多点校准加最小二乘法拟合,但对于体重秤来说两点校准足够了。

4.4 按键交互与状态机设计

三个按键的功能分别是:去皮、单位切换、校准模式。按键处理我用了一个简单的状态机,每个按键有按下、释放、长按三种状态。短按触发功能,长按进入校准模式。

去皮功能的逻辑是:按下去皮键时,把当前ADC值记录为零点偏移,后续所有重量都减去这个偏移。这样用户可以先放一个容器上去,去皮后再放物体,得到的就是物体的净重。

单位切换在千克和磅之间切换,换算系数是2.20462。显示时根据单位选择不同的格式,千克保留两位小数,磅保留一位小数。

5. 仿真验证:没有实物时怎么把代码跑通

5.1 Proteus仿真环境的搭建

不是每个人都有条件立刻打板子、焊元件,所以Proteus仿真是一个很好的验证手段。我在Proteus里搭建了完整的电路:STM32F103、AD620、OLED、按键,传感器用电位器模拟——通过调节电位器改变差分电压,模拟不同重量下的传感器输出。

Proteus里没有AD620的模型,我用三个运放搭建的仪表放大器替代,增益设置成和AD620一样。虽然模型不完全一致,但信号链的逻辑是通的。OLED用Proteus自带的12864模型,I2C通信协议需要自己写驱动。

仿真最大的好处是可以随时暂停、查看变量、修改参数。比如我想看滤波前后的ADC值对比,直接在Proteus里加一个虚拟示波器就能看到波形。这种直观性是实物调试给不了的。

5.2 仿真中发现的三个问题

仿真跑起来后发现了几个问题,这些问题在实物上可能也会遇到:

第一个问题是ADC采样时间不够。Proteus里默认的采样时间太短,导致采样电容没充满,读数偏低。我把采样时间从7.5个周期改成55.5个周期后,读数就正常了。这提醒我实物上也要注意这个参数。

第二个问题是OLED刷新率太高导致闪烁。我一开始在主循环里每轮都刷新OLED,结果屏幕闪烁严重。后来改成每200ms刷新一次,显示就稳定了。这个经验在实物上同样适用,OLED的I2C速率有限,刷新太频繁反而效果差。

第三个问题是按键抖动。仿真里按键没有硬件消抖电路,按下时会产生多次触发。我在软件里加了20ms的延时消抖,问题解决。实物上虽然可以加RC消抖,但软件消抖更灵活,推荐两者都做。

5.3 从仿真到实物的差异与应对

仿真跑通不代表实物就能用,两者之间有几个关键差异:

噪声水平不同。仿真里是理想环境,没有电源噪声、没有电磁干扰。实物上ADC读数会跳得更厉害,所以滤波算法要留有余量。我在实物上把滑动平均的窗口从16次增加到32次,效果明显改善。

传感器特性不同。仿真里用电位器模拟,线性度完美。实物传感器的线性度、迟滞、蠕变都会影响精度。我在代码里加了非线性补偿,用分段线性的方式修正传感器误差。

时序差异。仿真里STM32的主频设置和实物可能不同,导致延时函数不准。我在实物上用了SysTick定时器做精确延时,不再依赖循环计数。

6. 调试过程中踩过的五个坑

6.1 读数一直漂移不定

第一次焊好板子上电,OLED显示的重量一直在跳,空载时从-200g跳到+300g。排查了半天,最后发现是AD620的REF引脚悬空了。原理图上明明画了分压电路,但焊接时漏焊了那两个10kΩ电阻。补上后读数立刻稳定在±10g以内。

这个坑的教训是:焊接后一定要对照原理图逐项检查,特别是电源和参考电压这种关键节点。后来我养成了习惯,每焊完一个模块就用万用表量一下关键电压,确认无误再焊下一个。

6.2 称重结果线性度差

标定后发现小重量准,大重量偏轻,误差能达到5%。检查后发现是AD620的增益设置电阻精度不够。我一开始用了5%的碳膜电阻,换成1%的金属膜电阻后线性度明显改善。另外,传感器的激励电压也要稳定,如果升压模块输出波动,增益再准也没用。

6.3 OLED显示乱码

OLED初始化后显示乱码,查了资料发现是I2C地址搞错了。0.96寸OLED通常有两个地址:0x78和0x7A,取决于模块上的电阻配置。我用的是0x78,但代码里写的是0x7A。改过来就好了。这个坑很典型,I2C设备地址一定要看模块手册或实测确认,不能想当然。

6.4 按键偶尔失灵

按键有时候按了没反应,有时候按一次触发两次。原因是按键引脚没有配置上拉电阻,浮空输入导致电平不确定。STM32内部有上拉电阻,但阻值较大,容易受干扰。我在外部加了10kΩ上拉电阻,并在软件里加了消抖逻辑,问题彻底解决。

6.5 电池供电时读数异常

用USB供电时一切正常,换成锂电池供电后读数就偏大。测量发现升压模块的输出纹波很大,达到100mV峰峰值。在升压模块输出端并联了一个220uF的低ESR电解电容后,纹波降到20mV以内,读数恢复正常。这个经验说明:电池供电的系统,电源质量往往比市电供电更差,滤波电容要舍得放。

7. 这个项目还能怎么玩:扩展思路与实用建议

7.1 加蓝牙模块实现手机记录

如果想加数据记录功能,最直接的方式是接一个HC-05蓝牙模块,通过串口把重量数据发到手机。手机端可以用现成的串口助手App接收,也可以自己写个简单的Android应用。注意蓝牙模块的供电要干净,否则会影响ADC精度。我试过在蓝牙发送数据时ADC读数会跳,后来在蓝牙模块电源脚加了LC滤波就好了。

7.2 加SD卡做本地存储

另一个思路是加MicroSD卡模块,把每次称重的数据带时间戳存成CSV文件。这样不需要手机就能记录历史数据。SD卡用SPI接口,和OLED共用SPI总线,通过片选信号区分。注意SD卡的写入操作比较耗时,不要在ADC采样中断里做,否则会影响采样时序。

7.3 低功耗优化

如果想让设备用电池撑更久,可以做低功耗优化。STM32在待机模式下电流只有几微安,按键中断唤醒。OLED在无操作30秒后自动关闭,需要时再点亮。传感器和AD620在不称重时可以断电,用MOS管控制电源。这样整体功耗能从几十毫安降到几毫安,电池续航从几天延长到几个月。

7.4 给新手的三个实用建议

第一,先仿真后打板。Proteus虽然不能完全替代实物,但能帮你把逻辑跑通,避免打板后才发现原理图错误。打板一次几十块钱,仿真不要钱,这笔账要算清楚。

第二,模块化调试。不要一次把所有代码写完再烧录,而是一个模块一个模块地验证。先让OLED显示"Hello",再加ADC采样,再加滤波,再加按键。每加一个功能就测试一次,出问题容易定位。

第三,保留调试接口。板子上一定要留出串口打印的接口,把关键变量实时输出到电脑上。OLED屏幕太小,显示不了太多信息,串口才是调试的主力工具。我在代码里加了一个调试宏,定义DEBUG时输出详细日志,发布时关掉即可。

这个秤我从画原理图到调通代码花了大约两周的业余时间,中间踩的坑比预想的多,但每解决一个问题,对模拟电路和嵌入式系统的理解就深一层。如果你也在做类似的项目,希望这些经验能帮你少走点弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询