ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于STM32的智能计价电子秤开发全解析:从称重传感器到标定算法

基于STM32的智能计价电子秤开发全解析:从称重传感器到标定算法 最近在整理过去做过的一个嵌入式项目翻到这套基于STM32的智能计价电子秤觉得挺有代表性的干脆把整条链路写出来。这个项目覆盖了传感器采集、信号放大、微控制器驱动、人机交互、标定算法和异常处理算是一个能完整跑通的嵌入式入门到进阶练手方案。无论你是准备做毕设还是想在职场上补一块硬件项目经验这套思路都可以直接套用。全文会从方案选型讲到最后的效果验证把核心代码、标定流程和踩坑记录都摊开说尽量还原我实际做这个项目的全过程。1. 项目整体设计与方案选型1.1 核心需求拆解先说说这个项目要解决什么问题。超市、食堂、快递站里常见的电子秤本质上就是一个称重传感器加上一个控制器再把结果显示出来。但如果只是做一台能称重的秤说实话没什么挑战性真正值钱的部分在于“智能计价”这几个字。我把需求拆成几个层次重量采集能稳定识别0到10kg范围内的物品精度目标做到±1g至少也要达到±5g的商用级水平。单价输入通过矩阵键盘输入商品单价单位是元/500g或者元/kg根据场景可切换。自动计价实时计算总价总价 重量 × 单价并且支持累加功能、去皮功能。显示交互LCD实时显示当前重量、单价、总价、状态提示。异常处理超量程报警、传感器异常提示、按键误触过滤。这个需求清单看起来很常规但真正做起来之后你会发现每个需求背后都牵扯出大量的硬件选型和软件架构问题。比如搭一个10kg量程的称重传感器满量程输出信号可能只有几毫伏到十几毫伏直接用STM32内置ADC根本没法分辨必须用外部高分辨率ADC芯片。再比如键盘的去抖和扫描方式直接决定了操作手感处理不好就让人很崩溃。1.2 主控方案对比为什么选了STM32选型这块我前后对比过三个方案传统的51单片机、Arduino、以及STM32。51单片机确实是教学级的经典选择很多教材里的电子秤都是51写的但那只是停留在“能跑”的层面。51是8位内核主频低、Flash小、外设接口少如果称重模块用的是I2C接口的HX711需要软件模拟时序做起来很勉强。而且后续想升级加入蓝牙、Wi-Fi模块、串口打印日志等功能51基本就力不从心了。Arduino开发确实快HX711、LCD、按键都有现成的库几天就能搭出原型但Arduino对底层硬件的控制能力弱而且很多性能参数和资源参数都比较受限。如果是拿来做个快速验证、做个demo确实合适但如果要做成一套有量产潜力的产品级别的方案Arduino的工程化能力明显不够。STM32的定位正好在二者之间它拥有ARMCortex-M核心具体根据型号有M0/M3/M4区别主频、Flash、RAM资源都足够充裕而且内置丰富外设USART、SPI、I2C、ADC、定时器、GPIOPWM等等。最重要的是STM32的固件库和HAL库已经发展得非常成熟网上资料多到爆炸运维成本和踩坑成本都大大降低了。我用的是STM32F103C8T6这块板子俗称“STM32最小系统板”淘宝几十块钱一片主频72MHz64KBFlash、20KB RAM48个引脚工程里还剩大量资源没用完。对一台电子秤来说这个配置属于“杀鸡用牛刀”但正因为资源充裕我才能在后期加进去掉电数据保存、无操作自动进入低功耗模式等附加功能如果当初选了51这些基本做不了。1.3 整体架构与模块划分这套系统的信息流向是物品放在秤盘上 → 压力使电阻应变片形变 → 惠斯通电桥输出电压差 → HX711采集放大为数字信号 → STM32通过模拟I2C读取数据 → 软件进行滤波与标定换算 → 结合键盘输入的单价计算总价 → LCD屏显示结果超限时蜂鸣器报警。系统可以划分为硬件层、驱动层、应用层三层我建议大家在动手之前先在纸上画出这个分层图不要直接开始写代码。作为嵌入式的核心思维代码要分模块模块要有清晰的边界否则后期调试的时候你会痛苦到怀疑人生。硬件层包括称重传感器、HX711模块、STM32最小系统板、1602LCD液晶屏、4×4矩阵键盘、蜂鸣器、电源模块。驱动层是各个外设的底层代码读HX711、扫描按键、驱动LCD、控制蜂鸣器。应用层则是业务逻辑包括重量滤波、标定系数换算、单价输入、总价计算、界面状态切换。这样的分层好处就在于每一层都可以单独测试和替换。比如开发初期HX711模块还没到货我可以先用可调电阻模拟传感器的输出先行验证读取代码LCD屏用的是I2C转接板写显示函数的时候也不用关心命令字和时序细节大大缩短了开发周期。2. 硬件电路设计与连接要点2.1 称重传感器的工作原理先花点篇幅说说称重这一块的核心原理因为后面所有的标定算法都建立在这个原理之上。工业上最常用的称重传感器是电阻应变式传感器核心结构是一个惠斯通电桥。简单说传感器内部有四个或者两个加两个补偿贴有金属箔应变片的弹性体当被测物体压在传感器上时弹性体发生微小形变应变片跟随形变其电阻值发生微小变化导致电桥失去平衡产生一个微弱的差分电压。以5kg量程的传感器为例在5V供电条件下满量程输出灵敏度通常为1mV/V左右也就是满量程时差分输出电压仅为5毫伏。这个信号如果用STM32自带的12位ADC参考电压3.3V能分辨的理论最低电压约0.8毫伏就算勉强能读出来误差也大得离谱。所以外接一个高性能ADC是必须的市面上最常见的方案就是HX711。HX711是专门为高精度电子秤设计的24位ADC芯片内置了可编程增益放大器PGA可选增益64和128。选增益128通道时它的满量程差分输入范围约为±20毫伏正好匹配称重传感器的输出范围。其内部集成了一个稳压电源可以为传感器供电采集到的数据通过两线串行接口DOUT/PD_SCK读取非常节省主控的I/O资源。2.2 HX711与STM32的接线我的连接方案如下HX711的VCC通过3.3V供电AVDD为传感器供电5V或3.3V均可由VCC决定HX711的DOUT接到STM32的PA6PD_SCK接到PA7HX711的GND与STM32的GND共地称重传感器的红线接E、黑线接E-、白线接A-、绿线接A这个接线顺序必须严格保证接反了读出来的数据会变成负数HX711模块供电滤波电容尽量靠近芯片我实测模块自带的滤波电容不够用独立加了两个100uF和0.1uF的电容后读数稳定性提升明显。说一下引脚配置我选PA6和PA7是因为它们附近的I/O口比较少后续扩展其他功能的时候不会冲突。无论用哪个GPIO只要配置为推挽输出和浮空输入即可。PD_SCK必须配置为推挽输出DOUT配置为上拉输入比较稳妥。2.3 键盘、显示与报警电路4×4矩阵键盘占用PA0、PA1、PA2、PA3、PB0、PB1、PB10、PB11这8个引脚采用逐行扫描方式读取。矩阵键盘最经典的坑是“幽灵按键”——因为引脚间的漏电和长线干扰有时候没按任何键系统却检测出一个键值。解决办法是软件去抖加行列扫描时序我在后面章节展开说明。LCD1602通过I2C转接板PCF8574T芯片接到STM32的PB6和PB7上这是STM32硬件I2C1的标准引脚。注意很多国产开发板上的硬件I2C不太好用经常卡死在忙等待状态所以我这里驱动用的软件模拟I2C稳定可靠以后无论是换芯片还是换引脚都不受影响。蜂鸣器接在PB12低电平导通也就是高电平时不响、拉低才响这样设计主要是考虑到三极管驱动电路里低电平驱动更安全不会出现单片机复位瞬间误触发报警的情况。3. 核心软件实现方案3.1 工程结构规划代码框架要提前规划好我采用的是标准的分目录结构SmartScale/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── stm32f1xx_it.h │ │ └── ... │ └── Src/ │ ├── main.c │ ├── stm32f1xx_it.c │ └── ... ├── Drivers/ │ └── CMSIS/... ├── Hardware/ │ ├── hx711.c/h │ ├── key.c/h │ ├── lcd1602_i2c.c/h │ └── beep.c/h └── App/ ├── scale.c/h └── ui.c/h工程里我把Hardware层与App层严格分离Hardware层的每个文件只做一件事比如hx711.c里面只有读数、复位、通道切换这些原子操作App层的scale.c负责重量换算、滤波、计价业务逻辑。这样做的好处是后续如果要换HX711为其他的ADC芯片比如ADS1231只需要把hx711.c的接口保持一致上层代码一行都不用改。3.2 HX711驱动与数据读取HX711的数据读取时序是DOUT引脚为低电平时表示转换完成此时主控向PD_SCK连续发送25个脉冲前24个脉冲依次移出24位数据第25个脉冲用于切换通道和增益。读取的代码如下// 等待数据就绪 uint8_t HX711_IsReady(void) { return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) Bit_RESET; } // 读取一次原始值 uint32_t HX711_Read(void) { uint32_t count 0; uint8_t i; while (HX711_IsReady() 0); // 等待DOUT拉低 for (i 0; i 24; i) { HX711_SCK_HIGH(); count count 1; if (HX711_IsReady()) { count; } HX711_SCK_LOW(); } HX711_SCK_HIGH(); HX711_SCK_LOW(); // 第25个脉冲 return count; }这段代码有几个地方要提醒读取完成后返回的count是24位无符号数但HX711输出的是二进制补码格式所以转换为有符号数时需要判断最高位例如int32_t value (int32_t)(count 0x800000) ? (int32_t)(count | 0xFF000000) : (int32_t)count;另外模块上电时HX711会进入自我校准状态大约需要几十毫秒这段时间内千万不要去读数据否则读取的数据全都是乱的。我习惯在初始化函数里先延时300ms再开始读取就当让它“醒一醒”。3.3 重量滤波与平滑原始ADC读数是非常不稳定的尤其是在有人走动、桌面震动的情况下数值会上下跳动几十到几百个count。如果直接拿这个数算重量屏幕上的数字会像抽风一样跳。我第一版代码只用简单平均值滤波结果效果很差。平均值滤波最大的问题就是它对瞬时大跳变没有任何抵抗力一个大的干扰噪声就能把所有数据拉偏。后来改成了滑动窗口的中值平均滤波每次读取时保留最近10个数据去掉最大值和最小值剩下8个求平均。这个滤波方式对脉冲干扰的抑制效果非常好而且是纯整数运算完全不占资源在STM32上跑起来毫无压力。核心代码如下#define FILTER_N 10 int32_t Scale_GetStableValue(void) { int32_t buf[FILTER_N]; int32_t sum 0, max, min; uint8_t i; for (i 0; i FILTER_N; i) { buf[i] HX711_ReadRaw(); } max buf[0]; min buf[0]; for (i 0; i FILTER_N; i) { if (buf[i] max) max buf[i]; if (buf[i] min) min buf[i]; sum buf[i]; } return (sum - max - min) / (FILTER_N - 2); }实测效果在未滤波时ADC噪声幅度约±30个count滤波后降到±3个count以内对于0.1g精度的目标这个信噪比已经足够。3.4 标定算法如何把ADC值换算成重量这是整个项目里最容易出错的部分也是我花时间最多的地方。电子秤的标定原理并不复杂传感器的输出在量程范围内近似线性所以我们可以用两点法来建立“ADC读数-重量”之间的线性关系。标定过程分两步。第一步空秤状态记录零点读数记作zero_offset第二步放上已知重量的标准砝码比如1kg记录读数weight_read。此时比例系数为[ scale_factor \frac{weight_read - zero_offset}{known_weight} ]之后任意时刻的重量计算就是[ weight \frac{raw - zero_offset}{scale_factor} ]我把这个比例系数以浮点数形式存储在STM32的内部Flash中掉电不丢失。由于STM32F103没有硬件浮点单元FPU纯浮点运算虽然可用但速度稍慢考虑到称重场景对实时性要求并不苛刻完全可以接受。还有一个关键问题温度漂移。传感器在刚上电时参数还在变化算法上就需要“自动零点追踪”——当检测到重量稳定且逼近零点持续3秒时自动修正zero_offset。这个功能对实际使用体验的提升非常大要知道很多便宜电子秤用久了之后空秤不归零就是没做这个功能。3.5 按键扫描与输入逻辑矩阵键盘的扫描算法下面这段是核心uint8_t Key_Scan(void) { uint8_t row, col, keycode KEY_NONE; static uint8_t key_state 0; // 状态机 for (row 0; row 4; row) { // 置当前行为低其他行为高 SetRow(row, GPIO_LOW); for (col 0; col 4; col) { if (ReadCol(col) GPIO_LOW) { // 列检测到低电平 keycode KEYMAP[row][col]; } } SetRow(row, GPIO_HIGH); } // 释放处理 return keycode; }如果代码里只做上面这个基础扫描肯定会有严重问题一是抖动二是按住键之后会连续触发无数次。我的处理方法是引入了一个简单的状态机再加上20ms的软件延时去抖状态0空闲检测到有键按下进入状态1并记录当前时间状态1确认20ms后再次检查如果仍然按下判定为有效按键进入状态2状态2等待释放按键释放后回到状态0。这个不阻塞的扫描方式非常重要因为它不会影响到HX711的数据读取节奏。就算用户一直按着某个键主循环的其他任务依然在正常跑。3.6 计价逻辑与UI状态计价模块的状态机我设计了四个状态状态SCALE_READY正常称重模式实时显示重量和总价状态SCALE_TARE去皮模式记录当前容器重量为皮重显示去皮后的净重状态SCALE_PRICE_INPUT单价输入模式键盘输入数值并确认状态SCALE_OVERLOAD超量程保护模式蜂鸣器急促报警禁止继续称重。每次主循环里调用一次状态机处理函数。以单价输入为例用户最多输入6位数字允许最多两位小数例如12.50就表示每500g价格12.50元。输入完成后按“确认”键系统自动保存单价并切回称重状态。因为使用了状态机整个代码的耦合度低每个状态对应一个明确的处理函数后期加功能时非常方便。3.7 LCD显示与刷新策略LCD1602虽然有8个字符×2行的显示区但我在设计显示内容时充分利用了它的空间。第一行显示重量和状态格式为W: 1.234 kg第二行显示单价和总价格式为P: 12.50 T: 15.42。要注意的是LCD的刷新频率不要太高我在主循环里控制每100ms刷新一次显示。刷新太频繁会频繁占用MCU的I/O资源而且肉眼也看不出什么变化刷新太慢则会让数字看起来一顿一顿的手感差。100ms这个值是我实际测试调整出来的兼顾了响应速度和稳定性。同时LCD刷新里还有一个细节显示数字前先把原来显示数字的区域清除位置如果没有对齐屏幕上就会残留“鬼影”。我的处理方式是所有显示都使用 sprintf 格式化到同一个字符串缓冲区每次整行重写这样彻底避免了局部残留问题。4. 标定实战与问题排查记录4.1 标定流程的完整操作标定是整个项目从“能跑”到“准确”的核心步骤具体流程如下上电后等待10秒让传感器和HX711的供电充分稳定空秤状态下进入标定模式长按“去皮确认”键系统自动读取零点读数并保存在秤盘上放置1kg标准砝码按“确认”键系统读取加载读数系统自动计算scale_factor并写入Flash标定完成测试多个重量点100g、500g、1kg、2kg、5kg记录误差。我在标定中发现一个很容易被忽视的问题标定时测得的比例系数并不是一成不变的它和放大增益档位强相关。如果你切换了HX711的增益通道标定必须重新做。因为增益128和增益64对应同一个传感器的原始数据比例关系完全不同忘记这点会让人抓狂。4.2 实测数据与误差分析我测试的一组数据挂在下面环境温度约25摄氏度传感器量程10kg| 标准重量 | 显示重量 | 误差 | 备注 | | 0g | 0g | 0g | 零点稳定 | | 100g | 99g | -1g | 误差在允许范围 | | 500g | 501g | 1g | | | 1kg | 1000g | 0g | | | 2kg | 2001g | 1g | | | 5kg | 5002g | 2g | 满载附近误差略大 |整体来看线性度表现不错最大误差2g在千分之二以内已经超过了很多商用电子秤的精度指标。误差偏大的主要原因除了传感器本身的非线性外还有标准砝码自身的误差和放置位置偏离秤盘中心产生的偏载误差。4.3 最让人头疼的“读数漂移”问题这个项目里我遇到过的最诡异的问题就是读数漂移。刚上电时读数是准的跑了一段时间后同一个砝码读出来的重量越来越小或者越来越大变化幅度能达到20到30g。一开始我以为是软件滤波不够改来改去毫无进展。后来用万用表量了HX711的AVDD电压才发现这个电压一直在缓慢下降。原因是我用了一个廉价的线性稳压模块给整个系统供电稳压模块在传感器和MCU这两个负载同时在跑的时候带载能力不足导致电压跌落。HX711的AVDD下降后传感器的激励电压也随之下降输出自然就漂了。解决方式是换成独立的电源模块主控部分用AMS1117-3.3稳压传感器HX711直接用优质的5V电源供电。改完电源之后漂移问题基本消失。这个坑值得单独记一笔电子秤这类高精度模拟采集系统电源的设计优先级甚至比代码更高。供电不干净、不稳定后面花再多时间调滤波算法都是治标不治本。4.4 其他常见问题速查| 症状 | 可能原因 | 解决方案 | | 读数一直是0 | HX711 DOUT引脚接线错误或芯片未复位 | 检查接线复位引脚脉冲检查VCC是否达到3.3V以上 | | 传感器放东西读数反而变小 | 传感器接线顺序错误 | 检查E/E-/A/A-顺序读数为负时可软件取绝对值但应优先修复接线 | | 屏幕显示乱码 | I2C地址不对或PCF8574接线错误 | 扫描I2C地址检查SDA/SCL是否接反 | | 按键按下了没反应 | 去抖时间过长、行列映射错误 | 调整KEYMAP矩阵确认行列GPIO配置 | | 偶尔死机 | 电源不稳定、IIC总线被占用 | 加去耦电容、检查IIC时序超时机制 | | 数值波动大 | 滤波窗口太短或滤波逻辑有误 | 增加滑动窗口长度检查是否误用了平均值而没去掉最大最小值 |4.5 标定库与数据存储的一个小技巧STM32F103的Flash写入要按页擦写一页是1KB。为了保存标定参数我专门开辟了一个Flash页来存储结构体。结构体里包含标定标志、scale_factor、zero_offset、单价等数据并且自己实现了一个简单的读写函数每次写入前先擦除整页。一个容易忽略的坑是Flash擦写期间CPU会暂停执行此时如果正好在读传感器数据就会出现一次异常大的跳变。我的办法是保存参数时临时屏蔽所有中断保存完成后再恢复这样避免了因Flash写入打断ADC读取导致数据异常的问题。5. 项目复盘与可扩展方向5.1 这个项目教会我的核心经验做完这个电子秤项目最大的体会是嵌入式项目不是“把代码写完就完了”而是一个“硬件选型 → 软件设计 → 联合调试 → 反复优化”的闭环过程。很多看起来简单的问题比如读数波动、数值漂移、按键失灵背后往往是硬件和软件交织在一起的综合问题只盯着一端是解决不了的。另一个体会是模块化设计的重要性。我的代码里每个硬件驱动都封装成了独立的接口这给调试带来了极大的便利。比如HX711的驱动被写成只负责读出原始数据标定逻辑完全放在App层调试时我只需要盯着串口打印的原始ADC值很快就能定位问题在硬件还是算法。5.2 还能往哪些方向扩展这套方案其实留了很大的扩展空间。我列几个我后续准备尝试或者身边朋友做过成功的方向增加串口或蓝牙模块把称重数据实时上传到上位机或手机App做成物联网电子秤使用FreeRTOS对任务进行实时调度在系统复杂度提升后状态机模式会显得吃力RTOS能更好地管理多任务时序将数据存储升级为外接EEPROM或SD卡这样不占用宝贵的内部Flash空间增加多个称重传感器做偏载补偿提高大尺寸秤台的精度。我个人最推荐的方向是接入Wi-Fi模块做一个数据上报功能因为联网之后电子秤的玩法就完全不一样了可以称重数据统计、远程校准、批次管理直接往智能零售设备的方向靠。寄生在这个方向上的技术点也很有趣值得花时间研究。最后分享一个调试阶段的个人经验找一台带串口输出的开发板看日志会比仪器更方便。我习惯在工程里加一个DEBUG_USART的printf通道把关键状态变量比如原始ADC值、标定系数、滤波结果实时输出到串口无论是在实验室还是现场调试效率都提升了不止一个档次。这个习惯我建议每个做嵌入式的朋友都认真养成。
返回列表