基于STM32F103C8T6的高精度电子秤设计

基于STM32F103C8T6的高精度电子秤设计

一、系统总体设计

1.1 硬件架构

系统架构:
┌─────────────────────────────────────────────────────┐
│                 STM32F103C8T6核心控制器               │
├─────────┬─────────┬──────────┬──────────┬──────────┤
│ 称重模块 │ 显示模块 │ 输入模块  │ 存储模块  │ 通信模块  │
│  HX711  │  OLED   │  按键    │  EEPROM  │  UART   │
└─────────┴─────────┴──────────┴──────────┴──────────┘

1.2 功能特性

二、硬件设计

2.1 核心元件清单

元件 型号/参数 数量 作用
主控芯片 STM32F103C8T6 1 核心控制器
称重传感器 电阻应变式(5kg) 1 重量感应
称重芯片 HX711 1 24位AD转换
显示屏 0.96寸OLED SSD1306 1 显示重量信息
存储芯片 AT24C02/AT24C16 1 存储校准参数
按键 轻触开关 4 功能控制
蜂鸣器 5V有源 1 提示音
电源 AMS1117-3.3V 1 3.3V稳压

2.2 电路连接

STM32F103C8T6引脚分配:
PA0  -> HX711 DOUT (数据输出)
PA1  -> HX711 PD_SCK (时钟)
PA2  -> 按键1 (模式切换)
PA3  -> 按键2 (去皮)
PA4  -> 按键3 (校准)
PA5  -> 按键4 (单位切换)
PB6  -> OLED SCL (I2C时钟)
PB7  -> OLED SDA (I2C数据)
PB8  -> 蜂鸣器控制
PB9  -> AT24Cxx SDA (备用)
PB10 -> AT24Cxx SCL (备用)
PB13 -> UART1 TX (调试输出)
PB14 -> UART1 RX (调试输入)
VCC  -> 3.3V电源
GND  -> 公共地

HX711连接:
VCC  -> 3.3V
GND  -> GND
DT   -> PA0
SCK  -> PA1
E+   -> 传感器激励+
E-   -> 传感器激励-
A+   -> 传感器信号+
A-   -> 传感器信号-

2.3 电源设计

// 电源需求分析:
// 1. 传感器激励电压:5V (HX711内部LDO提供)
// 2. 主控电压:3.3V
// 3. 总电流:<100mA
// 4. 建议使用锂电池供电,通过TP4056充电管理

三、软件架构设计

3.1 文件结构

Electronic_Scale/
├── Core/
│   ├── Inc/
│   │   ├── hx711.h
│   │   ├── oled.h
│   │   ├── at24cxx.h
│   │   ├── key.h
│   │   ├── scale.h
│   │   ├── timer.h
│   │   └── uart.h
│   ├── Src/
│   │   ├── hx711.c
│   │   ├── oled.c
│   │   ├── at24cxx.c
│   │   ├── key.c
│   │   ├── scale.c
│   │   ├── timer.c
│   │   └── uart.c
│   └── Startup/
│       └── startup_stm32f103xb.s
├── Drivers/
│   ├── STM32F1xx_HAL_Driver/
│   └── CMSIS/
├── User/
│   ├── main.c
│   └── stm32f1xx_hal_msp.c
├── README.md
└── Electronic_Scale.ioc

3.2 核心模块

  1. HX711驱动:24位AD数据读取
  2. 称重算法:滤波、校准、单位转换
  3. OLED显示:重量、单位、状态显示
  4. 按键处理:去皮、校准、单位切换
  5. EEPROM:校准参数存储
  6. 串口调试:实时数据输出

四、核心代码实现

4.1 主程序 (main.c)

#include "main.h"
#include "hx711.h"
#include "oled.h"
#include "key.h"
#include "scale.h"
#include "uart.h"
#include "timer.h"

// 全局变量
Scale_Data_t scale_data;
System_State_t sys_state = STATE_NORMAL;
uint8_t unit_type = UNIT_G;  // 默认单位:克

int main(void)
{
    // 系统初始化
    HAL_Init();
    SystemClock_Config();
    
    // 初始化外设
    MX_GPIO_Init();
    MX_I2C1_Init();
    MX_USART1_UART_Init();
    
    // 初始化模块
    OLED_Init();
    HX711_Init();
    Key_Init();
    Timer_Init();
    
    // 初始化串口调试
    UART_Init();
    printf("Electronic Scale System Start!\r\n");
    
    // 从EEPROM加载校准参数
    Scale_LoadCalibration();
    
    // 显示启动界面
    OLED_ShowStartup();
    HAL_Delay(1000);
    
    // 主循环
    while (1)
    {
        // 1. 读取按键状态
        Key_Scan();
        
        // 2. 读取重量数据
        if (HX711_IsReady())
        {
            int32_t raw_data = HX711_Read();
            scale_data.raw_value = raw_data;
            
            // 滤波处理
            raw_data = Scale_Filter(raw_data);
            
            // 计算重量
            scale_data.weight = Scale_CalculateWeight(raw_data);
            
            // 单位转换
            scale_data.display_weight = Scale_ConvertUnit(scale_data.weight, unit_type);
            
            // 稳定判断
            scale_data.is_stable = Scale_CheckStable(scale_data.weight);
            
            // 串口输出(调试)
            if (scale_data.is_stable)
            {
                printf("Weight: %.2f g\r\n", scale_data.display_weight);
            }
        }
        
        // 3. 更新显示
        OLED_UpdateDisplay(scale_data.display_weight, unit_type, scale_data.is_stable);
        
        // 4. 处理功能状态
        switch (sys_state)
        {
            case STATE_CALIBRATION:
                Scale_CalibrationProcess();
                break;
                
            case STATE_TARE:
                Scale_Tare();
                sys_state = STATE_NORMAL;
                break;
                
            case STATE_UNIT_CHANGE:
                Scale_ChangeUnit();
                sys_state = STATE_NORMAL;
                break;
                
            default:
                break;
        }
        
        // 5. 低功耗处理
        Scale_PowerManage();
        
        HAL_Delay(10);  // 10ms循环
    }
}

