☰
基于STM32与HX711的智能语音电子秤设计与实现
2026/10/31 8:20:42 网站建设 项目流程

前阵子刚把手头一个智能语音电子秤项目整理完,代码、原理图、仿真工程都打包开源了。讲真,这种项目单看“称重”这个动作并不惊艳,但如果你正在学STM32,想找一个能把GPIO、外部ADC驱动、UART串口、I2C显示、按键交互全部串起来实战的完整样本,它非常适合。整台秤的BOM成本能压进50块,语音报重对老人和视力不好的用户尤其友好。本文就把整个项目的设计链路讲透:为什么选这套方案、原理图每一路怎么走、固件里哪些代码是核心、以及仿真和实调时最容易翻车的几个点。

1. 为什么偏偏是这个方案:从使用场景到器件选型

1.1 使用场景对电子秤的功能需求

先聊需求。一台给家用的小型智能语音电子秤,需要满足三件事:称得准、看得清、听得懂。称得准,指的是在5kg量程内至少要有1g的分辨率,放个鸡蛋、放把青菜,数字不能跳来跳去;看得清,是说有个屏幕能直接显示重量,不要靠数码管猜小数点;听得懂,就是每次稳定称重后,直接用中文报出“当前重量500克”,不用低头看屏幕。

由此拆出来的功能清单其实不短:高精度重量采集、去皮清零、满量程标定、重量变化稳定判定、OLED实时显示、语音播报、电量或电源指示灯、按键交互。这些功能再往下拆,就变成了硬件选型和软件架构的输入条件。

我见过很多人一上来就想用51单片机做电子秤,但做到语音播报那一步就卡住了——51的串口资源、内存空间、定时器数量都不够从容,而且后续想接个蓝牙模块、加个Wi-Fi,几乎要从头换平台。所以这个项目直接用STM32F103C8T6,不仅是因为它在国内量大便宜,更因为它能让整个项目有足够的“升级后路”。

1.2 主控、ADC、语音模块的选型逻辑

主控选了STM32F103C8T6,64KB Flash、20KB RAM,72MHz主频,跑一个称重加语音的项目绰绰有余。更重要的是,它支持SWD调试、有多个UART、有硬件I2C,生态资料极其丰富,遇到问题随便一搜就有答案,非常适合作为学习项目和开源项目的基础平台。

称重信号采集是整个项目中技术含量最高的环节。常规做法有两种:一种是用仪表放大器加独立ADC,另一种是直接用电子秤专用的24位ADC芯片HX711。我选了HX711,理由很直接:它内部集成了低噪声放大器、24位Sigma-Delta ADC、稳压电源和时钟,外围只需要几个电容电阻,就能把传感器输出的毫伏级差分信号直接变成数字量。

语音模块的选择,我把它分成两类。一类是语音合成方案,常见型号有SYN6288、XFS5152CE,这类模块内部有中文合成引擎,给它发GBK编码的中文文本,它就能自己“读”出来,适合播报动态变化的重量数字;另一类是预录音播放方案,比如WT588D、DY-SV17F,音质好、价格低,但要提前把语音烧进去,遇到任意重量值只能一段段拼接,使用体验很割裂。

电子秤恰恰需要播报“当前重量358克”这种动态数字,所以我最终选了SYN6288方案。虽然单个模块比预录音方案贵十几块,但从项目完整度和拓展性来看,这个差价很值。OLED显示屏选了最常见的0.96寸I2C接口SSD1306,四根线就能接好,省IO,调试也省心。

1.3 系统整体架构与数据流设计

整套系统的数据流是单向链式结构:称重传感器把重量变成微弱的电阻变化,HX711把电阻桥的差分电压放大并转换成24位数字量,STM32读取原始ADC值,经过数字滤波和标定运算得到重量值,然后一分为二:一路送给OLED显示,另一路拼成中文语音指令通过串口发给SYN6288,由模块合成语音并播放。

这条链路每个环节都有讲究。传感器是模拟前端,负责把物理量转成电信号;HX711是模拟到数字的桥梁,决定了系统的分辨率上限;STM32是计算和调度中枢,负责滤波算法、状态管理和外设协调;语音模块是输出终端,决定了用户能不能“听懂”这台秤。

我把这个架构画成文字表格方便理解:

