一、系统架构设计
┌─────────────────────────────────────────────────────────────┐
│ STM32 IAP 电量计系统 │
├─────────────────────────────────────────────────────────────┤
│ 应用层 (User App) │ IAP层 (Bootloader) │ 硬件层 │
│ │ │ │
│ • SOC估算算法 │ • 固件升级管理 │ • STM32F103 │
│ • 电池参数监测 │ • Flash读写操作 │ • 电压/电流ADC │
│ • 充放电控制 │ • 通信协议解析 │ • 温度传感器 │
│ • 数据记录存储 │ • 版本校验 │ • EEPROM存储 │
└─────────────────────────────────────────────────────────────┘
二、实现
2.1 IAP Bootloader (bootloader.c)
c
/**
* @file bootloader.c
* @brief STM32 IAP Bootloader 主程序
* @platform STM32F103C8T6
*/
#include "stm32f10x.h"
#include "flash.h"
#include "uart.h"
#include "iap.h"
// IAP配置参数
#define BOOTLOADER_VERSION 0x0100 // 版本号 V1.0
#define APPLICATION_ADDRESS 0x08002000 // 应用程序起始地址
#define FLASH_PAGE_SIZE 1024 // Flash页大小 (1KB)
#define MAX_FIRMWARE_SIZE (48 * 1024) // 最大固件大小 (48KB)
// 命令定义
#define CMD_GET_VERSION 0x01
#define CMD_ERASE_FLASH 0x02
#define CMD_WRITE_FLASH 0x03
#define CMD_VERIFY_FLASH 0x04
#define CMD_JUMP_TO_APP 0x05
#define CMD_RESET 0x06
// 状态定义
#define ACK 0x79
#define NACK 0x1F
#define ERROR 0xFF
// 全局变量
uint8_t firmware_buffer[FLASH_PAGE_SIZE];
uint32_t firmware_size = 0;
uint32_t current_address = APPLICATION_ADDRESS;
// 系统初始化
void System_Init(void) {
// 初始化系统时钟
SystemClock_Init();
// 初始化串口 (用于IAP通信)
UART_Init(115200);
// 初始化Flash接口
FLASH_Init();
// 初始化GPIO (用于状态指示)
GPIO_Init_LED();
}
// 检查是否需要进入IAP模式
uint8_t Check_IAP_Mode(void) {
// 方法1: 检测特定引脚状态
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0) {
return 1; // 进入IAP模式
}
// 方法2: 检查应用程序栈顶地址是否有效
uint32_t app_stack_pointer = *(__IO uint32_t*)APPLICATION_ADDRESS;
if ((app_stack_pointer & 0x2FFE0000) != 0x20000000) {
return 1; // 应用程序无效,进入IAP模式
}
// 方法3: 检查EEPROM中的IAP标志
if (EEPROM_ReadByte(IAP_FLAG_ADDRESS) == 0xAA) {
EEPROM_WriteByte(IAP_FLAG_ADDRESS, 0x00); // 清除标志
return 1; // 进入IAP模式
}
return 0; // 正常运行应用程序
}
// 跳转到应用程序
void Jump_To_Application(void) {
typedef void (*pFunction)(void);
pFunction Jump_To_Application;
uint32_t JumpAddress;
// 关闭所有中断
__disable_irq();
// 设置栈顶指针
JumpAddress = *(__IO uint32_t*)APPLICATION_ADDRESS;
__set_MSP(JumpAddress);
// 跳转到应用程序复位向量
JumpAddress = *(__IO uint32_t*)(APPLICATION_ADDRESS + 4);
Jump_To_Application = (pFunction)JumpAddress;
// 跳转到应用程序
Jump_To_Application();
}
// IAP主循环
void IAP_Main_Loop(void) {
uint8_t cmd;
uint8_t status;
UART_SendString("IAP Bootloader V1.0 Ready\r\n");
while(1) {
// 接收命令
if (UART_ReceiveByte(&cmd, 1000) == 0) {
switch(cmd) {
case CMD_GET_VERSION:
UART_SendByte(ACK);
UART_SendWord(BOOTLOADER_VERSION);
break;
case CMD_ERASE_FLASH:
status = IAP_Erase_Application_Flash();
UART_SendByte(status ? ACK : NACK);
break;
case CMD_WRITE_FLASH:
status = IAP_Write_Firmware();
UART_SendByte(status ? ACK : NACK);
break;
case CMD_VERIFY_FLASH:
status = IAP_Verify_Firmware();
UART_SendByte(status ? ACK : NACK);
break;
case CMD_JUMP_TO_APP:
UART_SendByte(ACK);
Jump_To_Application();
break;
case CMD_RESET:
UART_SendByte(ACK);
NVIC_SystemReset();
break;
default:
UART_SendByte(NACK);
break;
}
}
}
}
int main(void) {
System_Init();
// 检查是否需要进入IAP模式
if (Check_IAP_Mode()) {
// 进入IAP模式
GPIO_SetBits(GPIOC, GPIO_Pin_13); // 点亮LED指示IAP模式
IAP_Main_Loop();
} else {
// 跳转到应用程序
GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 熄灭LED
Jump_To_Application();
}
while(1); // 不应该到这里
}
2.2 Flash操作模块 (flash.c)
c
/**
* @file flash.c
* @brief STM32 Flash操作函数
*/
#include "flash.h"
// Flash解锁
void FLASH_Unlock(void) {
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
}
// Flash加锁
void FLASH_Lock(void) {
FLASH->CR |= FLASH_CR_LOCK;
}
// 擦除Flash页
uint8_t FLASH_ErasePage(uint32_t page_address) {
uint32_t timeout = 0x000B0000;
// 等待上一个操作完成
while((FLASH->SR & FLASH_SR_BSY) && timeout) {
timeout--;
}
if(timeout == 0) return 0;
// 设置页擦除命令
FLASH->CR |= FLASH_CR_PER;
FLASH->AR = page_address;
FLASH->CR |= FLASH_CR_STRT;
// 等待擦除完成
timeout = 0x000B0000;
while((FLASH->SR & FLASH_SR_BSY) && timeout) {
timeout--;
}
// 清除页擦除标志
FLASH->CR &= ~FLASH_CR_PER;
return 1;
}
// 写入半字 (16位)
uint8_t FLASH_WriteHalfWord(uint32_t address, uint16_t data) {
uint32_t timeout = 0x000B0000;
// 等待上一个操作完成
while((FLASH->SR & FLASH_SR_BSY) && timeout) {
timeout--;
}
if(timeout == 0) return 0;
// 设置编程命令
FLASH->CR |= FLASH_CR_PG;
*(__IO uint16_t*)address = data;
// 等待编程完成
timeout = 0x000B0000;
while((FLASH->SR & FLASH_SR_BSY) && timeout) {
timeout--;
}
// 清除编程标志
FLASH->CR &= ~FLASH_CR_PG;
// 验证写入的数据
if(*(__IO uint16_t*)address != data) {
return 0;
}
return 1;
}
// 写入字 (32位)
uint8_t FLASH_WriteWord(uint32_t address, uint32_t data) {
uint16_t low_halfword = data & 0xFFFF;
uint16_t high_halfword = (data >> 16) & 0xFFFF;
if(!FLASH_WriteHalfWord(address, low_halfword)) return 0;
if(!FLASH_WriteHalfWord(address + 2, high_halfword)) return 0;
return 1;
}
// 擦除应用程序区域
uint8_t IAP_Erase_Application_Flash(void) {
uint32_t address;
uint8_t result = 1;
FLASH_Unlock();
for(address = APPLICATION_ADDRESS;
address < APPLICATION_ADDRESS + MAX_FIRMWARE_SIZE;
address += FLASH_PAGE_SIZE) {
if(!FLASH_ErasePage(address)) {
result = 0;
break;
}
}
FLASH_Lock();
return result;
}
// 写入固件数据
uint8_t IAP_Write_Firmware(void) {
uint8_t buffer[FLASH_PAGE_SIZE];
uint32_t address = APPLICATION_ADDRESS;
uint32_t bytes_received = 0;
uint16_t i;
FLASH_Unlock();
// 接收固件大小
if(UART_ReceiveWord(&firmware_size, 5000) != 0) {
FLASH_Lock();
return 0;
}
// 接收固件数据并写入Flash
while(bytes_received < firmware_size) {
uint16_t chunk_size = FLASH_PAGE_SIZE;
if(firmware_size - bytes_received < chunk_size) {
chunk_size = firmware_size - bytes_received;
}
// 接收一页数据
if(UART_ReceiveBuffer(buffer, chunk_size, 10000) != 0) {
FLASH_Lock();
return 0;
}
// 写入Flash
for(i = 0; i < chunk_size; i += 4) {
uint32_t word_data = buffer[i] |
(buffer[i+1] << 8) |
(buffer[i+2] << 16) |
(buffer[i+3] << 24);
if(!FLASH_WriteWord(address + bytes_received + i, word_data)) {
FLASH_Lock();
return 0;
}
}
bytes_received += chunk_size;
UART_SendByte(ACK); // 发送确认
}
FLASH_Lock();
return 1;
}
// 验证固件
uint8_t IAP_Verify_Firmware(void) {
uint32_t address;
uint32_t crc_calculated = 0;
uint32_t crc_received;
// 计算CRC
for(address = APPLICATION_ADDRESS;
address < APPLICATION_ADDRESS + firmware_size;
address += 4) {
crc_calculated ^= *(__IO uint32_t*)address;
}
// 接收上位机计算的CRC
if(UART_ReceiveWord(&crc_received, 5000) != 0) {
return 0;
}
return (crc_calculated == crc_received) ? 1 : 0;
}
2.3 电量计核心算法 (battery_monitor.c)
c
/**
* @file battery_monitor.c
* @brief 电池电量计核心算法
*/
#include "battery_monitor.h"
#include "adc.h"
#include "eeprom.h"
#include "math.h"
// 电池参数结构
typedef struct {
float voltage; // 电池电压 (V)
float current; // 电池电流 (A)
float temperature; // 电池温度 (°C)
float soc; // 荷电状态 (%)
float soh; // 健康状态 (%)
float full_capacity; // 满充容量 (Ah)
float remaining_capacity; // 剩余容量 (Ah)
uint8_t charge_status; // 充电状态
uint32_t cycle_count; // 循环次数
} Battery_Status_t;
// 电池配置参数
typedef struct {
float nominal_voltage; // 标称电压 (V)
float cutoff_voltage; // 截止电压 (V)
float full_voltage; // 满充电压 (V)
float capacity; // 标称容量 (Ah)
float internal_resistance; // 内阻 (Ω)
float temperature_coefficient; // 温度系数
uint8_t cell_count; // 电芯数量
} Battery_Config_t;
static Battery_Status_t battery_status;
static Battery_Config_t battery_config;
// 初始化电池监控
void Battery_Monitor_Init(void) {
// 加载电池配置
Battery_Load_Config();
// 初始化ADC
ADC_Init();
// 初始化电量计
Coulomb_Counter_Init();
// 初始化开路电压表
OCV_Table_Init();
}
// 更新电池状态
void Battery_Update_Status(void) {
// 1. 读取ADC值
battery_status.voltage = ADC_Read_Voltage();
battery_status.current = ADC_Read_Current();
battery_status.temperature = ADC_Read_Temperature();
// 2. 计算SOC (安时积分 + 开路电压校正)
Calculate_SOC();
// 3. 计算SOH
Calculate_SOH();
// 4. 更新充电状态
Update_Charge_Status();
// 5. 存储到EEPROM
Save_Battery_Status();
}
// 安时积分法计算SOC
void Coulomb_Counter_Update(void) {
static float accumulated_charge = 0;
float sample_time = 0.1f; // 100ms采样间隔
// 累积电荷 (Q = I × t)
accumulated_charge += battery_status.current * sample_time / 3600.0f; // 转换为Ah
// 计算剩余容量
battery_status.remaining_capacity = battery_config.capacity + accumulated_charge;
// 限制范围
if(battery_status.remaining_capacity > battery_config.capacity) {
battery_status.remaining_capacity = battery_config.capacity;
}
if(battery_status.remaining_capacity < 0) {
battery_status.remaining_capacity = 0;
}
// 计算SOC百分比
battery_status.soc = (battery_status.remaining_capacity / battery_config.capacity) * 100.0f;
}
// 开路电压法校正SOC
void OCV_Correction(void) {
float ocv_soc;
// 根据开路电压查找SOC
ocv_soc = Lookup_OCV_Table(battery_status.voltage);
// 当电池静止时进行校正
if(fabs(battery_status.current) < 0.1f) { // 电流小于100mA
// 融合安时积分和开路电压结果
battery_status.soc = 0.9f * battery_status.soc + 0.1f * ocv_soc;
}
}
// 计算SOC (综合算法)
void Calculate_SOC(void) {
static uint8_t first_run = 1;
if(first_run) {
// 首次运行,根据开路电压估算SOC
battery_status.soc = Lookup_OCV_Table(battery_status.voltage);
battery_status.remaining_capacity = (battery_status.soc / 100.0f) * battery_config.capacity;
first_run = 0;
} else {
// 正常更新
Coulomb_Counter_Update();
OCV_Correction();
}
// 温度补偿
Temperature_Compensation();
}
// 温度补偿
void Temperature_Compensation(void) {
float temp_compensation;
if(battery_status.temperature < 0) {
temp_compensation = -0.5f; // 低温下容量降低
} else if(battery_status.temperature > 45) {
temp_compensation = -0.3f; // 高温下容量降低
} else {
temp_compensation = 0.0f;
}
battery_status.soc += temp_compensation;
// 限制SOC范围
if(battery_status.soc > 100.0f) battery_status.soc = 100.0f;
if(battery_status.soc < 0.0f) battery_status.soc = 0.0f;
}
// 计算SOH (健康状态)
void Calculate_SOH(void) {
float current_capacity;
static float initial_capacity = 0;
if(initial_capacity == 0) {
initial_capacity = battery_config.capacity;
}
current_capacity = battery_status.remaining_capacity;
battery_status.soh = (current_capacity / initial_capacity) * 100.0f;
// 更新循环次数
if(battery_status.charge_status == CHARGE_COMPLETE) {
battery_status.cycle_count++;
EEPROM_WriteWord(CYCLE_COUNT_ADDRESS, battery_status.cycle_count);
}
}
// 更新充电状态
void Update_Charge_Status(void) {
if(battery_status.current > 0.1f) {
battery_status.charge_status = CHARGING;
} else if(battery_status.current < -0.1f) {
battery_status.charge_status = DISCHARGING;
} else {
battery_status.charge_status = IDLE;
}
// 充满检测
if(battery_status.voltage >= battery_config.full_voltage &&
battery_status.current < 0.05f) {
battery_status.charge_status = FULLY_CHARGED;
}
// 过放检测
if(battery_status.voltage <= battery_config.cutoff_voltage) {
battery_status.charge_status = OVER_DISCHARGED;
}
}
// 开路电压表
float Lookup_OCV_Table(float voltage) {
// 12V铅酸电池OCV-SOC对照表
const float ocv_table[21][2] = {
{11.8f, 0.0f}, {11.9f, 5.0f}, {12.0f, 10.0f},
{12.1f, 15.0f}, {12.2f, 20.0f}, {12.3f, 25.0f},
{12.4f, 30.0f}, {12.5f, 35.0f}, {12.6f, 40.0f},
{12.7f, 45.0f}, {12.8f, 50.0f}, {12.9f, 60.0f},
{13.0f, 70.0f}, {13.1f, 80.0f}, {13.2f, 90.0f},
{13.3f, 95.0f}, {13.4f, 98.0f}, {13.5f, 100.0f},
{13.6f, 100.0f}, {13.7f, 100.0f}, {13.8f, 100.0f}
};
int i;
for(i = 0; i < 21; i++) {
if(voltage <= ocv_table[i][0]) {
if(i == 0) return ocv_table[0][1];
// 线性插值
float v1 = ocv_table[i-1][0];
float v2 = ocv_table[i][0];
float s1 = ocv_table[i-1][1];
float s2 = ocv_table[i][1];
return s1 + (voltage - v1) * (s2 - s1) / (v2 - v1);
}
}
return 100.0f;
}
2.4 应用程序主程序 (application.c)
c
/**
* @file application.c
* @brief 电量计应用程序主程序
*/
#include "battery_monitor.h"
#include "display.h"
#include "communication.h"
#include "eeprom.h"
// 系统状态
typedef enum {
SYS_INIT = 0,
SYS_NORMAL,
SYS_CHARGING,
SYS_DISCHARGING,
SYS_ALARM,
SYS_SHUTDOWN
} SystemState;
static SystemState system_state = SYS_INIT;
static uint32_t system_tick = 0;
// 初始化应用程序
void Application_Init(void) {
// 初始化系统时钟
SystemClock_Init();
// 初始化外设
GPIO_Init();
ADC_Init();
I2C_Init();
UART_Init(9600);
// 初始化电池监控
Battery_Monitor_Init();
// 初始化显示
Display_Init();
// 初始化通信
Communication_Init();
// 加载配置参数
Load_System_Config();
system_state = SYS_NORMAL;
}
// 主循环
void Application_Main_Loop(void) {
while(1) {
// 1. 更新电池状态
Battery_Update_Status();
// 2. 更新系统状态
Update_System_State();
// 3. 显示更新
Display_Update();
// 4. 通信处理
Communication_Process();
// 5. 报警检测
Alarm_Check();
// 6. 低功耗处理
Low_Power_Management();
// 7. 延时
Delay_Ms(100); // 100ms周期
}
}
// 更新系统状态
void Update_System_State(void) {
switch(system_state) {
case SYS_NORMAL:
if(battery_status.charge_status == CHARGING) {
system_state = SYS_CHARGING;
} else if(battery_status.charge_status == DISCHARGING) {
system_state = SYS_DISCHARGING;
}
break;
case SYS_CHARGING:
if(battery_status.charge_status == FULLY_CHARGED) {
system_state = SYS_NORMAL;
} else if(battery_status.charge_status == DISCHARGING) {
system_state = SYS_DISCHARGING;
}
break;
case SYS_DISCHARGING:
if(battery_status.soc < 10.0f) {
system_state = SYS_ALARM; // 低电量报警
} else if(battery_status.charge_status == CHARGING) {
system_state = SYS_CHARGING;
}
break;
case SYS_ALARM:
if(battery_status.soc < 5.0f) {
system_state = SYS_SHUTDOWN; // 强制关机
} else if(battery_status.charge_status == CHARGING) {
system_state = SYS_CHARGING;
}
break;
case SYS_SHUTDOWN:
// 执行关机程序
System_Shutdown();
break;
}
}
// 通信协议处理
void Communication_Process(void) {
uint8_t cmd;
if(UART_Data_Available()) {
cmd = UART_ReadByte();
switch(cmd) {
case 0x01: // 读取电池状态
Send_Battery_Status();
break;
case 0x02: // 读取系统信息
Send_System_Info();
break;
case 0x03: // 设置参数
Receive_Configuration();
break;
case 0x04: // 进入IAP模式
Enter_IAP_Mode();
break;
case 0x05: // 重启系统
NVIC_SystemReset();
break;
}
}
}
// 进入IAP模式
void Enter_IAP_Mode(void) {
// 设置IAP标志到EEPROM
EEPROM_WriteByte(IAP_FLAG_ADDRESS, 0xAA);
// 发送确认
UART_SendByte(0xAA);
// 延时后重启
Delay_Ms(100);
NVIC_SystemReset();
}
int main(void) {
// 初始化应用程序
Application_Init();
// 主循环
Application_Main_Loop();
return 0;
}
2.5 通信协议 (communication.c)
c
/**
* @file communication.c
* @brief 电量计通信协议
*/
#include "communication.h"
#include "battery_monitor.h"
// 通信帧格式
typedef struct {
uint8_t header; // 帧头 0xAA
uint8_t length; // 数据长度
uint8_t command; // 命令字
uint8_t data[32]; // 数据
uint8_t checksum; // 校验和
} CommFrame_t;
// 发送电池状态
void Send_Battery_Status(void) {
CommFrame_t frame;
frame.header = 0xAA;
frame.command = 0x01;
frame.length = 16; // 16字节数据
// 填充电池数据
memcpy(&frame.data[0], &battery_status.voltage, 4);
memcpy(&frame.data[4], &battery_status.current, 4);
memcpy(&frame.data[8], &battery_status.temperature, 4);
memcpy(&frame.data[12], &battery_status.soc, 4);
// 计算校验和
frame.checksum = Calculate_Checksum(&frame);
// 发送帧
UART_SendBuffer((uint8_t*)&frame, sizeof(CommFrame_t));
}
// 计算校验和
uint8_t Calculate_Checksum(CommFrame_t *frame) {
uint8_t sum = 0;
uint8_t *ptr = (uint8_t*)frame;
uint8_t i;
for(i = 0; i < frame->length + 3; i++) { // 不包括校验和字段
sum += ptr[i];
}
return sum;
}
三、EEPROM存储管理
c
/**
* @file eeprom.c
* @brief EEPROM存储管理
*/
#include "eeprom.h"
// EEPROM地址定义
#define IAP_FLAG_ADDRESS 0x00
#define BATTERY_CONFIG_ADDRESS 0x10
#define CYCLE_COUNT_ADDRESS 0x30
#define SOC_CALIBRATION_ADDRESS 0x40
// 写入字节
void EEPROM_WriteByte(uint16_t address, uint8_t data) {
// 使用STM32的Flash模拟EEPROM
FLASH_Unlock();
FLASH_WriteHalfWord(EEPROM_START_ADDRESS + address, data);
FLASH_Lock();
}
// 读取字节
uint8_t EEPROM_ReadByte(uint16_t address) {
return *(__IO uint8_t*)(EEPROM_START_ADDRESS + address);
}
// 保存电池配置
void Save_Battery_Config(void) {
uint8_t *config_ptr = (uint8_t*)&battery_config;
uint16_t i;
FLASH_Unlock();
for(i = 0; i < sizeof(Battery_Config_t); i++) {
FLASH_WriteHalfWord(EEPROM_START_ADDRESS + BATTERY_CONFIG_ADDRESS + i, config_ptr[i]);
}
FLASH_Lock();
}
// 加载电池配置
void Load_Battery_Config(void) {
uint8_t *config_ptr = (uint8_t*)&battery_config;
uint16_t i;
for(i = 0; i < sizeof(Battery_Config_t); i++) {
config_ptr[i] = EEPROM_ReadByte(BATTERY_CONFIG_ADDRESS + i);
}
}
参考代码 STM32 IAP 电量计源码 www.youwenfan.com/contentcsu/60461.html
四、编译与部署
4.1 编译配置
makefile
# Makefile 配置
TARGET = battery_meter
MCU = cortex-m3
CFLAGS = -O2 -Wall -Wextra
LDFLAGS = -Tlinker.ld
# 源文件
SOURCES = bootloader.c flash.c battery_monitor.c application.c \
communication.c eeprom.c adc.c uart.c gpio.c
# 编译规则
all: $(TARGET).bin
$(TARGET).bin: $(TARGET).elf
arm-none-eabi-objcopy -O binary $< $@
$(TARGET).elf: $(SOURCES)
arm-none-eabi-gcc $(CFLAGS) $(SOURCES) $(LDFLAGS) -o $@
4.2 部署步骤
- 烧录Bootloader : 使用ST-Link烧录
bootloader.bin到0x08000000 - 运行Bootloader: 系统启动后进入IAP模式
- 烧录应用程序 : 通过串口发送
application.bin到0x08002000 - 跳转运行: 发送跳转命令,系统运行电量计应用
4.3 测试命令
bash
# 通过串口发送测试命令
echo -ne "\x01" > /dev/ttyUSB0 # 读取电池状态
echo -ne "\x04" > /dev/ttyUSB0 # 进入IAP模式
五、优化建议
- ADC采样优化: 使用DMA传输,减少CPU干预
- SOC算法优化: 增加卡尔曼滤波,提高估算精度
- 低功耗优化: 空闲时进入STOP模式,定时唤醒采样
- 通信优化: 使用Modbus RTU协议,提高兼容性