STM32实战:基于STM32F103的智能体重秤(HX711+OLED)

文章目录

    • 一、前言
      • [1.1 技术背景](#1.1 技术背景)
      • [1.2 应用场景](#1.2 应用场景)
      • [1.3 本文目标](#1.3 本文目标)
    • 二、系统架构设计
      • [2.1 硬件系统架构](#2.1 硬件系统架构)
      • [2.2 软件系统架构](#2.2 软件系统架构)
      • [2.3 项目文件结构](#2.3 项目文件结构)
    • 三、环境准备
      • [3.1 硬件准备](#3.1 硬件准备)
      • [3.2 称重传感器原理](#3.2 称重传感器原理)
      • [3.3 软件准备](#3.3 软件准备)
    • 四、核心模块实现
      • [4.1 HX711驱动](#4.1 HX711驱动)
        • [4.1.1 HX711驱动头文件](#4.1.1 HX711驱动头文件)
        • [4.1.2 HX711驱动实现](#4.1.2 HX711驱动实现)
      • [4.2 数字滤波算法](#4.2 数字滤波算法)
        • [4.2.1 滤波算法头文件](#4.2.1 滤波算法头文件)
        • [4.2.2 滤波算法实现](#4.2.2 滤波算法实现)
      • [4.3 校准与单位转换](#4.3 校准与单位转换)
        • [4.3.1 校准算法](#4.3.1 校准算法)
    • 五、故障排查与问题解决
    • 六、总结
      • [6.1 核心知识点回顾](#6.1 核心知识点回顾)
      • [6.2 性能指标](#6.2 性能指标)
      • [6.3 扩展方向](#6.3 扩展方向)

一、前言

1.1 技术背景

体重秤作为最常见的健康监测设备之一,已经从传统的机械式发展到了智能化、数字化的阶段。智能体重秤不仅可以精确测量体重,还能通过蓝牙或WiFi将数据同步到手机APP,实现长期的健康数据追踪和分析。

HX711是一款专为高精度电子秤设计的24位A/D转换器芯片,集成了低噪声可编程放大器,可以直接与称重传感器(应变片式)连接。它具有以下特点:

  • 24位高精度ADC:分辨率可达1/16777216
  • 可编程增益放大器(PGA):支持32、64、128倍增益
  • 低噪声:输入噪声低至50nV
  • 简单接口:两线串行通信(时钟+数据)
  • 低功耗:典型工作电流仅1.5mA

STM32F103作为主控芯片,配合HX711和OLED显示屏,可以构建一个成本低、精度高、功能完善的智能体重秤系统。

1.2 应用场景

智能体重秤的应用场景包括:

  • 家庭健康管理:日常体重监测、体重变化趋势分析
  • 健身房/运动场馆:会员体重管理、健身效果追踪
  • 医院/诊所:患者体重监测、用药剂量计算
  • 工业称重:小量程精密称重、配料称重
  • 农业应用:农产品分级、饲料配比

1.3 本文目标

通过本教程,你将学到:

  • HX711芯片的工作原理与驱动方法
  • 称重传感器(应变片)的基本原理
  • 数字滤波算法(滑动平均、卡尔曼滤波)
  • 重量校准与标定方法
  • OLED中文显示与UI设计
  • 按键去皮(Tare)功能实现
  • 数据存储与历史记录管理
  • 低功耗设计与电池管理

完成本教程后,你将能够独立开发一个具备以下功能的智能体重秤:

  • 称重范围:0.1kg - 150kg
  • 精度:±10g
  • 去皮功能
  • 单位切换(kg/lb)
  • 历史记录存储(最近10次)
  • OLED中文显示
  • 自动关机省电

技术栈:

  • 主控芯片:STM32F103C8T6(Cortex-M3,72MHz)
  • ADC芯片:HX711(24位高精度ADC)
  • 称重传感器:半桥/全桥应变片式传感器(最大150kg)
  • 显示模块:0.96寸OLED(SSD1306,I2C接口)
  • 存储芯片:AT24C02(EEPROM,存储校准参数和历史记录)
  • 输入设备:3个按键(去皮、单位切换、查看历史)
  • 电源管理:3.7V锂电池 + TP4056充电模块

二、系统架构设计

2.1 硬件系统架构

电源系统
数据管理
人机交互模块
主控处理单元
信号处理单元
称重传感单元
应变片传感器

半桥/全桥式
惠斯通电桥

电阻变化→电压
HX711芯片

24位ADC + PGA
可编程放大器

增益32/64/128
24位ADC

高精度转换
STM32F103C8T6

Cortex-M3 72MHz
数字滤波

滑动平均/卡尔曼
校准算法

线性标定
OLED显示屏

128x64 中文显示
3个按键

去皮/单位/历史
AT24C02

校准参数+历史记录
3.7V锂电池
TP4056

充电管理
AMS1117-3.3

稳压

2.2 软件系统架构

硬件抽象层 HAL
设备驱动层
算法层
应用层
主应用程序

scale_app.c
UI管理器

ui_manager.c
菜单系统

menu.c
滤波算法库

filter.c
校准算法库

calibration.c
单位转换

unit_conversion.c
HX711驱动

hx711_driver.c
OLED驱动

oled_driver.c
按键驱动

key_driver.c
EEPROM驱动

eeprom_driver.c
GPIO驱动

HAL_GPIO
I2C驱动

HAL_I2C
定时器驱动

HAL_TIM

2.3 项目文件结构

📄 项目文件清单

复制代码
SmartScale/
├── Core/
│   ├── Inc/
│   │   ├── main.h
│   │   ├── hx711_driver.h
│   │   ├── scale_app.h
│   │   ├── filter.h
│   │   ├── calibration.h
│   │   ├── unit_conversion.h
│   │   ├── oled_driver.h
│   │   ├── key_driver.h
│   │   ├── eeprom_driver.h
│   │   ├── ui_manager.h
│   │   └── font.h
│   └── Src/
│       ├── main.c
│       ├── hx711_driver.c
│       ├── scale_app.c
│       ├── filter.c
│       ├── calibration.c
│       ├── unit_conversion.c
│       ├── oled_driver.c
│       ├── key_driver.c
│       ├── eeprom_driver.c
│       ├── ui_manager.c
│       └── font.c
├── Drivers/
│   ├── STM32F1xx_HAL_Driver/
│   └── CMSIS/
└── README.md

三、环境准备

3.1 硬件准备

必需硬件清单:

序号 器件名称 型号/规格 数量 备注
1 STM32最小系统板 STM32F103C8T6 1 核心控制器
2 HX711模块 带屏蔽罩版本 1 ADC转换
3 称重传感器 半桥/全桥 150kg 1 重量检测
4 OLED显示屏 0.96寸 I2C 1 显示
5 EEPROM芯片 AT24C02 1 数据存储
6 按键 轻触开关 3 用户输入
7 电阻 10KΩ 3 按键上拉
8 杜邦线 母对母 若干 连接
9 ST-Link V2 1 调试下载
10 面包板 标准 1 搭建电路

HX711模块引脚定义:

引脚 功能 连接至STM32
VCC 电源 3.3V
GND GND
DOUT 数据输出 PA0(输入)
PD_SCK 时钟输入 PA1(输出)

称重传感器接线(半桥式):

线色 功能 连接至HX711
E+(激励正) E+
E-(激励负) E-
S+(信号正) A+
绿 S-(信号负) A-

3.2 称重传感器原理

应变片式称重传感器基于惠斯通电桥原理工作:

复制代码
        E+ (激励电压)
         |
    R1 ---+--- R2
         |     |
    S+ --+-----+-- S-  (输出信号)
         |     |
    R3 ---+--- R4
         |
        E- (地)

当传感器受力时,应变片电阻发生变化(R1、R4增大,R2、R3减小),导致电桥失衡,产生微弱的电压差。HX711将这个微弱的模拟信号放大并转换为数字信号。

关键参数:

  • 灵敏度:通常为1.0mV/V或2.0mV/V
  • 激励电压:5V或3.3V
  • 输出范围:±几毫伏
  • 量程:根据传感器规格(如150kg)

3.3 软件准备

开发环境:

  • Keil MDK-ARM 5.38+ 或 STM32CubeIDE 1.12.0+
  • STM32F1xx HAL库
  • 串口调试助手

四、核心模块实现

4.1 HX711驱动

HX711使用简单的两线串行通信协议,时序要求严格。

4.1.1 HX711驱动头文件

📄 创建文件:Core/Inc/hx711_driver.h

c 复制代码
/**
 * @file hx711_driver.h
 * @brief HX711称重传感器驱动头文件
 * 
 * 提供HX711初始化、数据读取、增益设置等功能
 */

#ifndef __HX711_DRIVER_H
#define __HX711_DRIVER_H

#include "stm32f1xx_hal.h"
#include <stdint.h>
#include <stdbool.h>

/* HX711引脚配置 */
#define HX711_DOUT_PORT     GPIOA
#define HX711_DOUT_PIN      GPIO_PIN_0
#define HX711_SCK_PORT      GPIOA
#define HX711_SCK_PIN       GPIO_PIN_1

/* 增益设置 */
typedef enum {
    HX711_GAIN_128 = 1,     /* 通道A,增益128 */
    HX711_GAIN_32 = 2,      /* 通道B,增益32 */
    HX711_GAIN_64 = 3       /* 通道A,增益64 */
} HX711_GainTypeDef;

/* HX711句柄结构体 */
typedef struct {
    int32_t offset;         /* 零点偏移值(去皮值) */
    float scale;            /* 比例系数(校准系数) */
    HX711_GainTypeDef gain; /* 当前增益 */
    bool is_ready;          /* 初始化状态 */
} HX711_HandleTypeDef;

/* 函数声明 */

/**
 * @brief 初始化HX711
 * @param gain 增益设置
 * @retval 0 成功,-1 失败
 */
int HX711_Init(HX711_GainTypeDef gain);

/**
 * @brief 检查HX711数据是否就绪
 * @retval true 数据就绪,false 未就绪
 * @note DOUT引脚为低电平时表示数据就绪
 */
bool HX711_IsReady(void);

/**
 * @brief 读取原始24位数据
 * @retval int32_t 原始ADC值(有符号24位)
 * @note 阻塞等待直到数据就绪
 */
int32_t HX711_ReadRaw(void);

/**
 * @brief 读取原始数据(非阻塞)
 * @param data 输出数据指针
 * @retval 0 成功读取,-1 数据未就绪
 */
int HX711_ReadRawNonBlocking(int32_t* data);

/**
 * @brief 设置增益
 * @param gain 增益值
 * @retval None
 * @note 下次读取时生效
 */
void HX711_SetGain(HX711_GainTypeDef gain);

/**
 * @brief 进入省电模式
 * @retval None
 * @note PD_SCK保持高电平超过60μs进入省电模式
 */
void HX711_PowerDown(void);

/**
 * @brief 退出省电模式
 * @retval None
 */
void HX711_PowerUp(void);

/**
 * @brief 获取HX711句柄
 * @retval HX711_HandleTypeDef* 句柄指针
 */
HX711_HandleTypeDef* HX711_GetHandle(void);

#endif /* __HX711_DRIVER_H */
4.1.2 HX711驱动实现

📄 创建文件:Core/Src/hx711_driver.c

c 复制代码
/**
 * @file hx711_driver.c
 * @brief HX711称重传感器驱动实现
 */

#include "hx711_driver.h"
#include "delay.h"

/* 私有变量 */
static HX711_HandleTypeDef hx711_handle = {
    .offset = 0,
    .scale = 1.0f,
    .gain = HX711_GAIN_128,
    .is_ready = false
};

/* 私有函数 */

/**
 * @brief 微秒级延时(使用SysTick或定时器)
 */
static void HX711_DelayUs(uint32_t us) {
    // 使用DWT计数器或简单延时循环
    // 72MHz主频,1us约72个时钟周期
    volatile uint32_t count = us * 72 / 5;
    while (count--) {
        __NOP();
    }
}

/**
 * @brief 设置SCK引脚电平
 */
static void HX711_SCK_Set(bool level) {
    HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, 
                      level ? GPIO_PIN_SET : GPIO_PIN_RESET);
}

/**
 * @brief 读取DOUT引脚电平
 */
static bool HX711_DOUT_Read(void) {
    return HAL_GPIO_ReadPin(HX711_DOUT_PORT, HX711_DOUT_PIN) == GPIO_PIN_SET;
}

int HX711_Init(HX711_GainTypeDef gain) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    /* 使能GPIO时钟 */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    /* 配置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引脚为输出 */
    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);
    
    /* 初始化SCK为低电平 */
    HX711_SCK_Set(false);
    
    /* 设置增益 */
    hx711_handle.gain = gain;
    
    /* 等待HX711就绪 */
    uint32_t timeout = 100000;
    while (!HX711_IsReady() && timeout > 0) {
        timeout--;
    }
    
    if (timeout == 0) {
        return -1;  /* 初始化失败 */
    }
    
    /* 读取一次数据以设置增益 */
    HX711_ReadRaw();
    
    hx711_handle.is_ready = true;
    return 0;
}

bool HX711_IsReady(void) {
    return !HX711_DOUT_Read();  /* DOUT为低电平表示数据就绪 */
}

int32_t HX711_ReadRaw(void) {
    int32_t data = 0;
    
    /* 等待数据就绪 */
    while (!HX711_IsReady()) {
        /* 可以添加超时处理 */
    }
    
    /* 关中断,确保时序精确 */
    __disable_irq();
    
    /* 读取24位数据(MSB优先) */
    for (int i = 0; i < 24; i++) {
        HX711_SCK_Set(true);
        HX711_DelayUs(1);  /* 最小0.1μs */
        
        data = data << 1;
        if (HX711_DOUT_Read()) {
            data++;
        }
        
        HX711_SCK_Set(false);
        HX711_DelayUs(1);  /* 最小0.1μs */
    }
    
    /* 发送增益脉冲(设置下次转换增益) */
    for (int i = 0; i < hx711_handle.gain; i++) {
        HX711_SCK_Set(true);
        HX711_DelayUs(1);
        HX711_SCK_Set(false);
        HX711_DelayUs(1);
    }
    
    /* 开中断 */
    __enable_irq();
    
    /* 符号扩展(24位有符号数转32位) */
    if (data & 0x800000) {
        data |= 0xFF000000;
    }
    
    return data;
}

int HX711_ReadRawNonBlocking(int32_t* data) {
    if (!HX711_IsReady()) {
        return -1;
    }
    
    *data = HX711_ReadRaw();
    return 0;
}

void HX711_SetGain(HX711_GainTypeDef gain) {
    hx711_handle.gain = gain;
    /* 下次读取时生效 */
}

void HX711_PowerDown(void) {
    HX711_SCK_Set(false);
    HX711_DelayUs(1);
    HX711_SCK_Set(true);
    /* 保持高电平超过60μs进入省电模式 */
    HAL_Delay(1);
}

void HX711_PowerUp(void) {
    HX711_SCK_Set(false);
    /* 等待稳定 */
    HAL_Delay(1);
}

HX711_HandleTypeDef* HX711_GetHandle(void) {
    return &hx711_handle;
}

4.2 数字滤波算法

称重数据存在噪声,需要使用数字滤波算法平滑数据。

4.2.1 滤波算法头文件

📄 创建文件:Core/Inc/filter.h

c 复制代码
/**
 * @file filter.h
 * @brief 数字滤波算法库
 * 
 * 提供滑动平均滤波、中值滤波、卡尔曼滤波等算法
 */

#ifndef __FILTER_H
#define __FILTER_H

#include <stdint.h>

/* 滑动平均滤波器 */
#define MOVING_AVG_SIZE 10

typedef struct {
    int32_t buffer[MOVING_AVG_SIZE];
    uint8_t index;
    uint8_t count;
    int32_t sum;
} MovingAverageFilter;

/* 中值滤波器 */
#define MEDIAN_FILTER_SIZE 5

typedef struct {
    int32_t buffer[MEDIAN_FILTER_SIZE];
    uint8_t index;
} MedianFilter;

/* 卡尔曼滤波器 */
typedef struct {
    float Q;        /* 过程噪声协方差 */
    float R;        /* 测量噪声协方差 */
    float X;        /* 估计值 */
    float P;        /* 估计误差协方差 */
    float K;        /* 卡尔曼增益 */
} KalmanFilter;

/* 函数声明 */
void MovingAverage_Init(MovingAverageFilter* filter);
int32_t MovingAverage_Update(MovingAverageFilter* filter, int32_t newValue);

void MedianFilter_Init(MedianFilter* filter);
int32_t MedianFilter_Update(MedianFilter* filter, int32_t newValue);

void KalmanFilter_Init(KalmanFilter* filter, float Q, float R, float initialValue);
float KalmanFilter_Update(KalmanFilter* filter, float measurement);

#endif /* __FILTER_H */
4.2.2 滤波算法实现

📄 创建文件:Core/Src/filter.c

c 复制代码
/**
 * @file filter.c
 * @brief 数字滤波算法实现
 */

#include "filter.h"
#include <string.h>

/* 滑动平均滤波 */
void MovingAverage_Init(MovingAverageFilter* filter) {
    memset(filter, 0, sizeof(MovingAverageFilter));
}

int32_t MovingAverage_Update(MovingAverageFilter* filter, int32_t newValue) {
    /* 减去最旧的值 */
    filter->sum -= filter->buffer[filter->index];
    
    /* 添加新值 */
    filter->buffer[filter->index] = newValue;
    filter->sum += newValue;
    
    /* 更新索引 */
    filter->index++;
    if (filter->index >= MOVING_AVG_SIZE) {
        filter->index = 0;
    }
    
    /* 更新计数 */
    if (filter->count < MOVING_AVG_SIZE) {
        filter->count++;
    }
    
    /* 返回平均值 */
    return filter->sum / filter->count;
}

/* 中值滤波 */
void MedianFilter_Init(MedianFilter* filter) {
    memset(filter, 0, sizeof(MedianFilter));
}

int32_t MedianFilter_Update(MedianFilter* filter, int32_t newValue) {
    int32_t sorted[MEDIAN_FILTER_SIZE];
    
    /* 添加新值到缓冲区 */
    filter->buffer[filter->index] = newValue;
    filter->index++;
    if (filter->index >= MEDIAN_FILTER_SIZE) {
        filter->index = 0;
    }
    
    /* 复制缓冲区 */
    memcpy(sorted, filter->buffer, sizeof(sorted));
    
    /* 冒泡排序 */
    for (int i = 0; i < MEDIAN_FILTER_SIZE - 1; i++) {
        for (int j = 0; j < MEDIAN_FILTER_SIZE - i - 1; j++) {
            if (sorted[j] > sorted[j + 1]) {
                int32_t temp = sorted[j];
                sorted[j] = sorted[j + 1];
                sorted[j + 1] = temp;
            }
        }
    }
    
    /* 返回中值 */
    return sorted[MEDIAN_FILTER_SIZE / 2];
}

/* 卡尔曼滤波 */
void KalmanFilter_Init(KalmanFilter* filter, float Q, float R, float initialValue) {
    filter->Q = Q;
    filter->R = R;
    filter->X = initialValue;
    filter->P = 1.0f;
    filter->K = 0.0f;
}

float KalmanFilter_Update(KalmanFilter* filter, float measurement) {
    /* 预测步骤 */
    /* X(k|k-1) = X(k-1|k-1) */
    /* P(k|k-1) = P(k-1|k-1) + Q */
    filter->P = filter->P + filter->Q;
    
    /* 更新步骤 */
    /* K(k) = P(k|k-1) / (P(k|k-1) + R) */
    filter->K = filter->P / (filter->P + filter->R);
    
    /* X(k|k) = X(k|k-1) + K(k) * (Z(k) - X(k|k-1)) */
    filter->X = filter->X + filter->K * (measurement - filter->X);
    
    /* P(k|k) = (1 - K(k)) * P(k|k-1) */
    filter->P = (1 - filter->K) * filter->P;
    
    return filter->X;
}

4.3 校准与单位转换

4.3.1 校准算法

📄 创建文件:Core/Inc/calibration.h

c 复制代码
/**
 * @file calibration.h
 * @brief 称重校准算法
 */

#ifndef __CALIBRATION_H
#define __CALIBRATION_H

#include <stdint.h>

/* 校准参数结构体 */
typedef struct {
    int32_t zero_point;     /* 零点ADC值 */
    int32_t calib_point;    /* 校准点ADC值 */
    float calib_weight;     /* 校准重量(kg) */
    float scale_factor;     /* 比例系数 */
    bool is_calibrated;     /* 校准状态 */
} CalibrationParams;

/* 函数声明 */
void Calibration_Init(void);
int Calibration_SetZero(void);
int Calibration_SetPoint(float knownWeight);
float Calibration_ConvertToWeight(int32_t rawValue);
bool Calibration_IsCalibrated(void);
void Calibration_SaveParams(void);
void Calibration_LoadParams(void);
CalibrationParams* Calibration_GetParams(void);

#endif /* __CALIBRATION_H */

📄 创建文件:Core/Src/calibration.c

c 复制代码
/**
 * @file calibration.c
 * @brief 称重校准算法实现
 */

#include "calibration.h"
#include "hx711_driver.h"
#include "eeprom_driver.h"
#include "filter.h"
#include <math.h>

#define CALIBRATION_ADDR    0x00    /* EEPROM存储地址 */
#define CALIBRATION_MAGIC   0xCA1B  /* 校准数据魔数 */

static CalibrationParams calib_params = {
    .zero_point = 0,
    .calib_point = 0,
    .calib_weight = 1.0f,
    .scale_factor = 1.0f,
    .is_calibrated = false
};

void Calibration_Init(void) {
    /* 从EEPROM加载校准参数 */
    Calibration_LoadParams();
}

int Calibration_SetZero(void) {
    MovingAverageFilter filter;
    MovingAverage_Init(&filter);
    
    /* 采集多个样本求平均 */
    for (int i = 0; i < 50; i++) {
        int32_t raw = HX711_ReadRaw();
        MovingAverage_Update(&filter, raw);
        HAL_Delay(10);
    }
    
    calib_params.zero_point = MovingAverage_Update(&filter, 0);
    
    return 0;
}

int Calibration_SetPoint(float knownWeight) {
    if (knownWeight <= 0) {
        return -1;
    }
    
    MovingAverageFilter filter;
    MovingAverage_Init(&filter);
    
    /* 采集多个样本求平均 */
    for (int i = 0; i < 50; i++) {
        int32_t raw = HX711_ReadRaw();
        MovingAverage_Update(&filter, raw);
        HAL_Delay(10);
    }
    
    calib_params.calib_point = MovingAverage_Update(&filter, 0);
    calib_params.calib_weight = knownWeight;
    
    /* 计算比例系数 */
    int32_t delta_adc = calib_params.calib_point - calib_params.zero_point;
    if (delta_adc == 0) {
        return -1;
    }
    
    calib_params.scale_factor = knownWeight / (float)delta_adc;
    calib_params.is_calibrated = true;
    
    /* 保存校准参数 */
    Calibration_SaveParams();
    
    return 0;
}

float Calibration_ConvertToWeight(int32_t rawValue) {
    if (!calib_params.is_calibrated) {
        return 0.0f;
    }
    
    int32_t delta = rawValue - calib_params.zero_point;
    float weight = delta * calib_params.scale_factor;
    
    /* 去除微小抖动 */
    if (fabsf(weight) < 0.05f) {
        weight = 0.0f;
    }
    
    return weight;
}

bool Calibration_IsCalibrated(void) {
    return calib_params.is_calibrated;
}

void Calibration_SaveParams(void) {
    uint8_t buffer[sizeof(CalibrationParams) + 2];
    
    /* 写入魔数 */
    buffer[0] = (CALIBRATION_MAGIC >> 8) & 0xFF;
    buffer[1] = CALIBRATION_MAGIC & 0xFF;
    
    /* 复制校准参数 */
    memcpy(&buffer[2], &calib_params, sizeof(CalibrationParams));
    
    /* 写入EEPROM */
    EEPROM_Write(CALIBRATION_ADDR, buffer, sizeof(buffer));
}

void Calibration_LoadParams(void) {
    uint8_t buffer[sizeof(CalibrationParams) + 2];
    
    /* 从EEPROM读取 */
    EEPROM_Read(CALIBRATION_ADDR, buffer, sizeof(buffer));
    
    /* 检查魔数 */
    uint16_t magic = (buffer[0] << 8) | buffer[1];
    if (magic == CALIBRATION_MAGIC) {
        memcpy(&calib_params, &buffer[2], sizeof(CalibrationParams));
    }
}

CalibrationParams* Calibration_GetParams(void) {
    return &calib_params;
}

五、故障排查与问题解决

5.1 称重不准问题

问题1:称重数据漂移严重

现象: 空载时读数不为零,或读数随时间漂移

原因分析:

  • 温度漂移
  • 传感器未预热
  • 机械应力未释放
  • 电源不稳定

解决方案:

  1. 传感器预热
c 复制代码
/* 上电后预热30秒 */
void Scale_PowerOnInit(void) {
    printf("传感器预热中...\r\n");
    HAL_Delay(30000);  /* 预热30秒 */
    Calibration_SetZero();
    printf("预热完成,已归零\r\n");
}
  1. 温度补偿
c 复制代码
/* 读取温度传感器,进行温度补偿 */
float TemperatureCompensate(float weight, float temperature) {
    /* 温度系数:每度变化0.01% */
    float tempCoeff = 0.0001f;
    float tempDelta = temperature - 25.0f;  /* 25度为参考温度 */
    return weight * (1.0f - tempCoeff * tempDelta);
}

5.2 数据抖动问题

问题2:称重数据波动大

现象: 读数在小范围内快速波动

原因分析:

  • 环境振动
  • 电磁干扰
  • 滤波算法不合适

解决方案:

  1. 组合滤波
c 复制代码
/* 先中值滤波去噪,再滑动平均平滑 */
int32_t CombinedFilter(int32_t rawValue) {
    static MedianFilter medianFilter;
    static MovingAverageFilter avgFilter;
    static bool initialized = false;
    
    if (!initialized) {
        MedianFilter_Init(&medianFilter);
        MovingAverage_Init(&avgFilter);
        initialized = true;
    }
    
    int32_t median = MedianFilter_Update(&medianFilter, rawValue);
    int32_t average = MovingAverage_Update(&avgFilter, median);
    
    return average;
}

六、总结

6.1 核心知识点回顾

  1. HX711驱动:掌握了两线串行通信协议,实现了24位高精度ADC的数据读取。

  2. 数字滤波:实现了滑动平均、中值滤波、卡尔曼滤波等多种滤波算法,有效抑制噪声。

  3. 校准算法:理解了两点校准法,实现了零点和量程的自动校准。

  4. 单位转换:实现了kg/lb单位的实时切换。

  5. 数据存储:使用EEPROM保存校准参数和历史记录。

6.2 性能指标

指标 数值
称重范围 0.1kg - 150kg
精度 ±10g
分辨率 1g
采样率 10Hz
功耗 <50mA

6.3 扩展方向

  • 蓝牙通信:添加HC-05模块,实现数据无线传输
  • 手机APP:开发配套APP,实现数据可视化和分析
  • 多用户管理:支持多用户切换,独立记录数据
  • 体脂测量:添加电极片,实现体脂率测量
相关推荐
小白zlm2 小时前
预畸变双线性变换
单片机·嵌入式硬件·算法·电机控制
无垠的广袤2 小时前
【Titan RA8P1 Board】J-Link 调试
单片机·嵌入式·开发板·调试器·jlink
12.=0.5 小时前
【stm32_3】嵌入式软件系统架构
stm32·单片机·嵌入式硬件
会编程的小孩6 小时前
优先级与抢占实验
stm32·单片机
風清掦6 小时前
【江科大STM32学习笔记-10】I2C通信协议 - 10.1 软件I2C读写MPU6050
笔记·stm32·单片机·嵌入式硬件·物联网·学习
无垠的广袤7 小时前
【Titan RA8P1 Board】MNIST 数字识别
人工智能·单片机·瑞萨·mnist·数字识别·ra8p1·ruhmi
无垠的广袤8 小时前
【Titan RA8P1 Board】PyOCD 调试
单片机·嵌入式硬件·调试器
BackCatK Chen8 小时前
STM32保姆级入门教程|第6章:定时器中断原理 + 精准LED闪烁(1s_2s_3s)实战(功能超详细+CubeIDE手把手)
stm32·stm32cubeide·定时器中断·嵌入式入门·tim2·led精准闪烁·中断回调函数
水果里面有苹果8 小时前
24-NT5CC128M16IP-DI DDR3芯片 2Gbit
嵌入式硬件