环节器件职责关键指标
物理量感知铝制单点称重传感器重量→应变→电阻变化量程5kg,灵敏度1mV/V
信号调理HX711差分放大+24位ADC增益128,输出速率10Hz
主控STM32F103C8T6读取、滤波、标定、调度72MHz,20KB RAM
显示SSD1306 OLED 0.96寸实时显示重量和状态I2C接口,128x64
语音SYN6288中文文本合成语音UART,支持GBK
输入独立按键去皮、标定、播报内部上拉,低电平有效

这套架构从电气角度看很干净:模拟部分和数字部分通过HX711隔离,主控只处理数字信号,不会受到模拟前端的噪声干扰;语音模块虽然也是数字外设,但它带功放,瞬间电流大,必须在电源上单独处理,否则会把ADC的地拉脏。这些都是后面原理图和PCB阶段反复强调的重点。

2. 原理图逐模块拆解:传感器信号链路怎么走最稳

2.1 称重传感器与HX711的前级电路

称重传感器的本质是一个惠斯通电桥。标准四线制传感器的线色定义通常是:红色接E+(激励正)、黑色接E-(激励负)、绿色接A-(信号负)、白色接A+(信号正),但不同厂家的线序可能不同,接线前一定要对照传感器手册确认,不能凭颜色猜。

HX711的通道A输入就是A+/A-,内部增益默认128,非常适合5kg这种小量程传感器。HX711的E+/E-引脚对外输出激励电压,这个电压是芯片内部的稳压源提供的,可以直接给传感器桥路供电。设计时要注意,E+/E-之间要加一个10uF电解电容和0.1uF陶瓷电容并联,稳住桥路供电;A+/A-之间加0.1uF电容滤除高频干扰,同时两个差分引脚对地各接一个10nF电容,可以进一步压制共模噪声。

RATE引脚决定数据输出速率:接GND或悬空时是10Hz,接VCC时是80Hz。电子秤这种应用,10Hz完全够用,而且低速采样时数字滤波的效果更好,所以RATE直接接地或者不接。PD_SCK引脚是串行时钟输入,同时也是掉电控制引脚,这个引脚要求主控用推挽输出模式控制,时序上必须等DOUT变低后才能启动时钟。

有了这套前级电路,从传感器到主控之间再无其他有源器件,这对新手特别友好。我曾经见过有人试图用LM358去放大传感器信号再接ADC,结果温漂大得没法看,所以“高精度电子秤”这个场景,直接上HX711是性价比和稳定性最平衡的选择。

2.2 STM32最小系统与外设引脚分配

STM32F103C8T6最小系统包含几部分:8MHz主晶振、32.768kHz低速晶振(不跑RTC时可以省略)、复位电路、BOOT配置、SWD调试接口、3.3V供电。

主晶振的两个负载电容取值,很多人拿不准。以8MHz、CL=20pF的石英晶振为例,负载电容大致按2倍CL减去杂散电容估算:2×20pF减去PCB走线和引脚引入的约4~6pF杂散,得到34~36pF,两个引脚对地各放一个15~20pF电容就够用了。实际上我习惯用两个20pF并联在晶振两端,实测起振很可靠。如果晶振不起振,优先怀疑电容取值偏大或芯片虚焊,别一上来就怪晶振本身。

复位电路使用10kΩ上拉电阻加100nF对地电容,形成低电平复位的RC延时电路。BOOT0引脚直接通过10kΩ下拉接地,确保从内部Flash启动。SWD调试接口引出SWDIO和SWCLK两个引脚,外加GND和3.3V,四针就能烧录和在线调试。

以下是本项目完整的引脚分配表,建议打样前照着核对一遍:

功能STM32引脚复用/说明
HX711 SCKPA5GPIO推挽输出
HX711 DOUTPA6GPIO输入
SYN6288 RXDPA2USART2_TX
SYN6288 TXDPA3USART2_RX(可不用)
OLED SCLPB6I2C1_SCL
OLED SDAPB7I2C1_SDA
去皮按键PA0低电平有效
标定按键PA1低电平有效
语音播报按键PA8低电平有效
预留串口调试PA9/PA10USART1_TX/PA9, USART1_RX/PA10

2.3 语音模块、显示、按键和电源的电路细节

