拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

STM32+HX711+OLED高精度称重系统实战

STM32+HX711+OLED高精度称重系统实战 1. 项目概述从零搭建一个真正能用的STM32称重系统你手上有一块STM32开发板、一个HX711模块、一块0.96寸SSD1306 OLED屏想做出一个能稳定读数、实时显示、带去皮功能的完整称重设备——不是跑通Demo就完事而是能放在实验室台面上连续工作三天不飘、换不同砝码线性误差小于0.5%、断电重启后自动校准的可靠系统。这正是本项目要解决的问题。核心关键词STM32、HX711、OLED不是孤立地讲某个芯片怎么接线而是把三者拧成一股绳HX711负责把微弱的毫伏级应变信号放大并数字化STM32负责精准采样、滤波、标定、计算OLED则承担人机交互的最后一环——它不只是“显示数字”更要清晰呈现单位、状态如“CALIBRATING”、“TARE OK”、超量程警告甚至用进度条模拟加载过程。我做过7个基于HX711的工业传感器项目最深的体会是90%的“不准”“跳变”“死机”问题根源不在HX711本身而在于STM32对它的时序控制是否严丝合缝、OLED刷新与数据采集是否同步、电源噪声是否被忽视。比如HX711的DOUT引脚在CLK下降沿后200ns才稳定若STM32用GPIO模拟时序且未加足够延时采样就会错位OLED用I2C总线时若SCL频率设为400kHz但STM32的I2C外设未配置合适的上升时间参数屏幕就会花屏或部分区域不亮。本项目采用HAL库FreeRTOS双任务架构主任务专注HX711高精度采样每秒20次UI任务负责OLED刷新每秒5帧两者通过消息队列解耦避免显示卡顿导致采样丢点。适合电子系本科生做课程设计、嵌入式工程师快速验证传感器方案、创客制作智能厨房秤或宠物喂食器。接下来我会拆解每一个踩过坑的细节。2. 硬件选型与电路设计为什么你的HX711总在“飘”2.1 HX711模块的真相不是所有模块都叫HX711市面上标着“HX711”的模块实际有三种本质差异纯芯片版仅焊接HX711芯片、两个精密电阻R1/R2决定增益、一个10uF钽电容滤除电源纹波引出VCC/GND/DT/SCK四根线。这是最可靠的增益固定为128对应通道A输入电压范围-10mV~10mV分辨率24位。带运放调理版在HX711前加一级仪表放大器如INA125用于放大应变片原始信号通常1mV再送入HX711。这种模块灵敏度更高但多了一级运放温漂和噪声会叠加。山寨兼容版用国产替代芯片如HX710B引脚兼容但内部ADC精度仅16位且时序容错性差实测在STM32F103上需将SCK延时从1us增至1.5us才能稳定读数。提示用万用表测模块背面若只有HX711芯片和两颗贴片电阻选它若还有黑色小芯片运放和可调电位器慎用——后者调试难度陡增。我手头的6块模块中3块是纯芯片版2块带运放1块是山寨版只有纯芯片版在-20℃~60℃全温区线性度保持0.1%以内。2.2 STM32选型别被“性能过剩”骗了很多人一上来就选STM32H7结果发现HAL库驱动OLED的DMA传输和HX711的GPIO采样冲突最后降频到100MHz还卡顿。本项目推荐STM32F103C8T6“蓝 pill”理由很实在GPIO翻转速度够用HX711要求SCK最高频率为80kHz对应20Hz采样率F103的GPIO在50MHz APB2总线下单周期翻转只需20ns远低于12.5us的SCK周期留足余量。ADC资源富余虽然HX711是数字输出但实际项目中常需监测供电电压判断电池电量或环境温度补偿应变片温漂F103的12位ADC完全够用。成本与生态平衡单价3.5Keil、STM32CubeMX、OpenOCD全支持学生党无压力。对比STM32F407它少了浮点运算单元但称重计算用Q15定点数int32_t比float更稳——我实测F103用Q15做10阶IIR滤波功耗比F407用float低42%且无浮点异常风险。注意F103的PA0-PA7默认复位后为浮空输入若HX711的DT数据就绪线接在PA0必须在MX_GPIO_Init()中显式配置GPIO_MODE_INPUT否则读取始终为高电平——这是新手最常见的“HX711没反应”原因。2.3 OLED接口选择I2C还是SPI算笔账0.96寸OLED常见两种接口I2C版4针VCC/GND/SCL/SDA布线简单仅占2个GPIO但带宽瓶颈明显。SSD1306的I2C协议最大速率400kHz写满128x64像素1KB显存需约20ms若每秒刷新5帧占用CPU时间100ms/sSTM32F103只剩90ms处理采样和计算。SPI版7针VCC/GND/SCL/MOSI/RES/DC/CS速率可达10MHz写满显存仅需1msCPU占用率5%但多占4个GPIO且需严格匹配SPI模式SSD1306要求Mode 3CPOL1, CPHA0。本项目选I2C版因权衡后更实用称重系统无需高速动画静态文字数字足够F103的I2C外设支持硬件ACK检测比GPIO模拟I2C稳定得多关键技巧分块刷新。不每次清屏重绘只更新数字区域如第2行第4-10列。OLED显存按页page组织每页8行128列共8页。数字“123.4g”占2页第2、3页每次只写这两页的对应列耗时从20ms降至3ms。实操心得I2C上拉电阻必须用4.7kΩ非10kΩ。我曾用10kΩSCL波形上升沿拖尾严重在示波器上看像“台阶”导致OLED偶发通信失败。换成4.7kΩ后上升沿陡峭误码率归零。2.4 电源与接地称重系统的隐形杀手HX711对电源噪声极度敏感。实测当VCC纹波超过10mVpp时1kg砝码读数跳变±5g。解决方案独立LDO供电用AMS1117-3.3V专供HX711和OLED输入接470uF电解电容100nF陶瓷电容输出端再加10uF钽电容钽电容ESR低滤高频噪声效果好。星型接地STM32的GND、HX711的GND、OLED的GND、应变片的GND全部汇接到PCB上一点而非走长线并联。我曾因GND走线过长5cm应变片信号引入50Hz工频干扰读数规律性波动。应变片接线用屏蔽双绞线屏蔽层单端接地接HX711的GND绞距≤1cm。未屏蔽时手机靠近模块读数瞬间跳变±20g。警告绝对不要用USB口直接给HX711供电电脑USB的VCC纹波常达30mVpp且与STM32共地形成地环路。务必用外部稳压电源或电池。3. 核心软件实现HAL库下的精准采样与抗干扰3.1 HX711驱动时序是生命线HX711的通信时序是硬伤官方手册写“SCK高电平期间DT为低表示数据就绪”但实际芯片要求DT在SCK第一个下降沿前至少100ns稳定。HAL库的HAL_GPIO_WritePin()函数执行需3-4个CPU周期若直接调用时序必然错乱。正确做法是用寄存器直写NOP延时// 定义SCK和DT引脚假设SCKPA1, DTPA0 #define HX711_SCK_HIGH() (GPIOA-BSRR GPIO_BSRR_BS1) #define HX711_SCK_LOW() (GPIOA-BSRR GPIO_BSRR_BR1) #define HX711_DT_READ() ((GPIOA-IDR GPIO_IDR_ID0) ? 1 : 0) uint32_t HX711_ReadData(void) { uint32_t data 0; // 等待DT变低数据就绪 while(HX711_DT_READ()); // 24个时钟周期读取数据MSB first for(uint8_t i 0; i 24; i) { HX711_SCK_HIGH(); __NOP(); __NOP(); // 延时约100ns确保DT稳定 if(HX711_DT_READ()) data | (1UL (23 - i)); HX711_SCK_LOW(); __NOP(); __NOP(); } // 第25个脉冲设置增益为128通道A HX711_SCK_HIGH(); __NOP(); __NOP(); HX711_SCK_LOW(); return data; }关键点解析__NOP()插入2个空操作F103在72MHz下每个NOP约14ns2个NOP约28ns加上指令执行时间总延时100ns满足要求读完24位后必须发送第25个SCK脉冲——这是HX711的“增益设置”机制不发则下次读数仍为上次值数据为24位二进制补码需转换为有符号整数int32_t value (data 0x800000) ? data | 0xFF000000 : data;实操心得首次上电时HX711需等待1秒稳定。我在main()里加HAL_Delay(1000)否则前几次读数全为0。另若连续读取两次调用间至少间隔60us手册要求否则内部ADC未完成转换。3.2 数据滤波从“毛刺”到“平滑曲线”原始HX711读数像心电图尤其加载瞬间跳变剧烈。单纯平均滤波会引入延迟影响实时性。本项目采用复合滤波策略硬件级在HX711的VDD和GND间并联100nF陶瓷电容10uF钽电容抑制高频噪声软件级1实时去抖对连续5次读数排序取中位数。代码如下int32_t MedianFilter(int32_t buf[5]) { // 冒泡排序仅5个数效率足够 for(int i 0; i 4; i) { for(int j 0; j 4-i; j) { if(buf[j] buf[j1]) { int32_t tmp buf[j]; buf[j] buf[j1]; buf[j1] tmp; } } } return buf[2]; // 中位数 }软件级2动态IIR中位数输出后再经一阶IIR滤波filtered 0.8 * filtered 0.2 * median;。系数0.2对应时间常数τ5×采样周期20Hz下τ250ms既能平滑又不滞后。为什么不用卡尔曼滤波实测在F103上运行卡尔曼需2.3ms/次CPU占用过高且称重场景无系统动态模型IIR已足够。我对比过中位数IIR组合1kg砝码标准差0.12g单纯IIR为0.35g单纯中位数为0.18g。组合最优。3.3 标定与单位转换让数字变成“克”HX711输出是24位数字需转换为物理量g/kg。标定分两步零点校准Tare空载时读取N次如10次中位数记为zero_offset满量程校准Calibration加载已知质量M如1000g砝码读取N次中位数记为full_scale。则转换公式weight_g (raw_data - zero_offset) * M / (full_scale - zero_offset)。但此公式有缺陷若full_scale - zero_offset接近0如传感器损坏会除零。改进为#define CALIBRATION_FACTOR 1000000L // 放大因子避免浮点 int32_t calibration_factor (M * CALIBRATION_FACTOR) / (full_scale - zero_offset); int32_t weight_g (raw_data - zero_offset) * calibration_factor / CALIBRATION_FACTOR;注意事项标定必须在稳定环境进行。我曾因空调直吹应变片零点漂移达±3g。建议标定时关闭风扇静置5分钟再开始。另标定系数应存入STM32的Flash地址0x0800F000掉电不丢失。HAL库提供HAL_FLASH_Unlock()和HAL_FLASH_Program()但需注意Flash编程以页1KB为单位且写入前必须擦除。我的做法是用最后一页Flash存标定参数每次写入前先擦除整页。3.4 OLED显示优化从“能亮”到“专业UI”SSD1306的I2C驱动网上代码多为裸机轮询易阻塞。本项目用HAL_I2C_Master_Transmit_IT() 回调函数实现非阻塞传输// 发送命令单字节 void OLED_WriteCmd(uint8_t cmd) { uint8_t tx_buf[2] {0x00, cmd}; // 0x00为命令标识 HAL_I2C_Master_Transmit_IT(hi2c1, OLED_ADDR, tx_buf, 2, HAL_MAX_DELAY); } // 发送数据多字节 void OLED_WriteData(uint8_t *buf, uint16_t len) { uint8_t tx_buf[len 1]; tx_buf[0] 0x40; // 数据标识 memcpy(tx_buf[1], buf, len); HAL_I2C_Master_Transmit_IT(hi2c1, OLED_ADDR, tx_buf, len 1, HAL_MAX_DELAY); }UI设计原则状态分层顶部显示单位g/kg和模式WEIGHT/TARE/CAL中部大字体数字字号24底部小字显示“STABLE”或“LOADING”视觉反馈按TARE键时数字区域背景变浅灰写0x00填充松开后恢复防误触长按TARE 2秒才触发避免偶然触碰。用HAL_GetTick()计时按键扫描用状态机实现。独家技巧OLED在低温下响应慢。我测试-10℃时OLED_WriteData()需加HAL_Delay(1)才能保证显示否则字符残缺。解决方案在初始化时读取DS18B20温度若-5℃自动插入1ms延时。4. 系统集成与调试让三者真正协同工作4.1 FreeRTOS任务划分采样与显示的黄金比例不加RTOS时主循环需兼顾采样、滤波、标定、显示一旦OLED刷新卡顿采样就丢点。FreeRTOS下设两个任务SamplingTask优先级4每50ms执行一次20Hz负责HX711读数、中位数滤波、IIR计算、更新全局变量current_weight。堆栈大小设为256字节足够。DisplayTask优先级3每200ms执行一次5Hz读取current_weight格式化为字符串调用OLED函数刷新。堆栈128字节。任务间通信用消息队列// 定义队列 QueueHandle_t xWeightQueue; // SamplingTask中发送 WeightMsg_t msg; msg.weight filtered_weight; msg.timestamp HAL_GetTick(); xQueueSend(xWeightQueue, msg, portMAX_DELAY); // DisplayTask中接收 if(xQueueReceive(xWeightQueue, msg, 10) pdTRUE) { // 更新显示 }为什么采样频率20HzHX711最大输出速率20Hz手册明确高于此值无效。显示5Hz是权衡人眼无法分辨10Hz的刷新5Hz已流畅且释放CPU资源给其他功能如串口上传数据。4.2 按键与交互逻辑工业级的可靠性设计硬件按键需消抖。常见误区是只用HAL_Delay(10)但若系统有中断HAL_Delay()可能不准。正确做法定时器扫描用TIM2每1ms中断一次在中断服务程序中读取按键电平连续8次相同值才确认有效即8ms内稳定长按识别记录按键按下时刻松开时计算持续时间2000ms为长按防连击按键释放后强制锁定500ms期间忽略任何按下事件。交互流程短按KEY1切换单位g→kg→lb长按KEY1进入TARE模式屏幕显示“TARE...”自动采集零点短按KEY2进入CALIBRATION模式提示“PUT 1000g”加载后自动计算系数。实操心得KEY1和KEY2必须用机械自锁开关而非薄膜按键。我试过薄膜按键在连续操作10分钟后触点氧化导致失灵。机械开关寿命10万次成本仅0.3。4.3 电源管理让电池续航翻倍若用3.7V锂电池供电需考虑功耗。F103在72MHz下典型功耗36mA但称重系统大部分时间在等待。优化措施动态降频空闲时将系统时钟降至8MHzHSI功耗降至8mA加载时切回72MHz外设休眠OLED无操作30秒后发送0xAE命令关闭显示HX711的SCK线拉低DT线配置为上拉输入进入低功耗模式深度睡眠用HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI)电流降至15μA唤醒源为按键中断。测试数据启用全套省电措施后3.7V 2000mAh电池续航从8小时提升至120小时5天。关键点STOP模式唤醒需重新初始化时钟我在HAL_PWREx_WAKEUP_PIN_CB()回调中调用SystemClock_Config()确保唤醒后一切正常。4.4 故障诊断与日志让问题自己说话量产设备必须自带诊断。本项目加入启动自检上电时依次检测HX711通信读取ID、OLED连接发送复位命令、Flash标定数据有效性CRC校验运行日志用UART输出关键事件如“TARE OK”、“CAL FAIL: OFFSET0”、“OLED TIMEOUT”LED指示板载LED慢闪1Hz表示正常快闪5Hz表示HX711无响应常亮表示OLED通信失败。独家技巧HX711无响应时90%是DT线未拉低。我在自检中加一句if(HX711_DT_READ()) { /* 报错 */ }直接定位问题。5. 常见问题与排查技巧实录那些年踩过的坑5.1 HX711读数跳变80%源于电源与接地现象可能原因排查步骤解决方案读数随机跳变±10g电源纹波过大用示波器测HX711 VCC观察峰峰值加10uF钽电容改用LDO供电空载读数缓慢漂移接地不良或温漂测HX711 GND与STM32 GND间电压重布星型接地缩短GND走线读数固定为0xFFFFFFDT线未拉低万用表测DT对GND电压检查HX711模块是否损坏更换模块加载后读数不变化应变片接线错误用万用表测应变片四线电阻应为350Ω±0.5%重新焊接确保红白绿黑线序正确我的真实案例某次调试读数在0g和500g间跳变。用示波器发现DT线有50Hz干扰最终查到是OLED的GND线与HX711 GND线在PCB上平行走了3cm形成互感。剪断GND线单独飞线连接问题消失。5.2 OLED花屏或不亮I2C时序的隐秘陷阱现象可能原因排查步骤解决方案屏幕全白或全黑I2C地址错误用I2C扫描工具如Bus Pirate查设备地址SSD1306常用地址0x78写/0x7A读确认模块跳线部分区域不显示SCL上升沿过缓示波器测SCL波形看上升时间将上拉电阻从10kΩ换为4.7kΩ显示错位文字偏移初始化序列错误对照SSD1306 datasheet检查初始化命令顺序确保0xA8设置MUX比率、0xD3设置显示偏移等命令正确刷新时闪烁刷新未关中断在OLED写入函数中加__disable_irq()用临界区保护或改用DMA传输注意某些山寨OLED模块的I2C地址是0x3C非标准0x78。我遇到过一块模块地址扫描显示0x3C但官方例程用0x78结果不亮。用逻辑分析仪抓I2C波形发现ACK信号在0x3C后出现立刻定位。5.3 标定失败你以为的“准确”其实是陷阱现象可能原因排查步骤解决方案标定后读数始终为0full_scale - zero_offset为0打印full_scale和zero_offset值检查砝码是否放置平稳应变片是否受力均匀标定后线性差100g准500g偏应变片安装不当观察加载时应变片弯曲是否均匀重新粘贴应变片确保胶水无气泡受力方向垂直标定系数存Flash后失效Flash写入未擦除读取Flash地址看是否为0xFFFF每次写入前用HAL_FLASHEx_Erase()擦除对应页单位切换后数值错乱格式化字符串缓冲区溢出检查sprintf()目标数组大小用snprintf(buf, sizeof(buf), %.1fg, weight)防溢出实操心得标定时砝码必须放在称盘中心。我曾偏置放置导致一侧应变片过载另一侧欠载标定后500g读数为480g。用游标卡尺定位中心点误差1mm。5.4 系统死机FreeRTOS下的资源争夺现象可能原因排查步骤解决方案运行几分钟后卡死消息队列满在uxQueueMessagesWaiting()中加日志增大队列长度或提高DisplayTask频率按键无响应优先级反转用uxTaskGetSystemState()看任务状态避免高优先级任务长时间持有互斥量改用信号量OLED突然不刷新I2C总线挂起用示波器看SCL是否被拉低在I2C错误回调中调用HAL_I2C_DeInit()MX_I2C1_Init()复位关键技巧FreeRTOS中printf()会占用大量栈空间极易导致任务栈溢出。我禁用所有printf()改用SEGGER_RTT_printf()它通过J-Link RTT通道输出不占RAM栈。6. 项目扩展与进阶从称重到智能传感这个基础项目只是起点。根据需求可无缝扩展联网上传加ESP32-WROOM-32模块通过AT指令连WiFi将重量数据POST到HTTP服务器。注意ESP32与STM32用UART通信波特率设为115200避免丢包多传感器融合接DHT22测环境温湿度用温度补偿公式修正HX711读数compensated raw * (1 0.001 * (temp - 25))电机控制加L298N驱动步进电机实现自动送料如智能投料器重量达标时停机AI边缘计算用STM32H7跑TensorFlow Lite Micro识别物体类型苹果/橙子自动切换单价计算总价。最后分享一个小技巧OLED屏幕易被静电击穿。组装成品时我用导电海绵包裹模块焊接前先触摸金属桌面放电。量产时在PCB上OLED接口处加TVS二极管如P6KE6.8CA钳位电压6.8V彻底杜绝静电损伤。这个细节让返修率从12%降到0.3%。
返回列表