STM32F103C8T6与HX711电子秤开发实战:从硬件对接到精准测量
1. 项目概述与硬件选型
在嵌入式开发领域,重量测量是一个经典而实用的应用场景。使用STM32F103C8T6搭配HX711芯片构建电子秤系统,不仅成本低廉,还能深入理解模拟信号采集与数字滤波的核心技术。这个蓝色药丸大小的STM32芯片,虽然只有64KB Flash和20KB RAM,但完全能够胜任电子秤的数据处理任务。
核心硬件组件:
- STM32F103C8T6最小系统板(俗称"蓝药丸")
- HX711 24位ADC模块
- 应变式称重传感器(常见规格5kg/10kg)
- 0.96寸OLED显示屏(可选,用于本地显示)
- 精密可调电阻(用于校准)
提示:购买称重传感器时注意额定载荷,过小的量程会导致传感器损坏,过大的量程则影响测量精度。一般DIY项目选择5kg规格比较合适。
硬件连接示意图:
| 信号线 | STM32引脚 | HX711引脚 | 备注 |
|---|---|---|---|
| VCC | 3.3V | VCC | 也可接5V,但需电平匹配 |
| GND | GND | GND | 共地至关重要 |
| DT (数据) | PA1 | DOUT | 配置为上拉输入 |
| SCK (时钟) | PA2 | PD_SCK | 配置为推挽输出 |
2. HX711驱动开发与底层配置
HX711作为专为电子秤设计的ADC芯片,其驱动时序是开发的关键。与常规SPI/I2C器件不同,HX711采用独特的同步串行协议,需要精确控制时钟边沿。
驱动开发要点:
- 初始化GPIO:
void HX711_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // SCK引脚配置为推挽输出 GPIO_InitStruct.Pin = GPIO_PIN_2; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // DT引脚配置为上拉输入 GPIO_InitStruct.Pin = GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); // 初始时钟低电平 }- 数据读取函数优化版(带超时检测):
int32_t HX711_ReadData(void) { uint32_t timeout = 100000; while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET) { if (--timeout == 0) return 0; // 超时返回0 } int32_t value = 0; for (uint8_t i = 0; i < 24; i++) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); delay_us(1); value <<= 1; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); delay_us(1); if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET) { value++; } } // 第25个脉冲选择通道和增益 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); delay_us(1); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); return value ^ 0x800000; // 补码转换 }常见问题排查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 读取值始终为0 | 接线错误或接触不良 | 检查DT/SCK连接,确认共地 |
| 数值跳动幅度大 | 电源噪声或机械振动 | 增加滤波电容,稳定安装结构 |
| 测量值随温度漂移 | 传感器温度补偿不足 | 采用温度传感器进行补偿 |
| 响应速度慢 | 滤波参数过于保守 | 调整卡尔曼滤波的Q/R参数 |
3. 传感器校准与滤波算法
校准是电子秤准确度的关键。我们需要分两步进行:硬件零点和满量程校准。
校准流程:
- 零点校准(无负载时):
void Calibrate_Tare(void) { uint32_t sum = 0; for (int i = 0; i < 10; i++) { sum += HX711_ReadData(); HAL_Delay(100); } config.tare_value = sum / 10; // 存储零点值 Save_Config(); // 保存到EEPROM }- 满量程校准(施加已知重量):
void Calibrate_Scale(float known_weight) { uint32_t sum = 0; for (int i = 0; i < 10; i++) { sum += HX711_ReadData(); HAL_Delay(100); } config.scale = (sum / 10 - config.tare_value) / known_weight; Save_Config(); // 保存到EEPROM }改进型卡尔曼滤波实现:
typedef struct { float q; // 过程噪声协方差 float r; // 测量噪声协方差 float p; // 估计误差协方差 float k; // 卡尔曼增益 float x; // 估计值 } KalmanFilter; float Kalman_Update(KalmanFilter* kf, float measurement) { // 预测 kf->p = kf->p + kf->q; // 更新 kf->k = kf->p / (kf->p + kf->r); kf->x = kf->x + kf->k * (measurement - kf->x); kf->p = (1 - kf->k) * kf->p; return kf->x; } // 初始化示例 KalmanFilter kf = { .q = 0.001f, .r = 0.01f, .p = 1.0f, .x = 0.0f };滤波参数调优建议:
快速响应场景(如动态称重):
- Q = 0.1, R = 1.0
- 牺牲平滑性换取速度
高精度静态测量:
- Q = 0.001, R = 0.01
- 响应慢但数据稳定
4. 系统集成与性能优化
将各个模块整合为完整系统时,需要考虑任务调度、显示刷新和用户交互的协调。
主程序架构:
void main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); HX711_Init(); OLED_Init(); Load_Config(); // 从EEPROM加载校准参数 KalmanFilter kf = { .q = 0.003f, .r = 0.03f, .p = 1.0f }; while (1) { int32_t raw = HX711_ReadData(); float filtered = Kalman_Update(&kf, (raw - config.tare_value) / config.scale); // 显示刷新(每秒4次) static uint32_t last_disp = 0; if (HAL_GetTick() - last_disp > 250) { OLED_ShowWeight(filtered); last_disp = HAL_GetTick(); } // 按键处理 if (Read_Key() == KEY_TARE) { Calibrate_Tare(); } HAL_Delay(10); } }电源管理技巧:
- 在VCC和GND之间添加100μF电解电容和0.1μF陶瓷电容
- 使用LDO稳压器(如AMS1117-3.3)而非开关电源
- 传感器供电线路单独走线,避免数字噪声干扰
机械安装注意事项:
- 称重平台与传感器之间采用刚性连接
- 保证受力方向与传感器敏感轴一致
- 避免侧向力影响测量精度
- 使用橡胶垫隔离环境振动
5. 高级功能扩展
基础功能实现后,可以考虑添加以下增强功能:
多传感器并联配置:
float Read_MultiSensors(void) { float sum = 0; for (int i = 0; i < SENSOR_COUNT; i++) { Select_Sensor(i); // 切换多路复用器 sum += (HX711_ReadData() - config.tare[i]) / config.scale[i]; } return sum / SENSOR_COUNT; }蓝牙APP监控(基于HC-05模块):
void Send_To_App(float weight) { char buf[32]; snprintf(buf, sizeof(buf), "{\"weight\":%.2f}\r\n", weight); HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), HAL_MAX_DELAY); }自动休眠与唤醒:
void Check_Sleep(void) { static float last_weight = 0; static uint32_t stable_time = 0; if (fabs(current_weight - last_weight) < 0.5f) { stable_time += 100; if (stable_time > 10000) { // 10秒无变化 Enter_LowPowerMode(); } } else { stable_time = 0; } last_weight = current_weight; }实际项目中,我发现称重传感器的预热时间对精度影响很大。冷启动后前10分钟的读数会漂移约0.5%,解决方案是在首次校准时等待足够的热机时间,或者采用温度补偿算法。