基于STM32的智能体重秤DIY:从硬件选型到固件开发的完整实战

发布时间:2026/11/3 9:03:52
基于STM32的智能体重秤DIY:从硬件选型到固件开发的完整实战

1. 从零拆解一台STM32智能体重秤:这个项目到底在做什么

体重秤这东西,家家户户都有,但真正自己动手做一台带联网、带数据显示、带历史记录的智能体重秤,和去超市买一台成品,完全是两码事。我当初动这个念头,是因为家里那台老式电子秤只能显示一个数字,连个体重趋势都看不到,更别提什么数据记录了。市面上带蓝牙的智能秤动辄两三百,功能还未必合心意,索性自己用STM32搭一台,成本算下来不到八十块,还能顺便把嵌入式开发的整套流程走一遍。

这个项目的核心,说白了就是用STM32单片机做大脑,配合压力传感器采集重量信号,经过ADC转换和滤波算法处理后,把重量数值显示在屏幕上,同时可以通过串口或者无线模块把数据传到手机或电脑上做记录。整个系统涉及硬件电路设计、PCB绘制、单片机固件开发、上位机数据处理几个环节,是一个典型的嵌入式综合项目。对于正在学STM32的人来说,这种项目比点个LED灯、读个按键要有价值得多,因为它把GPIO、ADC、定时器、串口通信、中断、LCD驱动这些知识点全都串起来了。

我见过太多人学STM32的方式是跟着教程跑一遍例程,然后换个项目又不知道从哪下手。问题出在没有一个完整的项目把零散的知识点串成线。智能体重秤这个项目的好处在于,它的需求非常明确——称重、显示、传输,每个需求对应一个技术模块,模块之间的耦合关系也清晰。你做完这个项目,基本上对STM32的常用外设就有了实战级别的理解。

这个项目适合什么人?如果你已经学过STM32的基础知识,能看懂原理图,会用Keil或者STM32CubeIDE建立工程,那这个项目就是你的进阶练手首选。如果你是完全零基础,建议先把GPIO和ADC的基本用法过一遍再来看,不然会卡在很基础的地方。整个项目从硬件到软件,我会把每个环节的关键决策和踩过的坑都讲清楚,你照着做基本能复现出来。

2. 硬件选型与电路设计:为什么这么搭而不是那么搭

2.1 主控芯片的选择逻辑

STM32的型号多到让人眼花缭乱,从F0到F7、H7,价格从几块到上百块不等。做体重秤这个项目,我选的是STM32F103C8T6,也就是大家常说的"蓝板"或者"最小系统板"。为什么选它?原因很实际:第一,它的ADC是12位的,对于体重秤这种精度要求不算极端的场景完全够用;第二,它的外设资源丰富,有多个定时器、串口、SPI、I2C,方便扩展显示模块和通信模块;第三,它的资料和社区支持是最丰富的,遇到问题搜索一下基本都能找到答案;第四,价格便宜,淘宝上核心板也就十几块钱。

有人可能会问,为什么不选STM32F030或者STM32G0系列?这些芯片更便宜,性能也够用。但问题在于,F103的生态太成熟了,标准外设库和HAL库的例程遍地都是,对于学习和快速原型开发来说,时间成本比芯片差价重要得多。如果你是要做量产产品,那确实应该考虑成本更优的方案,但作为个人项目和学习用途,F103C8T6是最稳妥的选择。

还有一个细节值得注意:F103C8T6的ADC参考电压默认是VDDA,也就是3.3V。这意味着ADC的测量范围是0到3.3V,对应的数字量是0到4095。压力传感器输出的信号必须在这个范围内,否则要么测不到,要么会损坏引脚。这一点在选传感器的时候就要考虑进去。

2.2 压力传感器的选型与信号调理

体重秤的核心传感器是压力传感器,常见的有电阻应变片式和电容式两种。我选的是电阻应变片式的称重传感器,量程50kg,输出灵敏度通常是1mV/V。什么意思呢?就是说如果你给它供5V的激励电压,满量程50kg的时候,它输出的差分电压只有5mV。这个信号非常微弱,直接接到STM32的ADC引脚上根本测不出来,因为STM32的ADC分辨率是3.3V/4096≈0.8mV,而传感器满量程输出才5mV,信噪比太差了。