4.2 称重传感器驱动 (hx711.c)

#include "hx711.h"
#include "main.h"
#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

// 增益设置
#define GAIN_128  1  // 通道A,增益128
#define GAIN_64   3  // 通道A,增益64
#define GAIN_32   2  // 通道B,增益32

static uint8_t gain = GAIN_128;  // 默认增益128

// 初始化HX711
void HX711_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // 使能GPIO时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // 配置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);
    
    // 配置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);
    
    // 初始时SCK为低电平
    HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET);
    
    // 等待芯片就绪
    HX711_WaitReady();
    
    // 设置增益
    HX711_SetGain(gain);
}

// 等待HX711准备就绪
uint8_t HX711_WaitReady(void)
{
    uint32_t timeout = 1000000;  // 超时时间
    
    while (HAL_GPIO_ReadPin(HX711_DT_PORT, HX711_DT_PIN))
    {
        timeout--;
        if (timeout == 0)
        {
            return 0;  // 超时
        }
    }
    
    return 1;  // 准备就绪
}

// 检查是否就绪
uint8_t HX711_IsReady(void)
{
    return (HAL_GPIO_ReadPin(HX711_DT_PORT, HX711_DT_PIN) == GPIO_PIN_RESET);
}

// 设置增益
void HX711_SetGain(uint8_t gain_value)
{
    gain = gain_value;
    
    // 读取一次数据来设置增益
    HX711_Read();
}

// 读取原始数据
int32_t HX711_Read(void)
{
    uint32_t data = 0;
    uint8_t i;
    
    // 等待数据就绪
    while (HX711_IsReady() == 0);
    
    // 读取24位数据
    for (i = 0; i < 24; i++)
    {
        // 产生时钟脉冲
        HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_SET);
        __nop(); __nop(); __nop(); __nop();  // 短暂延时
        
        // 读取数据位
        data = data << 1;
        if (HAL_GPIO_ReadPin(HX711_DT_PORT, HX711_DT_PIN))
        {
            data++;
        }
        
        HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET);
        __nop(); __nop(); __nop(); __nop();  // 短暂延时
    }
    
    // 设置增益(额外的时钟脉冲)
    for (i = 0; i < gain; i++)
    {
        HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_SET);
        __nop(); __nop(); __nop(); __nop();
        HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET);
        __nop(); __nop(); __nop(); __nop();
    }
    
    // 处理24位有符号数
    if (data & 0x800000)  // 如果是负数
    {
        data |= 0xFF000000;  // 符号扩展
    }
    
    return (int32_t)data;
}

// 读取平均值(多次采样取平均)
int32_t HX711_ReadAverage(uint8_t times)
{
    int32_t sum = 0;
    uint8_t i;
    
    for (i = 0; i < times; i++)
    {
        sum += HX711_Read();
        HAL_Delay(1);
    }
    
    return sum / times;
}

// 获取标定值(去皮)
int32_t HX711_GetTareValue(uint8_t times)
{
    return HX711_ReadAverage(times);
}

// 电源控制
void HX711_PowerDown(void)
{
    // 将SCK置高至少60us进入掉电模式
    HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_SET);
    HAL_Delay(1);  // 延时1ms确保进入掉电模式
}

void HX711_PowerUp(void)
{
    HAL_GPIO_WritePin(HX711_SCK_PORT, HX711_SCK_PIN, GPIO_PIN_RESET);
}

4.3 称重算法模块 (scale.c)

#include "scale.h"
#include "hx711.h"
#include "at24cxx.h"
#include "filter.h"
#include <math.h>

// 全局变量
static float calibration_factor = 415.0f;  // 默认校准系数
static int32_t tare_value = 0;            // 去皮值
static float zero_offset = 0;              // 零点偏移
static uint8_t filter_enabled = 1;         // 滤波使能

// 滤波缓冲
#define FILTER_BUFFER_SIZE 10
static float weight_buffer[FILTER_BUFFER_SIZE];
static uint8_t buffer_index = 0;

// 称重数据结构
Scale_Data_t scale_data;

