多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

基于STM32与HX711的智能计价电子秤完整设计与实现

基于STM32与HX711的智能计价电子秤完整设计与实现 前阵子帮开小卖部的亲戚做了一个基于STM32的智能计价电子秤。起初我心想称重乘单价而已能有多复杂等真正把方案落地才发现从传感器信号链、24位ADC、标定算法到按键状态机每一环都有坑。下面把这套项目的完整方案整理出来硬件怎么选、电路怎么搭、固件怎么写、标定怎么做、实测精度如何以及我踩过的几个典型坑一次性讲透。不论你是做嵌入式课程设计、毕业设计还是打算入门STM32的信号采集与处理这套方案都是一个可以完整复现的练手项目。1. 从称重到计价需求拆解与整体方案选型1.1 为什么传统电子秤满足不了这个场景传统电子秤只能显示重量。市场摊位要算钱要么卖家用笔算要么按计算器速度慢还容易出错。智能计价电子秤要解决的问题其实就三个准确称出质量、让卖家输入单价、自动算出金额并同时显示。看似简单但把这三个需求落到嵌入式方案上每条都有几个难点。称重要准意味着模拟前端要有足够的分辨率和抗干扰能力单价输入要顺手意味着按键和显示交互要合理金额计算看着简单但涉及小数和重量实时刷新处理不好会出现跳字、抖字乃至计算错误。所以这个项目虽然看起来不就是个秤实际横跨了传感器信号调理、ADC采集、滤波算法、人机交互、定点数运算和低功耗设计等多个嵌入式基本功非常适合当作一个完整的练手项目来做。1.2 方案选型为什么是STM32F103C8T6加HX711我这次选的组合很常见STM32F103C8T6做主控HX711做称重ADC。先说主控。F103C8T6是Cortex-M3内核主频72MHzFlash 64KBRAM 20KB价格便宜生态成熟标准库和HAL库的例子一搜一大把对课程设计和毕业设计来说资料足够出问题也好排查。更重要的是它的外设接口足够灵活I2C、SPI、UART、GPIO都是现成的后续想加蓝牙模块、WiFi模块、微型打印机都不会被接口卡住。再来说HX711。它是专为电子秤设计的24位Sigma-Delta ADC内部集成了可编程增益放大器增益可选32、64、128带两个差分输入通道数据输出速率10Hz或80Hz可调。通俗点说它就是把称重传感器输出的微小电压信号直接放大并数字化输出24位数字结果然后通过两线串行接口送给单片机。两线接口意味着单片机端只占用两个GPIO无论用标准库还是HAL库都很容易驱动。为什么不直接用STM32内置ADC这个问题后面专门讲先放一个简单结论STM32F103的12位ADC直接采样称重传感器信号分辨率差了太多达不到计价秤需要的精度。1.3 技术指标与功能清单动手之前先明确指标不然后面验证无从下手。这套方案我定的设计指标是最大称量5kg显示分辨率1g综合误差±2g以内标定后、常温条件下采样刷新率大约10次/秒单价输入范围0.00元到99.99元/500g金额显示范围0.00元到999.99元附加功能去皮、清零、超量程报警、开机自动回零功能清单对应下来就是一个5kg量程的电阻应变式称重传感器、一块HX711模块、STM32F103C8T6最小系统板、一块0.96寸OLED显示屏、一个4x4矩阵键盘、一个蜂鸣器外加电源和结构件。整机成本大约在五六十元以内作为课程设计或者毕业设计完全够用。这里补充一个会影响后面的设计决策单价按每500g输入还是按kg输入直接决定计价计算逻辑。国内菜市场习惯按斤标价电子秤行业标准里单价和金额也按每500g填报我按每500g处理代码里统一用分做整数运算避免浮点数误差。这一点在计价模块会展开讲。2. 称重传感器与HX711信号链上每一环都不能省2.1 电阻应变式传感器是怎么把重量变成电信号的称重传感器内部是电阻应变片贴在弹性体上。当被测物体压到弹性体时弹性体发生微小形变应变片电阻值跟着变化四片应变片搭成惠斯通电桥把电阻变化转换成差分电压输出。用大白话说你在秤盘上放东西传感器会吐出一个很小的电压差这个电压差正比于重量。但问题在于这个电压实在太小了。以我用的5kg量程传感器为例激励电压5V时满量程输出约5mV也就是说每克重量大约对应1微伏的电压变化。1微伏什么概念普通数字万用表的毫伏档都很难稳定分辨。所以这个信号必须经过高精度差分放大和24位ADC数字化才能被单片机使用。这就是HX711存在的意义。2.2 HX711的关键参数解读HX711是在称重领域里非常经典的一颗ADC芯片几个关键参数我列出来分辨率24位无失码内置PGA增益32/64/128可选差分输入范围±20mV增益128时数据速率10SPS/80SPS可选工作电压2.6V到5.5V通信接口2线数字接口重点解释两个容易被忽略的点。第一是输入范围。增益128时差分输入范围是±20mV也就是说传感器满量程5mV只占了ADC量程的四分之一对应数字码值大约在200万左右。按5kg满量程分摊每克重对应约400个码值做1g显示分辨率绰绰有余甚至显示0.1g也有余量。第二是采样率。HX711默认的10SPS模式噪声最小适合静态称重80SPS模式下速度变快但噪声会明显变大。电子秤本质是静态称重场景我建议固定用10SPS不要为了显示刷新快去切80SPS那是饮鸩止渴。2.3 传感器接线颜色和HX711引脚说明市面上的称重传感器线色基本统一但也见过乱标的接线前最好用万用表确认一下红E电桥正激励黑E-电桥负激励白A或S信号正绿A-或S-信号负HX711模块上常见的标注是E、E-、A、A-、B、B-。A通道接称重传感器B通道空着这个方案用不到。HX711与STM32的接线只需要两根线我分配如下HX711引脚STM32引脚说明VCC3.3V模块供电和MCU同电源域GNDGND共地DOUTPB0数据输出空闲时为高PD_SCKPB1时钟输入由主机驱动3. 电源、显示与按键硬件设计里的细节决定成败3.1 电源方案LDO为什么比DC-DC更适合这套电路智能计价秤对电源噪声很敏感。HX711内部的24位ADC精度很高对供电纹波特别敏感如果把5V直接通过DC-DC降压给HX711供电开关电源的纹波会直接耦合进称重结果表现为读数跳变。我的做法是整体用USB 5V输入再通过AMS1117-3.3转成3.3VHX711的VCC和AVDD都从3.3V取模拟电源路径上再加0.1uF和10uF电容组合。另一个细节是传感器激励。很多现成的HX711模块在板子上已经做了传感器激励引脚的引出并放了去耦电容所以只要VCC本身干净激励就不会太差如果自己画板建议让E从经过LC滤波的模拟电源路径取并且靠近HX711摆放去耦电容。传感器激励电压波动会直接影响输出信号用LDO稳住激励是提升稳定性的关键一步。3.2 显示器件选型OLED还是LCD1602显示我最后选了0.96寸I2C接口OLEDSSD1306驱动。原因有三第一个是显示内容多OLED可以自定义显示三行以上信息把重量、单价、金额和状态提示放在同一屏LCD1602只有两行信息排版很憋屈。第二个是I2C接线省引脚SDA和SCK两根线就能搞定留给按键和HX711的IO很充裕。第三个是带字库的LCD模块在5V和3.3V电平转换上容易出问题OLED在3.3V系统里是天然配套。OLED驱动代码网上很多这里不展开但要注意SSD1306是屏幕刷新率不高的器件不要在每次重量变化时都全屏清空重绘应该只在数值变化时更新对应区域否则会出现明显闪烁还会拖慢主循环连累HX711的读取时序。这个问题我在后面的踩坑记录里还会具体说。3.3 矩阵键盘和状态提示按键我用了4x4矩阵键盘占用8个GPIO。为什么不用独立的键盘芯片因为矩阵键盘的扫描逻辑本身就是一个很好的嵌入式基本功状态机、消抖、组合键都能练到而且成本最低。键位布局我定义为123清零456去皮789单价0.确认退格单价键进入单价输入模式确认键结束输入回到称重模式。这个交互逻辑不是随便定的它模拟了市面上商用收款秤的通用操作方法用户不需要看说明书就能上手。蜂鸣器接在PB5用于按键反馈和超载报警。超载判定条件很简单滤波后的重量超过最大量程的105%蜂鸣器连续鸣叫显示屏显示OVER。4. 固件实现从HX711驱动到计价状态机的完整逻辑4.1 HX711数据读取别小看两线时序HX711的读取时序是单片机不断读取DOUT引脚当DOUT从高电平变为低电平时表示一次24位转换数据已就绪。此时单片机在PD_SCK上产生24个上升沿每个上升沿从DOUT移出一位数据MSB先出数据是24位二进制补码。读完24位后再产生第25个上升沿这个脉冲用来设置下一轮转换的增益和通道——发一个脉冲表示下次按A通道128倍增益工作如果发两个脉冲是按B通道32倍发三个脉冲是A通道64倍。核心代码大概是这样的int32_t HX711_Read(void) { int32_t value 0; uint8_t i; // 等待数据就绪DOUT由高变低 while (HAL_GPIO_ReadPin(HX711_DOUT_GPIO_Port, HX711_DOUT_Pin) ! GPIO_PIN_RESET); // 读取24位数据 for (i 0; i 24; i) { HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_SET); value (value 1) | HAL_GPIO_ReadPin(HX711_DOUT_GPIO_Port, HX711_DOUT_Pin); HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_RESET); } // 第25个脉冲设置下一次为A通道、增益128 HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_RESET); // 24位补码扩展为int32 if (value 0x800000) { value | 0xFF000000; } return value; }这里面有三个坑。第一个是等待DOUT变低前如果主循环里还干别的事比如刷OLED很可能错过DOUT下降沿所以要么在定时中断里读要么保证主循环调用这个函数的周期小于HX711的100ms转换周期。第二个是除了第25个脉冲之外不要在24个数据位之间额外增加延时但每位移完之后PD_SCK拉低的时间要大于1微秒才能满足HX711时序要求。第三个是补码扩展必须做否则读出来的负值会被当成正数去皮和回零全都会乱套。4.2 滤波算法称重读数为什么不能直接展示即使HX711质量不错原始读数也避免不了轻微的跳动。原因包括传感器机械振动、电源纹波、环境电磁干扰。如果直接把原始值换算成克数显示屏幕末位会一直跳顾客看着就不专业。我采用的组合滤波策略先用一个5点中值滤波剔除明显干扰再对中值滤波后的序列做10次滑动平均。中值滤波的作用是去掉脉冲型干扰比如不小心碰到秤台产生的冲击滑动平均的作用是平滑随机噪声。这个组合在静态称重场景下效果很好代价是响应速度慢了一点大约0.5秒后读数稳定对计价秤来说这个延迟用户基本无感换来的是干净的显示划算。4.3 标定算法把ADC码值翻译成克数HX711输出的是数字码值跟克数之间没有必然换算关系。每只传感器的灵敏度、安装偏差、增益误差都不一样所以必须标定。标定其实就两步测零点和测已知重量。第一步空秤状态下读滤波后的码值记为zero_raw。第二步放一个已知重量的砝码比如1000g标准砝码读滤波后的码值记为span_raw。那么每克对应的码值就是scale_per_gram (float)(span_raw - zero_raw) / 1000.0f;之后任意时刻的重量计算公式weight_g (float)(raw - zero_raw - tare_raw) / scale_per_gram; if (weight_g 0.5f) weight_g 0.0f;tare_raw是去皮时记录的空容器读数。标定参数需要保存在掉电不丢失的地方我用的是STM32内部Flash的最后一页F103C8T6有64KB Flash最后1KB专门留出来通过HAL_FLASH_Program写入上电时再读出来。标定流程在固件里做成一个隐藏菜单长按单价键5秒进入标定模式第一次提示放空秤按确认第二次提示放1000g砝码再按确认标定完成后自动退出参数写Flash。这个交互不要学那些把标定代码写死、只能改源码重烧的做法因为传感器用久了会疲劳秤体搬运或更换传感器后都需要重新标定留一个操作入口非常实用。4.4 计价计算用整数避免浮点误差再次强调金额计算全部用整数。单价的单位是分重量单位是克如果约定单价是每500g的价格那么金额分的计算公式是uint32_t amount_cents (uint32_t)weight_g * price_cents / 500;举例重量350g单价8.00元/500g单价换算成分是800。350乘以800除以500等于560也就是5.60元。全部是32位整数运算没有浮点精度问题。为什么不用float单次浮点看不出问题但如果连续多次计算再累加单精度只有约7位有效数字商用计价场景不允许这种隐性问题。这个思路在嵌入式里就叫定点化——能用整数就不用浮点。重量显示也做了处理显示值保留一位小数但计价用的是未舍入的内部值避免出现显示8.0克但金额按8.9克算的乌龙。显示和计算分离这也是智能秤和普通读个数自己算秤的重要区别。4.5 按键状态机从输入到称重的切换逻辑固件整体用一个简单状态机组织主要状态有三个称重状态默认实时显示重量、单价、金额单价输入状态数字键输入单价显示区显示当前输入标定状态按标定流程引导用户操作按键消抖用20ms延时加电平确认。单价输入用字符串拼接再转整数的方式实现限制最多5位对应99.99元输入过程中.键作为小数分隔控制。确认键结束输入并保存单价同时在非易失存储里也存一份下次开机还会记住上次的单价。这里有一个容易忽略的交互细节去皮后重量显示为0但内部tare_raw有了偏移量此时如果放上商品计算逻辑要同时考虑zero_raw和tare_raw千万别把去皮偏移覆盖掉零点偏移否则重新开机后秤会飘。5. 实测数据、误差归因和几个典型踩坑记录5.1 实测精度数据标定完成后我连续测了几组数据。用一套实验室电子天平分辨率0.01g做参照结果如下参照重量(g)本机显示(g)误差(g)0.000.00.010.0010.10.1100.0099.8-0.2500.00500.20.21000.001000.40.42000.001999.6-0.45000.005003.03.0前段线性很好接近满量程时误差明显增大。这个趋势是正常的传感器本身有非线性满量程附近灵敏度会略有变化。如果要求全量程都做到±2g就需要做多点分段标定比如每500g取一个标定点做线性插值而不是只用一点斜率。对菜市场计价场景来说常见称重区间在100g到2kg这个精度已经足够。5.2 踩坑一HX711供电纹波导致读数乱跳第一版我用了一个3.3V的DC-DC模块直接给整板供电结果空秤读数波动超过±30个码值换算成重量就是乱跳。排查过程是先怀疑传感器换了一只新传感器问题依旧再怀疑滤波算法去掉滤波直接读原始值跳动依旧最后用示波器看供电发现DC-DC的开关纹波大概有50mV左右而HX711的模拟电源对噪声非常敏感。换成AMS1117线性稳压后空秤波动降到±3个码值以内问题解决。这个坑说明一个原则模拟前端和数字电路的电源必须分开处理。至少做到HX711的VCC前面加磁珠或者LC滤波PCB布局上模拟地和数字地单点汇接。5.3 踩坑二标定时砝码放偏导致标定失真标定时我把1000g砝码随手放在秤盘边上标定完成后发现小重量段全部偏小。原因是传感器对偏载很敏感同一重量放在秤盘中心和边缘输出不一样。这个现象叫偏载误差正规电子秤会有四角调整但我们的简易结构做不到所以标定时机必须保证砝码放在秤盘中心并且用同一个参考位置来做。实测同样1000g砝码中心位置和边缘位置的码值差大约0.3%反映到重量上就是3g。5.4 踩坑三机械结构引起的回零慢秤盘靠四颗螺丝固定在传感器上最初螺丝拧紧程度不统一导致每次放上重物再拿走后回零要等好几秒甚至回不到原来位置。原因有两个一是传感器弹性体受力不均产生了应力残留二是螺丝孔处的结构变形导致迟滞。处理办法是四颗螺丝按对角线顺序、用相同力矩拧紧同时给秤盘和传感器支架之间加了一个薄的硅胶垫吸收装配应力。改完之后回零恢复时间缩短到2秒以内。5.5 踩坑四OLED刷新闪烁和局部更新一开始我在主循环里每次读到新重量就全屏清一次再重绘结果OLED刷新频率跟不上屏幕一直在闪。SSD1306的I2C速率有限全屏64x32的缓冲区刷新一次要几十毫秒10Hz的显示更新叠加这个耗时主循环被卡死HX711的读数据时序都受影响。后来改成只在数值变化时更新对应文字区域刷新时间大幅缩短HX711读取也能稳定在10Hz周期内完成。这也是一个提醒显示刷新和传感器采集在裸机方案里是互相争抢CPU的两个任务一定要估算好各自的耗时别让它们互相拖累。6. 这套方案还能怎么扩展以及我个人的一些建议6.1 扩展方向通信、存储与操作系统如果做毕设想出彩这套方案可以往几个方向延伸。第一个是加通信模块比如HC-05蓝牙或者ESP8266 WiFi把每次称重的重量、单价、金额、时间打包上传到手机App或者小程序实现店铺销售数据统计。第二个是加微型热敏打印机打印含二维码的购物小票这样整套系统就非常接近商用的POS秤。第三个是引出串口和计算机做上位机联调展示重量波形和标定曲线这个方向适合往嵌入式上位机的方向延展。软件层面的扩展首推FreeRTOS。当前裸机状态机已经够用但一旦加了蓝牙、打印、存储、屏幕多个任务裸机循环就会变得难以维护。用FreeRTOS之后HX711采集、显示刷新、按键扫描、通信协议各自独立任务用队列传递数据代码结构会清晰很多。我在这个项目验证完裸机版本之后也顺手把FreeRTOS版本跑通了整体线程模型就是四个任务加两个消息队列开发效率提升明显。6.2 模块化设计把称重模块独立出来我的最终版本把电路分成了两块板一块是主控板包含STM32、OLED、键盘、蜂鸣器另一块是称重板包含HX711和传感器接口。两块板之间用排针排母连接。这个设计一开始看起来多此一举但后来发现好处很多一是调试时可以单独用USB转TTL工具直接测HX711原始码值不用带键盘屏幕二是称重板可以复用到其他项目比如改造成智能配料秤、液体计量器三是主控板升级换代不需要动模拟部分降低了重新验证的成本。6.3 个人心得做这类项目时间和精力怎么分配最后说点个人的体会。做完这个项目最大的感受是整个方案的难点根本不在STM32本身而在于模拟信号处理和机械结构。打个比方STM32就像一个人学习能力强什么活都能干但HX711和传感器才是真正出力气的地方它们的电源、布线、安装方式决定了最终称重准不准。很多人做电子秤项目把精力全放在代码上结果程序写得很花哨秤却连基本精度都达不到这就是本末倒置。如果你也想复现这个项目我的建议是先把HX711原始码值通过串口打印出来用肉眼观察空秤和加载时的码值变化把信号链路调稳了再开始写显示、计价、状态机这些上层逻辑。硬件不稳定软件写再多也是空中楼阁。反过来只要传感器信号稳定后面的逻辑都是水到渠成的事。
返回列表