所以必须加信号调理电路。我用的是HX711这款专用的24位ADC芯片,它就是为称重传感器设计的,内置了低噪声可编程增益放大器,增益可以选128倍,正好把微弱的差分信号放大到适合ADC采集的范围。HX711和STM32之间通过两个GPIO引脚通信,一个时钟线,一个数据线,时序模拟起来也不复杂。

这里有个坑要提醒:HX711的供电电压和STM32的IO电平要匹配。HX711可以工作在2.7V到5.5V,如果你用5V供电,它的数据输出高电平就是5V,直接接到STM32的3.3V引脚上可能会损坏芯片。稳妥的做法是HX711也用3.3V供电,或者加电平转换电路。我一开始用5V供电,结果STM32的引脚被拉高了,虽然没烧,但数据一直不稳定,后来改成3.3V供电就正常了。

2.3 显示模块与交互设计

显示部分我选的是0.96寸的OLED屏幕,I2C接口,分辨率128x64。为什么不用LCD1602?因为OLED更省引脚,I2C只需要两根线,而且显示效果更好,可以显示中文和图形。LCD1602虽然便宜,但需要至少6根数据线加3根控制线,占用引脚太多,而且只能显示字符,做界面不够灵活。

OLED的驱动芯片通常是SSD1306,STM32通过I2C发送命令和数据来控制显示。I2C的地址一般是0x78或0x7A,具体看模块上的跳线。这里要注意的是,I2C总线上需要加上拉电阻,一般是4.7kΩ到10kΩ。很多OLED模块自带上拉电阻,如果你的模块没有,就要自己加,否则通信会失败。

除了显示,我还加了一个按键用于去皮和切换显示模式。按键的电路很简单,一端接GPIO,一端接地,GPIO配置为上拉输入,按下时读到低电平。但按键消抖是个必须处理的问题,硬件上可以加0.1uF的电容,软件上可以用延时确认或者状态机的方式。我两种都用了,硬件消抖加软件状态机,效果很稳。

2.4 电源方案与功耗考量

整个系统的供电可以用USB 5V输入,经过AMS1117-3.3稳压到3.3V给STM32、OLED和HX711供电。AMS1117的压差大概是1.1V,5V输入降到3.3V完全没问题。但要注意散热,如果电流较大,AMS1117会发热,可以加个小散热片或者用DC-DC降压模块替代。

如果你想让体重秤用电池供电,那就要考虑低功耗设计。STM32F103在运行模式下的电流大概是几十毫安,加上OLED和HX711,整体功耗在100mA左右。用2000mAh的锂电池,大概能用20个小时。如果要延长续航,可以在不用的时候让STM32进入待机模式,通过按键中断唤醒。不过对于桌面使用的体重秤来说,直接插USB供电最省事,低功耗设计可以作为进阶优化。

3. 从原理图到PCB:画板子时最容易翻车的几个地方

3.1 原理图绘制中的引脚分配陷阱

画原理图之前,一定要先做引脚分配表。我见过太多人画到一半发现某个功能需要的引脚被占了,或者某个引脚不支持想要的功能。STM32F103C8T6有48个引脚,但真正能用的GPIO是32个左右,其中还有一些有特殊功能限制。

比如PA13和PA14默认是SWD调试接口,如果你把它们当普通GPIO用了,下次就没法用调试器下载程序了。PB3和PB4默认是JTAG接口,虽然可以重映射为普通GPIO,但需要先关闭JTAG功能。这些细节在画原理图的时候就要规划好,不然后面改起来很麻烦。

我的引脚分配是这样的:HX711用PB0和PB1,OLED的I2C用PB6和PB7,按键用PA0,串口用PA9和PA10。这样分配的好处是,所有引脚都在同一侧,布线方便,而且避开了调试接口和特殊功能引脚。

3.2 HX711与STM32的接口电路细节