// 初始化称重模块
void Scale_Init(void)
{
    // 初始化滤波缓冲
    for (int i = 0; i < FILTER_BUFFER_SIZE; i++)
    {
        weight_buffer[i] = 0;
    }
    
    // 加载校准参数
    Scale_LoadCalibration();
}

// 加载校准参数
void Scale_LoadCalibration(void)
{
    uint8_t data[4];
    
    // 从EEPROM读取校准系数
    if (AT24CXX_Read(0x00, data, 4) == 0)
    {
        // 解析浮点数
        uint8_t *p = (uint8_t*)&calibration_factor;
        for (int i = 0; i < 4; i++)
        {
            p[i] = data[i];
        }
    }
    
    // 读取去皮值
    if (AT24CXX_Read(0x10, data, 4) == 0)
    {
        uint8_t *p = (uint8_t*)&tare_value;
        for (int i = 0; i < 4; i++)
        {
            p[i] = data[i];
        }
    }
    
    // 读取零点偏移
    if (AT24CXX_Read(0x20, data, 4) == 0)
    {
        uint8_t *p = (uint8_t*)&zero_offset;
        for (int i = 0; i < 4; i++)
        {
            p[i] = data[i];
        }
    }
}

// 保存校准参数
void Scale_SaveCalibration(void)
{
    uint8_t data[4];
    
    // 保存校准系数
    uint8_t *p = (uint8_t*)&calibration_factor;
    for (int i = 0; i < 4; i++)
    {
        data[i] = p[i];
    }
    AT24CXX_Write(0x00, data, 4);
    
    // 保存去皮值
    p = (uint8_t*)&tare_value;
    for (int i = 0; i < 4; i++)
    {
        data[i] = p[i];
    }
    AT24CXX_Write(0x10, data, 4);
    
    // 保存零点偏移
    p = (uint8_t*)&zero_offset;
    for (int i = 0; i < 4; i++)
    {
        data[i] = p[i];
    }
    AT24CXX_Write(0x20, data, 4);
}

// 计算重量
float Scale_CalculateWeight(int32_t raw_value)
{
    float weight = 0;
    
    if (calibration_factor == 0)
    {
        calibration_factor = 415.0f;  // 防止除零
    }
    
    // 减去去皮值和零点偏移
    raw_value = raw_value - tare_value;
    
    // 计算重量(克)
    weight = (float)raw_value / calibration_factor;
    
    // 减去零点偏移
    weight -= zero_offset;
    
    return weight;
}

// 数字滤波处理
float Scale_Filter(float weight)
{
    if (!filter_enabled)
    {
        return weight;
    }
    
    // 更新滤波缓冲区
    weight_buffer[buffer_index] = weight;
    buffer_index = (buffer_index + 1) % FILTER_BUFFER_SIZE;
    
    // 计算移动平均
    float sum = 0;
    for (int i = 0; i < FILTER_BUFFER_SIZE; i++)
    {
        sum += weight_buffer[i];
    }
    
    return sum / FILTER_BUFFER_SIZE;
}

// 卡尔曼滤波实现
float Scale_KalmanFilter(float weight)
{
    static float x_last = 0;      // 上一次的最优估计值
    static float p_last = 1;      // 上一次的估计协方差
    float Q = 0.01;               // 过程噪声协方差
    float R = 0.1;                // 测量噪声协方差
    float kg;                     // 卡尔曼增益
    float x_mid;                  // 当前时刻的预测值
    float p_mid;                  // 当前时刻的预测协方差
    float x_now;                  // 当前时刻的最优估计值
    float p_now;                  // 当前时刻的最优估计协方差
    
    // 预测
    x_mid = x_last;
    p_mid = p_last + Q;
    
    // 更新
    kg = p_mid / (p_mid + R);
    x_now = x_mid + kg * (weight - x_mid);
    p_now = (1 - kg) * p_mid;
    
    // 更新状态
    x_last = x_now;
    p_last = p_now;
    
    return x_now;
}

// 单位转换
float Scale_ConvertUnit(float weight_gram, uint8_t unit)
{
    switch (unit)
    {
        case UNIT_G:    // 克
            return weight_gram;
            
        case UNIT_KG:   // 千克
            return weight_gram / 1000.0f;
            
        case UNIT_LB:   // 磅
            return weight_gram * 0.00220462f;
            
        case UNIT_OZ:   // 盎司
            return weight_gram * 0.035274f;
            
        default:
            return weight_gram;
    }
}

// 获取单位字符串
const char* Scale_GetUnitString(uint8_t unit)
{
    switch (unit)
    {
        case UNIT_G:  return "g";
        case UNIT_KG: return "kg";
        case UNIT_LB: return "lb";
        case UNIT_OZ: return "oz";
        default:      return "g";
    }
}

// 去皮功能
void Scale_Tare(void)
{
    // 读取当前值作为去皮值
    int32_t raw_data = HX711_ReadAverage(10);
    tare_value = raw_data;
    
    // 保存去皮值
    Scale_SaveCalibration();
    
    // 蜂鸣器提示
    Buzzer_Beep(100);
}

// 清零去皮
void Scale_ClearTare(void)
{
    tare_value = 0;
    Scale_SaveCalibration();
}

