基于STM32的HX711高精度称重系统开发实战
在嵌入式开发领域,称重系统的稳定性一直是工程师们关注的焦点。HX711作为一款专为电子秤设计的24位模数转换芯片,因其高性价比和简单易用的特性,成为了许多项目的首选。然而,不少开发者在实际应用中都会遇到一个共同的问题:读数不稳定,数值频繁跳动。这不仅影响用户体验,更可能导致商业称重设备的精度不达标。
1. HX711工作原理与常见问题分析
HX711芯片内部包含一个低噪声可编程放大器和一个高精度24位Σ-Δ ADC,专为称重传感器设计。它通过两个差分输入通道接收来自称重传感器的微弱电压信号,经过放大和模数转换后输出数字值。芯片采用简单的二线制串行接口(DOUT和PD_SCK),极大简化了与MCU的连接。
典型应用问题分析:
- 电源噪声干扰:HX711对电源质量极为敏感,使用不稳定的电源(如手机充电器直接供电)会导致基准电压波动,直接影响转换精度
- 读取时机不当:采用固定时间间隔读取数据,可能遇到数据未准备好的情况,造成读取错误或数值跳变
- 信号线布局不合理:长距离走线或靠近噪声源会导致信号完整性下降
- 温度漂移:未进行温度补偿会影响长期稳定性
提示:HX711的典型数据输出速率为10Hz(每100ms一次),但实际准备好数据的时间可能略有波动,这正是定时读取法不可靠的根本原因。
2. 硬件设计关键要点
一个稳定的称重系统始于合理的硬件设计。以下是HX711与STM32连接的推荐电路设计:
| 元件/参数 | 推荐值/型号 | 说明 |
|---|---|---|
| 供电电压 | 3.3V稳压源 | 建议使用LDO稳压器如AMS1117-3.3 |
| 退耦电容 | 100nF陶瓷电容 | 尽量靠近HX711的VCC引脚 |
| 称重传感器 | 5kg/10kg规格 | 根据实际需求选择合适量程 |
| 信号线长度 | <15cm | 过长的走线会引入噪声 |
| 滤波电路 | RC低通滤波器 | 在传感器输出端添加可提高稳定性 |
硬件连接示意图:
STM32 GPIO HX711 ------------ ----- PA0 (DT) --- DOUT PA1 (SCK) --- PD_SCK 3.3V --- VCC GND --- GND3. 查询法软件实现详解
查询法的核心思想是主动检测DOUT引脚状态,只有当数据准备好(DOUT为低电平)时才进行读取操作。这种方法避免了定时读取可能遇到的数据未准备好问题。
3.1 基础读取函数实现
#define HX711_DOUT_PIN GPIO_PIN_0 #define HX711_DOUT_PORT GPIOA #define HX711_SCK_PIN GPIO_PIN_1 #define HX711_SCK_PORT GPIOA int32_t HX711_Read(void) { // 等待数据准备好(DOUT变低) while(HAL_GPIO_ReadPin(HX711_DOUT_PORT, HX711_DOUT_PIN) == GPIO_PIN_SET); int32_t value = 0; // 读取24位数据 for(uint8_t i=0; i<24; i++) { HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_SET); delay_us(1); value <<= 1; if(HAL_GPIO_ReadPin(HX711_DOUT_PORT, HX711_DOUT_PIN) == GPIO_PIN_SET) { value++; } HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET); delay_us(1); } // 发送第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); // 补码转换(HX711输出为二进制补码) value ^= 0x800000; return value; }3.2 数据滤波算法
原始数据通常需要经过滤波处理才能获得稳定读数。常用的滤波方法包括:
- 移动平均滤波:取最近N次读数的平均值
- 中值滤波:取最近N次读数的中位数
- 卡尔曼滤波:适合动态称重场景
改进型移动平均滤波实现:
#define FILTER_WINDOW_SIZE 8 int32_t HX711_GetFilteredValue(void) { static int32_t buffer[FILTER_WINDOW_SIZE] = {0}; static uint8_t index = 0; static int32_t sum = 0; // 移除最旧的数据 sum -= buffer[index]; // 添加新数据 buffer[index] = HX711_Read(); sum += buffer[index]; // 更新索引 index = (index + 1) % FILTER_WINDOW_SIZE; return sum / FILTER_WINDOW_SIZE; }4. 系统校准与线性化处理
称重系统的精度很大程度上取决于校准过程。HX711的测量值x与实际重量y之间存在线性关系:y = kx + b,其中k为斜率,b为截距。
两点校准法步骤:
- 空载状态下读取测量值x0,记录y0=0
- 放置已知重量m的标准砝码,读取测量值x1,记录y1=m
- 计算斜率k和截距b:
k = (y1 - y0) / (x1 - x0) b = y0 - k * x0 - 实际测量时,应用公式:y = k * x + b
校准函数实现:
typedef struct { float scale; // 斜率k float offset; // 截距b } HX711_Calibration_t; void HX711_Calibrate(HX711_Calibration_t *cal, int32_t x0, int32_t x1, float known_weight) { cal->scale = known_weight / (x1 - x0); cal->offset = -cal->scale * x0; } float HX711_GetWeight(HX711_Calibration_t *cal, int32_t raw_value) { return cal->scale * raw_value + cal->offset; }5. 高级优化技巧
5.1 自动零点跟踪
在实际应用中,传感器可能会随时间产生微小漂移。实现自动零点跟踪可以补偿这种漂移:
#define ZERO_TRACK_THRESHOLD 10 // 视为空载的阈值(g) #define ZERO_TRACK_FACTOR 0.1 // 跟踪系数 float HX711_AutoZeroTrack(float current_weight, float *zero_offset) { if(fabs(current_weight) < ZERO_TRACK_THRESHOLD) { *zero_offset = *zero_offset * (1 - ZERO_TRACK_FACTOR) - current_weight * ZERO_TRACK_FACTOR; } return current_weight + *zero_offset; }5.2 温度补偿
对于高精度应用,温度变化会影响传感器输出。可通过以下方法补偿:
- 添加温度传感器(如DS18B20)
- 建立温度-偏移量查找表
- 实时调整零点偏移
float HX711_TempCompensate(float raw_weight, float temperature) { // 简化的线性温度补偿模型 static const float temp_coeff = -0.2f; // 温度系数(g/℃) static float ref_temp = 25.0f; // 参考温度 return raw_weight + (temperature - ref_temp) * temp_coeff; }6. 不同读取方法对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 查询法 | 稳定性高,资源占用低 | 需要轮询,可能增加延迟 | 大多数称重应用 |
| 中断法 | 响应及时,CPU占用低 | 需要额外中断资源 | 高速动态称重 |
| 定时法 | 实现简单 | 稳定性差,易丢失数据 | 不推荐用于正式产品 |
7. 完整项目集成示例
以下是将HX711模块集成到STM32项目中的典型流程:
- 硬件初始化
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_LOW; HAL_GPIO_Init(HX711_SCK_PORT, &GPIO_InitStruct); // DOUT引脚配置为输入 GPIO_InitStruct.Pin = HX711_DOUT_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(HX711_DOUT_PORT, &GPIO_InitStruct); // 初始状态:SCK低电平 HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET); }- 主应用逻辑
HX711_Calibration_t calib; float current_weight = 0; float zero_offset = 0; void main(void) { // 硬件初始化 HAL_Init(); SystemClock_Config(); HX711_Init(); // 校准过程(实际项目中可通过按键触发) int32_t x0 = HX711_GetFilteredValue(); // 空载读数 // 提示用户放置标准砝码 int32_t x1 = HX711_GetFilteredValue(); // 负载读数 HX711_Calibrate(&calib, x0, x1, 500.0f); // 假设使用500g砝码 while(1) { int32_t raw = HX711_GetFilteredValue(); float weight = HX711_GetWeight(&calib, raw); weight = HX711_AutoZeroTrack(weight, &zero_offset); // 更新显示或发送数据 printf("当前重量: %.1fg\r\n", weight); HAL_Delay(100); } }8. 常见问题排查
问题1:读数始终为0或最大值
- 检查电源电压是否稳定(3.3V±5%)
- 确认DOUT和SCK引脚连接正确
- 检查传感器连接是否牢固
问题2:数值随机跳动
- 确保传感器机械结构稳定无晃动
- 检查电源退耦电容是否靠近HX711
- 尝试增加滤波窗口大小
问题3:响应速度慢
- 减少滤波窗口大小
- 检查是否有不必要的延时
- 考虑使用中断法替代查询法
问题4:线性度差
- 重新进行两点校准
- 检查传感器是否过载
- 确保校准砝码精度足够
在实际项目中,我发现机械结构的稳定性往往比电路设计更容易被忽视。一个常见的错误是将传感器安装在有轻微晃动的支架上,这会导致读数持续波动。另外,使用质量较差的MicroUSB线供电也会引入显著的电源噪声,建议使用独立的稳压电源或高质量的电池供电。