STM32HAL库驱动HX711压力传感器:从接线校准到OLED显示的完整避坑指南
当你第一次拿到HX711压力传感器模块时,可能会被它小巧的体积和简单的四线接口所迷惑。但真正开始动手连接STM32开发板时,各种问题就会接踵而至:数值跳动不稳定、校准公式理解错误、OLED显示异常...本文将带你一步步避开这些坑,完成从硬件连接到软件实现的完整流程。
1. 硬件连接:那些容易忽略的细节
1.1 接线顺序的玄机
HX711模块通常有四个引脚:VCC、GND、SCK和DT。看似简单的连接,却藏着几个关键点:
- 电源隔离:建议为HX711单独供电,避免与STM32共用电源导致干扰。如果必须共用,确保电源线足够粗,并在VCC和GND之间添加100μF电容。
- 信号线处理:SCK和DT线长度不宜超过15cm,过长会导致信号衰减。如果必须延长,建议使用双绞线。
注意:不同厂家的HX711模块引脚颜色可能不同,务必以模块上的丝印为准,不要盲目按照颜色接线。
1.2 GPIO初始电平设置
在CubeMX中配置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; // 必须设置为HIGH GPIO_InitStruct.Level = GPIO_PIN_SET; HAL_GPIO_Init(HX711_SCK_GPIO_Port, &GPIO_InitStruct);如果初始电平设置为LOW,取下重物后会出现数值大幅跳动的现象。这是因为HX711内部需要在高电平状态下完成数据转换。
2. CubeMX配置:关键参数解析
2.1 时钟配置
HX711对时序要求严格,系统时钟配置不当会导致通信失败。推荐配置:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| HCLK | 72MHz | 保证足够的处理速度 |
| APB1 | 36MHz | 外设时钟 |
| APB2 | 72MHz | GPIO时钟 |
2.2 GPIO模式选择
DT引脚应配置为上拉输入模式:
GPIO_InitStruct.Pin = HX711_DT_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; // 必须启用上拉 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(HX711_DT_GPIO_Port, &GPIO_InitStruct);3. 代码实现:避开数据处理的坑
3.1 数据读取时序
HX711的24位数据需要严格按照时序读取,以下是一个稳定的读取函数:
int32_t HX711_GetData(void) { int32_t count = 0; uint8_t i; HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_RESET); HAL_Delay(1); // 等待DT变低 while(HAL_GPIO_ReadPin(HX711_DT_GPIO_Port, HX711_DT_Pin) == GPIO_PIN_SET); // 读取24位数据 for(i=0; i<24; i++) { HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_SET); HAL_Delay(1); count = count << 1; HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_RESET); HAL_Delay(1); if(HAL_GPIO_ReadPin(HX711_DT_GPIO_Port, HX711_DT_Pin) == GPIO_PIN_SET) { count++; } } // 第25个脉冲选择通道和增益 HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_RESET); HAL_Delay(1); // 补码转原码 return count ^ 0x800000; }3.2 数据处理与滤波
原始数据通常会有噪声,需要采用滑动平均滤波:
#define FILTER_SIZE 5 int32_t filter_buf[FILTER_SIZE] = {0}; uint8_t filter_index = 0; int32_t HX711_GetFilteredData(void) { filter_buf[filter_index] = HX711_GetData(); filter_index = (filter_index + 1) % FILTER_SIZE; int32_t sum = 0; for(uint8_t i=0; i<FILTER_SIZE; i++) { sum += filter_buf[i]; } return sum / FILTER_SIZE; }4. 校准实战:从理论到实践
4.1 校准原理详解
HX711的校准需要两个已知点:空载值和已知重量值。校准公式为:
weight = (raw_value - offset) * scale其中:
offset是空载时的原始值scale是比例系数,通过已知重量计算得出
4.2 分步校准指南
获取空载值:
int32_t offset = HX711_GetFilteredData();放置已知重量(如100g砝码)并获取新值:
int32_t known_weight_value = HX711_GetFilteredData();计算比例系数:
float scale = 100.0f / (known_weight_value - offset);应用校准公式:
float weight = (current_value - offset) * scale;
提示:如果没有标准砝码,可以使用手机等已知重量的物品。例如,200g手机对应的100g计算值为:
(phone_value - offset)/2 + offset
4.3 校准值存储
为避免每次上电重新校准,可以将offset和scale存入Flash:
typedef struct { int32_t offset; float scale; uint32_t crc; // 校验值 } HX711_Calib_t; void Save_Calibration(HX711_Calib_t *calib) { // 计算CRC32校验值 calib->crc = Calculate_CRC32((uint8_t*)calib, sizeof(HX711_Calib_t)-4); // 写入Flash HAL_FLASH_Unlock(); FLASH_Erase_Sector(FLASH_SECTOR_6, VOLTAGE_RANGE_3); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 0x08060000, *(uint32_t*)calib); HAL_FLASH_Lock(); } int Load_Calibration(HX711_Calib_t *calib) { // 从Flash读取 memcpy(calib, (void*)0x08060000, sizeof(HX711_Calib_t)); // 校验CRC uint32_t crc = Calculate_CRC32((uint8_t*)calib, sizeof(HX711_Calib_t)-4); return (crc == calib->crc) ? 0 : -1; }5. OLED显示优化
5.1 显示刷新策略
频繁刷新OLED会导致闪烁,建议采用差异刷新:
char current_display[20] = {0}; char new_display[20] = {0}; void Update_Display(float weight) { sprintf(new_display, "%.1fg", weight); // 只有数值变化时才刷新 if(strcmp(current_display, new_display) != 0) { OLED_ClearLine(2); // 清除特定行 OLED_ShowString(2, 2, new_display); strcpy(current_display, new_display); } }5.2 图形化显示
对于需要直观显示重量变化的场景,可以添加简单的条形图:
void Draw_Weight_Bar(float weight, float max_weight) { uint8_t max_width = 120; // OLED宽度 uint8_t bar_width = (uint8_t)((weight / max_weight) * max_width); OLED_DrawRectangle(10, 40, bar_width, 10, OLED_COLOR_NORMAL); OLED_Refresh(); }6. 常见问题排查
遇到问题时,可以按照以下步骤排查:
数值始终为0:
- 检查DT引脚是否接触良好
- 确认SCK引脚初始电平设置为HIGH
- 测量VCC电压是否在2.6V-5.5V范围内
数值跳动严重:
- 检查电源是否稳定,建议增加滤波电容
- 确保传感器机械结构稳固,无振动
- 尝试增加滤波算法的窗口大小
OLED无显示:
- 检查I2C/SPI线路连接
- 确认OLED初始化代码正确执行
- 测量OLED供电电压(通常为3.3V或5V)
7. 进阶技巧
7.1 自动去皮功能
实现按下按键自动去皮:
if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET) { HAL_Delay(50); // 消抖 if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET) { offset = HX711_GetFilteredData(); Save_Calibration(&calib); } }7.2 低功耗模式
对于电池供电的应用,可以间歇性唤醒HX711:
void Enter_LowPower_Mode(void) { // 设置SCK为高电平超过60μs使HX711进入休眠 HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_SET); HAL_Delay(1); // 配置STM32进入STOP模式 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); } void WakeUp_HX711(void) { // 拉低SCK唤醒HX711 HAL_GPIO_WritePin(HX711_SCK_GPIO_Port, HX711_SCK_Pin, GPIO_PIN_RESET); HAL_Delay(1); }7.3 多传感器切换
如果需要使用多个HX711模块,可以通过片选信号切换:
#define HX711_1_CS_H HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) #define HX711_1_CS_L HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define HX711_2_CS_H HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET) #define HX711_2_CS_L HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET) int32_t Read_HX711(uint8_t sensor_num) { if(sensor_num == 1) { HX711_1_CS_L; HX711_2_CS_H; } else { HX711_1_CS_H; HX711_2_CS_L; } return HX711_GetFilteredData(); }