这阵子帮人做了个基于STM32的智能计价电子秤方案从硬件选型、代码驱动到最终标定全程走了一遍。正好不少朋友在问这个题目能不能做成毕设、能不能真正商用我干脆把这个项目的完整实现思路和我实际踩过的坑一起整理出来。做这个东西的初衷很简单市面上几十块钱的厨房秤只能看重量遇到按单价算钱的场景水果摊、快递计费、小作坊配料根本不够用。而STM32方案成本低、外设全、资料多做一个能称重、能去皮、能通过按键设置单价并自动计算金额的智能电子秤是性价比极高的嵌入式练手项目。这套方案既可以作为毕业设计交差也能扩展成真正可用的产品原型无论你是刚学完51单片机想往上走还是准备找嵌入式相关工作想攒项目经验都值得完整走一遍。我在实际做的时候最初版本大概花了三天搭完硬件和基础驱动后面一个月都在调精度、处理各种边界情况。这篇文章会把整个项目的需求拆解、硬件接线、关键代码、定标校准、实测数据、踩坑记录全部讲清楚涉及到的每一处为什么这样做我都会说明白尽量让零基础的读者也能照着做出来。1. 项目需求拆解与系统方案为什么选STM32方案1.1 功能需求从使用场景反推智能计价电子秤的智能并不神秘本质就是把三个动作合并成一个感知重量、读取单价、计算总价。我先把需求理成一张表后续所有设计都围绕它展开。功能项需求描述实现难度称重最大量程5kg显示分辨率1g中去皮放容器后一键清零低单价输入支持0.1元步进调整中金额计算总价重量×单价实时更新低超量程报警超过5kg时蜂鸣/闪烁提示低显示重量、单价、金额三行同屏中围绕这个需求表系统天然分成四个模块传感器采集模块称重传感器HX711、主控模块STM32、人机交互模块OLED按键、电源模块。每个模块之间的接口非常清晰这也是这个项目适合做毕设的原因——它完整覆盖了嵌入式系统采集-处理-输出的闭环但又不需要你啃下射频、网口这些偏门内容。1.2 主控选型STM32F103C8T6是当前最优解很多人纠结要不要上F4、H7或者干脆用ESP32。我的观点是这个项目用STM32F103C8T6是性能和成本的最优平衡点。先说为什么不用51单片机。51的GPIO操作虽然简单但处理定时器、I2C、外部中断这些外设时要各种软件模拟而且Flash只有8KB级别一套OLED显示驱动加上HX711时序代码就已经快塞满了更别提后续还要加菜单逻辑、浮点运算。STM32F103C8T6虽然价格也就十几块钱但主频72MHz、64KB Flash、20KB RAM跑这些任务绰绰有余外设的资源也丰富得多。再解释一下为什么不用ESP32。ESP32的Wi-Fi/蓝牙功能在这个项目里属于冗余而且它的ADC精度并不好做称重这类高精度模拟量采集并不合适。STM32的库函数生态非常成熟标准外设库、HAL库、LL库资料铺天盖地出了问题几乎都能搜到答案。对于毕设或者项目练手资料多本身就是最大的开发效率保障。这个项目的核心链路是模拟小信号采集所以重点应该在传感器和ADC前端而不是主控本身。STM32在这里更多承担调度者角色通过GPIO模拟时序从HX711读数据做滤波处理换算重量驱动OLED显示扫描按键状态。这块板子完全撑得住。1.3 称重方案定型电阻应变式传感器与HX711的经典组合称重传感器我最开始试过直接用STM32内置ADC读电桥输出结果精度完全不可用。原因很简单5kg量程的电阻应变式传感器满量程输出只有约10mV激励电压5V、灵敏度2mV/V时你要在10mV范围里分辨1g对应的0.002mV这已经超出了STM32内置12位ADC的实用精度范围3.3V参考电压下1个LSB约0.8mV。HX711就是为解决这个问题而生的它是一颗24位高精度ADC芯片内置一个低噪声可编程增益放大器PGA增益可选32/64/128专为桥式传感器设计。用128倍增益时满量程5kg对应的ADC输出范围很宽1g的重量变化换算到ADC码值上清晰可见。它的通信接口只有两根线DOUT和PD_SCK跟SPI很像但更简单非常适合单片机用IO模拟时序去驱动。传感器选型上我建议直接买铝制单点式称重传感器量程选5kg或10kg。这类传感器本身就是为电子秤设计的四个角有固定孔中间的受力柱是称重台面支撑点安装起来比悬臂梁式省心得多。要注意的是传感器额定输出常见有1mV/V和2mV/V之分购传感器时留意一下规格量程留出20%以上余量比如你想称5kg就选6kg或10kg传感器这样线性度和重复性会好很多。2. 硬件接线与电路设计的关键细节2.1 最小系统板选型与常用外设接线总览开发方式我推荐直接买STM32F103C8T6核心板不要自己画最小系统。因为等你真正要量产或者交设计文档的时候再画PCB不迟前期快速跑通功能最重要。核心板自带了8MHz晶振、复位电路、LDO稳压、USB转串口插上USB线就能烧录调试。接线方案如下表这里有个容易忽视的地方HX711的传感器激励电压E和E-接的是外部5V而逻辑供电VCC建议接3.3V。原因是HX711的内部参考电压经稳压后数字接口电平与VCC相关接3.3V可以保证和STM32的GPIO电平直接匹配不需要电平转换。HX711引脚接STM32/电源说明VCC3.3V数字逻辑供电DVDD3.3V数字电源引脚可接VCCDOUTPB0数据输出PD_SCKPB1时钟输入E5V传感器激励正E-GND传感器激励负SA传感器信号正接HX711 AS-A-传感器信号负接HX711 A-OLED我用的0.96寸SSD1306接口是I2C把SDA接PB7、SCL接PB6用的是STM32的硬件I2C就是I2C1的两个引脚。按键做三个一个去皮键、一个加价键、一个减价键分别接PA0、PA1、PA2配上10kΩ下拉电阻到地按下时读到高电平。2.2 电源系统的常见坑模拟电路对纹波极其敏感这个项目最容易被忽视的就是电源。HX711的模拟输入要分辨微伏级别的差分信号如果电源纹波大读数就会上下乱跳。我第一次调试时直接用USB口的5V给传感器激励供电结果空秤读数稳定在±20g摆动完全没法用。后面改成这样解决的USB的5V先经过一个AMS1117-3.3给STM32供电传感器激励用一个单独的5V稳压源或者直接从USB的5V经过LC滤波再给传感器同时HX711的电源引脚并联一个100uF电解电容和一个0.1uF瓷片电容去耦。这样一来读数马上就稳了许多。如果你想让模拟部分更干净可以加一颗隔离型的DCDC或者低噪声LDO给传感器独立供电但做项目的话LC滤波大电容基本够用了。还有一个细节传感器信号线和激励线最好双绞减少空间电磁干扰尤其是旁边有继电器或者电机这种感性负载的时候。2.3 安装结构对精度的影响比电路更大这事我在教训里才深刻体会到。电子秤的传感器不是随便固定在桌面就行的传感器必须水平安装四条固定螺丝要均匀拧紧不能有某个角受力偏大。称重台面托板要放置在传感器受力柱的正上方重心尽量居中。如果台面四角受力不均同一个东西放在秤盘的左边和右边读数可能差别几十克。我实际做的时候用了块3mm厚的亚克力板当秤盘四角垫了四个橡胶脚垫然后中心位置有一个凸台压在传感器受力柱上这样大部分重力能垂直传递到传感器中心。虽然比不上商用秤的精密结构但调试下来四角差异控制在±3g以内作为教学或样品已经合格。3. 核心代码实现从HX711读取到金额计算全流程3.1 HX711时序驱动读懂24位补码是关键HX711的数据读取协议很简单但第一次接触的人往往会栽在数据格式上。官方时序是这样的当DOUT引脚变为低电平时表示转换完成、数据可读此时单片机给PD_SCK发送25个脉冲。前24个脉冲把24位数据逐位移出第25个脉冲用于设置下一轮转换的增益和通道默认128倍增益对应A通道这正是称重传感器常用的配置。我贴出成熟的读取代码框架配合注释说明uint32_t HX711_Read(void) { uint32_t buf 0; while (HAL_GPIO_ReadPin(DOUT_GPIO_Port, DOUT_Pin)); // 等待DOUT拉低数据就绪 for (uint8_t i 0; i 24; i) { HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_SET); buf buf 1; if (HAL_GPIO_ReadPin(DOUT_GPIO_Port, DOUT_Pin)) { buf; } HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_RESET); } // 第25个脉冲设置下一轮为A通道128倍增益 HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(SCK_GPIO_Port, SCK_Pin, GPIO_PIN_RESET); // 数据是二进制补码需要转换为有符号数 return buf ^ 0x800000; }这里有一个非常容易被忽略的点24位移出来的原始数据是补码格式最高位是符号位。如果直接把它当无符号数计算会出现空秤读数很大、放重物读数变小这种反直觉的现象。处理方式就是上面代码里的buf ^ 0x800000——把最高位翻转相当于把补码转成有符号数这样空秤约等于零点附近的小值加压时ID增大数值一正一负才符合直觉。3.2 读数滤波用去极值滑动平均兼顾响应速度和稳定性直接从HX711读到的单次数据即使在静态下也会有波动尤其是环境振动或者电源纹波大时。滤波策略我实测下来最有效的是去极值滑动平均每次连续采样N个原始值我取21个去掉其中的最大值和最小值剩下19个求平均。这样做能滤掉偶发的尖峰干扰而且比单纯的多重平均抗脉冲干扰能力更强。采样频率可以通过HX711的速率模式选择引脚RATE来设置接GND是10Hz接VCC是80Hz。我用的是10Hz因为80Hz虽然响应快但量化噪声明显增大静态读数会跳。采样循环和滤波逻辑如下#define SAMPLE_NUM 21 uint32_t raw_values[SAMPLE_NUM]; uint32_t HX711_Read_Filtered(void) { for (uint8_t i 0; i SAMPLE_NUM; i) { raw_values[i] HX711_Read(); delay_ms(5); } // 找出最大值、最小值 uint32_t max_val raw_values[0], min_val raw_values[0]; uint32_t sum 0; for (uint8_t i 0; i SAMPLE_NUM; i) { if (raw_values[i] max_val) max_val raw_values[i]; if (raw_values[i] min_val) min_val raw_values[i]; sum raw_values[i]; } return (sum - max_val - min_val) / (SAMPLE_NUM - 2); }3.3 重量与金额换算零点校准和标定系数的数学基础拿到稳定滤波的ADC值之后把它换算成重量本质上是一元线性回归重量(克) (当前ADC值 - 零点ADC值) / 每克ADC值两个参数需要标定零点ADC值空秤读数和每克ADC值标定系数。前者在开机初始化时采样一次就行后者需要一个标准砝码。我这里的做法是开机后空秤状态下取20次滤波读数求平均作为zero_offset然后放一个500g标准砝码测出adc_500g于是float scale (float)(adc_500g - zero_offset) / 500.0f; // 每克对应的ADC码值 float weight (float)(filtered_value - zero_offset) / scale; // 当前重量单位g金额计算就很简单了但有一个单位陷阱。市面上的电子秤通常以元/斤或元/千克为单位我建议代码里统一用元/500g即每斤的价格作为单价的用户输入单位然后显示的时候把克转换成斤来相乘float price_per_jin 5.60f; // 用户输入5.6元/斤 float weight_jin weight / 500.0f; float total_money weight_jin * price_per_jin;注意浮点数在单片机上做乘除没问题但显示到OLED上时最好把金额化为分整数比如int money_cents (int)(total_money * 100);然后拆出元和分来显示避免浮点转字符串时出现5.599999这种毛病。3.4 按键扫描与去皮逻辑状态消抖和长按连续调节按键处理看起来简单但设计不好会出现按一下数字跳好几下去皮键连击的情况。我用的方案是定时器每10ms扫一次按键连续读到3次相同电平才视为有效状态变化20ms以上消抖。加减价键做成短按每按一次单价±0.1元长按超过500ms则每100ms自动±0.5元方便从十几块快速调到几块。去皮逻辑有个细节要注意不能在每次读取循环里直接清零否则在读数的中间态会被反复清零。正确做法是检测到去皮按键按下后立即把当前滤波后的重量当作新零点也就是更新zero_offsetif (button_tare.pressed) { zero_offset HX711_Read_Filtered(); weight 0.0f; }这实际上把去皮和重新标零统一了容器放上去之后按一下系统就把当前重力视为0后面再放东西就只显示净重非常实用。4. 标定、精度控制与误差处理4.1 为什么必须做两点标定而不是直接读取说明书参数不少第一次做称重项目的人会有个疑问传感器和HX711都是现成的为什么不直接按官方例程里的固定系数转换原因在于传感器灵敏度有离散性同一批次都有±10%的偏差HX711内部放大器增益也有微小差异机械安装的应力分布会改变输出特性所以每个装配好的设备都必须单独做标定商用秤出厂前也都要逐台标定。我建议至少做两点标定如果追求更准可以做三点或四点。两点标定就是上面写到的一个零点和任意一个标准重量点算出线性系数。三点标定则是增加一个中间点计算两段不同斜率能修正传感器在小重量段的非线性但代码复杂度和标定工时要高不少。对5kg量程来说两点标定已经能把误差控制在±1%以内这在大多数应用场景是够用的。4.2 标定实操流程与数据记录标定一定要在机械结构完全固定、秤盘放置好之后进行别拆了外壳再标定否则装机之后系数全变了。我的流程是上电预热5分钟让传感器和HX711温度稳定。确保秤盘上没有任何东西按去皮键读取零点代码里记录zero_offset。放置500g标准砝码我用的是电子秤校验砝码没有的话用一瓶500ml矿泉水误差其实也能接受大概±2g左右。等待读数稳定记录ADC值算出scale。移除砝码再放上去重复3次确认重复性。如果重复性不好超过±3g先检查脚垫是否平整、螺丝是否松动再重新标定。实测一组数据给大家参考用的5kg传感器增益12810Hz速率空秤零点约820000标准重量(g)首次读数(g)二次读数(g)三次读数(g)最大误差(g)00101200201200200150050050149911000100010011000120002001200020022500050025001500024.3 温漂、蠕变和自动零点追踪这里有个坑是长期运行才会暴露出来的传感器和应变片的静态特性会随温度变化尤其是刚从冷环境搬到暖环境开机半小时内零点会缓慢漂移几十克。如果只是毕设演示这个问题不致命但要做成产品就必须处理。我采用的方案是自动零点追踪当系统检测到当前重量在±5g以内持续超过10秒时自动把当前值归零。这样即使传感器因温度漂移缓慢变化只要秤盘上没有物体系统就会慢慢回零显示不会一直停留在3g这种状态。注意去皮状态下不能启用自动零点追踪否则用户称重途中轻微晃动设备就可能被误认为是空秤而自动清零。具体代码类似这样放在主循环的最后int16_t display_weight (int16_t)(weight * 10); // 内部按0.1g分辨率判断 if (tare_enabled 0 display_weight -50 display_weight 50) { idle_timer; if (idle_timer 100) // 持续约1秒 { zero_offset HX711_Read_Filtered(); idle_timer 0; } } else { idle_timer 0; }5. 实测过程与踩坑记录5.1 显示协议验证OLED的I2C地址和显存刷新问题SSD1306这颗屏网上驱动代码很多容易踩的坑却有两个一是I2C地址是0x78还是0x7A。0.96寸OLED模块通常硬件将SA0引脚拉低地址是0x78即7位地址0x3C左移一位。如果读不到设备先量一下模块背面SA0和SA1的焊盘有没有被改动。二是OLED显存是1024字节128×64/8很多精简驱动为了省MCU内存选择页地址模式分页刷新。我建议用显存数组全量刷新虽然每帧要传1024字节I2C在400kHz速率下也就20多毫秒完全不影响体验但代码逻辑简单很多显示也不会出现刷新撕裂。STM32的硬件I2C有个经典毛病在F1系列上如果不加超时处理容易卡死在忙碌状态。实在不想折腾就直接用软件模拟I2COLED和HX711都靠GPIO翻电平性能在这个场景下没有区别。我最后是硬件I2C加250ms超时跑了两周没出问题供参考。5.2 读数漂移的排除过程从电源到线材再到机械应力有一次进行连续读数测试发现空秤读数每隔几十秒就会跳一下幅度十几克。排查链路是这样的先看是不是HX711的问题换了一块HX711模块故障依旧。再看是不是电源问题用电池单独给传感器供电居然还是跳。最后检查接线发现传感器信号线的一段靠近了STM32核心板的USB线上方USB线充电时产生了交变干扰。把信号线和电源线分开、重新整理线束之后问题消失。这个经历说明在模拟小信号系统里布线就是电气的机械结构整理走线和固定线束不是可有可无的事。给称重传感器信号线套铜箔屏蔽并单点接地是低成本高收益的做法。另外还有一次是传感器螺丝没上紧造成读数越来越慢地漂移那其实是机械蠕变不是电路问题。这种软故障最容易让人在代码里找原因找半天却没想到是结构件松动。标定前务必把所有机械连接处拧紧到扭力一致。5.3 超量程与异常输入处理代码层面要处理的边界条件也不少。超量程时我让蜂鸣器报警并在OLED上显示超出量程防止用户把5kg的东西硬压上去损坏传感器。单价输入限制在0.1到999.9元之间防止误触造成金额荒谬。去皮键要在重量稳定时才能生效避免抖动瞬间把波动值当新零点。金额显示我固定保留两位小数重量显示保留一位小数克这样在OLED有限的宽度上排版比较整齐。实测稳定从按下到显示更新的总延迟大约0.5秒主要受10Hz转换速率和滤波采样时间限制用户主观感受是即时响应的完全够用。5.4 烧录调试的体验优化串口打印和断点调试不能少开发过程中强烈建议在代码里保留一个串口调试输出开关通过宏定义控制。串口打印HX711原始ADC值、滤波后的值、标定系数、计算重量。在C8T6上没有多余UART的场景很罕见直接复用板载USB转串口芯片对应的PA9/PA10即可。串口打印给了问题定位极大的方便比如可以只打印原始ADC值来判断是传感器问题还是计费逻辑问题。如果你手上有ST-Link或者J-Link直接把SWD调试口接上在Keil里设断点、在线看变量比串口打印更好用。我实测下来这个项目用SWD调试的效率提升是肉眼可见的尤其是排查滤波数组越界、浮点溢出这类问题能直接定位到崩溃现场。6. 功能延伸与下一步扩展方向这套硬件平台做完电子秤之后还能顺理成章扩展出很多功能扩展空间也是这个项目作为毕设或产品的价值体现。第一加EEPROM存储标定参数。现在标定参数是存在代码里的每次重新烧录都要重新标定。加一颗AT24C02或者在STM32内部Flash里开一个扇区专门存zero_offset和scale开机自动加载这样换电池、断电重启后不用重新标定非常实用。标定时校准一下写完EEPROM再做校验读取确认无误后设备才算真正出厂。第二加蓝牙/Wi-Fi模块做数据上报。通过串口连接一个HC-06蓝牙模块或者ESP8266把称重数据传到手机App或小程序这在水果店结算、快递驿站场景里很有竞争力。技术上和主功能完全解耦相当于在现有串口打印的代码基础上换一个输出目标难度不高但给人的项目完整度印象完全不同。第三加打印功能。热敏打印机模块在电商上几十块钱就能买到串口指令基本兼容打一张带重量、单价、金额的小票整台设备就接近商用的电子秤了。注意打印时要记得把金额转换成字符数组并校验打印缓冲区的长度是否超出别让浮点转字符串这一步毁掉整单数据。我自己做完这版之后的最大感触是这个项目的代码量不算大核心驱动就三块——HX711、OLED、按键扫描但把所有边界情况都处理好、让设备真的稳定跑一周不带重启花费的精力和时间远超过写出第一版代码。做嵌入式项目往往是功能实现只是开始稳定可靠才是真正的分水岭。如果你照着这个方案做下来卡在某个环节的时候优先检查硬件连接和供电再去看代码逻辑不要一开始就怀疑是HX711芯片坏了。我做这个项目的分享就到这里希望这份完整的方案能帮你顺利跑通自己的智能电子秤。