基于STM32F103C8T6的高精度电子秤设计
一、系统总体设计
1.1 硬件架构
系统架构:
┌─────────────────────────────────────────────────────┐
│ STM32F103C8T6核心控制器 │
├─────────┬─────────┬──────────┬──────────┬──────────┤
│ 称重模块 │ 显示模块 │ 输入模块 │ 存储模块 │ 通信模块 │
│ HX711 │ OLED │ 按键 │ EEPROM │ UART │
└─────────┴─────────┴──────────┴──────────┴──────────┘
1.2 功能特性
- 量程:0-5kg,精度:±0.1g
- 单位切换:g/kg/lb/oz
- 自动去皮功能
- 数据存储与回显
- 低功耗模式
- 校准功能
- 串口通信调试
二、硬件设计
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 核心模块
- HX711驱动:24位AD数据读取
- 称重算法:滤波、校准、单位转换
- OLED显示:重量、单位、状态显示
- 按键处理:去皮、校准、单位切换
- EEPROM:校准参数存储
- 串口调试:实时数据输出
四、核心代码实现
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布局原则
- 模拟数字隔离:HX711和传感器走线远离数字电路
- 电源滤波:每个IC的VCC加0.1μF去耦电容
- 地平面:使用完整的地平面,模拟和数字地单点连接
- 信号走线:传感器信号线尽量短,使用差分走线
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 零点漂移
问题:空载时重量显示不为零
解决方案:
- 增加硬件滤波电容
- 软件自动零点跟踪
- 温度补偿算法
8.2 读数不稳定
问题:重量显示跳变
解决方案:
- 增加数字滤波阶数
- 优化采样时序
- 检查电源稳定性
8.3 校准失效
问题:重新上电后校准参数丢失
解决方案:
- 检查EEPROM写入时序
- 添加校验和验证
- 增加备份校准参数
九、完整项目配置
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的电子秤设计实现了:
- 高精度称重:通过HX711实现24位AD转换
- 稳定显示:OLED实时显示重量和状态
- 智能校准:支持两点校准和参数存储
- 多功能操作:去皮、单位切换、低功耗
- 可靠性能:数字滤波、温度补偿、异常处理
关键优化点:
- 采用移动平均和卡尔曼滤波提高稳定性
- 自动零点跟踪和温度补偿
- 低功耗设计延长电池寿命
- 完善的校准和调试功能