// 校准功能
void Scale_CalibrationProcess(void)
{
    static uint8_t cal_step = 0;
    static int32_t raw_zero = 0;
    static int32_t raw_weight = 0;
    
    switch (cal_step)
    {
        case 0:  // 等待空载
            OLED_ShowString(0, 0, "Cal: Remove Weight");
            if (Key_GetPress(KEY_CAL) == KEY_LONG_PRESS)
            {
                // 读取零点
                raw_zero = HX711_ReadAverage(20);
                cal_step = 1;
                Buzzer_Beep(100);
            }
            break;
            
        case 1:  // 等待加载已知重量
            OLED_ShowString(0, 0, "Cal: Add Std Weight");
            if (Key_GetPress(KEY_CAL) == KEY_LONG_PRESS)
            {
                // 读取标准重量时的值
                raw_weight = HX711_ReadAverage(20);
                cal_step = 2;
                Buzzer_Beep(100);
            }
            break;
            
        case 2:  // 输入标准重量值
            // 这里需要实现数值输入,简化处理,假设已知重量为500g
            {
                float std_weight = 500.0f;  // 500g标准砝码
                
                // 计算校准系数
                int32_t diff = raw_weight - raw_zero;
                if (diff != 0)
                {
                    calibration_factor = diff / std_weight;
                }
                
                // 保存校准参数
                Scale_SaveCalibration();
                
                OLED_ShowString(0, 0, "Calibration Done!");
                Buzzer_Beep(200);
                HAL_Delay(1000);
                
                cal_step = 0;
            }
            break;
    }
}

// 检查重量是否稳定
uint8_t Scale_CheckStable(float weight)
{
    static float last_weights[5] = {0};
    static uint8_t index = 0;
    static uint8_t stable_count = 0;
    
    // 更新历史数据
    last_weights[index] = weight;
    index = (index + 1) % 5;
    
    // 计算最近5次测量的标准差
    float sum = 0, mean = 0, variance = 0;
    
    for (int i = 0; i < 5; i++)
    {
        sum += last_weights[i];
    }
    mean = sum / 5;
    
    for (int i = 0; i < 5; i++)
    {
        variance += (last_weights[i] - mean) * (last_weights[i] - mean);
    }
    variance /= 5;
    
    // 判断是否稳定(标准差小于阈值)
    if (sqrt(variance) < 0.05)  // 0.05g阈值
    {
        stable_count++;
        if (stable_count >= 3)  // 连续3次稳定
        {
            return 1;
        }
    }
    else
    {
        stable_count = 0;
    }
    
    return 0;
}

// 切换单位
void Scale_ChangeUnit(void)
{
    static uint8_t unit_index = 0;
    
    unit_index = (unit_index + 1) % 4;
    
    switch (unit_index)
    {
        case 0: unit_type = UNIT_G; break;
        case 1: unit_type = UNIT_KG; break;
        case 2: unit_type = UNIT_LB; break;
        case 3: unit_type = UNIT_OZ; break;
    }
    
    Buzzer_Beep(50);
}

// 电源管理
void Scale_PowerManage(void)
{
    static uint32_t idle_time = 0;
    
    if (fabs(scale_data.weight) < 0.1)  // 重量接近零
    {
        idle_time++;
        
        // 空闲超过30秒进入低功耗模式
        if (idle_time > 3000)  // 3000 * 10ms = 30s
        {
            Scale_EnterSleepMode();
            idle_time = 0;
        }
    }
    else
    {
        idle_time = 0;
    }
}

// 进入睡眠模式
void Scale_EnterSleepMode(void)
{
    // 关闭显示屏
    OLED_DisplayOff();
    
    // HX711进入掉电模式
    HX711_PowerDown();
    
    // 等待按键唤醒
    while (Key_GetPress(KEY_MODE) != KEY_SHORT_PRESS)
    {
        HAL_Delay(10);
    }
    
    // 唤醒系统
    HX711_PowerUp();
    OLED_DisplayOn();
    
    // 重新初始化
    HX711_Init();
}

4.4 显示模块 (oled.c)

#include "oled.h"
#include "scale.h"
#include "fonts.h"

// OLED显示缓冲
uint8_t OLED_GRAM[128][8];

// 初始化OLED
void OLED_Init(void)
{
    // I2C初始化
    I2C_Init();
    
    // 发送初始化命令序列
    OLED_WriteCmd(0xAE);  // 关闭显示
    
    OLED_WriteCmd(0xD5);  // 设置时钟分频因子
    OLED_WriteCmd(0x80);
    
    OLED_WriteCmd(0xA8);  // 设置驱动路数
    OLED_WriteCmd(0x3F);
    
    OLED_WriteCmd(0xD3);  // 设置显示偏移
    OLED_WriteCmd(0x00);
    
    OLED_WriteCmd(0x40);  // 设置起始行
    
    OLED_WriteCmd(0x8D);  // 电荷泵设置
    OLED_WriteCmd(0x14);
    
    OLED_WriteCmd(0x20);  // 设置内存地址模式
    OLED_WriteCmd(0x02);  // 页地址模式
    
    OLED_WriteCmd(0xA1);  // 设置段重定向
    OLED_WriteCmd(0xC8);  // 设置COM扫描方向
    
    OLED_WriteCmd(0xDA);  // 设置COM硬件引脚配置
    OLED_WriteCmd(0x12);
    
    OLED_WriteCmd(0x81);  // 设置对比度
    OLED_WriteCmd(0xCF);
    
    OLED_WriteCmd(0xD9);  // 设置预充电周期
    OLED_WriteCmd(0xF1);
    
    OLED_WriteCmd(0xDB);  // 设置VCOMH电压倍率
    OLED_WriteCmd(0x40);
    
    OLED_WriteCmd(0xA4);  // 全局显示开启
    OLED_WriteCmd(0xA6);  // 设置正常显示
    
    OLED_WriteCmd(0xAF);  // 开启显示
    
    // 清屏
    OLED_Clear();
}

