1. 项目概述:STM32智能电子秤的设计初衷
在超市、菜市场甚至家庭厨房里,电子秤早已成为不可或缺的计量工具。作为一名嵌入式开发者,我最近完成了一个基于STM32的智能电子秤项目,它不仅具备基本称重功能,还创新性地集成了金额累计、语音提示和模拟收银等商业场景功能。这个项目最吸引我的地方在于,它完美展现了如何用低成本单片机实现专业级电子秤的核心功能。
传统电子秤通常只显示重量,而商业场景中店员需要手动计算金额,既容易出错又效率低下。我的设计采用STM32F103C8T6作为主控,配合HX711高精度称重模块,实现了重量测量、单价设置、金额计算和累计等完整业务流程。特别值得一提的是,通过JR6001语音模块和舵机驱动,系统能够模拟真实收银场景的语音提示和"钱箱弹出"动作,这种细节设计让整个用户体验更加专业和人性化。
2. 系统架构与核心模块选型
2.1 硬件组成框图解析
整个系统采用模块化设计思路,各功能模块通过明确的接口与主控连接。系统核心是STM32F103C8T6单片机,这款Cortex-M3内核的MCU具有72MHz主频和足够的GPIO资源,完全能满足电子秤实时性要求。以下是各模块的选型考量:
-
称重模块:选用HX711芯片配合5kg量程的应变式称重传感器。HX711是专为电子秤设计的24位ADC,内部集成可编程放大器,能直接处理称重传感器的mV级输出信号。其优势在于:
- 内部128倍增益可调
- 10Hz/80Hz可选输出速率
- 抗工频干扰设计
- 成本仅为专业称重方案的1/3
-
显示模块:采用1.44寸TFT彩屏而非传统的LCD1602,主要基于以下考虑:
- 彩屏能同时显示更多信息(重量、单价、金额、累计记录)
- 支持中文显示和更丰富的UI布局
- 虽然驱动复杂度稍高,但STM32的SPI接口能轻松应对
-
语音模块:JR6001语音芯片通过UART控制,预存了"已累计"、"请携带好物品"等提示音。相比简单的蜂鸣器提示,语音播报显著提升了用户体验。
-
执行机构:SG90舵机模拟收银钱箱弹出动作,通过到位开关触发,这种设计让整个结算过程更具仪式感。
2.2 关键电路设计要点
称重传感器的信号调理电路是整个系统精度的关键。HX711的典型应用电路需要注意:
- 传感器供电应采用独立的LDO稳压器(如AMS1117-3.3),避免数字电路噪声干扰
- 信号线应使用双绞线并尽量缩短长度
- 在HX711的AVDD和DVDD引脚就近放置0.1μF去耦电容
- 传感器金属壳体应良好接地,防止静电干扰
舵机驱动电路设计时,需注意:
- 单独供电(不直接从STM32取电)
- 添加100μF电解电容缓冲电流冲击
- 信号线串联100Ω电阻抑制振铃
3. 软件设计与核心算法实现
3.1 主程序流程与状态机设计
系统软件采用有限状态机(FSM)模型,主要状态包括:
- 初始化状态:完成各外设初始化、加载保存的参数
- 待机状态:显示欢迎界面,等待称重
- 称重状态:实时显示重量,等待单价设置
- 累计状态:处理金额累加逻辑
- 结算状态:触发舵机动作,语音提示
状态转换通过按键和传感器信号触发,确保业务流程清晰。以下是核心代码框架:
c复制typedef enum {
SYS_INIT,
SYS_STANDBY,
SYS_WEIGHING,
SYS_ACCUMULATING,
SYS_SETTLEMENT
} SystemState;
void main() {
SystemState state = SYS_INIT;
while(1) {
switch(state) {
case SYS_INIT:
hardware_init();
state = SYS_STANDBY;
break;
case SYS_STANDBY:
if(detect_weight()) state = SYS_WEIGHING;
break;
// 其他状态处理...
}
}
}
3.2 称重数据处理算法
HX711采集的原始数据需要经过多重处理才能得到准确重量:
-
去皮处理:上电时自动记录空载读数作为零点
c复制void tare() { offset = hx711_read_avg(10); // 取10次平均值 } -
数字滤波:采用滑动平均滤波抑制瞬时干扰
c复制#define FILTER_SIZE 5 int32_t filter_buf[FILTER_SIZE]; int32_t filter(int32_t new_val) { static uint8_t index = 0; filter_buf[index++] = new_val; if(index >= FILTER_SIZE) index = 0; int64_t sum = 0; for(uint8_t i=0; i<FILTER_SIZE; i++) { sum += filter_buf[i]; } return sum / FILTER_SIZE; } -
单位转换:根据校准参数将AD值转换为重量(kg)
c复制float ad_to_weight(int32_t ad_value) { return (ad_value - offset) / calibration_factor; }
注意:校准因子(calibration_factor)需要通过标准砝码标定获得,建议在Flash中保存多组温度补偿参数以适应不同环境。
3.3 金额累计逻辑实现
金额累计是商业电子秤的核心功能,我的设计实现了:
- 最多9次累计(可通过宏定义修改)
- 实时显示每笔交易明细
- 中途取消累计功能
关键数据结构设计:
c复制typedef struct {
float weight;
float unit_price;
float amount;
} Transaction;
Transaction records[MAX_RECORDS]; // MAX_RECORDS=9
uint8_t record_count = 0;
累计操作处理流程:
- 用户放置物品,设置单价
- 按下K3键,当前金额加入累计数组
- 语音提示"已累计"
- TFT屏幕下方显示该笔交易明细
- 重复1-4步直到完成所有物品称重
- 按下到位开关触发结算流程
4. 关键功能实现细节
4.1 自动去皮与传感器校准
电子秤的准确性依赖于正确的去皮和校准流程。我的实现方案包括:
-
上电自动去皮:
- 系统启动时检测传感器是否处于空载状态
- 若检测到负载(AD值超过阈值),则提示"请移除物品"
- 空载状态下连续采样20次取平均值作为零点
-
手动校准模式:
- 通过组合键(K1+K3长按3秒)进入校准模式
- 按照屏幕提示依次放置500g、1kg标准砝码
- 系统自动计算校准因子并保存至Flash
校准算法核心代码:
c复制void calibration() {
float known_weight = 1.0f; // 1kg标准砝码
int32_t ad1 = hx711_read_avg(20); // 空载读数
prompt_user("Place 1kg weight");
delay(5000);
int32_t ad2 = hx711_read_avg(20); // 负载读数
calibration_factor = (ad2 - ad1) / known_weight;
save_to_flash(CALIBRATION_ADDR, &calibration_factor, 4);
}
4.2 掉电保存功能实现
系统参数(单价上限、校准因子等)需要掉电不丢失,STM32内部Flash模拟EEPROM的方案如下:
-
定义参数结构体:
c复制typedef struct { float calibration_factor; float max_unit_price; uint8_t max_records; } SystemParams; -
Flash操作函数(基于STM32标准外设库):
c复制void save_to_flash(uint32_t addr, void *data, uint16_t len) { FLASH_Unlock(); FLASH_ErasePage(addr); uint32_t *p = (uint32_t*)data; for(uint16_t i=0; i<(len+3)/4; i++) { FLASH_ProgramWord(addr + i*4, p[i]); } FLASH_Lock(); }
重要提示:Flash写入前必须擦除整个页(1KB),频繁擦写会导致Flash寿命缩短(约1万次)。建议只在参数变更时保存,避免每次上电都写入。
4.3 语音提示与舵机控制
JR6001语音模块通过UART发送控制命令,预存的语音片段通过序号调用:
c复制void play_voice(uint8_t id) {
uart_send("$02"); // 模块地址
uart_send("P"); // 播放命令
printf("%02X", id); // 语音ID十六进制
uart_send("\r\n"); // 结束符
}
舵机控制采用PWM信号驱动,SG90的标准控制参数:
- 周期:20ms (50Hz)
- 0°脉冲:0.5ms (占空比2.5%)
- 90°脉冲:1.5ms (占空比7.5%)
- 180°脉冲:2.5ms (占空比12.5%)
STM32定时器配置代码:
c复制void pwm_init() {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 时钟使省...
// 时基配置:72MHz/(719+1)/(1999+1) = 50Hz
TIM_TimeBaseStructure.TIM_Period = 1999;
TIM_TimeBaseStructure.TIM_Prescaler = 719;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// PWM配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 150; // 初始1.5ms(90°)
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_Cmd(TIM3, ENABLE);
}
5. 系统优化与问题排查
5.1 称重稳定性优化措施
在实际测试中,我发现称重数据会出现以下问题:
- 短时间波动较大
- 长时间漂移
- 温度影响明显
采取的解决方案:
-
软件滤波优化:
- 在滑动平均滤波基础上增加中值滤波
- 动态调整滤波窗口大小(快速变化时用小窗口,稳定时用大窗口)
-
温度补偿:
- 增加DS18B20温度传感器
- 在不同温度下记录零点漂移曲线
- 实时根据温度调整零点
-
机械结构优化:
- 称重平台使用刚性材料(如铝合金)
- 传感器安装面保证平整
- 避免侧向力影响
5.2 常见问题排查指南
根据我的调试经验,整理出以下典型问题及解决方法:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 显示重量不稳定 | 电源噪声大 信号线干扰 机械振动 |
检查电源滤波电容 使用屏蔽线 增加软件滤波 |
| 去皮后仍有底数 | 传感器预压力不足 平台自重未完全传递 |
调整传感器安装螺丝 重新执行去皮操作 |
| 线性度差 | 传感器过载损坏 平台受力不均 |
更换传感器 检查平台水平度 |
| 语音不同步 | UART波特率不匹配 模块供电不足 |
核对模块手册设置波特率 单独给模块供电 |
5.3 功耗优化技巧
虽然商用电子秤通常不关心功耗,但对于电池供电的应用,我总结了以下优化方法:
-
动态调整采样率:
- 空闲时降低HX711数据速率(10Hz→1Hz)
- 检测到重量变化时恢复全速采样
-
背光控制:
- 30秒无操作后降低TFT背光亮度
- 通过光敏电阻自动调节亮度
-
低功耗模式:
c复制void enter_stop_mode() { RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); SystemInit(); // 唤醒后重新初始化时钟 }
6. 功能扩展与改进方向
这个基础框架可以进一步扩展为更专业的商业电子秤:
-
商品数据库:
- 添加EEPROM存储常用商品单价
- 通过编码快速调取商品信息
-
无线功能:
- 增加蓝牙模块连接手机APP
- 支持支付宝/微信支付接口
-
打印功能:
- 集成微型热敏打印机
- 打印购物小票
-
数据统计:
- 按日/周/月统计销售数据
- 通过U盘导出Excel报表
硬件改进建议:
- 选用S型称重传感器提升侧向力抗干扰能力
- 改用OLED显示屏降低功耗
- 增加防水设计适应潮湿环境
在软件开发方面,可以考虑:
- 移植FreeRTOS实现多任务管理
- 设计更友好的GUI界面
- 添加多国语言支持
这个项目让我深刻体会到,一个好的电子秤设计不仅需要精准的称重技术,更需要从用户角度出发的细节考量。比如累计金额时的语音反馈、结算时的舵机动作,这些看似简单的交互设计,却能显著提升产品的专业感和用户体验。