HX711的电路连接看起来简单,但有几个细节容易忽略。首先是模拟电源和数字电源的隔离,HX711的AVDD和DVDD最好分别加滤波电容,AVDD对噪声更敏感,要离芯片近一点。其次是传感器的屏蔽线要接地,否则容易引入干扰。我在第一次打板的时候没注意这些,结果读数跳动很大,后来在AVDD和AGND之间加了0.1uF和10uF的电容,读数就稳定多了。

还有一点,HX711的通道A增益是128,通道B增益是32。称重传感器接通道A,如果要接多个传感器,可以用通道B做辅助测量。但通道B的增益低,精度不如通道A,所以主传感器一定要接通道A。

3.3 PCB布局布线的实战经验

PCB布局的时候,模拟部分和数字部分要分开。HX711和传感器接口属于模拟部分,STM32和OLED属于数字部分,两者的地线要单点连接,避免数字噪声串到模拟地上。我一般会在PCB上划一块模拟区域,用0欧姆电阻或者磁珠把模拟地和数字地连起来。

走线方面,HX711的时钟线和数据线不要太长,尽量短而直。I2C的两根线要等长,并且加上拉电阻。电源线要粗一点,至少20mil,保证电流足够。晶振要靠近STM32的晶振引脚,负载电容要匹配,一般22pF左右,具体看晶振的规格书。

还有一个容易翻车的地方是丝印。画PCB的时候要把每个接口的功能标清楚,比如"传感器输入""OLED接口""电源输入",不然焊好之后自己都忘了哪根线是干嘛的。我吃过这个亏,后来每次画板子都会在丝印上标注清楚。

3.4 打板与焊接的注意事项

打板之前一定要做DRC检查,确保没有短路和断路。我一般会把PCB文件发给嘉立创或者捷配,五天左右就能收到。焊接的时候,先焊矮的元件,再焊高的元件。STM32的引脚比较密,建议用刀头烙铁加助焊剂拖焊,或者用热风枪。HX711是SOP封装,引脚间距1.27mm,手焊难度不大,但要注意不要连锡。

焊完之后先别急着上电,用万用表测一下电源和地有没有短路。我有个朋友焊完直接上电,结果AMS1117冒烟了,就是因为电源和地短路了。上电之后先测各路电压是否正常,3.3V和5V都要测,确认没问题再插STM32核心板。

4. 固件开发:ADC采样、滤波算法与显示驱动的完整实现

4.1 HX711的驱动时序与数据读取

HX711的通信协议是自定义的,不是标准的SPI或I2C,需要自己模拟时序。它的数据线在时钟上升沿输出数据,24位数据从高位到低位依次输出,第25个时钟脉冲用来选择下一次的通道和增益。具体时序是:当数据线从高变低时,表示数据准备好,此时开始发送时钟脉冲,每个上升沿读取一位数据。

我用的是GPIO模拟的方式,代码大概长这样:

uint32_t HX711_Read(void) { uint32_t data = 0; while(GPIO_ReadInputDataBit(HX711_DOUT_PORT, HX711_DOUT_PIN)); for(uint8_t i = 0; i < 24; i++) { GPIO_SetBits(HX711_SCK_PORT, HX711_SCK_PIN); delay_us(1); data <<= 1; if(GPIO_ReadInputDataBit(HX711_DOUT_PORT, HX711_DOUT_PIN)) data++; GPIO_ResetBits(HX711_SCK_PORT, HX711_SCK_PIN); delay_us(1); } GPIO_SetBits(HX711_SCK_PORT, HX711_SCK_PIN); delay_us(1); GPIO_ResetBits(HX711_SCK_PORT, HX711_SCK_PIN); if(data & 0x800000) data |= 0xFF000000; return data; }

这段代码里有个细节:读到的24位数据是补码形式,如果最高位是1,说明是负数,需要符号扩展。体重秤在空载的时候读数可能是个负数,这是正常的,因为传感器的零点有偏差。

4.2 重量计算的标定方法与公式推导