// 显示启动界面
void OLED_ShowStartup(void)
{
    OLED_Clear();
    
    // 显示标题
    OLED_ShowString(20, 0, "Electronic Scale");
    OLED_ShowString(30, 2, "STM32F103C8T6");
    OLED_ShowString(40, 4, "Version 1.0");
    OLED_ShowString(20, 6, "Initializing...");
    
    OLED_Refresh();
    
    // 显示进度条
    for (int i = 0; i < 128; i += 4)
    {
        OLED_DrawLine(i, 55, i + 2, 55, 1);
        OLED_Refresh();
        HAL_Delay(20);
    }
}

// 更新重量显示
void OLED_UpdateDisplay(float weight, uint8_t unit, uint8_t is_stable)
{
    char buffer[20];
    
    OLED_Clear();
    
    // 显示标题
    OLED_ShowString(0, 0, "Weight:");
    
    // 显示重量(大字体)
    if (is_stable)
    {
        // 稳定显示
        if (weight >= 1000)
        {
            sprintf(buffer, "%6.2f", weight);
        }
        else if (weight >= 100)
        {
            sprintf(buffer, "%6.3f", weight);
        }
        else
        {
            sprintf(buffer, "%6.4f", weight);
        }
        
        OLED_ShowString(0, 2, buffer);
        
        // 显示单位
        sprintf(buffer, "%s", Scale_GetUnitString(unit));
        OLED_ShowString(90, 2, buffer);
        
        // 显示稳定标识
        OLED_ShowString(0, 4, "Stable");
    }
    else
    {
        // 不稳定显示
        OLED_ShowString(0, 2, "----.--");
        OLED_ShowString(0, 4, "Unstable");
    }
    
    // 显示校准状态
    if (fabs(weight) < 0.01)
    {
        OLED_ShowString(0, 6, "Zero");
    }
    
    // 显示电池电量
    OLED_ShowBattery(100, 0);
    
    OLED_Refresh();
}

// 显示校准界面
void OLED_ShowCalibration(uint8_t step)
{
    OLED_Clear();
    
    switch (step)
    {
        case 0:
            OLED_ShowString(0, 0, "Step 1: Remove weight");
            OLED_ShowString(0, 2, "Press CAL to zero");
            break;
            
        case 1:
            OLED_ShowString(0, 0, "Step 2: Add 500g weight");
            OLED_ShowString(0, 2, "Press CAL to measure");
            break;
            
        case 2:
            OLED_ShowString(0, 0, "Calibration Complete!");
            OLED_ShowString(0, 2, "Factor: %.4f", calibration_factor);
            break;
    }
    
    OLED_Refresh();
}

// 显示电池电量
void OLED_ShowBattery(uint8_t x, uint8_t y)
{
    // 简化电池图标显示
    OLED_DrawRectangle(x, y, x + 20, y + 8, 1);
    OLED_DrawRectangle(x + 20, y + 2, x + 22, y + 6, 1);
    
    // 根据电量填充
    uint8_t level = Get_Battery_Level();  // 获取电池电量
    
    if (level > 80)
    {
        // 高电量:全填充
        OLED_Fill(x + 2, y + 2, x + 18, y + 6, 1);
    }
    else if (level > 20)
    {
        // 中等电量:部分填充
        uint8_t width = (level - 20) * 16 / 60;
        OLED_Fill(x + 2, y + 2, x + 2 + width, y + 6, 1);
    }
    else
    {
        // 低电量:显示警告
        OLED_Fill(x + 2, y + 2, x + 6, y + 6, 1);
    }
}

// 显示错误信息
void OLED_ShowError(const char* error_msg)
{
    OLED_Clear();
    
    OLED_ShowString(0, 0, "ERROR:");
    OLED_ShowString(0, 2, error_msg);
    OLED_ShowString(0, 4, "Press any key");
    OLED_ShowString(0, 6, "to continue");
    
    OLED_Refresh();
}

4.5 按键处理模块 (key.c)

#include "key.h"
#include "main.h"
#include "stm32f1xx_hal.h"

