简介:本资源是一套完整的基于STM32微控制器的智能电子称毕业设计实践包,面向嵌入式初学者、电子信息类本科生及毕设备赛学生,解决从硬件搭建、固件开发到文档撰写与答辩展示的一站式学习需求。压缩包共723个文件,含281个C源码与183个头文件(核心驱动与算法逻辑)、36个编译中间文件(.o/.d)、18个Arduino兼容.ino文件(便于模块验证)、16个Keil工程文件(.uvproj/.uvopt)、18个PDF技术文档(含原理图、流程图、软硬件分析报告)及6个Hex可执行镜像,整体容量39.61MB。已有950人下载学习,资源结构清晰,覆盖PCB设计、HX711传感器采集、OLED显示驱动、滤波校准算法实现等关键模块,并附带毕设答辩技巧、模块资料详解与完整项目报告模板,助读者快速掌握STM32嵌入式系统开发全流程。 做嵌入式这么多年,各种称重相关的需求遇到过不少,从简单的压力检测到工业料斗秤都碰过。但要说最适合入门、又能把整个链路跑通的,还得是“基于STM32的智能电子称”这种项目。它麻雀虽小五脏俱全,ADC采集、滤波算法、标定校准、人机交互全都有,做完一个,你对嵌入式开发的理解会上一个台阶。
这篇文章我就拿一个典型的STM32电子秤项目来拆,硬件上用的是最常见的STM32F103C8T6最小系统板,配合HX711称重ADC模块和5kg电阻应变式传感器,加上0.96寸OLED屏幕和几个按键,实现称重、去皮、单位切换、超重报警这些功能。无论你是准备做课程设计,还是想自己DIY一个桌面称,这套方案都能直接照着做,踩坑点我也会一并讲清楚。
1. 整体设计与方案选型
1.1 需求拆解:一个电子秤到底在解决什么问题
智能电子秤的核心本质,是把“物体的质量”这个物理量,经过一系列变换,最终变成屏幕上人能读懂的数值。这个链路拆开来看是四步:传感器把重量变成微弱的电阻变化,HX711把电阻变化转成数字信号,STM32读取数字信号并做软件处理,最后OLED把结果显示出来。
听起来简单,但每一步都有坑。传感器输出的差分信号只有毫伏级别,直接用STM32内置的12位ADC去采,分辨率根本不够。举个例子,5kg量程的传感器满量程输出大约是10mV(在5V激励下),如果直接用12位ADC在3.3V参考电压下采样,最小分辨率是3.3V/4096约等于0.8mV,换算成重量就是400g。也就是说你放个400g的东西,读数才会跳一下,这哪是电子秤,这是“电子猜测器”。
所以方案选型里最关键的一步,就是引入HX711这一颗24位高精度ADC芯片。它内部自带120倍可编程增益放大器,能把传感器输出的毫伏级信号放大到ADC能分辨的范围,还能直接输出数字信号给MCU读取。这才是整个项目“智能”二字的硬件基础。
1.2 为什么是STM32F103C8T6,而不是51或者Arduino
有些朋友可能会问,电子秤这种逻辑不复杂的项目,用STC89C52或者Arduino Uno不也行吗?确实能做,但我个人强烈建议用STM32F103C8T6,原因有三点。
第一,F103C8T6主频72MHz,Flash 64KB,RAM 20KB,这个资源量跑HX711驱动加OLED显示加滤波算法,CPU占用率连10%都不到。这意味着你后续想加蓝牙上传、加WiFi联网、加语音播报,都有充足的余量。用51的话,光是一个浮点运算滤波就要算半天,想扩展功能基本是奢望。
第二,STM32的生态太成熟了。HX711的驱动网上有一大堆参考,标准库和HAL库的例程都齐全。你碰到问题,搜索引擎一搜基本都有答案,学习成本其实比51更低。
第三,从学习角度讲,STM32是你从单片机向嵌入式Linux过渡的必经之路。寄存器操作、中断系统、定时器、DMA这些概念,在STM32上理解透了,以后玩RTOS、玩Zephyr都会顺手很多。用一个电子秤项目入门STM32,性价比非常高。
1.3 器件选型的细节:传感器和ADC的搭配逻辑
称重传感器选的是常见的电阻应变式半桥传感器,量程5kg。这种传感器在电子秤领域用得最广,淘宝上一搜一大把,四根线(红黑是激励,绿白是信号),用双孔悬臂梁结构,价格十几块钱。
选5kg这个量程是有讲究的。如果要做桌面称、厨房秤,5kg能覆盖绝大多数场景。而且传感器有个特性,在额定负载的20%到80%区间内线性度最好,你要是选个50kg的传感器去称200g的东西,精度反而不如5kg的。所以选传感器不是越大越好,要和你实际要称的物体重量匹配。
HX711模块选的是淘宝最常见的红板模块,板上自带稳压电路和8脚运放,卖家一般会配一个10uF和0.1uF的电容。这里有个细节要提醒:有些便宜的模块用的稳压芯片是78L05,纹波比较大,会导致读数跳变厉害。如果你发现装上后数据很不稳,优先检查模块供电。
2. 硬件核心细节与电路连接
2.1 HX711的工作原理与时序
HX711的工作原理,简单说就是一颗Δ-Σ(Delta-Sigma)型ADC,通过过采样和噪声整形技术,把高分辨率转换分摊到多次采样上。它有两个模拟输入通道A和B,A通道可以选128倍或64倍增益,B通道固定32倍增益。电子秤项目用的就是A通道128倍增益。
HX711和MCU之间用的是自定义的2线串行接口,只需要DOUT(数据输出)和SCLK(时钟输入)两根线。数据从高位到低位,一次24位,二进制补码格式。时序上有几个关键点:
- DOUT为高电平时表示ADC还在转换中,不能读数据
- DOUT拉低后,表示转换完成,数据准备好了
- 这时MCU给25个SCLK脉冲,前24个脉冲把24位数据移出来,第25个脉冲决定下一次转换的通道和增益(A通道128增益,就发1个脉冲到位后DOUT恢复高电平)
注意一个细节:如果发25个CLK是A通道128倍增益,如果发26个CLK就是B通道32倍增益,发27个CLK是A通道64倍增益。很多新手在这里搞错,导致读出来的数据完全不对。我建议固定用25个脉冲。
2.2 接线表与硬件连接实例
STM32和HX711、OLED、按键的接线我列了表,照着接就行。
| 模块 | 引脚 | STM32引脚 | 说明 |
|---|---|---|---|
| HX711 DOUT | PB0 | 数据输出 | 配置为浮空输入 |
| HX711 SCLK | PB1 | 时钟输入 | 配置为推挽输出 |
| OLED SDA | PB7 | I2C数据线 | 需接4.7k上拉 |
| OLED SCL | PB6 | I2C时钟线 | 需接4.7k上拉 |
| 按键1 | PA0 | 去皮/切换单位 | 接GND,内部上拉 |
| 按键2 | PA1 | 开关背光/校准 | 接GND,内部上拉 |
| 传感器红线 | HX711 E+ | 激励正极 | 接模块上的E+ |
| 传感器白线 | HX711 A- | 信号负极 | 接模块上的A- |
| 传感器绿线 | HX711 A+ | 信号正极 | 接模块上的A+ |
| 传感器黑线 | HX711 E- | 激励负极 | 接模块上的E- |
在这个接线图里,OLED用的是I2C,STM32的硬件I2C在F103C8T6上有时候会因为引脚配置不好而卡死,所以我这次干脆用软件模拟I2C,反正OLED显示数据量不大,软件模拟完全够用,还省心。
2.3 供电与PCB布局的关键注意事项
供电是整个电子秤最容易出问题的地方。HX711模块的VCC供电建议从STM32板子的5V引脚取,保证激励电压稳定。尽量避免和电机、舵机、继电器这类感性负载共用电源,否则电流突变会直接串到HX711的AVDD上,导致读数跳变。
如果后续要加蓝牙模块(比如HC-05,HC-08),要单独给蓝牙供电,并从电源输入端加一个100uF的电解电容和0.1uF的陶瓷电容去耦。我实测过,蓝牙发射瞬间电流能达到40-50mA,如果和传感器共用电源,称重数据会周期性跳变,一定要分开。
布局上还有一个容易被忽略的点:HX711模块和传感器之间的连线尽量短,最好用双绞线或者屏蔽线。传感器输出的差分信号幅度很小,只有几个毫伏,这时候线束就是天线,附近有电机、电源适配器,干扰会直接耦合进来。我做过一个实验,把传感器线从20cm加长到50cm,读数漂移从正负1g变成了正负8g,差距非常明显。
3. 软件核心实现与算法
3.1 HX711驱动:阻塞读取与超时保护
HX711的驱动代码并不复杂,但有几个细节要处理好。第一个是超时保护,DOUT等数据就绪的过程不能死等,万一传感器断线或者芯片没焊好,程序就会卡死在读取函数里。我一般在等待循环里加一个计数器,超过一定次数直接返回错误。
核心函数代码如下:
uint8_t HX711_Read(uint32_t *data) { uint32_t count = 0; uint8_t i; // 等待DOUT变低,表示数据就绪 while(HX711_DOUT_READ() == 1) { if(++count > 100000) { return 1; // 超时,返回错误 } } *data = 0; for(i = 0; i < 24; i++) { HX711_SCLK_HIGH(); delay_us(1); HX711_SCLK_LOW(); delay_us(1); *data = (*data << 1); if(HX711_DOUT_READ() == 1) { (*data)++; } } // 第25个脉冲,设置为A通道128倍增益 HX711_SCLK_HIGH(); delay_us(1); HX711_SCLK_LOW(); delay_us(1); // 转为有符号数 if(*data & 0x800000) { *data |= 0xFF000000; } return 0; }有几点需要强调:
delay_us建议用DWT(Data Watchpoint and Trace)来实现精确延时。HAL库的HAL_Delay最小粒度是1ms,直接用来做HX711时序会超时,我就在这里卡过很久。换成DWT之后可以用delay_us(1)精确微秒延时,问题迎刃而解。- 数据是24位二进制补码,如果最高位是1,说明是负数,要符号扩展到32位,否则后面计算会出错。
- 读取频率不要太高。HX711的RATE引脚接地时输出速率是10Hz,接高电平时是80Hz。称重场景下10Hz够用了,高采样率反而带来更多高频噪声。
3.2 数据滤波:滑动平均和中值滤波的组合
读取到的原始ADC值,即使是静态称重,也会有正负几克的波动,这是电阻应变片的噪声特性和HX711本身的量化噪声决定的。怎么把这部分波动压下来,是电子秤软件的灵魂。
我的方案是“中值滤波+滑动平均”二合一。先取5次原始读数,去掉最大值和最小值,剩下3个取平均,作为一次“净读数”。然后再对净读数做16次滑动平均。这套组合的好处是:中值滤波对付脉冲性干扰非常有效,滑动平均则能把白噪声平滑掉。代价是响应速度会慢一点点,但称重场景本来就不要求毫秒级响应。
滤波窗口的长度我自己调过,16次滑动平均在10Hz采样率下大约需要1.6秒才能稳定下来。如果你希望响应快一点,可以减到8次;但如果窗口太小,你会看到显示屏上的数字一直跳,观感很差。我建议是8到16之间取中间值。
3.3 标定算法与去皮功能的实现细节
标定是整个项目里最容易被忽略却又最关键的一步。HX711读出来的原始值本身没有物理意义,必须建立“原始值”和“重量”之间的线性关系。
标定的数学基础是$重量 = (原始值 - 零点值) / 斜率$。零点值就是空秤时滤波后的读数,斜率是单位重量对应的读数变化量。
具体操作流程:
- 空秤,读取100次滤波后的数据,取平均作为零点值zero。
- 放一个已知重量的砝码,比如200g标准砝码,读取滤波后的数据,记作value。
- 斜率 = (value - zero) / 200。
- 之后任意时刻的重量 = (当前读数 - zero) / slope。
这里我强烈建议,如果手头有多个砝码,比如100g、200g、500g,做多点标定再线性拟合,精度会更高。因为应变片传感器的线性度虽然在标称范围内很好,但边缘区间会有误差,多点拟合能把这部分误差平均掉。
去皮功能实现上要注意:去皮不是把零点值改了,而是维护一个皮重变量tare。显示重量 = (当前读数 - zero - tare) / slope。按下去皮键时,把当前读数记录为tare,之后所有重量减去这个皮重。如果把皮重写进zero,恢复出厂设置会麻烦得多。
float weight; uint32_t raw, filtered; float zero = 0, slope = 1, tare = 0; void Task_GetWeight(void) { if(HX711_Read(&raw) == 0) { filtered = Filter_Process(raw); weight = (filtered - zero - tare) / slope; if(weight < 0) weight = 0; if(weight > 5000) weight = 5000; // 超重限制 } }3.4 OLED显示与按键交互
显示部分我用的是0.96寸OLED,SSD1306驱动,128x64分辨率。显示内容规划了三屏:第一屏正常称重,显示大字重量和单位;第二屏显示原始ADC值,方便调试;第三屏显示电池电压或者环境温度(如果加了相应传感器)。
OLED显示有个小技巧:SSD1306是单色屏,刷屏比较慢,如果你用全屏刷新,肉眼会看到闪烁。正确做法是用局部刷新,只更新变化区域的缓冲区,再一次性推送到OLED的GRAM。重量数值每秒刷新10次就够,不需要跟着HX711的10Hz同步刷。
按键这里要处理防抖。最简单可靠的方案是“延时消抖+松手检测”:
void Key_Scan(void) { if(GPIO_ReadPin(KEY1_PORT, KEY1_PIN) == RESET) { delay_ms(10); // 延时消抖 if(GPIO_ReadPin(KEY1_PORT, KEY1_PIN) == RESET) { Tare_Process(); } while(GPIO_ReadPin(KEY1_PORT, KEY1_PIN) == RESET); // 等松手 } }这个处理比只做一次消抖靠谱得多,因为如果你不做“等松手”,一次按键可能触发好几次去皮,用户体验会很差。
4. 完整的实操复现与调试流程
4.1 从零搭建工程:环境准备与代码烧录
开发环境我建议直接用Keil MDK5,配合STM32CubeMX做初始化代码生成。有些朋友喜欢用VSCode + PlatformIO或者用STM32CubeIDE,但Keil在中文社区的资料最多,出了问题最好查。
具体步骤:
- 用STM32CubeMX新建工程,选STM32F103C8T6。
- 配置时钟:外部晶振8MHz,经PLL倍频到72MHz。
- 配置PB0为GPIO_Input,PB1为GPIO_Output,PA0、PA1为GPIO_Input。
- 配置I2C1为软件模拟,先不初始化硬件I2C。
- 生成代码后,把HX711_Read、OLED驱动、滤波算法分别写成独立的
.c和.h文件。
烧录用ST-Link V2,SWD模式四线(SWDIO、SWCLK、GND、3.3V)。这里有个老生常谈的坑:STM32的PA13和PA14默认是SWD的下载引脚,如果你把这两个引脚初始化成普通GPIO,会导致第二次下载失败。解决办法是把PA13、PA14的GPIO配置改为复用功能,或者在CubeMX里把SWD使能打开。
4.2 标定的标准化流程和记录表
标定看似简单,但操作不规范会导致数据雪崩。我把自己的标定流程分享出来:
- 硬件上电后等待30秒,让传感器和HX711模块充分热机,再进行标定。我实测过,冷启动后前10秒读数会漂移最多2-3g,热机后漂移能降到0.5g以内。
- 空秤清零,读取100次滤波数据,取平均记入zero。
- 依次放上100g、200g、500g砝码,记录每个点的滤波读数。
- 用最小二乘法拟合斜率和截距,写入Flash保存。
日志表可以这样记:
| 砝码质量(g) | 滤波后ADC值 | 增量(ADC/g) |
|---|---|---|
| 0 | 8312250 | - |
| 100 | 8332250 | 200 |
| 200 | 8352250 | 200 |
| 500 | 8412250 | 200 |
如果增量值在100g、200g、500g三个点上基本一致,说明传感器线性度很好,直接用斜率=200就行。如果三个点增量差别超过2%,说明传感器本身线性度一般,建议做多点分段拟合,每段的斜率独立存储。
标定参数保存建议写到STM32内部Flash的最后一个扇区,用两个备份,写之前先擦除,防止突然断电导致参数丢失。
4.3 整机调试验证:如何判断精度达标
硬件和软件都做完后,进入整机调试阶段。我一般按这五步走:
第一步,空秤稳定后观察读数,连续记录5分钟,看最大漂移量。如果超过正负1g,说明滤波参数或硬件供电还有问题。
第二步,放100g砝码,稳定2秒后读数,重复10次,计算最大值和最小值的差。这个值体现了重复性,好的系统应该在正负0.5g以内。
第三步,分别放1g、5g、10g、50g的小物体,看显示值是否卡在整数上不跳动。这反映了分辨率和噪声水平的综合指标。
第四步,用手轻压传感器支架,松开后看回零速度。如果5秒内回到零点,说明机械结构没问题;如果回不到零,说明传感器支架有卡滞或螺丝松了。
第五步,连续称重2小时后看温漂情况。手摸传感器外壳,如果发热明显,读数会往一个方向缓慢偏移,这种是激励电压偏高导致的,可以在HX711的AVDD输出端并联一个100uF的电解电容缓解。
这一套流程走下来,你的电子秤的精度和稳定性大致能心里有数。
5. 常见问题与排查技巧实录
5.1 HX711读数异常:全是0x800000怎么办
这是最多人遇到的问题。0x800000这个值相当于ADC输出的负满量程,出现这个情况基本可以断定是HX711没有和传感器正确通信。排查思路按优先级排列:
- 先查DOUT引脚电平。如果DOUT一直为高,说明HX711没就绪,最可能是供电不对或者传感器没接。量一下HX711模块的VCC有没有稳定5V,AVDD有没有2.5V左右的电压。
- 再查SCLK引脚有没有正确拉高拉低。可以在SCLK输出一个10Hz的方波,用示波器或万用表量,如果一直不变,检查STM32的GPIO配置是不是被复用成其他外设了。
- 最后查接线顺序。传感器红黑是激励,白绿是信号,反接会导致HX711读出来的数据符号反了,或者完全读不到。
如果你用的是USB转TTL供电给STM32,注意USB口的电流输出能力一般只有500mA,但HX711模块加OLED加蓝牙,电流能到200mA左右,如果供电线过细,压降会导致HX711工作不稳定,读出来的数据会周期性跳变。
5.2 称重数据跳变不稳定,如何排查
数据跳变的原因比较复杂,我整理了一个排查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 数据在正负10g以上跳变 | 供电纹波大,或者传感器线过长 | 换独立稳压源,缩短传感器线,用屏蔽线 |
| 数据周期性突变 | 有蓝牙/WiFi模块在发射 | 给无线模块单独供电,摄像头,加滤波电容 |
| 数据缓慢漂移 | 传感器没有完全热机 | 上电后等30秒再校准 |
| 读数在某个重量附近反复跳 | 示波器测量点引入了地回路 | 确保所有设备共地,且地线不开环 |
| 用手靠近时数据变化 | 人体感应干扰 | 给HX711和传感器加金属屏蔽罩并接地 |
我对“用手靠近时数据变化”这个现象多说一句,在冬天干燥环境下尤其明显。人体静电叠加到传感器差分信号上,会把ADC读数抬高。解决方式是给传感器加个金属屏蔽罩,并让屏蔽罩接地,同时在HX711的A+和A-引脚之间并联一个100pF的瓷片电容,滤掉高频静电分量。
5.3 标定后不准:砝码放上去显示不对
标定后不准,除了标定流程出错,还有一个经常被忽略的原因:传感器安装的机械结构有问题。电阻应变式传感器对安装要求很高,底座和承载面必须水平,固定螺丝必须拧紧。如果支架有晃动,导致传感器受力点不在设计位置,即使标定正确,实际称重也会有偏差。
我之前做过一个反面教材:传感器支架是用亚克力板加双面胶固定的,标定时读数正常,但实际称重误差超过5%。后来换成螺丝固定后,误差立刻降到1%以内。所以如果你发现标定数据是对的、滤波也没问题、但读数就是不对,一定要检查机械固定的可靠性。
关于标定的另一个细节:传感器在出厂时一般会标称额定输出,比如5kg的传感器额定输出是1.0mV/V,配合HX711的128倍增益,理论上满量程ADC增量大约是:激励电压5V × 1.0mV/V × 128 = 640mV,对应24位ADC在5V参考下大约536万多一点的计数。如果你标定出来的斜率偏离这个数量级太多,比如只有理论值的一半,说明传感器或者HX711有异常,先别急着继续调试,先检查硬件。
5.4 下载调试烧录:ST-Link连接失败的典型原因
最后补充一个嵌入式开发通用的坑。很多新手第一次给STM32F103C8T6下载程序时,会遇到“No target connected”或者“Internal command error”的报错。常见原因有三个:
第一,BOOT0引脚被拉高。F103的BOOT0拉高后芯片会进入ISP模式,SWD调试是连不上的。正常运行时BOOT0要接低电平。
第二,PA13和PA14被代码占用。如果你在代码里把PA13和PA14配置成了普通推挽输出,就会覆盖掉SWD功能。解决办法是程序里避免操作这两个引脚,或者在初始化前面加上__HAL_AFIO_REMAP_SWJ_NOJTAG();关闭JTAG只保留SWD。
第三,供电不足导致芯片复位循环。STM32F103C8T6在运行状态下电流约30-50mA,如果USB线电阻大,电压掉到2.8V以下,芯片会反复重启,SWD肯定连不上。量一下3.3V引脚的实际电压,低于3.0V就换个供电方案。
6. 扩展方向:如何把电子秤做“智能”
6.1 增加蓝牙BLE传输与手机App联动
做完基础版电子秤之后,最自然的升级方向是加蓝牙。HC-08模块(BLE 4.0)和STM32用串口通信非常简单,发送一个字节的帧协议就能把重量数据推给手机。
我建议帧格式定成这样:帧头0xAA,数据长度,重量高字节,重量低字节,校验字节。手机端手机可以用“蓝牙调试助手”这类现成App先调试,联通了再自己写Android或微信小程序的界面。
这里要注意蓝牙模块的波特率,HC-08默认是9600,但有些模块配置是115200。如果你发现发出来的数据是乱码,优先检查波特率。另外一个坑是HC-08的TXD和RXD要交叉接STM32的RXD和TXD,不要接成直连。
6.2 加入温度补偿提升精度
电阻应变片的灵敏度会随温度变化,这是一般电子秤精度做不高的原因之一。如果想把精度从正负1g提升到正负0.2g级别,需要加入温度补偿。
方案是加一个DS18B20数字温度传感器,贴到传感器金属表面,每10秒读一次温度。标定时记录不同温度下的斜率,做成一个查表数组:比如25度时斜率是200.0,30度时斜率是199.0。实际称重时根据当前温度插值出实际斜率。
我试过这个方案,温度补偿后,从冷启动到热稳定(传感器温度变化10度)的过程中,读数漂移能从正负3g降到正负0.5g左右,效果非常明显。代价只是代码里多一个温度传感器驱动和几十行插值代码,性价比很高。
6.3 用FreeRTOS重构软件框架
当你在这个项目上加入了蓝牙、温度补偿、按键菜单、OLED多页面显示之后,裸机while循环会变得越来越乱。这时候可以考虑把整个系统跑在FreeRTOS上。
任务划分建议:HX711读取任务(优先级高,10ms周期)、滤波和重量计算任务(50ms周期)、OLED刷新任务(100ms周期)、蓝牙上报任务(500ms周期)、按键扫描任务(20ms周期)。任务之间用队列传递重量值。
在STM32F103C8T6上跑FreeRTOS完全没有压力,RAM 20KB绰绰有余。我第一次把裸机程序重构成RTOS版本后,最大的感受是逻辑清晰了太多,每一块功能都在自己独立的任务里,再也不用在while(1)里到处加状态标志位。
最后再分享一个我自己踩过很多次坑的经验:电子秤这类项目,硬件机械结构的可靠性,往往比软件算法更能决定最终精度。软件滤波做得再好,如果传感器安装不稳固、供电纹波大,出来的数据还是废的。所以调试顺序一定是先保证硬件稳定,再调软件,别本末倒置,不然你会浪费大量时间在一个根本没焊牢的连接器上。
本文还有配套的精品资源,点击获取