SYN6288语音合成模块是TTL电平串口,供电范围一般在3.3~5V,但推荐稳压到3.3V使用,因为模块上的功放电源如果直接从5V取,会在喇叭发声时产生较大的电流波动。模块的RXD接STM32的PA2(USART2_TX),TXD接PA3。如果不需要模块回传播报完成状态,TXD可以不接,但建议留着,调试时能通过串口查看模块的返回信息,判断命令是否被正确接收。

SYN6288上电后需要大约500ms的自检和初始化时间,这段时间内发送任何命令都会被忽略。代码里要在初始化完成后延时至少800ms再开始语音操作。喇叭选择8Ω/1W左右的微型扬声器,这一步很关键——很多模块的“PWM直推喇叭”输出脚只能带很小的功率,选个阻抗功率不匹配的喇叭,声音又小又失真。

OLED的I2C接口需要两个4.7kΩ上拉电阻到3.3V,SSD1306默认7位I2C地址是0x3C,如果地址不对,显示会全白或全黑。按键部分用了三个独立按键,都配置成内部上拉输入,按下接地,软件里做10ms消抖即可,不需要外部电容。

电源是整个项目中容易被低估的部分。我采用的方案是USB 5V输入,经AMS1117-3.3稳压给主控、OLED和语音模块的数字部分供电;传感器激励和HX711的模拟部分直接从5V取电,再通过LC滤波后进入芯片的AVDD。这样设计的好处是模拟电源和数字电源在源头分开,语音模块功放导致的大电流波动不会直接污染HX711的模拟供电。

2.4 PCB布局布线的三条经验

原理图画完之后,打样PCB前有几条布线经验值得说。第一,HX711及其外围电容要尽量靠近传感器接口,差分信号线A+/A-从接口出来后直接进HX711,中间不要打过孔,不要绕行,两条线保持平行且短。第二,模拟地不要和数字地大面积相连,在STM32的GND引脚处通过一个0Ω电阻或磁珠单点连接,避免数字开关噪声回流到模拟地。第三,语音模块的功放输出线和喇叭线要远离HX711和传感器差分线,否则喇叭播放瞬间会在差分线上感应出噪声,直接表现为重量读数瞬间跳变。

开关电源或充电宝供电的情况下,5V进来的位置一定要放一个几十uF的电解电容再加一个100nF陶瓷电容,否则HX711在电机或喇叭工作瞬间很容易被拉垮。

3. 固件实现:HX711时序、标定算法与语音播报怎么配合

3.1 HX711读取时序的完整代码与关键逻辑

HX711的读取时序看起来简单,实际有几个细节不做对就会翻车。正常流程是:等待DOUT从高电平变为低电平,表示一次转换完成;然后由主控在PD_SCK上发出24个时钟脉冲,在脉冲上升沿之间读取DOUT电平,先读到的是最高位,24个脉冲读完后得到一个24位补码数据;最后再给一个脉冲,让芯片进入下一次转换周期。

对应代码我用HAL库写了一份,核心读取函数如下:

#include "hx711.h" static uint8_t HX711_IsReady(void) { return HAL_GPIO_ReadPin(HX711_DOUT_GPIO_Port, HX711_DOUT_Pin) == GPIO_PIN_RESET; } static int32_t HX711_ReadRaw(void) { uint32_t buffer = 0; uint16_t timeout = 0xFFFF; // 等待DOUT拉低,超时保护防止死等 while (!HX711_IsReady()) { if (--timeout == 0) { return 0x7FFF7FFF; // 超时返回一个明显异常值 } } for (uint8_t i = 0; i < 24; i++) { HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_SET); delay_us(1); buffer = (buffer << 1) | HAL_GPIO_ReadPin(HX711_DOUT_GPIO_Port, HX711_DOUT_Pin); HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_RESET); delay_us(1); } // 第25个脉冲,启动下一次转换 HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_SET); delay_us(1); HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_RESET); // 24位补码转32位有符号数 int32_t result = (int32_t)(buffer << 8) >> 8; return result; }

超时保护是必须的。如果传感器没接、芯片损坏或者DOUT引脚被其他信号占用,程序会一直卡在“等待DOUT拉低”的死循环里,整个系统就像死机了一样。加了超时机制后,遇到异常情况能立即返回一个明显不合理的数值,方便上层逻辑判断传感器是否正常。