HX711读出来的原始值是个24位的整数,需要转换成实际的重量值。转换公式是:重量 = (原始值 - 零点值) / 刻度系数。零点值就是空载时的读数,刻度系数是每克重量对应的原始值变化量。

标定的时候,先用一个已知重量的物体(比如1kg的砝码或者一袋500g的盐),记录原始值,然后计算刻度系数。比如空载时读数是100000,放上1kg砝码后读数是105000,那么刻度系数就是(105000-100000)/1000 = 5,也就是说每克对应5个原始值单位。

实际使用中,零点会漂移,所以每次开机或者按去皮键的时候都要重新采集零点值。我一般会连续采集10次取平均作为零点,这样更稳定。刻度系数只需要标定一次,存在Flash里,下次开机直接读取。

4.3 数字滤波:滑动平均与中值滤波的组合拳

原始数据肯定是有噪声的,直接显示会跳来跳去。滤波是必须的,但滤波算法不能太复杂,否则响应会变慢。我试过几种方案,最后用的是滑动平均加中值滤波的组合。

滑动平均就是取最近N次采样的平均值,N一般取10到20。中值滤波是取最近N次采样的中位数,可以去掉突发的尖峰干扰。我的做法是先用中值滤波去掉异常值,再做滑动平均平滑数据。这样既保留了响应速度,又抑制了噪声。

具体实现上,我用一个环形缓冲区存最近的采样值,每次新数据进来就替换最旧的数据,然后重新计算。中值滤波需要对缓冲区排序,数据量小的时候用冒泡排序就够了。滑动平均直接累加求和再除以N。

有个经验:滤波窗口的大小要根据采样率来定。如果采样率是10Hz,窗口取10,那响应延迟就是1秒,对于体重秤来说可以接受。如果窗口取50,延迟就是5秒,那就太慢了。我一般把采样率设在10到20Hz,窗口取8到12。

4.4 OLED显示驱动的移植与界面设计

OLED的驱动我用的现成的SSD1306库,移植的时候主要改I2C的底层读写函数。STM32的硬件I2C有时候不太稳定,我一般用软件模拟I2C,虽然速度慢一点,但可靠性高。软件I2C的时序就是标准的起始、停止、应答、发送字节、接收字节,网上有很多现成的代码可以参考。

界面设计上,我分了三个区域:顶部显示当前重量,中间显示单位(kg或lb),底部显示电池电量或者状态信息。重量数值用大字体显示,看起来更直观。我还加了一个趋势箭头,如果当前重量比上次重就显示向上箭头,轻了就显示向下箭头,这个功能虽然简单但很实用。

刷新率方面,OLED不需要每帧都刷新,我一般200ms刷新一次,既不会闪烁,也不会占用太多CPU时间。刷新的时候只更新变化的区域,不要全屏刷新,这样可以延长OLED的寿命。

4.5 串口通信与数据上传协议

串口通信我用的UART1,波特率115200,数据格式是8位数据位、1位停止位、无校验。发送的数据格式是自定义的,我定义了一个简单的帧结构:帧头(0xAA 0x55)+ 重量数据(4字节浮点数)+ 校验和(1字节)。上位机收到数据后先找帧头,然后读取重量数据,最后校验。

为什么要加帧头和校验?因为串口传输可能会丢字节或者错位,没有帧头的话上位机不知道从哪里开始解析。校验和可以检测数据是否在传输过程中出错。这个协议虽然简单,但足够可靠。

如果你要用蓝牙模块,比如HC-05或者HC-06,接线方式是把蓝牙模块的TXD接STM32的RXD,RXD接STM32的TXD,然后配对之后就可以像串口一样收发数据了。注意蓝牙模块的供电一般是3.3V到5V,要看清楚模块的规格。

5. 仿真验证:没有硬件也能跑通逻辑

5.1 Proteus仿真的搭建与元件替换

不是每个人都有条件马上打板焊接,这时候Proteus仿真就是个很好的验证手段。Proteus里可以找到STM32F103的模型,HX711和OLED也有对应的仿真元件。但要注意,Proteus里的元件库不一定和实物完全一致,比如HX711的仿真模型可能没有,这时候可以用一个模拟的ADC或者电位器来替代,模拟传感器输出的电压变化。