// 按键引脚定义
#define KEY_MODE_PIN    GPIO_PIN_2
#define KEY_MODE_PORT   GPIOA
#define KEY_TARE_PIN    GPIO_PIN_3
#define KEY_TARE_PORT   GPIOA
#define KEY_CAL_PIN     GPIO_PIN_4
#define KEY_CAL_PORT    GPIOA
#define KEY_UNIT_PIN    GPIO_PIN_5
#define KEY_UNIT_PORT   GPIOA

// 按键状态
static Key_State_t key_states[4] = {KEY_RELEASE};
static uint32_t key_press_time[4] = {0};
static uint8_t key_long_press_flag[4] = {0};

// 初始化按键
void Key_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // 使能GPIO时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // 配置按键引脚为上拉输入
    GPIO_InitStruct.Pin = KEY_MODE_PIN | KEY_TARE_PIN | KEY_CAL_PIN | KEY_UNIT_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

// 扫描按键状态
void Key_Scan(void)
{
    uint8_t i;
    GPIO_PinState pin_state;
    
    for (i = 0; i < 4; i++)
    {
        // 读取按键状态
        switch (i)
        {
            case 0: pin_state = HAL_GPIO_ReadPin(KEY_MODE_PORT, KEY_MODE_PIN); break;
            case 1: pin_state = HAL_GPIO_ReadPin(KEY_TARE_PORT, KEY_TARE_PIN); break;
            case 2: pin_state = HAL_GPIO_ReadPin(KEY_CAL_PORT, KEY_CAL_PIN); break;
            case 3: pin_state = HAL_GPIO_ReadPin(KEY_UNIT_PORT, KEY_UNIT_PIN); break;
        }
        
        // 按键状态处理
        if (pin_state == GPIO_PIN_RESET)  // 按键按下
        {
            if (key_states[i] == KEY_RELEASE)
            {
                // 首次按下
                key_states[i] = KEY_PRESS;
                key_press_time[i] = HAL_GetTick();
                key_long_press_flag[i] = 0;
            }
            else if (key_states[i] == KEY_PRESS)
            {
                // 持续按下
                if ((HAL_GetTick() - key_press_time[i] > LONG_PRESS_TIME) && 
                    (key_long_press_flag[i] == 0))
                {
                    // 长按触发
                    key_states[i] = KEY_LONG_PRESS;
                    key_long_press_flag[i] = 1;
                }
            }
        }
        else  // 按键释放
        {
            if (key_states[i] == KEY_PRESS)
            {
                // 短按释放
                key_states[i] = KEY_SHORT_PRESS;
            }
            else if (key_states[i] == KEY_LONG_PRESS)
            {
                // 长按释放
                key_states[i] = KEY_LONG_RELEASE;
            }
            else
            {
                key_states[i] = KEY_RELEASE;
            }
        }
    }
}

// 获取按键状态
Key_Press_t Key_GetPress(uint8_t key_num)
{
    Key_Press_t press_state = KEY_NONE;
    
    if (key_num >= 4) return KEY_NONE;
    
    switch (key_states[key_num])
    {
        case KEY_SHORT_PRESS:
            press_state = KEY_SHORT_PRESS;
            key_states[key_num] = KEY_RELEASE;
            break;
            
        case KEY_LONG_PRESS:
            press_state = KEY_LONG_PRESS;
            key_states[key_num] = KEY_RELEASE;
            break;
            
        case KEY_LONG_RELEASE:
            press_state = KEY_LONG_RELEASE;
            key_states[key_num] = KEY_RELEASE;
            break;
            
        default:
            break;
    }
    
    return press_state;
}

// 按键处理函数
void Key_Process(void)
{
    Key_Press_t press;
    
    // 检查模式键
    press = Key_GetPress(KEY_MODE);
    if (press == KEY_SHORT_PRESS)
    {
        // 切换工作模式
        if (sys_state == STATE_NORMAL)
        {
            sys_state = STATE_SETTING;
        }
        else
        {
            sys_state = STATE_NORMAL;
        }
        Buzzer_Beep(50);
    }
    
    // 检查去皮键
    press = Key_GetPress(KEY_TARE);
    if (press == KEY_SHORT_PRESS)
    {
        sys_state = STATE_TARE;
        Buzzer_Beep(50);
    }
    
    // 检查校准键
    press = Key_GetPress(KEY_CAL);
    if (press == KEY_LONG_PRESS)
    {
        sys_state = STATE_CALIBRATION;
        Buzzer_Beep(100);
    }
    
    // 检查单位键
    press = Key_GetPress(KEY_UNIT);
    if (press == KEY_SHORT_PRESS)
    {
        sys_state = STATE_UNIT_CHANGE;
        Buzzer_Beep(50);
    }
}

4.6 EEPROM存储模块 (at24cxx.c)

#include "at24cxx.h"
#include "i2c.h"

// AT24Cxx设备地址
#define AT24Cxx_ADDR 0xA0

// 初始化I2C
void I2C_Init(void)
{
    // I2C初始化代码
    // ...
}

// 向AT24Cxx写入一个字节
uint8_t AT24Cxx_WriteByte(uint16_t addr, uint8_t data)
{
    uint8_t res = 0;
    uint8_t buf[3];
    
    buf[0] = (uint8_t)(addr >> 8);  // 地址高字节
    buf[1] = (uint8_t)(addr & 0xFF); // 地址低字节
    buf[2] = data;
    
    // 启动I2C传输
    HAL_I2C_Master_Transmit(&hi2c1, AT24Cxx_ADDR, buf, 3, 1000);
    
    // 等待写入完成
    HAL_Delay(5);
    
    return res;
}

