
1. HX711模块深度解析第一次接触HX711模块时我被它小巧的体积和惊人的精度震撼到了。这个比指甲盖大不了多少的芯片居然能实现24位精度的模数转换完全颠覆了我对电子秤的认知。让我们先拆解这个神奇的小模块。HX711本质上是一个专为称重传感器设计的ADC芯片但它比普通ADC强大得多。内部集成了可编程增益放大器(PGA)支持128倍放大这意味着即使是最微弱的压力变化也能被捕捉到。我实测过用普通应变片传感器配合HX711能稳定检测到0.1克的变化这对于DIY电子秤来说已经绰绰有余。模块引脚看似简单但每个都有讲究VCC2.6-5.5V宽电压供电实测3.3V和5V都能稳定工作GND接地要特别注意最好与STM32共地DT数据线需要上拉电阻很多开发板已经内置SCK时钟信号线初始化电平设置很关键说到SCK引脚这里有个血泪教训刚开始我按照常规思路初始化时设为低电平结果读数总是跳变。后来查手册才发现HX711要求SCK空闲时为高电平。这个细节手册里写得明明白白但新手很容易忽略。2. 硬件连接实战指南接线看似简单但这里面的坑我几乎全踩过。先说传感器端常见的电阻应变式传感器有4线制和6线制我们常用的是4线制的传感器线色标准红色激励正极(EXC)黑色激励负极(EXC-)绿色信号正极(SIG)白色信号负极(SIG-)但注意不同厂家的线序可能不同我的第一个项目就因此烧了一个传感器。稳妥的做法是用万用表测量激励端电阻通常是350Ω左右信号端在无负载时接近0Ω。HX711与STM32的连接要特别注意DT接GPIO输入我习惯用PA0SCK接GPIO输出推荐PA1VCC接3.3VGND共地这里有个实用技巧在DT和VCC之间加个10K上拉电阻能显著提高信号稳定性。如果发现读数不稳定可以在电源端并联一个100μF的电解电容。3. CubeMX配置详解打开CubeMX新建工程时芯片型号要选对。我用的是STM32F103C8T6你们根据自己开发板选择。关键配置步骤如下RCC配置高速时钟(HSE)选择外部晶振时钟树配置到72MHz主频GPIO配置SCK引脚设为GPIO_Output初始电平必须设为High这个太重要了输出模式推挽输出不上拉不下拉系统内核SysTick定时器使能调试接口根据编程器选择生成代码前记得在Project Manager里勾选Generate peripheral initialization as a pair of .c/.h files工具链选MDK-ARM如果用Keil的话堆栈大小建议设为0x10004. 驱动代码编写技巧HX711的驱动代码看似简单但优化空间很大。这是我优化后的版本// hx711.h #pragma once #include stm32f1xx_hal.h #define HX711_DT_PIN GPIO_PIN_0 #define HX711_DT_PORT GPIOA #define HX711_SCK_PIN GPIO_PIN_1 #define HX711_SCK_PORT GPIOA void HX711_Init(void); int32_t HX711_Read(void); int32_t HX711_ReadAverage(uint8_t times);// hx711.c #include hx711.h #include delay.h void HX711_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; // SCK引脚初始化 GPIO_InitStruct.Pin HX711_SCK_PIN; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(HX711_SCK_PORT, GPIO_InitStruct); // DT引脚初始化 GPIO_InitStruct.Pin HX711_DT_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(HX711_DT_PORT, GPIO_InitStruct); HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_SET); } int32_t HX711_Read(void) { int32_t count 0; while(HAL_GPIO_ReadPin(HX711_DT_PORT, HX711_DT_PIN)); for(uint8_t i0; i24; i) { HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_SET); delay_us(1); count 1; HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET); delay_us(1); if(HAL_GPIO_ReadPin(HX711_DT_PORT, HX711_DT_PIN)) count; } // 第25个脉冲选择通道和增益 HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_SET); delay_us(1); HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET); delay_us(1); return count ^ 0x800000; }这段代码有几个优化点使用HAL库标准函数提高可移植性增加了去抖动处理时序控制更精确添加了注释说明关键操作5. 校准算法与重量计算校准是电子秤最关键的环节也是新手最容易出错的地方。经过多次实践我总结出一套可靠的校准流程空载校准int32_t tare_value HX711_ReadAverage(10); // 取10次平均值标准重量校准// 放置已知重量建议用100g砝码 int32_t known_weight_value HX711_ReadAverage(10); float scale_factor (known_weight_value - tare_value) / 100.0f; // 假设标准重量是100g实际重量计算float get_current_weight(void) { int32_t raw HX711_ReadAverage(5); return (raw - tare_value) / scale_factor; }这里有个重要细节温度会影响传感器输出。我在项目中添加了温度补偿float temp_compensated_weight(float raw_weight, float temperature) { // 根据实验数据得出的补偿系数 const float temp_coeff -0.0015f; return raw_weight * (1 temp_coeff * (temperature - 25.0f)); }6. 常见问题解决方案调试过程中遇到过各种奇怪问题这里分享几个典型案例问题1读数不稳定检查电源是否稳定建议用示波器看纹波确保传感器固定牢固无机械振动尝试增加采样次数比如从5次提高到20次问题2重量显示为0检查SCK初始电平是否为高确认传感器接线正确特别是EXC和EXC-测量传感器激励端电压正常应在1-2V之间问题3线性度差检查传感器量程是否合适尝试不同的增益设置修改第25个脉冲后的延时确保校准过程操作规范问题4长时间漂移定期自动去皮比如每小时一次增加温度传感器进行补偿检查机械结构是否发生形变7. 进阶优化技巧当基础功能实现后可以尝试这些优化数字滤波算法#define FILTER_SIZE 10 float moving_average_filter(float new_sample) { static float samples[FILTER_SIZE] {0}; static uint8_t index 0; static float sum 0; sum - samples[index]; samples[index] new_sample; sum samples[index]; index (index 1) % FILTER_SIZE; return sum / FILTER_SIZE; }自动休眠功能void enter_sleep_mode(void) { HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET); // 配置STM32进入低功耗模式 } void wake_up(void) { HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_SET); HAL_Delay(100); HX711_Read(); // 丢弃第一次读数 }过载保护#define MAX_SAFE_WEIGHT 5000 // 5kg float safe_get_weight(void) { float w get_current_weight(); if(w MAX_SAFE_WEIGHT) { HAL_GPIO_WritePin(LED_GPIO_PORT, LED_PIN, GPIO_PIN_SET); return MAX_SAFE_WEIGHT; } return w; }数据记录功能void save_calibration_data(int32_t tare, float scale) { FLASH_EraseInitTypeDef EraseInitStruct; uint32_t PageError 0; HAL_FLASH_Unlock(); EraseInitStruct.TypeErase FLASH_TYPEERASE_PAGES; EraseInitStruct.PageAddress 0x0801F000; EraseInitStruct.NbPages 1; HAL_FLASHEx_Erase(EraseInitStruct, PageError); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 0x0801F000, tare); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 0x0801F004, *(uint32_t*)scale); HAL_FLASH_Lock(); }8. 项目完整实现把以上所有模块整合起来main.c的核心逻辑应该是这样的int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); HX711_Init(); // 加载校准数据 int32_t tare 0; float scale 1.0; load_calibration_data(tare, scale); // 如果没有校准数据执行校准流程 if(tare 0) { calibrate_system(tare, scale); save_calibration_data(tare, scale); } while(1) { float weight moving_average_filter(get_current_weight()); display_weight(weight); if(no_activity_for(5*60*1000)) { // 5分钟无操作 enter_sleep_mode(); } HAL_Delay(200); } }显示部分可以根据实际硬件选择OLED、LCD或者串口输出。我更喜欢用OLED因为功耗低、显示清晰void display_weight(float weight) { char buf[16]; snprintf(buf, sizeof(buf), %.1fg, weight); OLED_ShowString(0, 0, (uint8_t*)buf, 16); }最后提醒一点产品化时一定要考虑EMC设计。我的第一个商业版本就曾因为电磁干扰导致称重不准后来在信号线上加了磁珠和TVS管才解决问题。