仿真的主要目的是验证代码逻辑,比如ADC读取、数据处理、显示刷新、串口发送这些流程是否正常。仿真的好处是可以随时修改代码、观察变量,不用烧录到硬件上。但仿真也有局限性,比如时序问题、电源噪声、信号完整性这些在仿真里体现不出来,所以仿真通过不代表硬件一定能跑通。

5.2 仿真中常见的时序问题与解决

Proteus仿真STM32的时候,最常见的问题是时钟配置不对。STM32F103的外部晶振是8MHz,经过PLL倍频到72MHz。如果仿真里的晶振频率设置错了,或者PLL配置不对,程序就跑不起来。我一般会在仿真里用内部时钟(HSI)先跑通,确认逻辑没问题再切换到外部时钟。

另一个常见问题是HX711的时序在仿真里不响应。因为Proteus的仿真速度是模拟的,不是实时的,delay_us这种微秒级延时在仿真里可能不准确。解决方法是把延时改大一点,或者在仿真里跳过HX711的等待循环,直接用模拟数据代替。

5.3 仿真与实物的差异及验证策略

仿真跑通之后,不要以为就万事大吉了。实物上电之后可能会遇到各种仿真里没有的问题,比如电源纹波导致ADC读数跳动、I2C上拉电阻不合适导致通信失败、晶振不起振等等。我的策略是先在仿真里验证核心算法和逻辑,然后在实物上分模块调试:先调电源,再调最小系统,然后调传感器,最后调显示和通信。

分模块调试的好处是,出了问题容易定位。如果一上来就把所有模块都焊上去,出了问题不知道是哪个模块的毛病。我一般会留出测试点,用示波器或者逻辑分析仪观察关键信号,比如I2C的时钟和数据线、HX711的时钟和数据线、串口的TX和RX。

6. 踩坑实录:那些让我熬夜到凌晨的Bug

6.1 ADC读数跳动:从电源到接地的完整排查

第一次焊好板子,上电之后OLED显示正常,但重量读数一直在跳,空载的时候从-50g跳到+80g,根本没法用。我一开始以为是滤波没做好,把滑动平均窗口从10加到50,结果跳动是小了,但响应变得极慢,放上砝码要等好几秒才稳定。

后来用示波器看HX711的模拟电源引脚,发现上面有大概20mV的纹波,频率是几十kHz。这个纹波来自STM32的数字电源,通过共地耦合到了模拟部分。解决方法是:在HX711的AVDD和AGND之间加了一个10uF的钽电容和一个0.1uF的陶瓷电容,并且把模拟地和数字地在电源入口处单点连接。改完之后,纹波降到了5mV以下,读数稳定在±5g以内。

这个坑让我明白一个道理:ADC读数不稳定,十有八九是电源和地的问题,不要一上来就怀疑算法。

6.2 I2C通信失败:上拉电阻与地址的坑

OLED一开始不亮,用逻辑分析仪抓I2C波形,发现时钟线有信号,但数据线一直是高电平,没有应答。检查代码,I2C地址写的是0x78,但模块上的跳线默认是0x7A。改了地址之后还是不行,后来发现模块上没有上拉电阻,而STM32的I2C引脚配置的是开漏输出,没有上拉就拉不高。

加了两个4.7kΩ的上拉电阻到3.3V,OLED就正常显示了。这里要注意,上拉电阻不能太小,否则电流太大会烧引脚;也不能太大,否则上升沿太慢,通信会失败。4.7kΩ到10kΩ是比较合适的范围。

6.3 串口数据乱码:波特率与时钟的匹配问题

串口调试的时候,上位机收到的全是乱码。检查波特率,两边都设的115200,看起来没问题。后来用示波器测STM32的TX引脚,发现实际波特率是9600左右,不是115200。原因是STM32的时钟配置错了,外部晶振是8MHz,但我按12MHz算的PLL参数,导致系统时钟不是72MHz,而是48MHz,串口波特率自然就不对了。