时序里的delay_us要精确,PD_SCK的高电平和低电平持续时间至少保证1us以上。HX711数据手册给出的最大时钟频率是5MHz左右,实际取1us的高/低电平时序已经足够稳定。要注意,delay_us如果用的是HAL_Delay,精度完全不够用,因为HAL_Delay是基于1ms节拍的。这里必须用DWT计数器或者空循环实现微秒级延时,我习惯用DWT实现一个delay_us函数,量程和精度都能覆盖。

3.2 数字滤波:为什么不能只做一次平均

HX711的原始数据在没有任何滤波的情况下,波动范围经常达到几十个ADC码,换算成重量可能就有几十克甚至上百克,完全不可用。如果只做一次简单平均,传感器轻微震动或电源噪声依然会让读数跳动。

我采用的是“滑动窗口中位值平均滤波法”,本质上就是先缓存16个采样值,去掉一个最大值和一个最小值,剩下14个取平均值。这个算法对脉冲干扰和随机噪声都有抑制效果,又能保留重量的真实变化趋势。

#define FILTER_NUM 16 static int32_t filter_buf[FILTER_NUM]; static uint8_t filter_index = 0; static int32_t HX711_GetFilteredValue(void) { int32_t buf[FILTER_NUM]; int32_t sum = 0; int32_t max, min; filter_buf[filter_index] = HX711_ReadRaw(); filter_index = (filter_index + 1) % FILTER_NUM; memcpy(buf, filter_buf, sizeof(buf)); max = buf[0]; min = buf[0]; for (uint8_t i = 0; i < FILTER_NUM; i++) { if (buf[i] > max) max = buf[i]; if (buf[i] < min) min = buf[i]; } uint8_t skip_flag = 0; for (uint8_t i = 0; i < FILTER_NUM; i++) { if (!skip_flag && buf[i] == max) { skip_flag = 1; continue; } if (!skip_flag && buf[i] == min) { skip_flag = 2; continue; } sum += buf[i]; } // 处理第二个极值 skip_flag = 0; for (uint8_t i = 0; i < FILTER_NUM; i++) { if (!skip_flag && buf[i] == min) { skip_flag = 1; continue; } } return sum / (FILTER_NUM - 2); }

这段代码里有一个小技巧:如果最大值或最小值是重复出现的,只跳过第一个极值,避免误删掉一个正常数据点导致窗口内只剩15个数据。实际调试下来,这个滤波器的效果非常明显,在非震动环境下读数可以做到稳定在±1个码以内,对应重量就是±0.2g左右。

3.3 零点校准与斜率标定:EEPROM里到底存什么

标定是电子秤精度的重要保障。整个标定分为两步:第一步是零点标定,也叫去皮;第二步是满量程标定,也就是确定“每个ADC码对应多少克”。

零点是空载状态下HX711的输出值。这个值不是0,而是一个随系统偏置变化的大数,可能正可能负。去皮操作需要连续采样20次取平均,把平均值存下来作为offset_value。

满量程标定则在秤盘上放一个已知重量的标准砝码,比如1000g,等读数稳定后采样20次取平均,得到cal_value,然后算出每克对应的ADC码数:

scale_factor = (float)(cal_value - offset_value) / (float)standard_weight_g;

重量计算就非常简单了:

weight_g = (float)(filtered_value - offset_value) / scale_factor;

offset_value和scale_factor要掉电保存。STM32F103C8T6没有真正的EEPROM,但可以用内部Flash模拟。内部Flash按页擦除,一页1KB,F103C8有64页。我把标定参数放在最后一页地址0x0800FC00,每次写入前先擦除整页。

写入Flash的代码要小心,Flash写入期间不能有任何中断去访问Flash,否则会触发硬件错误。我用的是标准库的HAL_FLASH接口,写入前要解锁,写完要加锁:

void SCALE_SaveParams(void) { uint32_t addr = 0x0800FC00; uint32_t data[2]; memcpy(&data[0], &offset_value, 4); memcpy(&data[1], &scale_factor, 4); HAL_FLASH_Unlock(); FLASH_Erase(FLASH_BANK_1, FLASH_SECTOR_0); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, data[0]); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + 4, data[1]); HAL_FLASH_Lock(); }

