简介:本资源是一套完整的基于STM32的智能电子称毕业设计实践套件,面向嵌入式初学者、电子信息类本科生及毕业设计学生,解决从硬件搭建、固件开发到文档撰写与答辩展示的一站式学习需求。压缩包共723个文件,含281个C源码与183个头文件(构成主控逻辑、AD采集、HX711传感器驱动、OLED显示等核心模块),36个编译中间文件(.o/.d)、18个PDF技术文档(含原理图、PCB、报告、答辩技巧)、16个Keil工程文件(.uvproj/.uvopt)及多份流程图与软件分析资料,整体大小为39.61MB。已有950人学习下载,资源结构清晰,覆盖“硬件设计—程序实现—系统调试—成果输出”全链路,特别包含毕设报告模板、答辩话术要点及模块级技术参数说明,可直接用于课程设计复现或毕业课题落地。 从毕设题目里拿到一个"基于stm32的智能电子称.rar"压缩包,展开一看,里面有工程文件、原理图、参考论文,但真正要从零把这个项目吃透、改到自己能跑起来,不少同学第一反应是"这咋弄"。其实智能电子秤这个题目放在STM32项目里,属于典型的外设组合练手项目——能覆盖ADC采集、传感器驱动、按键状态机、显示刷新、甚至低功耗设计,几乎把AMC基础模块全串起来了。这篇文章我就从硬件选型、信号链路、软件架构到调试经验,把这个项目完整拆开,讲清楚每一步为什么这么做,以及我在实际调试中踩过的坑。
先说结论:这个项目用到的核心外设没有一个是高难度的,它考验的不是单点技术,而是你怎么把各个模块可靠地捏在一起。电子秤的核心难点在模拟信号的稳定性,而不是单片机本身。如果信号漂、零点乱飘、称重不准,多半不是STM32的问题,而是电路和机械结构层面的问题。所以这篇文章的重点会比较偏"硬件工程化"和"信号链路"的分析,代码反而排在后面。
1. 为什么选STM32做电子秤:从方案对比看选型逻辑
1.1 三个候选方案的优缺点对比
电子秤的"大脑"常见有三类方案:专用称重芯片方案(比如海力士的HX711直接带MCU)、通用MCU方案(STM32、GD32等)、以及带计量认证的SOC方案。刚拿到题目的同学可能觉得HX711才是核心,但其实HX711只是一颗24位ADC前端芯片,它后端接MCU才能完成显示、按键、逻辑控制。所以真正要选择的是MCU平台。
把三个方案摆在一起看更直观:
| 方案 | 成本 | 开发难度 | 精度上限 | 典型场景 |
|---|---|---|---|---|
| 8位MCU + HX711 | 低 | 低 | 中,受MCU滤波能力限制 | 家用简易秤 |
| STM32F1系列 + HX711 | 中 | 中 | 高,可做数字滤波与标定补偿 | 教学毕设、工业台秤原型 |
| 专用计量SOC | 高 | 高 | 极高,已过认证 | 商用计价秤、贸易结算秤 |
从我自己的体验来说,STM32F103C8T6是这类项目最顺手的选择。原因有三:第一,它主频72MHz,做称重数据滤波、按键扫描、OLED刷新完全是性能过剩的,但这种过剩是好事情,意味着你不需要天天抠时序;第二,HAL库和标准库的资料实在太多,遇到问题几乎都能搜到现成答案;第三,F103系列有足够多的定时器、串口、I2C、SPI接口,就算后面你要加蓝牙模块、WiFi模块、多个传感器,资源也不会卡脖子。对于培训、毕设和原型验证,这是最稳的一条路。
1.2 系统架构:这颗电子秤肚子里的完整组成
按下电源,电子秤开始工作的核心流程是这样的:
- 称重传感器把重量变化转换成微弱的差分电压信号;
- HX711完成信号的放大和24位模数转换;
- STM32通过时序读取HX711的转换结果;
- 借助标定系数把ADC原始值换算成实际重量;
- OLED显示当前重量、单位、去皮状态;
- 按键触发去皮、单位切换、开关机等逻辑。
这套架构最容易被忽略、也最关键的,是第1步和第2步之间的模拟信号链路。HX711虽然集成了放大器和ADC,但它的输入信号非常微弱,满量程差分电压典型值只有10mV到20mV量级,此时任何来自电源的纹波、地线上的噪声、或者传感器受力不均匀导致的非线性误差,都会直接影响最终读数。我后面会专门展开讲这部分,因为这直接决定你的秤到底是"能显示数字"还是"称得准"。
2. 称重信号链路拆解:从电阻应变片到24位ADC
2.1 电阻应变式称重传感器的原理与接线
市面上常见的电子秤传感器,核心是一块弹性体(通常是铝合金),表面贴了电阻应变片。当重物压上去,弹性体发生形变,应变片电阻值随之变化,通过惠斯通电桥把电阻变化转换成电压变化。
单片机需要关注的不是电桥原理本身,而是传感器的四根线:E+(激励正)、E-(激励负)、S+(信号正)、S-(信号负)。正常情况下,E+和E-之间接5V或3.3V激励电压,S+和S-之间输出的差分电压就是我们要测量的信号。
这里插一个接线上的教训:传感器的线色在不同厂家之间并不统一。最常见的定义是红线E+、黑线E-、绿线S+、白线S-,但也有人会用黄线、蓝线。我建议拿到传感器第一件事不是猜测,而是用万用表量一遍:测E+到S+之间的电阻、E+到S-之间的电阻,两组阻值应该接近且等于电桥的等效半桥电阻(一般是350Ω左右,也有1000Ω的)。确认无误再接电路板,不然查半天都找不到信号异常的原因。
2.2 HX711的工作模式与关键引脚
HX711是这颗秤的核心模拟前端,我先把它的关键参数和配置方式列出来:
| 参数 | 数值 | 备注 |
|---|---|---|
| 通道数量 | 2(A/B) | A通道增益可选64或128,B通道固定32 |
| 有效精度 | 24位 | 配合128增益时满量程约±0.5mV |
| 输出速率 | 10Hz / 80Hz可选 | 速率越低噪声越小 |
| 供电电压 | 2.6V~5.5V | 建议模拟电源和数字电源分开 |
| 通信接口 | 2线(DOUT、PD_SCK) | 类似SPI,但用GPIO模拟即可 |
重点说下通道和速率的选择。A通道128倍增益是称重传感器的标准配置,满量程时输出灵敏度约等于第一脚到第二脚的电压差,输入范围能匹配大多数铝制悬臂梁传感器;输出速率方面,如果你只是做静态称重,10Hz足够,而且噪声明显比80Hz小。我实测下来,80Hz模式在无负载时的跳码范围大约是10Hz模式的3倍左右,所以除非你后续要做动态称重,否则老老实实用10Hz。
PD_SCK在读取周期内的高电平脉冲数决定通道和增益,这个细节容易踩坑:读出数据时送25个脉冲,前24个是数据位,第25个用来设置下一次转换的通道和增益。如果不小心把脉冲数写成26或27,HX711不会报错,但读出来的数据会变成另一个通道或增益下的结果,数值完全不可用。这种"看起来在读、实际全错"的问题非常隐蔽,排查时一定要先确认脉冲计数。
2.3 模拟电源的净化:HX711供电与参考电压
HX711的稳压和参考电压设计直接决定称重数据稳不稳。它有一个AVDD引脚,通常通过一个RC低通滤波接到传感器的E+激励端,同时这个AVDD电压也充当了ADC的参考电压。
这里有一个关键点:HX711的转换结果是比例式的,信号电压和参考电压都来自AVDD,所以只要AVDD在信号采集窗口内保持稳定,绝对电压的波动不会引起比例误差。但你如果用独立的基准电压源给HX711供电,传感器激励也独立供电,标准电阻的温漂就会转成实际误差。简单说:让AVDD既是传感器激励又是参考源,反而是最稳定、最简单的设计。
实操上,我习惯在AVDD引脚和对地之间并联一个100uF电解电容和100nF陶瓷电容,尽量靠近引脚放置,同时在传感器E+和E-之间接一个10uF以上的电容。这组电容能在瞬间负载变化时稳住电压,实测可以明显减少数据跳变。还有一个容易忽略的问题是HX711的数字地AGND和模拟地DGND,数据手册建议用0Ω电阻或磁珠单点相连,在高精度称重的场合一定要这么做,否则数字噪声会通过地平面耦合到模拟端,导致零点缓慢漂移。
3. 单片机端软件设计:HX711驱动、滤波算法与状态机
3.1 用GPIO模拟时序驱动HX711
HX711的时序非常简单,总共就两根线, DOUT数据输出, PD_SCK时钟输入。当DOUT从高电平变为低电平时,表示转换完成,STM32开始输出脉冲读数据。每一路脉冲,HX711在上升沿把一位数据放到DOUT上,STM32在脉冲高电平期间或下降沿后读取。24个脉冲读满后,再补1个脉冲设置下一次采集的通道和增益。
我用标准库写的初始化函数和读取函数大概是这样的骨架:
uint32_t HX711_Read(void) { uint32_t value = 0; uint8_t i; // 等待DOUT变低,表示转换完成 while (HX711_DOUT_READ() == 1); for (i = 0; i < 24; i++) { HX711_SCK_HIGH(); // 这里需要延时几百纳秒,确保数据建立 delay_us(1); value = value << 1; if (HX711_DOUT_READ() == 1) { value++; } HX711_SCK_LOW(); delay_us(1); } // 第25个脉冲,设置下一次转换通道和增益 HX711_SCK_HIGH(); delay_us(1); HX711_SCK_LOW(); delay_us(1); return value; }有几个细节要注意:第一,读取时建议关闭中断或者至少关闭同优先级的中断,避免脉冲时序被其他中断打断。因为HX711对时钟的相位要求虽然不严格,但如果你在24个脉冲中间插入一个几微秒的串口中断,可能导致时序错乱,数据移位。第二,第25个脉冲设置的是下一次转换,所以如果你循环调用这个函数,每次读到的都是上一次设置好的通道结果,第一次读出来的数据是无效的,通常要丢弃前两次数据。第三,DOUT端建议加上拉电阻,因为HX711的DOUT是开漏输出,不加外部上拉,信号上升沿可能不够陡,在长线连接时容易误读。
3.2 称重数据的滤波:滑动平均、中位值平均、限幅滤波怎么选
HX711的24位原始数据不会理想地稳定在一个值上,通常会在某个范围内跳变。跳变大小取决于供电质量、传感器灵敏度和电气环境。直接把这个原始值换算成重量,屏幕上的数字会像心跳一样乱跳,所以必须做数字滤波。
我试过三种方案,结论很明确:
- 算术平均:简单,但抗不了突发脉冲噪声,一个大的干扰值会把平均值拉偏很多;
- 中位值滤波:抗脉冲最好,但对连续变化的数据会造成滞后,称重响应偏慢;
- 滑动平均:实时性好,存储量小,对周期性噪声有很好的抑制作用。
实际项目里用了"滑动平均 + 限幅"的组合:先判断当前读数与上一次有效读数的差值,如果超过设定的阈值(比如500个码值),就丢弃这个点;如果没有超限,就把它送入一个长度为8的滑动平均队列。这样做既能滤除高频抖动,又能防止猛拉猛放造成的过冲显示。
#define FILTER_LEN 8 int32_t filter_buf[FILTER_LEN]; uint8_t filter_index = 0; int32_t filter_sum = 0; int32_t filter_last = 0; int32_t Weight_Filter(int32_t new_sample) { // 限幅:超过阈值则用上一次值替换 if (filter_last != 0) { int32_t diff = new_sample - filter_last; if (diff > 500 || diff < -500) { new_sample = filter_last; } } // 滑动平均 filter_sum -= filter_buf[filter_index]; filter_buf[filter_index] = new_sample; filter_sum += new_sample; filter_index = (filter_index + 1) % FILTER_LEN; filter_last = new_sample; return filter_sum / FILTER_LEN; }这个滤波组合在10Hz输出速率下表现非常稳定,空载时显示值可以在±1g以内停住。如果你对响应速度要求更高,可以把滑动窗口缩小到4,或者在数据平稳后自动切换为大窗口滤波,这个属于进阶优化,后面再讲。
3.3 称重状态机:空载稳定、加载稳定、超载保护的切换逻辑
电子秤不是上电就能直接用的,它要经历几个状态。我在代码里用了一个简单的状态机,每个状态对应不同的业务逻辑:
| 状态 | 触发条件 | 做的事情 |
|---|---|---|
| 开机自检 | 上电 | 读取零点、检查传感器连线、初始化显示 |
| 空载稳定 | 零点漂移小于阈值 | 每500ms记录一次零点,更新去皮基准 |
| 称重中 | 重量超过稳定阈值 | 持续采样,实时更新显示 |
| 称重稳定 | 连续N次读数变化小于阈值 | 锁定显示数值,提示可读取 |
| 超载 | 重量超过量程+预留量 | 显示超载报警,拒绝继续称重 |
状态切换的关键是"稳定判断":什么是稳定?我定义的一个简单标准是连续5次(0.5秒)的滤波值最大值与最小值之差小于2g,就认为称重结果稳定。这个阈值可以根据传感器量程调整,量程5kg的秤用2g,量程30kg的秤可以用10g。
这个状态机的价值在于它把"什么时刻读数才算准"这个模糊问题变成了可编程的逻辑。没有状态机的话,用户随便一放东西,屏幕上的数字持续跳动,就会觉得这个秤是坏的。很多人做电子秤毕设只到"能显示重量"就结束了,其实加上这个稳定判断逻辑,体验立刻上一个档次。
4. 标定、去皮与单位换算:电子秤核心算法的工程实现
4.1 两点标定法背后的数学原理
电子秤的标定,本质是建立ADC原始码值到实际重量之间的线性关系。我们假设这个关系是线性的:重量 = (ADC值 - 零点值) / 斜率。为了求斜率和零点,至少需要两个已知点,这就是两点标定。
实际操作时,我先开机让秤空载,等读数稳定后记录零点ZeroValue;然后放上一个标准砝码(比如1kg),等读数稳定后记录满点FullValue,那么斜率就是:
scale_factor = (FullValue - ZeroValue) / 标准砝码重量之后任意重量计算公式就是:
weight = (CurrentValue - ZeroValue) / scale_factor如果你有多个砝码,理论上可以做多点拟合来补偿传感器的非线性误差,但大多数铝制悬臂梁传感器在50%量程以内的线性度是相当不错的,两点标定已经够用。我试过用1kg、2kg、3kg三个点做分段线性插值,效果在满量程5kg的传感器上差异不大,常规毕设和家用场景两点就足够了。
标定系数一定要保存在Flash里,不能每次都重新标定。STM32F103内部Flash写入寿命是1万次,正常标定操作不会写很多次,但要注意写Flash的时候要关闭中断,否则在擦写期间来了中断会导致程序卡死。更稳妥的做法是把标定参数放在一个结构体里,写入Flash前计算校验值,读出来时先校验再使用,避免Flash数据损坏导致程序跑飞。
4.2 去皮逻辑的边界情况:毛重、净重、负值显示
去皮是电子秤最常用的功能,逻辑上很简单:用户放上一个容器,按下去皮键,系统把当前值记录为皮重TareValue,之后显示净重 = 当前重量 - 皮重。
但实现上有几个边界情况容易被忽略:
- 容器拿走后,净重会变成负值。这是正常的,要允许显示负值,并且允许再次去皮恢复零点;
- 再次去皮时,应该以当前显示值作为新的皮重增量,而不是直接用当前ADC值替换。否则连续去皮数次之后,零点会混乱;
- 去皮范围和量程的关系要处理好:如果当前毛重已经超过满量程,去皮按键应该被忽略,否则会出现一个"负数超载"状态。
我用的去皮逻辑是:
void Tare_Process(void) { gross_weight = Get_FilteredWeight(); // 当前毛重 if (gross_weight <= MAX_LOAD) { tare_offset = gross_weight; display_weight = 0; } }这样实现有一个好处:如果用户放了一个5kg的桶在10kg量程的秤上,去皮后显示的净重是0,此时继续加东西,毛重超过10kg才会报警,但净重没有超过量程的话,显示依然正常。这个"基于毛重判断量程"的逻辑,和市面上大多数商用秤是一致的,防止了去皮后还能无限制加物体的安全隐患。
4.3 单位切换与精度显示的细节
单位切换看似简单,实际上涉及显示小数位的控制。称重行业常用的单位组合是kg和斤,也有用kg和lb的。换算关系如下:
1斤 = 0.5kg 1lb = 0.45359237kg
如果显示精度固定为0.01kg(即10g),在kg单位下显示"1.00",切到斤应该是"2.00",这没问题。但如果切换到lb,结果会是"2.2046",显示位数不够就会产生进位误差。实际项目里一般会为不同单位配置不同的小数位和步进值:kg显示到小数点后两位,lb显示到小数点后三位,斤显示到小数点后两位。
还有一个细节:显示分辨率不能超过传感器和ADC的实际精度。HX711加常规传感器,在10Hz采样率下有效分辨率大约在0.5g到1g之间(针对3kg量程)。如果你把显示设为0.01g,屏幕会一直跳动,看起来就像坏了。宁可把显示步进取大一点,比如5g或10g步进,换取稳定的阅读体验。
5. OLED显示与按键交互:用户体验从"能用"到"好用"
5.1 OLED驱动方式选择:I2C还是SPI
STM32电子秤最流行的显示模块是0.96寸OLED,分为I2C和SPI两种接口。I2C版本只需要两根线,接线简单,适合本项目;SPI版本刷新速度快,但需要额外的片选、数据命令选择线。
从实际效果看,电子秤的数据刷新频率不高(5到10Hz),I2C接口的OLED完全够用。我个人倾向用I2C方案,因为可以把SCL和SDA引脚分别接到PB6和PB7(F103的I2C1),代码也更简洁。
I2C OLED驱动需要注意的一点:OLED的I2C地址通常是0x3C,但有些模块是0x3D,检测不到的时候不要怀疑代码有问题,先扫描一下地址。另外,I2C通信要对OLED的RAM进行整屏刷新,一次全屏刷新大约需要几百字节,F103跑在72MHz下毫无压力,但用软件模拟I2C的话要注意延时控制,太快了模块跟不上。
5.2 用DWT替代标准库的延时,解决屏幕刷新卡顿
设计过程中如果你用标准库自带的SysTick延时,可能会有个问题:SysTick被延时函数占用后,就无法再用它做系统节拍。而OLED刷新、按键扫描、HX711读取这些操作都需要延时,如果用阻塞式延时函数,整机运行起来会有明显的"一卡一卡"现象。
我采用的方式是用Cortex-M3内核的DWT计数器做微秒延时,把SysTick释放给系统做1ms时基,这样既能有精确的微秒级延时,又有可靠的毫秒级调度:
void DWT_Delay_Init(void) { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; } void DWT_Delay_us(uint32_t us) { uint32_t start = DWT->CYCCNT; uint32_t ticks = us * (SystemCoreClock / 1000000); while ((DWT->CYCCNT - start) < ticks); }DWT计时器的精度取决于CPU时钟频率,F103在72MHz下,一个计数周期约13.9ns,微秒级延时非常精确。用这个方法,OLED单次刷新的阻塞时间可以控制在几百微秒以内,整机流畅度明显提升。这个方法我在其他项目里也复用得很顺,算是换了个思路解决了SysTick被占用的老问题。
5.3 按键防抖与长按、短按的识别
电子秤面板上至少需要3个按键:开机/去皮、单位切换、背光/设置。简单的独立按键如果直接查询电平,按下瞬间机械触点会产生抖动,肉眼看着只是"啪嗒"一下,但单片机可能已经识别了十几次动作。
常用的防抖方案是"延时20ms再确认电平",优点是简单可靠,缺点是在6个按键以上时会占用大量主循环时间。对于电子秤这种按键数量少的设备,我觉得用定时扫描的有限状态机更优雅:
- 每10ms扫描一次按键电平;
- 如果连续3次读到低电平,确认按下;
- 按键保持低电平超过600ms,识别为长按;
- 松开后,短按执行短按动作,长按执行长按动作。
这个逻辑还有一个好处:可以方便地扩展组合键。比如短按+长按同时触发数据锁定,或者长按开关机。我把按键扫描放在1ms时基中断里,用标志位通知主循环处理,按键响应非常灵敏,也不会阻塞其他任务。
6. 实测数据与调参经验:零点漂移、温漂和量程误差怎么压下去
6.1 空载跳码测试:多少范围内算合格
把秤装好上电,不加载任何物体,连续采集HX711原始值并打印到串口,你可以看到跳码情况。我这里有一组实测数据,采集1000个点,输出速率10Hz,5kg传感器,3.3V供电:
| 测试条件 | 原始值范围 | 峰峰值 | 等效重量波动 |
|---|---|---|---|
| 供电稳定,传感器悬空 | 8201530 ~ 8201580 | 50 | ±0.3g |
| 供电稳定,传感器平放桌面 | 8201500 ~ 8201570 | 70 | ±0.4g |
| 开关电源供电,未加滤波电容 | 8201000 ~ 8202500 | 1500 | ±9g |
| 加100uF电解电容+100nF陶瓷电容 | 8201480 ~ 8201560 | 80 | ±0.5g |
这个数据很能说明问题:开关电源的噪声是最大的干扰源。很多同学用开发板自带的USB供电,表面看起来工作稳定,但实际上USB口的5V噪声不小,传导到HX711的模拟电源后,称重值就会像心电图一样跳。解决方式不是换电池,而是在电源入口增加LC滤波,并且让HX711的供电走独立的滤波支路。
如果空载跳码的等效重量波动能控制在±1g以内,这个秤的稳定性就合格了。如果超过这个范围,先检查电源滤波,再检查传感器到HX711的连线是否过长、是否与数字线平行。
6.2 标定后的满量程偏差与线性度验证
标定完成后,用不同重量的砝码测试,常见的问题是小重量准、大重量偏大(或偏小)。这通常是传感器本身的非线性误差,或者机械结构上受力不均匀引起的。
我习惯测5个点:空载、20%量程、50%量程、80%量程、100%量程,记录每个点的误差:
| 标准重量 | 显示重量 | 绝对误差 | 相对误差 |
|---|---|---|---|
| 0g | 0g | 0 | 0% |
| 1000g | 1001g | +1g | 0.1% |
| 2500g | 2503g | +3g | 0.12% |
| 4000g | 3996g | -4g | -0.1% |
| 5000g | 4985g | -15g | -0.3% |
可以看到,越接近满量程,误差越大。这时候有两个处理思路:一是做多点线性校正,二是检查机械装配。对于毕设和原型来说,更建议先检查机械结构——传感器是否水平安装、受力点是否在同一平面、是否有预紧力。传感器弹性体如果被螺丝拧得太紧,会造成初始应力,直接影响满量程端的线性度。我遇到过"小重量准、大重量偏小"的情况,最后发现是传感器固定螺丝太长,顶到了弹性体的应变区,把螺丝换成沉头螺钉后问题就消失了。
6.3 温差导致的零点漂移与补偿策略
电子秤还有一个常见现象:开机的时候显示0,放一会儿,慢慢变成"0.5g"或者"-1g"。这是传感器电阻应变片的温漂以及HX711模拟前端温漂共同作用的结果。普通铝制传感器温漂系数大约在0.05%FS/C°,如果你所在环境温差达到10°C,满量程5kg的传感器就可能产生2.5g的零点漂移。
缓解温漂的工程手段有三层:
- 硬件层:传感器和HX711尽量远离发热源,比如MCU和OLED的背光驱动电路。我在PCB布局时把传感器端子放在板子一端,MCU放在另一端,中间用地线隔开;
- 软件层:开机后做一次零点采集,并每隔一段时间自动更新零点,但要注意只能在"确认没有负载"的情况下更新,否则会把真实重量吃掉;
- 算法层:如果要求更高,可以在传感器附近放一个温度传感器(如DS18B20),建立零点-温度曲线做软件补偿。实话说,家用电子秤很少做这层,温度补偿更多出现在工业设备里。
对本项目来说,做到前两层就够用了。最关键的是"开机自动清零"这个功能——它能在传感器热机完成前就把初始偏差抹掉,用户体验提升非常明显。很多商用秤的做法就是开机后显示0.0,然后才允许称重,背后其实就是这个逻辑。
7. 进阶扩展:从"能称重"到"可联网"的升级路径
7.1 用串口空闲中断实现不定长数据接收
如果后续要把电子秤数据传给上位机或者路由器ESP8266,串口通信就绕不开。基础做法是串口一次接收固定长度的数据,但如果数据长度不固定,传统的逐字节接收+定时器超时判断比较麻烦。
F103的HAL库提供了一种优雅的方案:串口空闲中断(IDLE)。当串口在一帧数据之后出现空闲总线时,会产生IDLE中断,此时__HAL_UART_GET_FLAG(&huart, UART_FLAG_IDLE)会置位。配合DMA接收,可以实现不定长数据的自动收发。
核心思路是:串口DMA工作在循环模式,一旦收到数据就自动存到缓冲区,当检测到IDLE中断时,计算"DMA当前计数器和上次处理计数器的差值",就知道这段时间收到了多少个字节,然后按帧解析。这个方案在STM32接ESP8266、接蓝牙模块、接上位机的场景里非常通用,值得花时间掌握。
7.2 加入ESP8266实现数据上传
项目做完基础称重功能后,最常见的扩展方向是把重量数据通过ESP8266上传到服务器或手机APP。ESP8266与STM32之间用串口通信,STM32通过AT指令控制WiFi连接和数据发送。
需要注意的是,ESP8266的供电峰值电流能到300mA甚至更高,不能直接从STM32的3.3V引脚取电,必须用单独的LDO或DC-DC模块。我在项目里用了一个MP1584降压模块,把输入电压降到3.3V给ESP8266供电,要求能力在1A以上,同时在地线连接上尽量短粗,否则WiFi发射瞬间的压降会导致模块重启,表现就是"偶尔发不出去数据"。
7.3 用FreeRTOS管理称重任务的多线程化
当功能多了,裸机while循环就有点力不从心:OLED要刷新、HX711要采集、串口要收发、按键要扫描,每个都是周期任务。这时把代码迁移到FreeRTOS上,用任务和队列来组织逻辑,会让代码结构清晰很多。
我规划的任务分配是:
| 任务名 | 优先级 | 周期 | 工作内容 |
|---|---|---|---|
| ADCTask | 高 | 100ms | 读取HX711、滤波、标定换算 |
| DisplayTask | 中 | 200ms | OLED数据显示刷新 |
| KeyTask | 低 | 20ms | 按键扫描与状态机处理 |
| CommTask | 中 | 事件触发 | 串口数据收发与解析 |
任务之间通过队列传递数据,比如ADCTask把计算好的重量值放入队列,DisplayTask从队列取数据刷新屏幕,这样就避免了共享全局变量的竞争问题。FreeRTOS在STM32F103C8T6上跑没有任何压力,RAM占用大约1KB多,绰绰有余。
有人会问:一个电子秤有必要上RTOS吗?如果只是基础功能,确实没必要;但如果你想把电子秤做成一个平台,还要加菜单、加存料管理、加IoT上报,那裸机代码会越来越难维护。我在后面几个版本里就全切FreeRTOS了,整个项目的可持续性一下子好了很多。
8. 调试工具与典型问题排查链路
8.1 串口打印与可变参数格式化
调试电子秤最直接的手段,是在STM32工程里重定向printf到串口,然后通过串口助手看HX711原始值、滤波值和计算重量。
HAL库重定向printf的方法很简单,重写fputc函数即可:
#include "stdio.h" int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF); return ch; }需要注意串口的波特率不要设太高,115200最常用。打印的内容建议同时包含ADC原始值、滤波值、零点值和计算后的重量值,这样你不用反复改代码就能观察到整个信号链路的各个环节。
8.2 用逻辑分析仪抓HX711时序
当代码看起来没问题但读数不稳定时,有一招很管用:用逻辑分析仪抓DOUT和PD_SCK两根线的波形。HX711的时序是典型的"一高一低、按脉冲读数"模式,逻辑分析仪能直观地告诉你:脉冲宽度够不够、读到的位对不对、第25个脉冲有没有漏掉。
我之前遇到一个奇怪的问题:每隔一段时间,读数就会跳到一个明显离谱的值,但很快又恢复正常。用逻辑分析仪一看,发现是按键扫描的中断打断了HX711读取过程,导致中间的某个位被重复读了一次。把按键扫描改成在主循环中处理,不在中断里做,问题就消失了。
8.3 常见故障与解决方案速查表
| 现象 | 可能原因 | 解决方向 |
|---|---|---|
| 上电读数直接为0x800000(最低位) | HX711未正常启动或DOUT悬空 | 检查供电、DOUT上拉、复位时序 |
| 读数不变化,一直是某个值 | 传感器接线错误或SCK时序错误 | 万用表检查传感器四线,逻辑分析仪查时序 |
| 空载跳动大 | 电源纹波大、地线处理不当 | 加LC滤波、单点接地、区分模拟数字地 |
| 标定后重量偏小 | 传感器安装过紧或有预紧力 | 检查机械装配,重新标定 |
| 去皮后负数一直增长 | 零点更新逻辑错误 | 去掉自动零点更新或增加无负载判断 |
| OLED显示花屏 | I2C速率过快或地址不匹配 | 降低I2C时钟,确认模块地址 |
这些故障我在项目里几乎全部遇到过,排查顺序永远是:硬件连接→供电质量→时序→代码逻辑。不要一上来就怀疑代码,电子秤这种设备,硬件问题的比例远高于软件问题。
在这个项目上,我最大的体会是:MCU编程本身其实没有太多难点,真正的挑战在于把一颗传感器、一个ADC前端和几行滤波代码组合成一套稳定可靠的测量系统。能做到空载不飘、加载准确、去皮干净、超载报警,这个电子秤就不是一个"演示程序",而是一个接近产品状态的设备。如果后面继续往物联网方向走,串口接上ESP8266,数据即可上传云端,再配合FreeRTOS把任务理顺,整套系统就具备很强的扩展性了。希望这篇文章能帮你绕开我踩过的坑,把项目做扎实。
本文还有配套的精品资源,点击获取