修正时钟配置后,串口就正常了。这个坑的教训是:时钟配置是STM32开发的基础,一定要用示波器或者MCO引脚输出时钟信号验证一下,不要想当然。

6.4 按键抖动导致的误触发

按键按一下,有时候会触发好几次。这是典型的按键抖动问题。我在硬件上加了0.1uF的电容,软件上用了状态机消抖:检测到按键按下后,延时20ms再检测,如果还是按下状态,才认为是有效按键。释放的时候也做同样的处理。这样改完之后,按键就再也没误触发过。

7. 项目扩展与进阶玩法

7.1 接入WiFi模块实现数据上云

如果你想让体重秤的数据自动上传到手机或者电脑,可以加一个ESP8266或者ESP32模块,通过串口和STM32通信。STM32把重量数据发给ESP8266,ESP8266通过WiFi发送到服务器或者MQTT broker。这样你就可以在手机上随时查看体重记录,甚至生成趋势图。

ESP8266的AT指令集用起来比较简单,STM32只需要发送AT指令就能控制它连接WiFi、建立TCP连接、发送数据。但要注意ESP8266的供电电流比较大,峰值可能到300mA,所以电源要足够,最好单独给它一个LDO。

7.2 多用户识别与数据管理

如果家里有多个人用同一台秤,可以加一个用户切换功能。通过按键选择用户,每个用户的体重数据分开存储。STM32的Flash可以存几百条记录,每条记录包含时间戳和体重值。时间戳可以用RTC实时时钟,STM32F103有内置的RTC,加一个32.768kHz的晶振就能工作。

数据管理方面,可以做一个简单的菜单系统,用OLED显示,按键操作。菜单里可以查看历史记录、设置用户、校准传感器、切换单位等。这个功能会让项目的复杂度上升一个档次,但也会让你的嵌入式开发能力提升一大截。

7.3 低功耗优化与电池供电方案

如果要做电池供电的版本,低功耗优化是必须的。STM32F103在待机模式下的电流只有几微安,可以通过按键中断或者RTC闹钟唤醒。唤醒后快速采集一次重量,显示几秒钟,然后再次进入待机。这样用一节18650电池可以撑好几个月。

低功耗设计的几个要点:不用外设的时候关掉时钟,OLED不用的时候关闭显示,HX711不用的时候断电,LED指示灯尽量去掉或者用低功耗的。电源方面可以用TPS63020这样的升降压芯片,效率比AMS1117高很多。

7.4 外壳设计与3D打印

硬件调通之后,可以给它做个外壳。我用Fusion 360画了一个简单的底座和面板,用3D打印机打出来。底座里放PCB和电池,面板上开孔露出OLED和按键,传感器固定在四个角上。外壳的设计要考虑传感器的受力,不能让外壳本身卡住传感器,否则测出来的重量会偏大。

3D打印的材料用PLA就够了,强度足够,打印也容易。如果要做透明面板,可以用光固化打印或者亚克力切割。外壳的螺丝孔要留好公差,一般留0.2mm到0.3mm,不然螺丝拧不进去。

8. 写在最后:一些个人体会

这个项目我从画原理图到最终调通,前后花了大概两周的业余时间。中间踩了不少坑,但也学到了很多书本上学不到的东西。最大的体会是,嵌入式开发不是把代码写对就行了,硬件和软件的配合才是关键。很多时候代码没问题,但硬件上的一个小细节没注意到,整个系统就跑不起来。

另外,调试工具真的很重要。一个逻辑分析仪能帮你省下大量猜测的时间,我用的是一款几十块钱的USB逻辑分析仪,配合开源软件就能抓I2C、SPI、串口的波形。示波器也是必备的,看电源纹波、测时钟频率都离不开它。

最后说一句,这个项目的所有代码和原理图我都会整理好放在GitHub上,包括HX711的驱动、OLED的驱动、滤波算法、串口协议,都是可以直接复用的模块。你拿到之后可以根据自己的需求修改,比如换不同的传感器、加不同的通信模块、做不同的外壳。嵌入式开发的乐趣就在于,你可以完全掌控每一个细节,做出真正属于自己的东西。