// 从AT24Cxx读取一个字节
uint8_t AT24Cxx_ReadByte(uint16_t addr)
{
    uint8_t data = 0;
    uint8_t addr_buf[2];
    
    addr_buf[0] = (uint8_t)(addr >> 8);
    addr_buf[1] = (uint8_t)(addr & 0xFF);
    
    // 发送要读取的地址
    HAL_I2C_Master_Transmit(&hi2c1, AT24Cxx_ADDR, addr_buf, 2, 1000);
    
    // 读取数据
    HAL_I2C_Master_Receive(&hi2c1, AT24Cxx_ADDR, &data, 1, 1000);
    
    return data;
}

// 写入多个字节
uint8_t AT24Cxx_Write(uint16_t addr, uint8_t *buf, uint16_t len)
{
    uint16_t i;
    uint8_t res = 0;
    
    for (i = 0; i < len; i++)
    {
        res = AT24Cxx_WriteByte(addr + i, buf[i]);
        if (res != 0)
        {
            return res;
        }
    }
    
    return 0;
}

// 读取多个字节
uint8_t AT24Cxx_Read(uint16_t addr, uint8_t *buf, uint16_t len)
{
    uint16_t i;
    
    for (i = 0; i < len; i++)
    {
        buf[i] = AT24Cxx_ReadByte(addr + i);
    }
    
    return 0;
}

五、系统优化与校准

5.1 称重校准流程

// 校准程序
void Calibration_Routine(void)
{
    float known_weight;  // 已知标准重量
    
    // 1. 空载校准(零点)
    OLED_ShowString(0, 0, "Remove weight");
    OLED_ShowString(0, 2, "Press KEY to zero");
    
    while(Key_GetPress(KEY_CAL) != KEY_SHORT_PRESS);
    
    int32_t zero_raw = HX711_ReadAverage(20);
    zero_offset = zero_raw;
    
    Buzzer_Beep(100);
    
    // 2. 加载标准重量
    OLED_Clear();
    OLED_ShowString(0, 0, "Add 500g weight");
    OLED_ShowString(0, 2, "Press KEY to measure");
    
    while(Key_GetPress(KEY_CAL) != KEY_SHORT_PRESS);
    
    int32_t weight_raw = HX711_ReadAverage(20);
    known_weight = 500.0f;  // 500g标准砝码
    
    // 3. 计算校准系数
    int32_t diff = weight_raw - zero_raw;
    if (diff != 0)
    {
        calibration_factor = (float)diff / known_weight;
    }
    
    // 4. 保存参数
    Scale_SaveCalibration();
    
    OLED_Clear();
    OLED_ShowString(0, 0, "Calibration Done!");
    OLED_ShowString(0, 2, "Factor: %.4f", calibration_factor);
    
    Buzzer_Beep(200);
    HAL_Delay(2000);
}

5.2 温度补偿算法

// 温度传感器读取
float Read_Temperature(void)
{
    // 使用内部温度传感器或外部DS18B20
    // 简化实现
    return 25.0f;  // 默认25°C
}

// 温度补偿
float Temperature_Compensation(float raw_weight, float temperature)
{
    float compensated_weight = raw_weight;
    static float last_temperature = 25.0f;
    
    // 温度变化超过1°C时进行补偿
    if (fabs(temperature - last_temperature) > 1.0f)
    {
        // 温度系数(需要根据实际传感器测定)
        float temp_coef = -0.0005f;  // -0.05%/°C
        
        // 计算补偿
        float delta_temp = temperature - 25.0f;  // 相对于25°C的变化
        compensated_weight = raw_weight * (1.0f + temp_coef * delta_temp);
        
        last_temperature = temperature;
    }
    
    return compensated_weight;
}

六、调试与测试

6.1 串口调试功能

// uart.c
#include "uart.h"
#include <stdio.h>

// 重定向printf到串口
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif

PUTCHAR_PROTOTYPE
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
    return ch;
}

// 调试信息输出
void Debug_PrintScaleInfo(Scale_Data_t *data)
{
    static uint32_t counter = 0;
    
    if (counter++ % 100 == 0)  // 每100次循环输出一次
    {
        printf("\r\n=== Scale Debug Info ===\r\n");
        printf("Raw Value: %ld\r\n", data->raw_value);
        printf("Weight: %.4f g\r\n", data->weight);
        printf("Display: %.4f %s\r\n", 
               data->display_weight, 
               Scale_GetUnitString(unit_type));
        printf("Stable: %d\r\n", data->is_stable);
        printf("Cal Factor: %.4f\r\n", calibration_factor);
        printf("Tare Value: %ld\r\n", tare_value);
        printf("=========================\r\n");
    }
}

6.2 性能测试

