首页 / 资讯中心 / 文章详情

基于STM32与HX711的智能体重秤设计:从原理图到代码的完整实战

基于STM32与HX711的智能体重秤设计:从原理图到代码的完整实战 ★ FEATURED ARTICLE
1. 项目缘起与整体设计思路体重秤这个东西看起来简单真要做一台能拿得出手的智能体重秤里面的门道比想象中多得多。我前前后后做过三版第一版用51单片机凑合第二版换成STM32F103但ADC采样噪声大得离谱第三版才算是把精度、稳定性和交互体验都调到了一个能接受的水平。这个开源项目就是第三版的完整整理包含代码、原理图、仿真三件套适合正在找基于STM32的毕业设计题目的同学也适合想练手传感器ADC人机交互这条链路的嵌入式初学者。先说清楚这个项目到底做了什么以STM32F103C8T6为核心搭配电阻应变式称重传感器和HX711专用24位ADC实现0到150kg量程的体重测量精度可以做到±50g以内测量结果通过OLED或LCD1602显示同时支持按键去皮、单位切换、历史记录存储还可以通过串口把数据上传到上位机做曲线记录。整个系统成本控制在50元以内全部用模块化设计新手照着原理图连线和烧录代码就能跑起来。为什么选STM32而不是51这是很多人问的第一个问题。51单片机当然能做体重秤但它的ADC精度和运算能力有限做简单的阈值判断还行要做滤波算法、多任务调度、串口协议解析就力不从心了。STM32F103C8T6有12位ADC、72MHz主频、硬件SPI和UART价格也就十块钱出头性价比碾压。而且STM32的生态成熟HAL库、标准库、寄存器版本随便挑Keil仿真和Proteus仿真都支持得很好对学习和验证来说非常友好。整体方案的设计思路可以概括为四个字分而治之。把系统拆成传感采集、信号处理、人机交互、数据通信四个独立模块每个模块单独调试通过后再联调。这样做的好处是出问题的时候能快速定位——是传感器没输出还是ADC读数跳还是显示驱动有问题一测便知。很多人做项目喜欢一口气把所有代码写完再烧录结果一上电什么都不亮然后就开始抓瞎这种调试方式效率极低。提示做这类项目强烈建议先在面包板上分模块验证确认每个模块都能独立工作后再画PCB或焊接到洞洞板上。我见过太多人直接焊死结果发现某个模块有问题拆焊拆到怀疑人生。方案选型上还有几个关键决策值得展开说。称重传感器的选择常见的有电阻应变式、电容式、电磁力平衡式。电磁力平衡式精度最高但价格上千电容式受温湿度影响大电阻应变式是性价比最优解几十块钱就能买到量程50kg、精度0.02%FS的悬臂梁式传感器。ADC的选择STM32自带的12位ADC理论上够用但实际测试中噪声太大称重信号本身是毫伏级的微弱差分信号直接进MCU的ADC基本没法用。HX711是专门为称重传感器设计的24位ADC内置增益放大器和稳压电路直接差分输入输出就是数字量省去了模拟前端的设计麻烦。显示方案OLED比LCD1602贵几块钱但显示效果好太多而且I2C接口只占两个引脚代码也简单。如果预算紧张或者手头只有LCD1602也完全能用项目代码里两种驱动都保留了。2. 核心硬件细节与原理图解析2.1 称重传感器与HX711的接线要点电阻应变式称重传感器有四根线E激励正、E-激励负、A信号正、A-信号负。颜色上通常是红、黑、白、绿但不同厂家定义可能不同拿到手第一件事是用万用表测电阻确认——E和E-之间大约1kΩA和A-之间大约1kΩ激励和信号之间应该是无穷大或很高阻抗。如果测出来不对千万别接接反了可能烧HX711。HX711模块这边E、E-接传感器的激励A、A-接信号另一侧VCC、GND、SCK、DOUT接STM32。这里有个细节很多人忽略HX711的VCC最好用3.3V而不是5V虽然模块标称支持5V但5V供电时数字接口电平也是5V直接接STM32的3.3V IO口长期工作可能损伤引脚。如果非要用5VDOUT和SCK线上要加电平转换或者至少串个1kΩ电阻限流。原理图里我特别标注了去耦电容的位置HX711的VCC和GND之间并一个0.1μF陶瓷电容和一个10μF电解电容越靠近芯片越好。称重信号是微伏级别的电源上任何一点纹波都会直接反映到读数上。我实测过不加去耦电容时读数跳动在±200g加上之后降到±30g效果立竿见影。STM32最小系统部分晶振电路用8MHz无源晶振配两个20pF电容复位电路用10kΩ上拉加100nF电容BOOT0通过10kΩ电阻下拉到地SWD调试接口引出SWDIO和SWCLK两个引脚。这些是标准配置但新手容易在晶振电容值上犯错——负载电容要根据晶振规格书选不是随便抓一个就行。一般8MHz晶振配20pF是安全的但如果晶振起振困难可以试着减小到15pF或增大到30pF。2.2 电源方案与低功耗考量整个系统可以用USB 5V供电也可以锂电池供电。如果用电池需要加一个低压差稳压器比如AMS1117-3.3把电池电压降到3.3V。这里有个坑AMS1117的静态电流大约5mA对于电池供电来说偏大如果要做低功耗版本建议换成HT7333或者XC6206静态电流只有几微安。低功耗设计方面如果只是桌面使用其实不用太纠结。但如果想做成便携式可以在软件里加入自动休眠逻辑连续30秒没有重量变化且没有按键操作就关闭OLED显示、把STM32切到Stop模式HX711也断电。唤醒方式可以用按键中断或者HX711的DOUT引脚下降沿触发。这个功能我在代码里留了接口但没有默认开启因为调试阶段频繁休眠很烦人。原理图里还有一个蜂鸣器驱动电路用一个S8050三极管驱动有源蜂鸣器基极串1kΩ电阻接STM32的GPIO。有源蜂鸣器通电就响控制简单无源蜂鸣器需要PWM驱动可以发出不同频率的声音但代码复杂一些。项目里用的是有源蜂鸣器按键按下和测量完成时各响一声体验上更直观。2.3 显示模块的选型与接口OLED用的是0.96寸SSD1306I2C接口分辨率128x64。接线只有四根VCC、GND、SCL、SDA。I2C地址通常是0x78或0x7A具体看模块背面的标注。如果屏幕不亮先检查地址对不对再用示波器看SCL和SDA有没有波形。我遇到过好几次是杜邦线接触不良换根线就好了所以调试时别急着怀疑代码。LCD1602用的是并行接口需要RS、RW、E、D0-D7共11根线占IO口多但显示字符大远距离看得清楚。如果用的是I2C转LCD1602模块那就只需要四根线和OLED一样方便。项目代码里两种驱动都封装成了统一的显示接口切换显示模块只需要改一个宏定义。原理图文件我用的是嘉立创EDA格式也导出了PDF版本方便查看。如果你习惯用AD或者PADS可以自己重新画一遍正好练手。画原理图时注意网络标签要规范命名比如HX711_DOUT、HX711_SCK、OLED_SCL这种不要用NET1、NET2这种默认名否则后面查线查到你崩溃。3. 软件架构与核心代码实现3.1 HX711驱动与滤波算法HX711的驱动本身不复杂核心就是时钟同步串行读取24位数据。DOUT拉低表示数据准备好然后SCK给24个脉冲每个脉冲上升沿读出一位第25个脉冲用来选择下一次的通道和增益。代码里我用的是GPIO模拟时序没有用硬件SPI因为HX711的时序要求不严格模拟更灵活。long HX711_Read(void) { long count 0; unsigned char i; while(GPIO_ReadInputDataBit(HX711_DOUT_PORT, HX711_DOUT_PIN)); for(i 0; i 24; i) { GPIO_SetBits(HX711_SCK_PORT, HX711_SCK_PIN); count count 1; GPIO_ResetBits(HX711_SCK_PORT, HX711_SCK_PIN); if(GPIO_ReadInputDataBit(HX711_DOUT_PORT, HX711_DOUT_PIN)) count; } GPIO_SetBits(HX711_SCK_PORT, HX711_SCK_PIN); count count ^ 0x800000; GPIO_ResetBits(HX711_SCK_PORT, HX711_SCK_PIN); return count; }这段代码里有个关键点count ^ 0x800000是把24位有符号数转成32位有符号数的标准操作。HX711输出的是二进制补码最高位是符号位直接当正数处理会出错。这个细节很多教程里没讲清楚导致读数在零点附近乱跳。滤波算法我用的是滑动平均中值滤波的组合。滑动平均窗口取10个样本中值滤波取5个样本。为什么两个都用滑动平均对周期性噪声效果好但对突发尖峰脉冲无能为力中值滤波正好能干掉尖峰。两者结合实测读数稳定性从±100g提升到±20g以内。#define FILTER_SIZE 10 #define MEDIAN_SIZE 5 long filter_buf[FILTER_SIZE]; long median_buf[MEDIAN_SIZE]; long Get_Weight(void) { long raw HX711_Read(); long sum 0; long temp; int i, j; for(i FILTER_SIZE - 1; i 0; i--) filter_buf[i] filter_buf[i-1]; filter_buf[0] raw; for(i 0; i MEDIAN_SIZE; i) median_buf[i] filter_buf[i]; for(i 0; i MEDIAN_SIZE - 1; i) for(j i 1; j MEDIAN_SIZE; j) if(median_buf[i] median_buf[j]) { temp median_buf[i]; median_buf[i] median_buf[j]; median_buf[j] temp; } for(i 0; i FILTER_SIZE; i) sum filter_buf[i]; return (sum / FILTER_SIZE median_buf[MEDIAN_SIZE/2]) / 2; }标定环节是另一个容易翻车的地方。去皮和校准要分开做先空载读一个值作为零点再放一个已知重量的砝码读一个值算出比例系数。代码里用两个按键分别触发去皮和校准校准值存在STM32的Flash里掉电不丢失。这里注意Flash写入要解锁、擦除、写入、上锁顺序不能乱而且写入前要确保没有其他中断在访问Flash。3.2 主循环任务调度与状态机设计主循环我没有用RTOS而是用了一个简单的时间片轮询调度器。系统里有四个任务称重采集每100ms一次、按键扫描每20ms一次、显示刷新每200ms一次、串口上报每500ms一次。每个任务记录上次执行的时间戳到时间就执行没到就跳过。typedef struct { void (*task)(void); uint32_t interval; uint32_t last_tick; } Task_t; Task_t tasks[] { {Weight_Acquire, 100, 0}, {Key_Scan, 20, 0}, {Display_Update, 200, 0}, {Uart_Report, 500, 0} }; void Main_Loop(void) { uint8_t i; for(i 0; i sizeof(tasks)/sizeof(Task_t); i) { if(HAL_GetTick() - tasks[i].last_tick tasks[i].interval) { tasks[i].task(); tasks[i].last_tick HAL_GetTick(); } } }这种调度方式比RTOS简单没有栈溢出和优先级反转的风险对于这种小系统完全够用。但要注意任务执行时间不能超过调度间隔否则会阻塞其他任务。比如显示刷新如果用了软件I2C刷一屏可能要几十毫秒这时候称重采集就会被推迟。解决办法是把显示刷新拆成多次小操作或者用硬件I2C加DMA。状态机方面系统有五个状态待机、测量中、稳定显示、去皮、校准。状态切换由按键和重量变化触发。比如待机状态下检测到重量超过500g自动进入测量中重量稳定3秒后进入稳定显示蜂鸣器响一声提示按去皮键进入去皮状态把当前重量设为零点长按校准键进入校准状态等待放砝码。注意状态机切换时一定要做去抖动处理。按键抖动用20ms延时消抖重量变化用连续3次采样都超过阈值才确认。我第一版没做重量去抖结果人站上去稍微晃一下状态就来回跳显示的数字也跟着闪体验极差。3.3 串口通信协议与上位机对接串口上报用的是简单的文本协议每帧格式为W:1234g\r\n波特率115200。为什么用文本而不是二进制因为调试方便串口助手直接就能看不用写解析工具。如果要做产品二进制协议更紧凑但学习项目没必要。上位机我用的是VOFA一个国产的串口数据可视化工具支持波形显示和协议解析。把体重数据实时画成曲线能直观看到滤波效果和稳定过程。如果你用Python也可以用pyserial加matplotlib自己写一个代码不超过50行。import serial import matplotlib.pyplot as plt from collections import deque ser serial.Serial(COM3, 115200) data deque(maxlen200) plt.ion() fig, ax plt.subplots() line, ax.plot([], []) while True: try: raw ser.readline().decode().strip() if raw.startswith(W:): weight float(raw[2:-1]) data.append(weight) line.set_data(range(len(data)), data) ax.relim() ax.autoscale_view() plt.pause(0.01) except: pass这段代码跑起来后你能看到一条实时更新的体重曲线。站上去的时候曲线上升稳定后是一条水平线波动范围就是你的测量精度。如果曲线一直在漂说明零点没校准好或者电源有干扰。4. 仿真验证与实物调试4.1 Proteus仿真环境的搭建仿真这部分我要先泼一盆冷水Proteus里没有HX711的官方模型需要自己建或者找第三方库。我试过几种方案最靠谱的是用ADC0832或者MCP3421模拟HX711的行为写一个简单的Verilog或者用Proteus的脚本模型。但说实话仿真出来的结果和实物差距很大尤其是噪声特性完全对不上。那仿真还有什么用验证逻辑和时序。比如按键扫描的状态机对不对、显示刷新有没有冲突、串口协议解析是否正确这些在仿真里跑一遍能省不少调试时间。我通常的做法是先在Proteus里用理想信号源代替传感器确认整个软件流程跑通再上实物调传感器和ADC。Proteus仿真文件里我放了一个虚拟终端接串口可以直接看到上报的数据。还有一个示波器接HX711的SCK和DOUT能观察时序波形。如果你用Keil仿真也可以直接在Keil里看变量和寄存器但Keil不能模拟外设只能验证纯算法逻辑。4.2 实物调试的常见坑与排查实物调试才是真正的战场。我整理了一个排查表按现象查原因能覆盖90%的问题现象可能原因排查方法上电后屏幕不亮电源没接好、I2C地址错、初始化失败万用表测VCC、示波器看SCL/SDA、换地址试读数一直是0HX711没输出、DOUT一直高、SCK没波形测DOUT电平、检查接线、换模块读数跳动大电源纹波、地线环路、滤波不够加去耦电容、单点接地、增大滤波窗口读数线性度差传感器质量差、机械安装应力换传感器、检查安装是否平整按键不灵敏上拉电阻缺失、消抖时间不对加10kΩ上拉、调整消抖时间串口乱码波特率不匹配、晶振频率错核对波特率、测晶振频率校准后掉电丢失Flash写入失败、地址冲突检查Flash解锁序列、换地址地线环路这个问题特别隐蔽。我遇到过读数一直缓慢漂移换了传感器、换了HX711、加了电容都没用最后发现是传感器屏蔽线两端都接地了形成了地环路。把屏蔽线一端悬空后漂移立刻消失。所以接线时记住屏蔽线只能一端接地通常是接在MCU这边。另一个坑是机械安装应力。悬臂梁传感器对安装平整度要求很高如果固定面不平或者螺丝拧得太紧传感器会产生额外的应力导致零点漂移和线性度变差。正确的做法是传感器一端固定在刚性底座上另一端悬空受力面要平整螺丝拧到刚好紧固即可不要死拧。4.3 精度校准的实操步骤校准是决定最终精度的关键步骤我把它拆成五步预热上电后等待5分钟让HX711和传感器的温度稳定。温度变化会导致零点漂移预热后校准更准。去皮空载状态下按去皮键记录零点值。注意秤台上不能有任何东西包括灰尘。放砝码放一个已知重量的砝码建议用满量程的50%到80%比如100kg量程用50kg或80kg砝码。砝码要放在秤台中心避免偏载。计算系数系数 (砝码读数 - 零点值) / 砝码实际重量把这个系数写入Flash。验证换几个不同重量的砝码验证看误差是否在允许范围内。如果误差大检查砝码是否准确、秤台是否水平。提示如果没有标准砝码可以用已知重量的物品代替比如一桶5L矿泉水约5kg、一袋10kg大米。但要注意这些物品的重量标注可能有偏差校准精度会受影响。条件允许的话去菜市场找公平秤称一下。校准完成后建议做多点线性验证从0到满量程取5个点每个点测三次取平均画一条实际值vs读数的曲线。如果曲线不是直线说明传感器线性度不好或者机械安装有问题。好的传感器线性度应该在0.05%FS以内对应150kg量程就是±75g。5. 项目扩展与进阶玩法5.1 从体重秤到体脂秤的升级路径体重秤只能测重量体脂秤能测体脂率、肌肉量、水分率原理是生物电阻抗分析。在秤台上加四个电极片脚踩上去形成回路测人体阻抗。脂肪导电性差肌肉导电性好通过阻抗值反推体脂率。硬件上需要加一个交流恒流源和阻抗测量电路软件上需要建立阻抗-体脂的回归模型。这个升级难度不小但也不是遥不可及。交流恒流源可以用AD5933或者AFE4300专用芯片阻抗测量精度能到1%以内。回归模型可以用现成的公式比如Deurenberg公式输入体重、身高、年龄、性别和阻抗值输出体脂率。代码里我留了一个扩展接口有兴趣的可以自己接。5.2 数据上云与手机APP对接串口上报只能连电脑要做成物联网体重秤需要加WiFi模块比如ESP8266或ESP32或者蓝牙模块HC-05。ESP8266通过AT指令连WiFi用MQTT协议把数据发到服务器手机APP订阅主题就能看到。HC-05更简单手机蓝牙串口助手直接收数据但只能近距离。我试过用ESP-01S加STM32通过AT指令连MQTT代码量不大但AT指令的解析要处理好超时和重试。另一个方案是直接用ESP32替代STM32ESP32自带WiFi和蓝牙性能也够开发环境用Arduino或者ESP-IDF。但这样就偏离了STM32学习的初衷看你的目标是什么。5.3 多用户识别与历史记录如果家里多人使用可以加一个用户切换功能按按键选择用户1/2/3/4每个用户有独立的去皮值和历史记录。历史记录存在STM32的Flash里用环形缓冲区管理最多存100条满了覆盖最旧的。显示的时候可以翻页查看或者通过串口导出到电脑。Flash的擦写寿命大约10万次如果每天存10条能用27年完全够用。但要注意Flash写入前必须先擦除整个扇区STM32F103的扇区大小是1KB或2KB不能按字节擦。所以环形缓冲区的管理要按扇区来不能一条一条擦。代码里我用了一个简单的双扇区交替方案扇区A写满了写扇区B扇区B写满了擦除扇区A再写这样擦除次数减半寿命翻倍。实现起来不复杂但要注意掉电保护——写入过程中掉电可能导致数据损坏加一个CRC校验就能检测出来。6. 常见问题速查与避坑经验6.1 编译与烧录问题Keil5兼容C51和STM32是个老生常谈的问题。如果你同时装了C51和MDK可能会遇到芯片包冲突。解决办法是装两个不同版本的Keil分别放在不同目录或者用Keil5的Pack Installer单独安装STM32的芯片包。我现在的环境是Keil5 MDK加STM32F1的PackC51用另一台电脑省得折腾。STM32芯片包安装失败通常是网络问题可以离线下载Pack文件手动安装。Pack文件在Keil官网或者GitHub上都能找到下载后双击安装即可。如果提示版本不兼容检查Keil的版本号太老的版本可能不支持新的Pack。下载失败的原因很多SWD接线反了、BOOT0没下拉、芯片被锁了。先检查接线SWDIO和SWCLK不要接反GND一定要共地。如果芯片被读保护了用STM32CubeProgrammer解锁或者用FlyMcu擦除全片。BOOT0接高电平进Bootloader模式也能救回来。6.2 传感器与ADC问题HX711读数一直是0x800000说明DOUT一直高没有数据输出。检查传感器接线是否正确E和E-之间应该有1kΩ左右电阻A和A-之间也是。如果传感器坏了换一个。如果接线对了但还是没数据可能是HX711模块坏了换一个试试。读数跳动大先查电源。用示波器看VCC上的纹波如果超过50mV加电容或者换LDO。再查地线传感器地、HX711地、STM32地要单点共地不要形成环路。最后查机械传感器固定是否牢固秤台是否水平有没有风或者振动。线性度差通常是传感器质量问题。便宜的传感器线性度可能只有0.1%FS对应150kg就是±150g。如果要求高买好一点的传感器或者用多点校准加分段线性拟合。代码里我留了多点校准的接口最多支持5点用查表加插值的方式提高精度。6.3 显示与交互问题OLED显示乱码先检查I2C地址0x78和0x7A都试试。再检查初始化序列SSD1306的初始化命令比较多少一条都可能不显示。如果用的是SPI接口的OLED检查DC、RES、CS引脚是否接对。LCD1602显示不出字符对比度电位器要调调到能看到淡淡的方块为止。RW引脚接地表示只写接MCU表示可读可写如果只写的话RW接地就行。数据线D0-D7如果只用4位模式只需要接D4-D7代码也要相应改。按键不灵敏加10kΩ上拉到3.3V按键另一端接地。消抖时间20ms是经验值如果按键质量差可以加到50ms。如果用的是矩阵键盘注意行扫描和列扫描的时序避免鬼影。7. 个人实操体会与后续建议这个项目我从第一版到第三版前后花了大概两个月踩过的坑比代码行数还多。最大的体会是硬件项目三分靠代码七分靠调试。代码写对了只是第一步电源干不干净、地线有没有环路、机械安装有没有应力这些才是决定成败的关键。另一个体会是不要迷信仿真。Proteus仿真能验证逻辑但验证不了噪声、温漂、接触电阻这些真实世界的问题。我建议的流程是仿真验证逻辑面包板验证电路洞洞板验证稳定性最后才画PCB。每一步都确认没问题再往下走比一口气做完再调试效率高得多。后续如果想继续深入我建议三个方向一是加蓝牙或WiFi做成物联网体重秤二是加体脂测量做成体脂秤三是加历史记录和趋势分析做成健康管理终端。每个方向都有不少坑但也都有现成的方案可以参考。代码和原理图我都放在开源仓库里了有问题可以提Issue我看到会回。最后分享一个小技巧称重传感器的屏蔽线如果读数一直漂试试把屏蔽线从传感器端断开只接MCU端。很多时候漂移就是地环路引起的断开一端立刻就好。这个技巧帮我省了好几天调试时间希望对你也有用。
阅读完成 · 觉得有帮助?
咨询建站