ARTICLE DETAIL

资讯详情

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

STM32与HX711电子秤实战:从驱动编写到重量标定

STM32与HX711电子秤实战:从驱动编写到重量标定 简介一套基于 STM32CubeMX 与 HAL 库的 HX711 压力传感器开发资源面向 5kg 量程称重模块帮助嵌入式开发者解决高精度称重数据采集、串口实时输出与 OLED 屏幕显示问题。HX711 为 24 位 A/D 转换芯片资源中完整展示了其与 STM32 的配合方式。资源包为 zip 压缩格式大小 24.05MB共 1278 个文件以 C 源码、H 头文件、启动汇编文件、链接脚本和 .ioc 工程配置为主并附带编译生成的 hex/axf 文件工程可在 Keil 中直接打开编译。已有 5531 人学习下载适合需要快速上手 HX711 与 STM32 开发的学生和工程师。内容涵盖传感器引脚连接说明、皮重扣除与 GapValue 系数校准思路只需修改宏定义即可调整重量换算系数同时提供串口助手查看数据及带字库 OLED 实时显示的实现代码整体目录结构清晰可作为称重类项目的参考模板。 把HX711接上STM32通电之后打开串口助手看到的不是稳定数字而是在一组值里来回跳——这个场景我猜很多人经历过。HX711是电子秤项目里最常见的一款24位ADC压力传感器模块配合STM32F103C8T6这类入门级MCU再加一块0.96寸OLED就能做成一个完整的称重显示设备。这篇文章把整套流程拆开HX711的硬件机制、STM32CubeMX图形化配置、驱动代码编写再到标定公式推导全都过一遍而且用的是HAL库。适合手里有板子、想快速跑通“压力采集→串口输出→屏幕显示”这条链路的开发者参考。1. HX711的工作机制为什么这个模块只要两根线就能读数1.1 24位ADC的价值HX711是一片专为电子秤设计的24位高精度ADC内部集成了稳压电源、差分输入、可编程增益放大器和串行通信接口。之所以要24位是因为称重传感器的输出信号非常微弱——常见的悬臂梁式压力传感器满量程输出通常只有1mV/V到2mV/V激励电压为5V时满量程差分电压也就5mV到10mV。如果拿普通10位或12位ADC去采10mV信号被分到4096份里每份只有几微伏再加上噪声基本上什么都测不准。24位ADC把同样的信号分成16777216份即使放大倍数不高也能保持足够的分辨率。HX711内部还带一个低噪声稳压器可以给传感器直接供电简化了外围电路。这也是为什么几乎所有市面上的称重模块都拿它当核心芯片。1.2 通道、增益与数据格式HX711有A、B两个输入通道A通道增益可选128倍或64倍B通道固定32倍。A通道通常接称重传感器因为称重传感器是差分输出需要高增益来放大微弱信号。芯片通过PD_SCK引脚发送脉冲个数来切换通道和增益25个脉冲A通道128倍增益26个脉冲B通道32倍增益27个脉冲A通道64倍增益每次转换完成后DOUT引脚会从高电平拉低表示“数据准备好了”。这时主控单片机发送25个SCK脉冲就能在脉冲的下降沿之前依次读出24位数据。数据以二进制补码形式存放最高位是符号位所以读出来的24位数据要手动扩展到32位的有符号整数否则满量程会变成正的16777215而不是-8388608到8388607。1.3 接线与模块版本注意模块接线很简单核心就是四根线模块引脚接STM32说明VCC3.3V或5V很多模块板载稳压5V供电也行GNDGND共地必须接好SCK/PD_SCKPA0时钟输入由MCU控制DOUTPA1数据输出推挽输出有个容易踩的坑是丝印不统一。有的模块把SCK标成PD_SCK还有的版本叫做SPI模式——不过HX711本质上不是SPI协议它只是两根线的高电平脉冲时序不需要片选信号也不需要时钟极性和相位配置。实际操作时只要确认DOUT接MCU输入、SCK接MCU输出一般就不会接反。2. CubeMX图形化配置从时钟到外设的完整清单2.1 新建工程与时钟树设置打开STM32CubeMX选择STM32F103C8T6这个型号。第一个要处理的是RCC时钟。HSE选择Crystal/Ceramic Resonator也就是外部晶振然后在Clock Configuration页面里把系统时钟配置到72MHz。配置方法是HCLK输入72总线分频器会自动调整PLL Source选HSE倍频系数取9。如果不配外部晶振也可以直接用HSI内部8MHz时钟但串口波特率精度会差一些115200这种高速率下偶尔会出现误码。我习惯先把USART1和调试串口搞好因为后面所有调试信息都要靠它输出。如果这一步忘了开UART代码写完才发现就得回CubeMX重新勾选再生成工程还能保留但比较麻烦。2.2 Pinout配置清单在Pinout Configuration页面里依次配置PA0设置为GPIO_Output也就是接HX711 SCKPA1设置为GPIO_Input接HX711 DOUTUSART1设置为Asynchronous异步模式波特率1152008位数据无校验1位停止位I2C1设置为I2C标准模式或快速模式如果用的是0.96寸OLED且支持硬件I2C可以直接用I2C1的PB6和PB7很多教程喜欢用软件模拟I2C驱动OLED因为SSD1306对时序要求不算苛刻软件模拟也稳定。不过我建议能用硬件I2C就用硬件I2CHAL库对硬件I2C的支持很成熟代码量更少OLED刷新也不会被其他中断频繁打断。软件I2C只是多两个GPIO口和高低电平翻转的问题如果板子引脚紧张再考虑软件方案。2.3 中断与NVIC配置串口部分建议打开USART1全局中断方便后面做中断接收或空闲中断。如果只是调试用的串口输出不配置中断也能工作因为发送走的是阻塞轮询方式但接收数据时会遇到“收一次就再也不进中断”的情况——多半是中断标志没清除或者没有配置NVIC优先级。最稳妥的做法是在CubeMX里把USART1中断优先级设置成1到3之间的一个值别用0给系统滴答中断留点余地。HX711本身不需要外部中断读取完全靠等待DOUT拉低所以不用在EXTI上花时间。生成代码前在Project Manager里选好Toolchain为MDK-ARMCode Generator勾选“Generate peripheral initialization as a pair of .c/.h files per peripheral”这样每个外设单独一个文件代码结构清楚改起来方便。3. 驱动代码与数据稳定化让原始值从跳变成稳3.1 读取一个完整24位整数CubeMX生成代码之后在main.c或者单独建一个hx711.c先实现最底层的读取函数。核心逻辑是等待DOUT从高变低然后连续产生24个SCK脉冲沿路把DOUT电平读进来拼成一个32位的整数再做符号扩展。下面是经过超时保护的基础读取函数uint8_t HX711_Read(int32_t *value) { uint32_t data 0; uint16_t timeout 10000; while (HAL_GPIO_ReadPin(HX711_DOUT_GPIO_Port, HX711_DOUT_Pin) GPIO_PIN_SET) { if (--timeout 0) return 0; } for (uint8_t i 0; i 24; i) { HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_SET); data 1; if (HAL_GPIO_ReadPin(HX711_DOUT_GPIO_Port, HX711_DOUT_Pin) GPIO_PIN_SET) data | 1; 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位补码转32位有符号数 */ if (data 0x800000) data | 0xFF000000; *value (int32_t)data; return 1; }注意最后那个第25个脉冲极其关键。HX711的协议要求读数据时至少要发出25个SCK脉冲前24个脉冲移出数据第25个脉冲告诉芯片继续按当前通道和增益工作。如果漏了这第25个脉冲下次读取时芯片可能切到别的通道数据乱掉。3.2 为什么读出来的值会跳很多人第一次读HX711发现零点状态下数值不停变化就以为是传感器坏了。实际上HX711本身分辨率极高一颗5kg的传感器满量程输出10mV增益128倍后也就是1.28V左右分配到24位ADC上每个LSB对应的重量可能只有零点几克。此时传感器自身的噪声、电源纹波、甚至导线间的电磁干扰都会被放大到几克乃至十几克的波动。应对办法是软件滤波。我自己测试下来最实用的组合是“多次采样取平均值”或者“去掉最大最小值再取平均”。称重场景不像高速采集100ms读一次完全够用10次采集中值平均之后静态读数能稳定到±1克以内。下面这个函数就是连续读20次取平均每次间隔5msint32_t HX711_GetAverage(uint8_t times) // times通常取10或20 { int32_t sum 0; int32_t raw 0; for (uint8_t i 0; i times; i) { if (HX711_Read(raw)) sum raw; HAL_Delay(5); } return sum / times; }3.3 初始化阶段的保护还有两个细节值得写进代码里。第一个是上电后HX711需要几十毫秒才会进入稳定工作状态CubeMX生成的main函数里加个HAL_Delay(200)再循环读取避免开头几个异常值进滤波。第二个是超时保护不能省。如果传感器信号线松了、模块没供电DOUT就一直保持高电平while循环会卡死整个程序。所以我在读取函数里加了timeout返回0表示读取失败主程序可以用这个标志位做错误提示。4. 标定的本质原始值如何换算成重量4.1 一点法还是两点法HX711输出的是ADC原始值不是重量。重量和原始值呈线性关系所以标定就是要找出这条直线的斜率。一个常见的误区是查芯片手册去算一个固定的换算系数比如“多少原始值等于1克”。这行不通因为传感器量程、灵敏度、供电电压、增益设置都会影响斜率每个传感器个体也有差异必须实际标定。最简单的做法是零点加砝码的一点标定float scale 0.0f; int32_t zero 0; // 空载时读取的平均值 // 放一个已知重量的砝码比如1000g读取稳定值 int32_t raw_1000 HX711_GetAverage(20); scale (float)(raw_1000 - zero) / 1000.0f; // 后续换算重量 float weight (float)(current_raw - zero) / scale;这是利用“直线过原点”的假设把零点当作(0,0)另一个点确定斜率。实际应用中由于传感器安装应力、ADC偏移等因素零点不一定完全落在原点上所以更准确的是两点标定记录空载时的原始值raw0和已知重量weight0再记录放砝码时的raw1和weight1然后解出斜率gain和偏移offsetfloat gain (weight1 - weight0) / (float)(raw1 - raw0); float offset weight0 - gain * (float)raw0; // 任一时刻的重量 float weight gain * (float)current_raw offset;两点法能同时消除零点漂移和满量程误差是电子秤项目里最实用的标定方式。4.2 标定步骤与数据记录实际操作时我建议这样标定空载状态下让设备稳定运行至少10秒取20次平均得到raw0放一个接近量程60%到80%的标准砝码同样稳定后取20次平均得到raw1记录砝码实际重量weight1把gain和offset通过串口打印出来存到本地方便以后微调砝码选择很有讲究。太轻的砝码比如10克信号幅值小噪声占比大标定出来的斜率误差会被放大太重超出传感器线性区标定结果也不准。尽量选传感器量程的1/2左右效果最好。没有标准砝码时可以用已知重量的纯净水但要注意容器自重也要算进去而且水会蒸发标定过程要快。4.3 标定参数保存与恢复标定参数如果每次开机都要重新标定那项目就太费劲了。STM32F103C8T6内部有64KB Flash留出最后一页存标定数据是常见做法。使用HAL库的Flash接口时要注意先擦除再写入而且擦除以页为单位。写入时可以把gain和offset打包成一个结构体写进固定地址开机时读取并做一次合理性校验比如gain必须在某两个阈值之间否则就认为没有标定过提示用户重新标定。这件事最好在设计初期就考虑进去别等代码写完再改。我就是一开始没做Flash存储每次断电重新标定后来加了存储功能整个设备才真正能用。5. 串口与OLED双通道输出工程的最后一步5.1 printf重定向到串口CubeMX生成的HAL库默认没有把printf映射到串口需要自己重写fputc函数。在usart.c文件末尾加上#include stdio.h int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 0xFFFF); return ch; }然后勾选Keil MDK里Target选项卡的“Use MicroLIB”选项就能直接使用printf格式化输出了。不加MicroLIB的话标准库的printf会占用大量Flash空间而且重定向可能不生效。调试输出建议设计成一个方便看的数据帧格式。比如每200ms输出一次格式为raw: 123456, weight: 125.3g, status: OK如果后续要接上位机做数据记录也可以用CSV格式比如raw,weight。这样在串口调试助手里就能直接看到传感器原始值和换算后的重量排查问题时特别有用。HX711模块的数据手册说24位有效位数实际用下来真正稳定的位数大概在20位左右算法做好之后串口看到的重量能稳定到0.1g级别。5.2 OLED显示内容与刷新节流屏幕显示部分我用的是0.96寸SSD1306 OLEDI2C接口。CubeMX里配好硬件I2C1之后把SSD1306的驱动库拷到工程里初始化函数里传入hi2c1调用OLED_Init()就能点亮屏幕。显示内容除了实时重量还可以加上状态信息比如是否标定、当前滤波模式、供电电压等。OLED刷新有个禁忌不要在while循环里每一轮都全屏刷新会明显看到闪烁。称重显示场景下每秒刷新1到2次就足够肉眼观察了也就是每500ms到1000ms刷新一次。我这里用一个简单的毫秒时间戳控制if (HAL_GetTick() - last_display_time 500) { OLED_Clear(); OLED_ShowString(0, 0, Weight:); OLED_ShowNum(0, 16, (int)(weight * 10), 5, 12); // 放大10倍显示一位小数 OLED_ShowString(70, 16, g); OLED_Refresh(); last_display_time HAL_GetTick(); }关于显示内容不要在OLED上频繁更新小数点的位置否则数字跳变时会出现残影。最好固定位数比如统一显示5位整数加一位小数不足位的地方补零。5.3 串口接收“只收一次”的排查经验标题里还提到了“串口中断接收只收一次”这个热搜问题跟串口相关我额外多说两句。如果开了USART1中断接收却发现只能进一次回调大概率是中断标志没有处理干净。HAL库的接收中断回调里一般会重新调用HAL_UART_Receive_IT()开启下一次接收这句一定不能遗漏否则中断标志被HAL库清除后就没人再开启接收了。另外检查一下NVIC里USART1中断使能是否勾选以及CubeMX生成的DMA配置是否和普通中断冲突。如果项目里串口数据量比较大用空闲中断加DMA是更稳定的方案。空闲中断可以在接收完一帧数据后立刻触发配合DMA半满中断和全满中断能有效避免“高速数据进来时CPU来不及处理”导致丢字节的问题。这个方案稍微复杂一点但一旦调好后续不管接什么上位机都省心。6. 实机调试中的坑与应对6.1 电源纹波是最大的天敌HX711对电源噪声极其敏感。我接过一个临时用面包板搭的电路STM32和HX711共用一个USB转串口模块的5V供电结果数值在几百的范围内乱跳。后来把HX711的VCC改从STM32板上的3.3V引脚单独取电又在模块电源引脚旁边加了一颗100uF电解电容和一颗0.1uF陶瓷电容读数立刻稳了不少。如果系统里有电机、继电器这类大电流负载一定要做电源隔离或者至少物理上把模拟电源和数字电源分开布局。传感器信号线也要注意。尽量用双绞线或者屏蔽线连接长度不要超过30厘米。导线太长会有天线效应把你不需要的工频干扰全部收进来滤波算法再强也没用。6.2 传感器量程与ADC量程匹配很多刚入门的朋友会买一个5kg的传感器却用10kg的满量程来标定甚至把传感器超量程使用。压力传感器有一个线性工作区超出量程后输出电压不再随重量线性变化就算软件标定得再准重量大了照样差。更严重的是超量程会让弹性体产生永久变形传感器最终报废。选传感器时还要算一遍确保满量程输出经过增益放大后不会超出ADC参考电压。5V激励电压、2mV/V灵敏度的传感器满量程输出10mV经过128倍放大是1.28V在HX711内部参考电压范围内没问题。但如果是那种满量程输出比较高的特种传感器128倍增益之后可能削波这时要切成64倍增益。6.3 热漂移与零点漂移设备连续运行半小时以上很多传感器会随着温度升高出现零点飘移这是传感器弹性体和应变片的物理特性决定的不是电路问题。解决思路有两种一是尽量让设备工作在稳定的环境温度里不要在窗户边或者发热源旁边二是在软件里增加“去皮”功能或者定期自动采集空载数据刷新零点。我在实际项目中做过一个简单开机自动去皮上电后假设秤盘上没东西等系统稳定后自动记录零点。但这个逻辑有个前提必须确保上电时秤盘是空的否则程序会把东西的重量当成零点。更稳妥的做法是加一个独立按键用户按一下执行去皮这样才能应对所有场景。6.4 模块版本与供电电压差异市面上HX711模块版本很多有些用5V供电有些做了电平转换支持3.3V。STM32F103C8T6的引脚耐压是5V兼容的但如果用的是STM32G0系列或者GD32的某些型号IO不一定耐压5V那就需要确认模块电平是否兼容。另外HX711模块板载稳压芯片不同最低工作电压也不同有些模块3.3V供电时传感器激励电压会低于标称值导致满量程输出偏小影响标定精度。如果发现标定完的scale在不同供电电压下偏差很大检查一下模块上有没有跳线或焊盘可以切换参考电压。没有的话就固定用一个电压供电不要今天3.3V明天5V。6.5 给新手的最后建议这套方案跑通之后建议再做三件事把读取时间统计一下看看一次完整读取加滤波周期是多少方便做采样频率规划把异常数据识别逻辑加进去比如重量突变超过阈值就标记为无效最后就是多做几组不同重量的标定测试验证线性度。HX711这个模块虽然便宜但用好了可以承担很精确的称重任务。我自己测下来做好滤波和标定之后1kg量程的模块静态精度做到±0.5g以内是没问题的。当然调试过程中还会遇到一些看似莫名其妙的问题比如读出来永远是0、或者永远是满量程。遇到这种先别急着改代码用万用表量一下传感器E和E-之间的电压有没有正常建立再量A和A-之间的差分电压在不同负载下有没有变化。传感器没输出软件写得再好也白搭。本文还有配套的精品资源点击获取
返回列表