// 精度测试程序
void Accuracy_Test(void)
{
    float test_weights[] = {10.0f, 50.0f, 100.0f, 200.0f, 500.0f, 1000.0f};
    int num_tests = sizeof(test_weights) / sizeof(test_weights[0]);
    
    printf("Starting accuracy test...\r\n");
    
    for (int i = 0; i < num_tests; i++)
    {
        printf("\r\nPlace %.1fg weight and press any key", test_weights[i]);
        
        while(Key_GetPress(KEY_MODE) != KEY_SHORT_PRESS);
        
        // 读取10次取平均
        float sum = 0;
        for (int j = 0; j < 10; j++)
        {
            int32_t raw = HX711_Read();
            float weight = Scale_CalculateWeight(raw);
            sum += weight;
            HAL_Delay(100);
        }
        
        float avg_weight = sum / 10.0f;
        float error = avg_weight - test_weights[i];
        float error_percent = (error / test_weights[i]) * 100.0f;
        
        printf("\r\nTest %d: %.1fg -> %.4fg, Error: %.4fg (%.2f%%)\r\n",
               i+1, test_weights[i], avg_weight, error, error_percent);
    }
    
    printf("\r\nAccuracy test completed!\r\n");
}

参考代码 基于STM32f103C8T6的电子秤设计 www.youwenfan.com/contentcnv/71942.html

七、PCB设计与布局建议

7.1 PCB布局原则

  1. 模拟数字隔离:HX711和传感器走线远离数字电路
  2. 电源滤波:每个IC的VCC加0.1μF去耦电容
  3. 地平面:使用完整的地平面,模拟和数字地单点连接
  4. 信号走线:传感器信号线尽量短,使用差分走线

7.2 抗干扰设计

// 软件抗干扰措施
#define ENABLE_SOFTWARE_FILTER  1
#define ENABLE_OUTLIER_REJECTION 1
#define ENABLE_MOVING_AVERAGE    1

// 异常值剔除
float Remove_Outliers(float *data, int count)
{
    // 排序
    qsort(data, count, sizeof(float), compare_float);
    
    // 去掉最高和最低的20%
    int trim_count = count / 5;
    float sum = 0;
    
    for (int i = trim_count; i < count - trim_count; i++)
    {
        sum += data[i];
    }
    
    return sum / (count - 2 * trim_count);
}

八、常见问题与解决方案

8.1 零点漂移

问题:空载时重量显示不为零

解决方案

  1. 增加硬件滤波电容
  2. 软件自动零点跟踪
  3. 温度补偿算法

8.2 读数不稳定

问题:重量显示跳变

解决方案

  1. 增加数字滤波阶数
  2. 优化采样时序
  3. 检查电源稳定性

8.3 校准失效

问题:重新上电后校准参数丢失

解决方案

  1. 检查EEPROM写入时序
  2. 添加校验和验证
  3. 增加备份校准参数

九、完整项目配置

9.1 CubeMX配置

// 使用STM32CubeMX生成基础代码
// 配置项目:
// 1. 系统时钟:72MHz
// 2. GPIO: 按键输入,蜂鸣器输出
// 3. I2C1: OLED显示,100kHz
// 4. USART1: 串口调试,115200bps
// 5. Timer2: 系统滴答,1ms中断
// 6. ADC1: 电池电压检测

9.2 编译配置

// Keil工程设置
// 1. 优化等级:-O2
// 2. 使用MicroLib
// 3. 栈大小:0x400
// 4. 堆大小:0x200
// 5. 启用FPU支持

十、扩展功能

10.1 蓝牙连接

// 添加HC-05蓝牙模块
void Bluetooth_Init(void)
{
    // 初始化串口2
    MX_USART2_UART_Init();
    
    // 蓝牙AT命令配置
    UART_SendString(&huart2, "AT+NAME=ElectronicScale\r\n");
    HAL_Delay(100);
    UART_SendString(&huart2, "AT+BAUD=9600\r\n");
    HAL_Delay(100);
}

// 通过蓝牙发送重量数据
void Bluetooth_SendWeight(float weight)
{
    char buffer[32];
    sprintf(buffer, "%.2f\r\n", weight);
    HAL_UART_Transmit(&huart2, (uint8_t*)buffer, strlen(buffer), 1000);
}

10.2 数据记录

// SD卡数据记录
void SDCard_LogData(float weight, uint32_t timestamp)
{
    FIL file;
    FRESULT res;
    char buffer[64];
    
    // 打开文件
    res = f_open(&file, "weight_log.csv", FA_OPEN_APPEND | FA_WRITE);
    if (res == FR_OK)
    {
        // 写入数据
        sprintf(buffer, "%lu,%.2f\r\n", timestamp, weight);
        f_write(&file, buffer, strlen(buffer), NULL);
        
        // 关闭文件
        f_close(&file);
    }
}

总结

这个基于STM32F103C8T6的电子秤设计实现了:

  1. 高精度称重:通过HX711实现24位AD转换
  2. 稳定显示:OLED实时显示重量和状态
  3. 智能校准:支持两点校准和参数存储
  4. 多功能操作:去皮、单位切换、低功耗
  5. 可靠性能:数字滤波、温度补偿、异常处理

关键优化点:

专注于matlab/simulink,电子电路,编程