简介:本资源是一套完整的基于51单片机的电子秤毕业设计实现方案,面向电子、自动化、测控等专业本科生及课程设计/毕设实践者,解决称重系统硬件搭建、信号采集、软件算法与人机交互等典型工程问题。压缩包共104个文件,含Keil源程序工程(.c/.h/.uvproj等)、Proteus仿真工程(.dsn/.pdsprj)、电路原理图(.sch)、技术手册与论文资料(.doc/.pdf/.ppt)、调试过程文件(.bak/.lst/.hex)及实操说明(.txt/.xlsx),覆盖从原理设计、代码开发、仿真验证到实物调试全流程,总大小17.89MB。已有195人学习下载,资源结构清晰,工程文件保留多版本备份(如Administrator/yong双用户配置),便于理解开发迭代过程;配套技术文档详述HX711信号调理、数码管动态扫描、去皮校准算法及蜂鸣器反馈逻辑,可直接用于毕设答辩、课程报告或二次开发参考。 电子秤这种项目,几乎是每个单片机学习者绕不开的一个经典设计。但说句实话,很多人照着网上的模板抄完,原理图能跑,程序能烧,真正问起来为什么这么接、为什么这么写,却答不上来。这次我基于51单片机+HX711+SEG4B(四位共阴数码管)+10KG量程传感器+蜂鸣器+3按键的完整方案,从硬件选型到软件逻辑,再到Proteus仿真和实物校准,把整个设计链路重新梳理了一遍。这篇文章不会只丢给你一堆代码和原理图,而是会讲清楚每一部分背后的取舍逻辑、实际调试中容易踩的坑,以及那些文档里基本不会写的细节。
1. 方案选型与整体架构:为什么是51+HX711+SEG4B
1.1 主控芯片的选择逻辑
在这个项目里,我用的主控是STC89C52RC,属于经典的MCS-51内核单片机。有人可能会问,现在ARM Cortex-M系列这么普及,STM32价格也下来了,为什么还要用51来做电子秤?
原因有三个。第一,电子秤的核心任务其实就是“采集+计算+显示”,对主控的运算能力要求极低。HX711芯片已经把模拟信号的放大和AD转换全部做完了,单片机要做的只是通过时序读取24位数据,再做一个线性标定换算。这个工作量用51完全够用,何必杀鸡用牛刀。第二,51单片机的GPIO口是准双向口,驱动能力虽然弱,但配合三极管或直接驱动共阴数码管的段选,正好够用,电路结构简单,教学意义强。第三,从课程设计和毕业设计的答辩角度来说,51方案更容易被评委老师理解,你解释得清楚,本身就加分。
1.2 称重传感器与HX711搭配的合理性
称重传感器选择的是10KG量程的铝制悬臂梁式传感器,内部是四个电阻应变片组成的惠斯通电桥。传感器输出的差分信号幅度非常小,满量程时也只有几毫伏,必须经过高精度运放放大后才能被ADC采集。HX711就是专门干这个的。
HX711内部集成了一个低噪声可编程增益放大器(PGA),增益可选32、64和128,还集成了24位高精度Σ-Δ型ADC。在电子秤应用中最常见的配置是增益128,此时HX711可以检测到非常微小的电压变化。配合5V供电和10KG传感器,做到1克的分辨率是完全可行的。多说一句,很多人纠结HX711到底是“称重专用芯片”还是“通用ADC”,从数据手册看,它确实是为桥式传感器量身定制的,内部有专门的时钟电路和稳压输出,整体方案非常成熟,这也是它成为电子秤设计标配的原因。
1.3 系统整体结构和信号流程
整个系统的数据链路是这样的:传感器受力产生微小的差分电压变化,经过HX711内部仪表放大器放大,再经过24位ADC转换为数字信号,单片机通过模拟时序从HX711读出数据,经过滤波和标定换算得到实际重量,最后把重量值刷新到数码管上。按键负责去皮、校准、开关蜂鸣器等交互功能,蜂鸣器在按键操作和超重时产生声音提示。
这种“传感器+专用ADC+通用MCU”的三层架构,是工业测量设备中最常见的设计范式。理解了这个架构,后面看电路图和写程序时思路会清晰很多。
2. 硬件电路设计与器件细节:从原理图到实物
2.1 电源与去耦设计
这个项目可以用USB供电,也可以用5V直流电源适配器供电。表面上看这只是一个电源接口的选择,但实际布线时有个容易忽略的点:如果直接用USB供电,HX711的模拟电源AVDD和数字电源DVDD都来自同一个5V,电流波动可能会耦合到模拟部分,造成读数不稳定。
我的做法是:在电源入口放一个100uF电解电容滤低频纹波,然后在HX711的AVDD引脚旁边并联一个0.1uF陶瓷电容和10uF钽电容,分别滤高频和低频噪声。单片机数字部分和传感器模拟部分的地,在电源入口处单点汇接。这样做之后,读数的稳定性会有非常明显的改善。实测下来,同样的传感器,GND处理得好不好,零点漂移能差到3到5倍。
2.2 SEG4B数码管驱动方式
SEG4B是四位共阴数码管模块。很多新手会纠结一个问题:要不要用锁存器或移位寄存器来驱动?我的答案是:看引脚资源和刷新方式。
如果直接用51单片机的P0口做段选,P2口的部分引脚做位选,四个位用三极管8550驱动,那么一个数码管模块要占用大约12个IO口。51单片机总共就32个IO口,还要留出HX711的2个口(DT和SCK)、按键的3个口、蜂鸣器的1个口,这样算下来IO刚好够用,只是余量不多了。所以没有额外加锁存器,直接动态扫描驱动。刷新频率要做到至少1ms切换一位,四位数码管一个完整扫描周期是4ms,刷新率250Hz,肉眼看不出闪烁。
位选三极管必须用PNP型(如8550),因为共阴数码管的公共端要接地才能点亮,而用PNP管时基极拉低导通,导通后集电极输出高电平到公共端,这个是常规驱动方式。反过来用NPN管会比较别扭,容易犯错误。段选电阻我选了220欧姆,实测在5V供电下,数码管亮度适中,长时间工作不会明显发热。
2.3 HX711的接线与时钟配置
HX711模块通常有8个引脚:VCC、GND、SCK、DT、E+、E-、A+、A-。其中E+和E-接传感器的激励电压正负端,A+和A-接传感器的信号输出正负端。SCK和DT接单片机任意两个GPIO即可。
这里有个细节值得注意:HX711的SCK引脚用于输出时钟脉冲来控制数据读取,每次从DT引脚读取一位数据需要24个脉冲,读到24位数据后,再输出1到25个额外脉冲可以选择通道和增益。数据手册推荐的时序是:先将SCK拉低,等待DT变为低电平表示数据准备好,然后连续输出24个脉冲读取数据,最后再输出特定数量的脉冲来设置下一次转换的增益。标准配置下,25个脉冲对应通道A增益128,26个脉冲对应通道A增益64,27个脉冲对应通道B增益32。在我的程序里选择的是25个脉冲,也就是通道A、增益128。
还有一个容易踩的坑:HX711模块的供电电压会影响激励电压,进而影响传感器灵敏度。有些模块上有稳压芯片,可以把供电电压稳定到5V或3.3V给传感器桥路供电。如果是3.3V版本的模块,传感器在5V标定下的灵敏度和实际工作电压下的输出会有差异,这时候标定系数自然也会变,不要照搬网上的系数,一定要用砝码实际标定。
2.4 按键与蜂鸣器电路设计
按键部分用了三个独立按键:去皮键、校准键、开关蜂鸣器键。按键电路没有做得很复杂,每个按键串联一个10K上拉电阻到VCC,按下时接地,单片机检测到低电平即可。需要注意的是,51单片机的P3口内部有弱上拉,所以有些人不接外部上拉也能检测到,但为了波形可靠,我还是接上了外部上拉电阻。加上按键去抖动处理,可以用硬件RC滤波,也可以在程序里用延时或状态机消抖。程序里我用了10ms延时消抖,实测稳定可靠。
蜂鸣器选了有源蜂鸣器,驱动方式用NPN三极管S8050。有源蜂鸣器内置振荡源,只要通电就会响,单片机只需要控制基极高低电平即可。这里有个容易忽略的地方:蜂鸣器是感性负载,关断瞬间会产生反向感应电动势,可能会通过电源线干扰单片机和HX711的读数。所以务必要在蜂鸣器两端并联一个续流二极管(1N4148即可,方向要反接),否则你可能会发现,每次按键蜂鸣器响的瞬间,数码管读数会跳一下。
3. 核心代码实现与算法逻辑
3.1 HX711读取时序的实现
HX711的读取是软件模拟时序,这个函数的写法有很多种,但核心逻辑必须严格符合芯片手册。我的实现如下:
#include <reg52.h> sbit HX711_SCK = P1^5; sbit HX711_DT = P1^6; // 从HX711读取一次AD值,返回24位有符号数 unsigned long HX711_Read(void) { unsigned long value = 0; unsigned char i; HX711_SCK = 0; // 等待DT变为低电平,表示数据准备好 while(HX711_DT); for(i = 0; i < 24; i++) { HX711_SCK = 1; value = value << 1; HX711_SCK = 0; if(HX711_DT) value++; } // 25个脉冲:进入通道A,增益128 HX711_SCK = 1; value = value ^ 0x800000; // 转为有符号数 HX711_SCK = 0; return value; }有些资料会在读取前先加上延时,我实测下来,只要SCK高低电平的维持时间不小于1us,时序就是可靠的。51单片机在12MHz晶振下,一个NOP约1us,直接操作IO也可以满足要求。但要注意,如果用的是STC系列的单片机,在1T模式下速度会快很多,这时候如果你不加延时,实际的脉冲宽度可能太窄导致读取错误。所以我的做法是在循环里插入几个NOP,确保脉冲宽度足够。
3.2 均值滤波与数据平滑
HX711的原始数据有波动是正常的,尤其是人在秤上轻微晃动时,数据会不停跳动。因此在读取到原始AD值后,不能直接拿去算重量,必须做滤波处理。
我的做法是连续读取10次,去掉最大值和最小值,然后求平均。这种去极值平均滤波法在电子秤里非常常用,既有中值滤波的抗脉冲干扰能力,又有算术平均滤波的平滑效果。实现代码如下:
unsigned long HX711_Filter(void) { unsigned long buf[10]; unsigned long sum = 0; unsigned long max, min; unsigned char i; for(i = 0; i < 10; i++) { buf[i] = HX711_Read(); } max = buf[0]; min = buf[0]; for(i = 1; i < 10; i++) { if(buf[i] > max) max = buf[i]; if(buf[i] < min) min = buf[i]; } for(i = 0; i < 10; i++) { sum += buf[i]; } sum = sum - max - min; return sum / 8; }有一个细节要注意:如果每次读取都调用了延时函数,那么整个滤波过程会占用不少时间,可能会影响数码管动态扫描的刷新率。我的建议是HX711的读取间隔就靠芯片自身的转换时间决定,不需要额外延时太多。HX711的转换速率默认是10Hz,也就是说每秒可以读大约10次,每次读数之间必须有足够的间隔,否则读出来会是上一次的缓存数据。
3.3 重量标定算法
这是整个程序最核心的部分,也是很多初学者最容易困惑的地方。电子秤的标定本质上是一个线性映射问题:传感器受力后,HX711输出的AD值和重量之间存在一个近似的线性关系,所以只需要知道两个已知点,就可以求出斜率和截距。
我的标定思路是这样的:首先在空秤状态下读取一个零点值(offset),然后放上一个已知重量的标准砝码(比如1kg或5kg),读取对应的AD值,两者相减得到差值,用砝码重量除以这个差值,就得到了每个AD单位对应的重量值,也就是标定系数。
long offset = 0; // 零点AD值 float scale = 0.001; // 每个AD单位对应的克数,初始值随便给 // 按键去皮:记录当前AD值为零点 void Tare_Key(void) { offset = (long)HX711_Filter(); } // 放入标准砝码weight_g后执行校准 void Calibrate_Key(unsigned int weight_g) { long current = (long)HX711_Filter(); long delta = current - offset; if(delta != 0) { scale = (float)weight_g / (float)delta; } } // 计算当前重量 float Get_Weight_Gram(void) { long current = (long)HX711_Filter(); long delta = current - offset; float weight = (float)delta * scale; if(weight < 0) weight = 0; return weight; }这段代码看起来简单,但实际使用时有个问题:标定系数scale在断电后会丢失,每次上电都要重新校准吗?我的做法是:把标定好的scale值写入EEPROM(STC系列单片机内部有EEPROM),每次上电时先读出来,这样只需要出厂时校准一次,以后每次开机就能直接用。有些设计干脆用预设的固定系数,省去校准流程,但那样误差会比较大,因为不同传感器的灵敏度差异可能高达百分之几。
3.4 数码管动态扫描与显示处理
数码管显示部分用定时器中断做动态扫描。我开了一个定时器0中断,中断频率设为1ms,每次中断刷新一位数码管。显示内容放在一个全局变量数组里,主循环只要更新这个数组即可,显示和称重逻辑互不干扰。
unsigned char code digit_table[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; // 0-9共阴段码 unsigned char display_buf[4]; // 待显示的数字,低地址对应左边第一位 void Timer0_ISR(void) interrupt 1 { static unsigned char pos = 0; // 先消隐 P2 = 0xFF; // 位选全部关闭(假设低电平有效,这里根据实际电路调整) // 放置段码 P0 = digit_table[display_buf[pos]]; // 选中当前位 P2 = ~(0x01 << pos); // 低电平选中,根据实际共阴驱动调整 pos++; if(pos >= 4) pos = 0; }具体到共阴数码管的段码值,0x3F对应数字0,这个应该很熟。显示小数点和单位的话,可以再定义一个带小数点的段码数组,在需要显示小数点的那一位单独控制。
这里有个容易出问题的细节:动态扫描时,如果位选切换和段码更新没有做到先关位选再更新段码,就会出现“拖影”现象,也就是一位数字轻微显示在相邻位上。正确顺序是:先关闭所有位选(消隐),再更新段码,最后打开当前位选。
3.5 按键处理与功能逻辑
三个按键我分别定义为去皮、校准、蜂鸣器开关。按键扫描放到主循环里,用状态机的方式实现短按检测,避免阻塞主循环。这里我分享一个实用的按键编程思路:不需要延时消抖,而是定时中断每10ms扫描一次按键,连续两次检测到同一状态才认为按键有效。这种方式不阻塞CPU,称重实时性更好。
功能逻辑上,去皮键的作用是记录当前AD值为零点,这个比较直观。校准键的设计我做了个特殊处理:长按3秒进入校准模式,在校准模式下数码管会显示一个提示值,按一下去皮键加一定的校准重量,按一下校准键减一定的校准重量,再次长按校准键保存退出。这个交互在实物上调试起来比较方便,不用每次改代码来调整系数。
4. Proteus仿真调试与常见问题排查
4.1 仿真环境搭建
Proteus里做这个仿真,元件清单大致包括:AT89C51或STC89C52、HX711(Proteus库里有这个模型)、称重传感器模型(Graph Based Analogue Sensor或直接用两个可调电阻模拟电桥输出变化)、SEG4B共阴数码管、按键、蜂鸣器、晶体管等。
仿真和实物的一个主要区别在于:HX711的Proteus模型是一个比较理想化的模型,不会模拟实际芯片的噪声和零点漂移。所以仿真时数据会很稳定,但实物上读到的数据肯定会有波动。这也是为什么一定要把滤波算法写好的原因之一。
4.2 HX711模型在仿真中的连接
在Proteus中,HX711模型的引脚和实物模块一致。传感器部分我用了一个滑动变阻器来模拟电桥的不平衡输出:把滑动变阻器跨接在E+和E-之间,滑动端接到A+或A-的其中一个输入端,另一个输入端接在E+和E-的分压点上。通过改变滑动变阻器的位置,可以模拟传感器受力后输出电压的变化。
这样仿真时有一个好处,你可以直接观察AD值是否随着输入电压变化而变化,很方便验证接线和程序的正确性。如果程序能正常显示数字变化,说明时序是通的,再去排查硬件问题就更有针对性。
4.3 仿真中常见的三大类问题
第一类是数码管不亮或显示乱码。这个首先检查共阴和共阳段码是否匹配。SEG4B是共阴管,用共阳的段码表结果就是乱的。其次检查位选驱动方式,Proteus里如果没有加三极管,直接用GPIO输出位选信号,要注意电平逻辑是否和硬件一致。
第二类是按键无效。Proteus里按键按下时默认是高电平还是低电平,取决于按键模型和上拉/下拉电阻的设置。如果程序检测低电平触发但仿真里按下变成了高电平,自然就没反应。这个排查起来很简单,用虚拟示波器或电压探针看引脚电平变化即可。
第三类是重量值不动。比如滑动变阻器调节后,数码管显示的值始终不变。这种情况多半是HX711的时序没有通过,程序卡在等待DT变低电平的while循环里。在Proteus仿真中,HX711的DT信号需要SCK脉冲的配合才会变化,如果程序里初始化时SCK电平状态不对,DT可能一直为高或一直为低,程序就死循环了。
4.4 仿真和实物的关键差异
如果只做仿真,很多东西会被“完美”二字带过去。但实物调试时你会遇到仿真完全遇不到的问题:电源纹波导致的读数漂移、传感器受力非线性、按键抖动更严重、蜂鸣器干扰、数码管亮度不均、导线接触不良导致的数据偶尔跳变。所以我的建议是:先用仿真把程序逻辑跑通、把电路功能验证清楚,然后再焊实物,这样可以把问题范围缩小很多,不至于一上来就硬件软件一起错,很难定位。
5. 实物调试、标定测量与误差修正
5.1 上电前的检查清单
实物焊接完成后,先别急着烧程序。用万用表依次检查:电源正负极是否短路、各芯片电源引脚电压是否正常(5V)、传感器E+和E-两端电压是否约为供电电压、HX711的AVDD是否正常、数码管各段是否有短路。这五步检查最多花五分钟,但能帮你避免烧芯片的悲剧。我见过太多人一上电数码管冒烟,最后发现是段选和位选搞反了。
5.2 标定步骤与数据记录
上电正常后,先烧录程序,然后进行标定。我的标定步骤是:
- 空载状态下等待读数稳定,按“去皮”键记录零点。
- 放上标准砝码(建议用5kg,尽量接近满量程,这样标定出来的线性更准)。
- 按“校准”键,程序记录当前AD值并计算系数,存入EEPROM。
- 取下砝码,检查零点是否回到0,如果不是,再按一次去皮。
- 分别放上1kg、2kg、3kg、5kg砝码,记录显示值,画出误差曲线。
这个过程中我实测到的数据大概是这样的(不同传感器会有差异,仅作参考):
| 标准重量(g) | 显示重量(g) | 绝对误差(g) |
|---|---|---|
| 0 | 0 | 0 |
| 1000 | 1001 | +1 |
| 2000 | 1999 | -1 |
| 3000 | 3001 | +1 |
| 5000 | 5002 | +2 |
整体误差在正负2克以内,对于10KG量程的家用秤来说,这个精度已经可以接受。如果误差偏大,往往不是程序问题,而是机械结构问题,比如传感器安装面不平、受力点偏离中心、螺丝没有拧紧。
5.3 误差来源分析与修正方法
电子秤的误差来源大致有三个:传感器本身的非线性误差、ADC转换的量化误差、机械安装造成的受力不均匀。
HX711的24位ADC量化误差其实非常小,基本可以忽略。真正影响大的是传感器非线性。悬臂梁式传感器在满载时非线性误差一般在0.02%到0.05%之间,对应10KG也就是2到5克。如果你发现满载时误差明显增大,可以考虑使用分段标定:在0-2kg、2-5kg、5-10kg各取一个标定点,然后程序里根据重量区间选择不同的标定系数。这个方法在很多工业仪表里叫“多点校正”,能有效降低非线性误差。
机械安装方面,传感器的一端固定,另一端受力,一定要保证受力方向垂直于传感器表面,不能有侧向力。安装螺丝不要拧得太紧导致传感器变形,也不要太松导致晃动。这个在实物的稳定性测试里表现得非常明显。
5.4 关于HX711的速率和稳定性进一步优化
如果你觉得默认10Hz的采样速率不够用,可以修改HX711的速率选择引脚RATE。当RATE接VCC时,输出速率10Hz;接GND时,输出速率80Hz。很多模块上这个引脚是悬空或通过跳线帽配置的。需要指出的是,提高采样速率会略微增加噪声,但不一定能提高精度。对于静态称重来说10Hz完全够用,80Hz更多用于动态称重或峰值捕捉场景。
稳定性方面,除了滤波算法,还可以做软件上的零点跟踪:当重量值在一段时间内小于某个阈值(比如5克)时,自动把它归零。这个功能在实物上体验很好,可以避免零点温漂导致的微小读数变化。但如果要做合法计量器具,这个功能允许与否就要看计量标准的要求了,作为学习设计,加上它有助于理解自动归零的原理。
6. 实际使用体验与代码维护心得
整套系统调试完成后,我把它放在办公室用了一个多星期,每天称一些小物件。体会最深的有三个点:
第一个是去皮功能的使用频率远高于校准功能。凡是称容器、称包装物,都会用到去皮。所以去皮键一定要好用,按下去响应要快,反馈要清晰。我在程序里加了按键触发时的短暂蜂鸣提示,这个细节在体验上非常加分。
第二个是长期运行后,传感器会有一点零漂,但幅度不大,一般几克以内。所以设计成每次上电自动去皮是个很实用的功能。我在上电初始化时,会自动等待传感器稳定,然后将当前值置为零点,这样用户不用每次开机都手动按去皮。
第三个是显示器上的单位标注。因为SEG4B只有四位数字,无法显示“kg”或“g”的字母,我用了两个LED灯来指示单位:一个绿LED亮表示克,一个红LED亮表示千克。按一下单位切换键,可以在克和千克之间切换显示,LED随之变化。这个小功能虽然简单,但让整个作品完成度提升了一个档次。
代码维护方面的一个心得是:一定要把标定参数(零点、系数)和程序逻辑分离。所有需要根据实际硬件调整的参数集中放在一个config.h头文件里,传感器换了、量程换了,只需要改这个文件和重新校准一次,程序主体完全不用动。这样在多版本迭代时省了很多事。
最后再分享一个可能会帮到你的小技巧:在调试阶段,把HX711读到的原始AD值通过串口打印到电脑上,观察它的变化,比只看数码管上的数字高效得多。你可以直观地看到在静止状态下的噪声幅度、在加载瞬间的跳变过程、按键按下时是否对AD值产生了干扰。用串口调试助手配合波形显示工具,很快就能定位问题到底出在传感器、电路还是程序上。我在前几次做类似项目时靠着这个方法省了大量排查时间,强烈建议你也试试。
本文还有配套的精品资源,点击获取