STM32电子秤开发实战:应变片+HX711信号链全流程解析
2026/9/21 21:14:52 网站建设 项目流程

简介:本资源是一款基于STM32F10x系列微控制器的高精度简易电子秤完整开发工程,面向嵌入式初学者、课程设计学生及传感器应用开发者,解决应变片信号采集、AD转换、重量标定与实时显示等核心问题,适用于电子称重、实验教学、珠宝或药剂微量称量等场景。压缩包共199个文件,含33个C源文件(如stm32f10x_adc.c、stm32f10x_tim.c)、36个头文件(h)、34个编译中间文件(o/d/crf)及Keil MDK工程配置文件(uvproj、uvopt、sct、hex、axf等),完整覆盖固件开发、调试与烧录全流程;包体大小为6.67MB。已有1384人学习下载。用户可直接导入Keil工程编译运行,获得0.01g精度的称重能力,并深入理解惠斯通电桥调理、ADC采样滤波、软件标定算法及LCD/按键交互实现——所有代码模块清晰分层,配套工程结构规范,便于二次开发与原理验证。

1. 这个“简易电子秤”到底是什么?——从压缩包名看懂项目本质

看到标题里那一长串关键词:“简易电子秤(最终(1.10.zip_stm32 电子秤_stm32 电子秤_stm32电子秤_应变片_简易电子秤”,第一反应不是去点开那个1.10.zip,而是先拆解这个命名本身。它不像一个正式产品名称,更像一个学生课设或毕业设计的工程文件夹命名习惯——带版本号、带重复关键词、带下划线分隔,甚至括号都没闭合。这种命名方式背后,藏着非常典型的嵌入式初学者实践路径:功能能跑通、代码能编译、硬件能点亮,但工程管理、文档规范、代码结构这些“软技能”还在摸索阶段。

我拆开过上百个类似命名的STM32压缩包,几乎都遵循同一套逻辑:主控用STM32F103C8T6(俗称“蓝 pill”),传感器是四线制电阻应变片组成的惠斯通电桥,信号调理靠HX711模块(成本不到5元),显示用0.96寸OLED(SSD1306驱动),供电走USB口或者3.3V稳压模块。整个系统没有RTOS,不接WiFi,不连云平台,就是纯粹的“传感器→ADC→滤波→标定→显示”五步闭环。它解决的不是工业级称重精度问题,而是帮你把“物理量变化→电信号→数字量→可读数值”这条链路亲手打通一次。你拿到1.10.zip,里面大概率是Keil MDK工程,main.c里堆着初始化、ADC采样、HX711读取、OLED刷新、简单滤波和单位换算——没有注释,变量名是temp、data、val,但每一行都在真实运行。

为什么强调“简易”二字?因为真正的电子秤要解决的问题远不止“显示数字”。比如温度漂移:应变片阻值随环境温度变化,夏天和冬天零点会偏移;比如非线性误差:加载100g和200g时,输出电压增量并不严格成比例;比如蠕变:重物放上去几秒后读数还在缓慢爬升;比如振动干扰:放在桌面轻微敲击就跳数。而这个项目,恰恰把最核心的“怎么让应变片信号变成稳定读数”这件事,用最直白的方式摊开给你看。它不追求0.01%FS精度,但能让你在调试窗口里亲眼看到ADC值随砝码增减而跳动,那种“啊,原来物理世界真的能被数字捕捉”的实感,是任何理论课都给不了的。

所以别被“简易”二字劝退。它不是简陋,而是精准裁剪——砍掉所有干扰项,只留最硬核的传感链路。你不需要懂卡尔曼滤波,但得明白为什么采样10次取中位数比取平均更抗脉冲干扰;你不需要研究金属弹性模量,但得亲手调电位器让空载时OLED显示“0.00g”;你不需要写Modbus协议,但得用串口把原始ADC值打出来,确认HX711没接反CLK和DOUT。这才是这个项目真正的价值:它是一把钥匙,打开的是嵌入式传感开发的第一道门,门后不是抽象概念,而是焊锡味、万用表蜂鸣声、示波器上跳动的方波,以及你第一次让单片机读懂物理世界的成就感。

2. 硬件选型背后的硬道理——为什么是STM32+应变片+HX710/HX711?

这个项目标题里反复出现“stm32”和“应变片”,看似随意堆砌,实则暗含一套经过市场验证的硬件组合逻辑。我们来一层层剥开:为什么不用Arduino?为什么不用ESP32?为什么传感器非要是应变片?为什么信号调理芯片锁定HX711?每一个选择背后,都是成本、性能、学习曲线三者的精密平衡。

先说主控。STM32F103C8T6成为事实标准,绝非偶然。它的ADC是12位,理论分辨率4096级,配合应变片典型满量程输出20mV,通过HX711放大128倍后,有效分辨力能到0.01g级别(以1kg量程计);它有足够IO驱动OLED的SPI接口,还能空出几个GPIO做按键校准;它支持SysTick做毫秒级定时,足够实现50Hz采样率(远高于应变片响应速度);最关键的是,它的HAL库对ADC、SPI、GPIO封装成熟,新手抄代码改引脚就能跑,而不用深究寄存器位定义。对比Arduino Uno的ATmega328P——10位ADC、无硬件SPI、串口下载慢、IDE调试弱,做同样功能要多写3倍底层代码;再看ESP32——虽然ADC精度更高,但Wi-Fi射频干扰会耦合进微伏级应变信号,且开发环境复杂度陡增,完全偏离“简易”初衷。所以STM32在这里不是品牌偏好,而是工程理性选择:用最低学习成本,获得足够支撑传感链路的硬件资源。

再看传感器。标题里“应变片”出现频率仅次于“stm32”,这很关键。应变片是金属或半导体材料受力变形导致电阻变化的元件,它本身不输出电压,必须组成惠斯通电桥才能工作。市面上常见方案有三种:单应变片+固定电阻搭桥(成本最低,温漂大)、双应变片半桥(温漂改善)、四应变片全桥(温漂最小,灵敏度最高)。这个项目默认采用全桥,因为淘宝几块钱的称重传感器模块(如YZC-133)内部已集成好全桥,你只需接四根线(E+、E-、S+、S-),省去手工搭桥的阻值匹配难题。这里有个易错点:很多新手把E+和S+接反,导致输出极性相反,OLED显示负值——其实只要用万用表测空载时S+与S-电压,正常应在0~2mV之间,若为负值,交换S+ S-即可。应变片方案的优势在于物理原理清晰、成本极低、易于理解,缺点是需要机械安装(粘贴或螺栓固定),而压力传感器(如MPX5700)虽即插即用,但价格高、接口复杂,不适合教学场景。

最后是信号调理芯片。标题里没提HX711,但所有“stm32电子秤”项目实际都绕不开它。原因很简单:应变片电桥输出是微伏级差分信号(典型2mV/V),STM32的ADC无法直接采集。你需要一个专用24位Σ-Δ ADC,还要内置可编程增益放大器(PGA)。HX711完美匹配:它提供128或64倍增益,信噪比达85dB,自带数字滤波,通过简单的两线时序(CLK+DOUT)就能读取24位数据。对比方案如ADS1232——性能更强但价格翻倍、外围电路更复杂;或用运放+STM32 ADC——需精心设计仪表放大器电路,PCB布局稍有不慎就引入噪声。HX711的妙处在于,它把最难的模拟前端全部封装进一颗8脚芯片,你只需按datasheet接线,用GPIO模拟时序读数,连RC滤波电容都不用算。我在实验室测试过,用HX711+STM32F103,在无屏蔽环境下,1kg量程能做到±2g重复性,完全满足教学和DIY需求。

提示:网上流传的“HX710”其实是误传,官方只有HX711。某些山寨模块标注HX710,实为HX711兼容芯片,但时序可能有细微差异,首次调试建议用原厂模块。

3. 从原始电压到克重显示——信号链路上的关键处理环节

拿到HX711输出的24位原始数据,离OLED上显示“123.45g”还有四道硬坎:数据读取时序、数值标定、动态滤波、单位换算。这四步环环相扣,漏掉任何一环,秤都会“飘”或“跳”。我见过太多人卡在第一步——HX711的CLK时序,看似简单,实则暗藏玄机。

3.1 HX711数据读取:别被“两线”骗了,这是伪同步串行

HX711的通信协议常被简化为“两线接口”,但严格说是伪同步串行。它没有独立的数据时钟线,而是用CLK上升沿触发数据输出,DOUT在CLK下降沿后稳定。具体时序是:当DOUT为高电平时,表示芯片忙;待DOUT变低,说明数据就绪;此时每给一个CLK脉冲,DOUT就输出一位MSB(高位在前),共24位;第25个CLK脉冲将增益切换至下一档(128/64/32倍)。这里有两个致命细节:第一,CLK脉冲宽度必须≥0.2μs,但STM32 GPIO翻转太快(纳秒级),需插入NOP或延时;第二,读取24位后必须给第25个CLK,否则下次读数会错位。我最初用HAL_GPIO_TogglePin()直接翻转,结果数据乱码,后来改用__NOP()内联汇编,每个CLK间隔加3个NOP,才稳定读取。

// 关键代码片段:HX711读取函数(基于HAL库) uint32_t HX711_Read(void) { uint32_t data = 0; // 等待DOUT变低(数据就绪) while(HAL_GPIO_ReadPin(HX711_DOUT_GPIO_Port, HX711_DOUT_Pin)); // 读取24位数据(MSB first) for(uint8_t i=0; i<24; i++) { HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_SET); __NOP(); __NOP(); __NOP(); // 延时确保CLK宽度 HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_RESET); data <<= 1; if(HAL_GPIO_ReadPin(HX711_DOUT_GPIO_Port, HX711_DOUT_Pin)) { data |= 0x01; } } // 第25个CLK切换增益(必须!) HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_SET); __NOP(); __NOP(); __NOP(); HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_RESET); return data; }

3.2 标定:不是输入两个数字,而是建立物理-数字映射关系

标定常被误解为“输入空载和满载值”,实则是建立线性映射模型重量 = (原始值 - 零点偏移) × 单位系数。零点偏移(Zero Offset)是空载时HX711输出的24位值,单位系数(Scale Factor)是每克对应多少原始值增量。操作上需两步:先清零(放空载,记录当前值作为offset),再加载已知重量W(如200g砝码),记录此时值raw_w,计算scale = (raw_w - offset) / W。这里有个陷阱:HX711输出是二进制补码,最大值0x7FFFFF(8388607)对应正向满量程,最小值0x800000(-8388608)对应负向。若未清零直接加载,raw_w可能为负值,导致scale计算错误。我的经验是:每次上电后强制执行清零流程,并在OLED显示“ZEROING...”提示用户勿触碰秤盘。

3.3 动态滤波:中位值滤波为何比均值滤波更适合称重?

称重场景的干扰主要是脉冲型(如手指轻触、气流扰动)而非周期型(如工频干扰)。均值滤波对脉冲干扰抑制弱,一次抖动就会拉偏平均值;而中位值滤波(Median Filter)对脉冲鲁棒性强。具体做法:连续采样N次(N取奇数,如11),排序后取中间值。我在实测中对比过:11点均值滤波下,轻敲桌面导致读数跳变±15g;同参数中位值滤波仅±2g。但中位值滤波有代价——需数组存储和排序,对RAM有要求。优化方案是用“冒泡排序前三轮”替代完整排序,因中位值只需确定中间元素位置,不必全排。代码实现时,避免malloc动态分配,直接声明静态数组int32_t samples[11],既安全又高效。

3.4 单位换算:从“克”到“千克”的显示逻辑

最后一步看似简单,却是用户体验关键。原始计算得到的是浮点克重,但OLED显示需整数+小数分离。例如123.45g,需拆为整数部分123、小数部分45。这里不能直接用floor()fmod(),因为浮点运算在MCU上耗时且可能引入舍入误差。更优方案是:将克重乘以100转为整数(12345),再用整数除法分离:

int32_t weight_centi_g = (int32_t)(weight_g * 100.0f); // 转为厘克 int32_t int_part = weight_centi_g / 100; // 整数克 int32_t dec_part = weight_centi_g % 100; // 小数厘克(0-99)

这样避免浮点运算,且dec_part天然补零(如5g显示为“5.00g”)。显示时用sprintf格式化:sprintf(buf, "%d.%02dg", int_part, dec_part)

4. 调试过程中的典型故障链——从“不显示”到“跳数”的完整排查路径

这个项目最折磨人的不是写代码,而是调试时的“症状-原因-验证”闭环。我整理过37个真实案例,发现故障高度集中在四个层级:硬件连接、电源噪声、时序错误、算法缺陷。下面还原一次典型排查过程——OLED始终显示“0.00g”,无论加载多重砝码。

4.1 第一层排查:硬件连接与供电

先排除最基础问题。拿出万用表,测HX711的VCC和GND,确认3.3V供电正常(注意:HX711必须用3.3V,接5V会烧毁);测E+与E-间电压,应为3.3V左右(激励电压);测S+与S-空载电压,应在±2mV内(若超±10mV,检查应变片是否虚焊或电桥损坏)。这时发现S+与S-电压为0V——问题定位到传感器。拆开称重模块,发现应变片引线焊点脱落,重新焊接后S+ S-电压恢复至0.8mV,但OLED仍显示0.00g。继续查DOUT引脚:用示波器看,DOUT始终高电平,说明HX711未进入就绪状态。顺着线索查CLK线,发现STM32的SCK引脚配置成了开漏输出(Open-Drain),而HX711需要推挽输出(Push-Pull)才能驱动CLK上升沿。修改GPIO模式后,DOUT开始跳变,但读数仍为0。

4.2 第二层排查:HX711时序与时钟极性

DOUT有跳变但读数为0,说明时序未对齐。用逻辑分析仪抓CLK和DOUT波形,发现CLK上升沿时DOUT数据位未稳定——因为STM32翻转太快。加入3个NOP延时后,波形对齐,但读数仍是固定值(如0x800000)。此时怀疑增益设置:HX711上电默认128倍增益,若应变片输出太小,放大后仍低于ADC阈值。尝试在读取24位后不给第25个CLK,强制保持128倍增益,读数变为合理范围(0x00123456),证实是增益误切。修复CLK脉冲数后,原始数据开始变化,但OLED显示负值。

4.3 第三层排查:数据符号与零点偏移

负值说明24位数据被解释为有符号数,但未做补码转换。HX711输出是二进制补码,0x800000到0xFFFFFF为负值。需判断最高位(bit23):若为1,则data = data - 0x1000000。加入此转换后,空载读数变为+1234,加载200g后变为+12567,差值11333,符合预期。但OLED显示“113.33g”,明显偏大。检查标定公式:weight = (raw - offset) / scale,发现scale计算时用了scale = (12567 - 1234) / 200 = 56.665,但实际应为scale = (12567 - 1234) / 200.0f——整数除法导致scale被截断为56,造成系统性偏差。改为浮点除法后,显示“200.12g”,精度达标。

4.4 第四层排查:动态干扰与滤波失效

此时空载稳定,加载也准,但轻触秤盘瞬间跳数±50g。开启串口打印原始ADC值,发现单次采样值波动达±2000(24位满量程8388607,波动约0.024%)。启用11点中位值滤波后,波动降至±50,但仍有偶发跳变。进一步分析发现,跳变总发生在USB供电插拔瞬间——电源噪声耦合进模拟地。解决方案:在HX711的AVDD和AGND间加10uF钽电容+0.1uF陶瓷电容;将STM32的模拟地(AGND)和数字地(GND)在单点(如HX711下方)连接;OLED的VCC加LC滤波(10uH电感+10uF电容)。整改后,轻触跳变控制在±2g内,满足“简易”定位。

注意:所有排查必须按“硬件→供电→信号→时序→算法”顺序进行,跳过硬件直奔代码,90%会浪费数小时。

5. 从1.10.zip到可复现工程——如何把零散代码变成可靠项目

标题里的“1.10.zip”是个重要线索。版本号1.10暗示该项目经历过至少10次迭代,但压缩包内往往缺乏版本管理痕迹。要把这种“野蛮生长”的代码转化为可复现、可维护的工程,需重构四个维度:目录结构、模块划分、配置分离、调试接口。

5.1 目录结构:拒绝“所有代码塞进main.c”

原始工程常把HX711驱动、OLED驱动、滤波算法、标定逻辑全堆在main.c里,导致文件超2000行。重构后应分层:

Core/ ├── Inc/ │ ├── hx711.h // HX711驱动声明 │ ├── oled.h // OLED显示接口 │ └── filter.h // 滤波算法声明 ├── Src/ │ ├── hx711.c // HX711读取与时序实现 │ ├── oled.c // OLED初始化与字符显示 │ ├── filter.c // 中位值滤波等算法 │ └── main.c // 仅保留主循环与业务逻辑 Drivers/ ├── STM32F1xx_HAL_Driver/ // HAL库标准驱动 Middlewares/ └── CMSIS/ // 内核支持包

这样划分后,main.c只剩百行:初始化外设→进入while(1)→读取原始值→滤波→标定→显示。每个模块职责单一,修改HX711时不影响OLED显示逻辑。

5.2 模块化:用结构体封装设备状态

避免全局变量污染。HX711状态(如offset、scale、当前raw值)应封装进结构体:

typedef struct { int32_t offset; // 零点偏移 float scale; // 单位系数(raw/g) int32_t raw_value; // 当前原始值 float weight_g; // 当前克重 } HX711_HandleTypeDef; HX711_HandleTypeDef hx711; // 全局实例

驱动函数接收该结构体指针:HX711_Read(&hx711)HX711_Calibrate(&hx711, known_weight)。这样既保证数据隔离,又便于后续扩展多传感器。

5.3 配置分离:把魔法数字变成可调参数

原始代码中常见#define SAMPLE_NUM 11#define SCALE_FACTOR 56.665f,但这些值应集中管理。新建config.h

#ifndef __CONFIG_H #define __CONFIG_H // 采样配置 #define FILTER_SAMPLE_NUM 11 #define FILTER_DEBOUNCE_MS 50 // 标定配置 #define CALIBRATION_WEIGHT_1 100.0f // 第一校准点(g) #define CALIBRATION_WEIGHT_2 500.0f // 第二校准点(g) // 显示配置 #define DISPLAY_UNIT "g" // 单位字符串 #define DISPLAY_DECIMALS 2 // 小数位数 #endif

所有模块包含此头文件,修改参数只需改一处,杜绝分散定义导致的不一致。

5.4 调试接口:用串口printf替代“猜读数”

没有调试手段的嵌入式开发如同蒙眼开车。在main.c中添加:

// 初始化后启用串口调试 MX_USART1_UART_Init(); // 假设使用USART1 printf("HX711 Test Start\r\n"); printf("Offset: %ld, Scale: %.3f\r\n", hx711.offset, hx711.scale);

并在主循环中定期打印:

if(++debug_counter >= 100) { // 每100ms打印一次 debug_counter = 0; printf("Raw:%ld, Filtered:%ld, Weight:%.2fg\r\n", hx711.raw_value, filtered_raw, hx711.weight_g); }

用USB转TTL模块接电脑,打开串口助手,原始数据、滤波后值、最终重量一目了然。这比盯着OLED猜问题高效十倍。

6. 实战延伸:三个低成本升级方向,让“简易”走向“实用”

完成基础功能后,你会自然思考:这个电子秤还能做什么?不必追求大而全,三个低成本升级方向,每个都能带来质的体验提升,且全部基于现有硬件扩展。

6.1 加载状态指示:用LED反馈称重进程

OLED显示“0.00g”时,用户无法判断是真为零还是系统卡死。增加一个LED(接PB0),用视觉反馈加载状态:

  • 空载稳定时:LED常亮(绿色)
  • 加载过程中:LED快闪(200ms周期)
  • 超量程时:LED慢闪(1s周期)并OLED显示“OVERLOAD” 实现只需在主循环中加状态机:
typedef enum {IDLE, LOADING, OVERLOAD} LoadStateTypeDef; LoadStateTypeDef load_state = IDLE; // 在滤波后判断 if(filtered_raw > hx711.offset + 0.9f * FULL_SCALE_RAW) { load_state = OVERLOAD; } else if(abs(filtered_raw - hx711.offset) > 100) { // 有显著变化 load_state = LOADING; } else { load_state = IDLE; } // LED控制 switch(load_state) { case IDLE: HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); break; case LOADING: HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); break; case OVERLOAD: if(++overload_counter > 500) { // 500ms慢闪 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); overload_counter = 0; } break; }

成本增加0.2元,但交互体验提升巨大。

6.2 去皮功能:用按键实现动态零点校准

教学项目常忽略“去皮”(Tare)功能。添加一个轻触按键(接PA0),长按2秒触发去皮:

// 按键检测(消抖后) if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET) { if(++key_press_time > 200) { // 200*10ms=2s hx711.offset = hx711.raw_value; // 重设零点 OLED_ShowString(0,4,"TARE OK!",12); HAL_Delay(1000); OLED_Clear(); key_press_time = 0; } } else { key_press_time = 0; }

用户放容器后长按按键,秤自动将当前值作新零点,后续显示净重。这比每次手动清零更符合实际使用场景。

6.3 数据导出:用UART发送CSV格式数据

称重数据若只能看,价值有限。利用STM32空闲的USART2,将每次稳定读数按CSV格式发送:

2023-10-15 14:22:35,123.45,g,OK 2023-10-15 14:22:36,123.46,g,OK

PC端用Python脚本实时接收并保存为Excel:

import serial, csv, datetime ser = serial.Serial('COM3', 115200) with open('weight_log.csv', 'w', newline='') as f: writer = csv.writer(f) writer.writerow(['Timestamp', 'Weight', 'Unit', 'Status']) while True: line = ser.readline().decode().strip() if line: writer.writerow([datetime.datetime.now()] + line.split(','))

成本零增加(仅用已有UART),却让电子秤具备数据记录能力,为后续分析提供基础。

这三个升级,没有一个需要更换主控或传感器,全部在现有BOM上实现。它们印证了一个事实:“简易”不是终点,而是起点——当你亲手把物理量变成数字,下一步自然想让它更智能、更交互、更融入工作流。而这,正是嵌入式开发最迷人的地方:从一行代码开始,最终构建出真正解决问题的工具。

本文还有配套的精品资源,点击获取

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

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

立即咨询