读取参数时要注意,Flash默认状态是0xFF,如果从来没写过,读出来的数字没有任何意义。所以每次上电先用一个校验标志,我约定地址0x0800FC08处写入特殊魔数0xA5A5A5A5,只有校验成功才加载Flash里的参数,否则强制进入一次空载标定流程。

3.4 语音播报:中文文本怎么拼、协议怎么封装

SYN6288的串口协议是固定帧格式:一字节帧头0xFD、两字节数据区长度、一字节命令字、一字节编码格式,后面跟真正的文本内容。

void Voice_Play(const char *text) { uint8_t frame[256]; uint16_t len = strlen(text) + 3; // 命令字1字节 + 编码格式1字节 + 文本长度 if (len > 250) { return; } frame[0] = 0xFD; frame[1] = (len >> 8) & 0xFF; frame[2] = len & 0xFF; frame[3] = 0x01; // 文本合成命令 frame[4] = 0x00; // GBK编码文本 memcpy(&frame[5], text, strlen(text)); HAL_UART_Transmit(&huart2, frame, len + 2, 200); }

直接把“当前重量xxx克”这串文本拼好发出去就行:

void Voice_ReportWeight(float weight_g) { char text[64]; if (weight_g >= 1000.0f) { sprintf(text, "当前重量%.2f千克", weight_g / 1000.0f); } else { sprintf(text, "当前重量%.0f克", weight_g); } Voice_Play(text); }

有个很容易踩的坑:SYN6288只认GBK/GB2312编码,如果工程源文件保存成UTF-8,字符串里的中文字节会变成UTF-8编码,语音模块读出来就是乱码。解决办法有两个:要么把整个工程的源文件编码改为GB2312,要么在代码里直接写GBK字节的十六进制转义。我在工程里遇到过不少次“为什么显示乱码”的问题,最后都是编码导致的。

另外,语音模块第一次上电到就绪需要时间,如果开机立刻发送语音命令,前几条会被丢弃。建议在main初始化里加一个800ms的串口等待延时,等模块“叮”一声自检完成后再开始业务逻辑。

3.5 主循环状态机:按键、称重、播报怎么调度

整个主程序不搞复杂操作系统,就用一个简单的状态机。状态分为:初始化、空闲、去皮中、标定中、播报中。

typedef enum { STATE_INIT, STATE_IDLE, STATE_TARING, STATE_CALIBRATING, STATE_SPEAKING } scale_state_t; while (1) { switch (state) { case STATE_INIT: SCALE_LoadParams(); OLED_Clear(); Voice_Play("智能电子秤开机,请放置物品"); state = STATE_IDLE; break; case STATE_IDLE: weight = SCALE_GetWeight(); OLED_ShowWeight(weight); if (key_tare_pressed) { state = STATE_TARING; } if (key_cal_pressed) { state = STATE_CALIBRATING; } if (key_speak_pressed) { Voice_ReportWeight(weight); } break; case STATE_TARING: SCALE_Tare(); OLED_ShowMessage("去皮完成"); state = STATE_IDLE; break; case STATE_CALIBRATING: OLED_ShowMessage("请放置1000克砝码"); SCALE_Calibrate(); OLED_ShowMessage("标定完成"); state = STATE_IDLE; break; } HAL_Delay(20); }

这个状态机的好处是把所有业务逻辑变成清晰的事件驱动。按键消抖放在外部中断回调里,只负责置位标志位,真正处理在状态机内完成。这样即使按键在任何一个状态被按下,也有明确的处理路径,不会互相干扰。

4. Proteus仿真:没有真实硬件也能跑通整个称重流程

4.1 仿真环境准备与元件查找

每次在群里看到有人问“Proteus里为什么没有STM32”或者“为什么没有HX711”,我都先建议升级版本。Proteus 8.9以后,STM32F103系列和HX711的仿真模型都已经集成在库里面了,不需要额外安装第三方库。如果你还在用老版本,找不到元件是正常的,别浪费时间折腾,直接换新版本装一下,体验完全不同。

打开Proteus后,在元件搜索框里分别搜索以下元件:

  • STM32F103C8:主控模型
  • HX711:称重ADC模型
  • STRAIN_GAUGE或电位器:模拟称重传感器
  • VTERM:虚拟终端,用于观察串口输出
  • 按键BUTTON、OLED等可按需添加

4.2 搭建仿真电路和传感器模拟方法

在Proteus里搭建仿真电路,核心要解决的是“怎么模拟传感器输出”。两种方法我都试过。

第一种是直接用Proteus自带的STRAIN_GAUGE应变片元件,它属于可变的桥式电阻模型,能比较接近真实传感器的行为。把应变片接到HX711的E+/E-和A+/A-之间,通过修改应变片的形变量参数,就能模拟秤盘上放不同重量时差分信号的变化。

第二种更简单粗暴,用两个精密电位器和几个固定电阻搭一个差分信号源,让A+/A-之间产生毫伏级的电压差。因为HX711在仿真模型里也能正常采样,只要改变电位器旋钮,就能看到ADC值跟着变化,再从虚拟终端里观察对应的重量输出。

个人建议新手优先用第二种,原理清楚、参数可控、调起来直观。应变片模型虽然更真实,但参数设置比较繁琐,一不小心就调出超范围的电压,反而让人迷惑。

4.3 软件配置:hex加载、晶振频率、虚拟终端

仿真最关键的一步是把固件加载到STM32模型里。在Proteus里双击STM32F103C8芯片,在Program File属性里选择Keil工程生成的hex文件。这一步最容易出错的是晶振频率设置:STM32模型的Crystal Frequency属性必须设成8MHz,因为固件里的系统时钟、串口波特率都是基于8MHz外部晶振计算出来的,如果这里设置不对,虚拟终端里看到的串口输出全是乱码。

串口部分,从STM32的PA9引脚连到虚拟终端的RXD,PA10连到TXD,就能在虚拟终端里看到printf打印的调试信息和语音指令文本。PROTEUS里连接虚拟终端时,注意它的RX要接MCU的TX,TX接MCU的RX。

4.4 仿真验证与预期观察:怎么判断系统对了

仿真跑起来后,按照下面这个流程过一遍,能比较全面验证系统逻辑:

  • 上电启动:虚拟终端能看到开机初始化信息,OLED(如果有模型)显示初始状态
  • 去皮操作:按下去皮按键,终端输出“TARE done”,零点偏移量被更新
  • 重量变化:调节电位器模拟放置物品,终端持续打印当前重量,重量值随电压变化稳定变化
  • 语音指令输出:按播报键后,终端打印出发送给语音模块的原始字节帧和中文文本内容,这表明语音逻辑正确

我在仿真验证时,会刻意把HX711的差分输入调到接近满量程,再快速调回零点,观察重量数据是否能快速收敛。如果滤波参数设置合理,重量值在几个刷新周期内就能稳定下来,不会出现长时间震荡。Proteus仿真的一大优势是能随时暂停、单步、查看变量,这比真实硬件调试快太多了。

仿真始终是仿真,它验证的是“逻辑正确性”和“状态机完备性”,不代表真实硬件上就万事大吉。传感器精度、电源纹波、焊接可靠性这些物理层面的问题,仿真完全覆盖不到,该踩的坑还是要实调阶段再踩一遍。

5. 实调阶段的顽固问题与解决记录

5.1 DOUT永远不拉低:卡死等数据的根因排查

项目上电后第一个遇到的严重问题,是程序永远卡死在等待HX711 DOUT拉低的循环里。终端没有输出,OLED也不刷新,芯片像死机一样。

排查链路是这样的:先测HX711的VCC和GND,供电正常;再测E+/E-之间电压,发现只有0.2V,明显不对。这时候怀疑传感器接线问题,用万用表测传感器四根线的电阻,红黑之间应该有几百欧到一千欧左右的阻值,绿白之间也差不多。实测红黑阻值正常,绿白阻值接近无穷大,说明信号线内部虚断,换了一个传感器后,DOUT立刻正常了。

这说明一个经验:HX711的DOUT不拉低,优先怀疑传感器通道,而不是怀疑HX711芯片。传感器是纯机械结构,运输挤压、焊接过热、螺丝拧太紧都可能把内部应变片弄断。另外,第一次上电时如果传感器线接反,芯片不会烧,但输出数据完全不可用,表现也是DOUT持续高电平。

5.2 电源噪声导致的读数跳变:语音模块是最大干扰源

硬件调通后,遇到一个很典型的软故障:空载时重量读数稳定在0g附近,但只要语音模块一播报,读数瞬间跳几克到几十克,播报结束又恢复。

最后定位到电源共地问题:语音模块的功放电源和HX711的模拟电源在同一路5V上,喇叭发声时瞬时电流可达几百毫安,这段电流在电源线上产生的压降和噪声直接传导给了HX711的AVDD。解决方法是把HX711的模拟供电单独用一节LC滤波:5V先进一个10uH电感,电感后对地接一个100uF电容和一个100nF电容,再进HX711的AVDD。语音模块的电源则在主板上通过一段较宽的铜箔单独走线,与模拟部分在源端汇合。

这个问题的本质是“单点接地”和“功率地与模拟地分离”。很多新手只关注数字和模拟的信号流向,忽略了电源回流路径,结果就是现象奇怪、排查困难。

5.3 语音模块乱码和首条命令丢失

语音模块第一次上电时,我发了一条提示音“欢迎使用智能电子秤”,结果播出来的完全不是中文,是含糊不清的字节音。

排查发现两个问题叠加。第一是波特率:模块出厂默认9600,但我在CubeMX里把USART2初始化为115200,自然乱码。SYN6288和多数语音模块默认都是9600,改代码前最好先确认模块手册,而不是想当然。第二是编码:当时源文件用了UTF-8,中文字符串转成GBK字节前没有转换,导致模块收到非GBK编码。把工程源文件编码切回GB2312后,乱码彻底解决。

首条命令丢失则是上电时序问题。SYN6288上电后需要几百毫秒完成内部初始化,期间串口收到的命令不会处理。解决方法是主程序启动后先延时800ms,再发送开机提示语音。这个问题在仿真里完全看不出来,只有真实硬件才会暴露。

5.4 标定系数的数量级参考与快速验证法

很多人标定后心里没底,不知道算出来的scale_factor是否正常。我给出一个经验参考:以5kg传感器、5V激励、HX711增益128为例,满量程时HX711的差分输入约为5mV左右,经128倍放大后约640mV,24位ADC满量程范围约对应0.6V左右;每克大约能分到几十到几百个ADC码。如果算出来的scale_factor小于50或者大于5000,大概率是硬件接线有问题或者标定流程没走对。

快速验证标定结果的方法:空载去皮后,放一个已知重量的小物品,比如一枚一元硬币大约是6g,看显示重量是否在5.8~6.2g之间。如果偏差大,重新执行标定流程,不要用软件强行修正,因为根源通常在硬件。

5.5 下载调试类错误:从CubeMX到ST-Link的常见问题

调试过程中还遇到过一个拦截问题:用ST-Link下载固件时,报错“no stm32 target found”,连接不上目标芯片,但板子电源正常。

排查顺序建议是:先检查ST-Link与板子的接线——SWDIO、SWCLK、GND、3.3V四根线有没有插反或虚接;然后看BOOT0是否为低电平,如果BOOT0被拉到高电平,芯片会进入系统存储器模式,不响应SWD连接;再确认目标板供电电压、ST-Link驱动状态。还有一个容易被忽略的问题:STM32F103的SWD引脚如果代码里被复用成GPIO功能,首次下载后二次下载会失败,解决办法是用ST-Link Utility执行整片擦除,或者按住复位键再点下载。

Keil里还要确认Debug选项选择了ST-Link调试器,并且Flash Download配置里勾选了Reset and Run。如果这些都确认无误还连不上,换一根杜邦线往往比查半天配置更有效。

经验收尾:这台秤做完之后,我建议你再往前走几步

整个项目从仿真验证到实物跑通,前后花了一周多,大部分时间其实是耗在电源噪声和模块时序这些“看起来不应该有问题”的地方。开源包里已经有代码、原理图和仿真工程,如果你照着做出来能用,但千万别满足于此。我建议下一步在固件里加一个蓝牙串口透传,把重量数据发给手机App,或者加一个Wi-Fi模块把数据推到云端,做粮仓监测或宠物喂食器的雏形;也可以把语音模块换成带功放的更大功率型号,做成桌面助手。我在实际使用中还发现,把OLED改成动态显示历史重量曲线能显著提升使用频率,但这需要换一个更大的屏幕,128x64的像素画曲线实在勉强。项目虽小,链路完整,能把每个模块为什么这么设计讲清楚,就算没白做。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询