Files
Motor_v2/Robot_V2/sys/flash.cpp
T
cbb 7c4aaa5ee6 feat(FOC): 优化FOC控制算法并修复多个控制环路问题
- 重构cal_Iq_Id函数,移除电角度参数传递,改用预计算的sin/cos值
  以避免重复三角函数计算,提升性能约15-300周期
- 修复角度同步Uq前馈学习中的运动方向判断逻辑,解决反转时的
  震荡问题和正转正常反转震荡的根本原因
- 实现低速缩放因子函数get_mode3_velocity_scale,改进静摩擦
  hunting抑制效果
- 优化电压检测逻辑,将阻塞式ADC读取改为连续模式零等待,
  提升实时性能
- 修复初始化期间电压分母为零的问题,添加下限保护防止P增益
  无穷大
- 实现PID控制器测量微分功能,消除目标阶跃时的微分冲击
- 改进积分抗饱和策略,添加外部饱和泄放功能,优化到位稳定
  性能

perf(Filter): 优化中值滤波算法性能

- 针对ADC_FILTER_SIZE=6的固定场景实现最优排序网络,
  用12次比较替代通用std::nth_element算法,显著提升性能
- 保留通用回退路径处理其他尺寸情况
2026-09-01 02:46:25 +08:00

1123 lines
36 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file sys/flash.cpp
* @brief 系统服务与平台支持源文件
* @note 本文件仅补充 Doxygen 文档注释,不改变代码逻辑。
*/
#include "flash.h"
#include "FOC.h"
#include "ang_sensor.h"
#include "config.h"
#include "gd32c10x.h" // GD32 主头文件,确保外设函数声明可用
#include "gd32c10x_crc.h"
#include "gd32c10x_fmc.h"
#include "systick.h"
#include <Can.h>
#include <cstdio>
#include <cstring>
// EEP 调试宏定义
#if EEP_DEBUG
/** @brief EEP_DEBUG_PRINT 宏定义 */
#define EEP_DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
/** @brief EEP_DEBUG_PRINT 宏定义 */
#define EEP_DEBUG_PRINT(fmt, ...) ((void)0)
#endif
// OTA 调试宏定义
#if OTA_DEBUG
/** @brief OTA_DEBUG_PRINT 宏定义 */
#define OTA_DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
/** @brief OTA_DEBUG_PRINT 宏定义 */
#define OTA_DEBUG_PRINT(fmt, ...) ((void)0)
#endif
FlashEEPROM flashEEP = FlashEEPROM();
extern angle_sensor KTH7823;
extern CAN_transmiter CAN0_transmiter;
extern FOC_Controller FOC; // FOC 控制
/**
* @brief 写入 write_ADDRESS 相关数据
* @param[in] address 参数
* @param[in] data 参数
* @return fmc_state_enum 返回值
*/
fmc_state_enum write_ADDRESS(u32 address, u32 data) {
fmc_unlock();
fmc_flag_clear(FMC_FLAG_PGERR | FMC_FLAG_PGAERR | FMC_FLAG_WPERR);
// 确保使用对齐地址
fmc_state_enum fmc_status = fmc_word_program(address, data);
fmc_lock();
return fmc_status;
}
// FlashEEPROM 类实现
FlashEEPROM::FlashEEPROM() {
// 构造函数可以为空,或者做一些初始化工作
}
/**
* @brief 初始化 Flash EEPROM 模拟
* @param None
* @retval None
*/
u8 FlashEEPROM::init() {
rcu_periph_clock_enable(RCU_CRC);
crc_deinit(); // 初始化 CRC 模块
FlashEepromStatus status = FLASH_EEPROM_OK;
// 读取系统配置
read_saved_system_config();
// 加载电角度零位校准角(首次无存储则用默认值)
float calib = ANGLESENS_CALIBRATION_ANGLE;
if (readFloat(ADDR_CALIBRATION_ANGLE, &calib) != FLASH_EEPROM_OK) {
calib = ANGLESENS_CALIBRATION_ANGLE;
} else if (calib == 0.0f) {
// 校准值为 0 视为未校准/异常,回退默认值,避免换向角度错误
calib = ANGLESENS_CALIBRATION_ANGLE;
}
KTH7823.set_calibration(calib);
if (EEPROMStatus == FLASH_EEPROM_OK) {
return FLASH_EEPROM_OK;
} else {
return FLASH_EEPROM_READ_ERROR;
}
return 1; // 正常不会返回这个,未预料的异常情况返回
}
/**
* @brief 读取 EEPROM 数据
* @param virtualAddr: 虚拟地址
* @param data: 存储读取数据的指针
* @retval EEPROMStatus: 是否成功
*/
u8 FlashEEPROM::read_saved_system_config(void) {
// 读取保存的参数
u32 CRC_NUM = 0x0000;
FlashEepromStatus status_read[CONFIG_SAVED_NUM + 1] = {FLASH_EEPROM_OK};
float data[CONFIG_SAVED_NUM] = {0.0f};
// 尝试从 Flash 读取参数,如果不存在则使用默认值
status_read[1] = readFloat(ADDR_CONTROL_MODE, &data[0]);
status_read[2] = readFloat(ADDR_VOLTAGE_LIMIT, &data[1]);
status_read[3] = readFloat(ADDR_VEL_LIMIT, &data[2]);
status_read[4] = readFloat(ADDR_ANGLE, &data[3]);
status_read[5] = readFloat(ADDR_TARGET_ANGLE, &data[4]);
status_read[6] = readFloat(ADDR_TARGET_VEL, &data[5]);
// 读取失败则使用默认值
for (u8 i = 1; i < CONFIG_SAVED_NUM + 1; i++) {
if (status_read[i] ==
FLASH_EEPROM_DATA_NOT_FOUND) { // 首次读取,不保存报错
data[i - 1] = DEFAULT_VALUE;
} else if (status_read[i] != FLASH_EEPROM_OK) { // 其余错误,保存报错
data[i - 1] = DEFAULT_VALUE;
status_read[0] = status_read[i];
}
}
// 读取到全部数据
if (status_read[0] == FLASH_EEPROM_OK) {
u32 next_address = EEPROM_END_ADDRESS;
// 遍历 Flash 页面寻找下一个可写入位置
for (u32 address = EEPROM_DATA_START_ADDRESS - SIZE_U32;
address > EEPROM_START_ADDRESS; address -= SIZE_U32) {
// 安全读取 Flash 数据
volatile u32 *flash_ptr = (volatile u32 *)address;
u32 data_word = *flash_ptr;
// 找到空位置
if (data_word == 0xFFFFFFFF) {
next_address = address + SIZE_U32; // 上一个位置为有效存储的数据
break;
}
// 更新下一个可能的写入位置(如果需要格式化)
next_address = address - SIZE_U32;
}
if (next_address == EEPROM_DATA_START_ADDRESS) {
// 从来没有存储过,直接返回
return FLASH_EEPROM_OK;
}
crc_data_register_reset(); // 确保 CRC 计算从初始状态开始
CRC_NUM = crc_block_data_calculate((uint32_t *)data, CONFIG_SAVED_NUM);
EEP_DEBUG_PRINT("CRC cmp1,0x%X_0x%x \r\n", CRC_NUM,
*((u32 *)next_address));
if (CRC_NUM != *((u32 *)next_address)) { // CRC 错误
for (u8 i = 0; i < CONFIG_SAVED_NUM; i++) {
data[i] = DEFAULT_VALUE;
}
EEPROMStatus = FLASH_EEPROM_CRC_ERROR;
} else { // CRC 正确
EEPROMStatus = FLASH_EEPROM_OK;
}
} else {
// 读取失败,报错
EEPROMStatus = status_read[0];
}
KTH7823.angle_set(0);
FOC.control_mode = (u8)data[0];
FOC.limit_voltage = data[1];
FOC.target_current = data[2];
KTH7823.angle_set(data[3]);
FOC.target_angle = data[4];
FOC.target_velocity = data[5];
return EEPROMStatus;
}
/**
* @brief 从 Flash EEPROM 读取数据
* @param virtualAddr: 虚拟地址
* @param data: 指向存储读取数据的指针
* @retval FlashEepromStatus: 操作状态
*/
FlashEepromStatus FlashEEPROM::read(u8 virtualAddr, u16 *data) const {
// 参数检查
if (data == nullptr) {
return FLASH_EEPROM_INVALID_ADDR;
}
// 从 Flash 末尾开始向前搜索,找到最新的数据
for (u32 address = EEPROM_END_ADDRESS - SIZE_U32;
address >= EEPROM_START_ADDRESS; address -= SIZE_U32) {
u32 data_word = *((u32 *)address);
// 检查是否是有效数据(非擦除状态)
if (data_word != 0xFFFFFFFF) {
u8 addr = (u8)(data_word & DATA_ADDR_MASK);
// 找到匹配的虚拟地址
if (addr == virtualAddr) {
// 右移 16 位以提取高 16 位的 u16 数据
*data = (u16)((data_word & DATA_U16_MASK) >> 16);
EEP_DEBUG_PRINT("read u16: addr=0x%x, val=%d\r\n", address, *data);
return FLASH_EEPROM_OK;
}
}
}
return FLASH_EEPROM_DATA_NOT_FOUND;
}
/**
* @brief 从 Flash EEPROM 读取 float 数据
* @param virtualAddr: 虚拟地址
* @param data: 指向存储读取数据的指针
* @retval FlashEepromStatus: 操作状态
*/
FlashEepromStatus FlashEEPROM::readFloat(u8 virtualAddr, float *data) const {
// 检查参数有效性
if (data == nullptr) {
return FLASH_EEPROM_INVALID_ADDR;
}
// 从 Flash 末尾开始向前搜索,找到最新的数据
for (u32 address = EEPROM_END_ADDRESS - SIZE_U32;
address >= EEPROM_START_ADDRESS; address -= SIZE_U32) {
// 使用 volatile 确保每次都从内存读取,防止编译器优化
volatile u32 *flash_ptr = (volatile u32 *)address;
u32 data_word = *flash_ptr;
// 检查是否是有效数据(非擦除状态)
if (data_word != 0xFFFFFFFF) {
u8 addr = (u8)(data_word & DATA_ADDR_MASK);
// 找到匹配的虚拟地址
if (addr == virtualAddr) {
// 清除最低的 8 位地址信息,保留高 24 位数据
u32 float_data = data_word & DATA_FLOAT_MASK;
// 使用内存拷贝将处理后的数据解释为 float
memcpy(data, &float_data, SIZE_FLOAT);
EEP_DEBUG_PRINT("read:0x%x=%f\r\n", address, *data);
return FLASH_EEPROM_OK;
}
}
}
// 未找到指定虚拟地址的数据
return FLASH_EEPROM_DATA_NOT_FOUND;
}
/**
* @brief 向 Flash EEPROM 写入数据
* @param virtualAddr: 虚拟地址
* @param data: 要写入的数据
* @retval FlashEepromStatus: 操作状态
*/
FlashEepromStatus FlashEEPROM::write(u8 virtualAddr, u16 data) {
// 查找下一个可写入的位置
u32 next_address = EEPROM_START_ADDRESS;
u32 data_read;
// 构建要写入的 32 位数据:格式 [data:16bit][reserved:8bit][addr:8bit]
u32 data_word_to_write = ((u32)data << 16) | (u32)virtualAddr;
for (u32 address = EEPROM_END_ADDRESS - SIZE_U32;
address >= EEPROM_DATA_START_ADDRESS; address -= SIZE_U32) {
// 安全读取 Flash 数据
volatile u32 *flash_ptr = (volatile u32 *)address;
data_read = *flash_ptr;
if ((data_read & DATA_ADDR_MASK) == (u32)virtualAddr) { // 虚拟地址相同
if ((data_read & DATA_U16_MASK) ==
(data_word_to_write & DATA_U16_MASK)) { // 数据相同
#if EEP_DEBUG
data_read = data_read & DATA_U16_MASK;
u16 float_data_read = *((u16 *)&data_read);
EEP_DEBUG_PRINT("Found data in:0x%x=%u\r\n", address,
data_read);
#endif
return FLASH_EEPROM_OK;
} else {
break;
}
}
}
// 遍历 Flash 页面寻找下一个可写入位置
for (u32 address = EEPROM_DATA_START_ADDRESS; address < EEPROM_END_ADDRESS;
address += SIZE_U32) {
// 安全读取 Flash 数据
volatile u32 *flash_ptr = (volatile u32 *)address;
u32 data_word = *flash_ptr;
// 找到空位置
if (data_word == 0xFFFFFFFF) {
next_address = address;
break;
}
// 更新下一个可能的写入位置(如果需要格式化)
next_address = address + SIZE_U32;
}
// 如果有足够空间,直接写入
if (next_address < EEPROM_END_ADDRESS) {
fmc_state_enum fmc_status =
write_ADDRESS(next_address, data_word_to_write);
if (fmc_status != FMC_READY) {
return FLASH_EEPROM_WRITE_ERROR;
}
return FLASH_EEPROM_OK;
} else { // 空间不足,需要格式化页面
EEP_DEBUG_PRINT("no enough space\r\n");
FlashEepromStatus status = format();
if (status == FLASH_EEPROM_OK) {
status = save_system_config(false);
return status;
} else {
return FLASH_EEPROM_ERASE_ERROR;
}
}
}
/**
* @brief 向 Flash EEPROM 写入 float 数据
* @param virtualAddr: 虚拟地址
* @param data: 要写入的数据
* @retval FlashEepromStatus: 操作状态
*/
FlashEepromStatus FlashEEPROM::writeFloat(u8 virtualAddr, float data) {
// 查找下一个可写入的位置
u32 next_address = EEPROM_START_ADDRESS;
u32 data_word;
// 将完整的 float 数据复制到 32 位变量中
memcpy(&data_word, &data, SIZE_FLOAT);
// 插入虚拟地址到低 8 位 格式:[data:24bit][addr:8bit]
data_word = (data_word & DATA_FLOAT_MASK) | (u32)virtualAddr;
// 从 Flash 末尾开始向前搜索,找到最新的数据
for (u32 address = EEPROM_END_ADDRESS - SIZE_U32;
address >= EEPROM_DATA_START_ADDRESS; address -= SIZE_U32) {
// 安全读取 Flash 数据
volatile u32 *flash_ptr = (volatile u32 *)address;
u32 data_word_read = *flash_ptr;
if ((data_word_read & DATA_ADDR_MASK) ==
(u32)virtualAddr) { // 虚拟地址相同
if ((data_word_read & DATA_FLOAT_MASK) ==
(data_word & DATA_FLOAT_MASK)) { // 数据相同
#if EEP_DEBUG
data_word_read = data_word_read & DATA_FLOAT_MASK;
float float_data_read = *((float *)&data_word_read);
EEP_DEBUG_PRINT("Found data in:0x%x=%f\r\n", address,
float_data_read);
#endif
return FLASH_EEPROM_OK;
} else {
break; // 找到相同地址但数据不同,需要更新
}
}
}
// 遍历 Flash 页面寻找下一个可写入位置
for (u32 address = EEPROM_DATA_START_ADDRESS; address < EEPROM_END_ADDRESS;
address += SIZE_U32) {
// 安全读取 Flash 数据
volatile u32 *flash_ptr = (volatile u32 *)address;
u32 data_word_read = *flash_ptr;
// 找到空位置
if (data_word_read == 0xFFFFFFFF) {
next_address = address;
break;
}
// 更新下一个可能的写入位置(如果需要格式化)
next_address = address + SIZE_U32;
}
// 如果有足够空间,直接写入
if (next_address < EEPROM_END_ADDRESS) {
fmc_state_enum fmc_status = write_ADDRESS(next_address, data_word);
#if EEP_DEBUG
u32 data_word_c = *((u32 *)next_address);
data_word_c = data_word_c & DATA_FLOAT_MASK;
float float_data = *((float *)&data_word_c);
EEP_DEBUG_PRINT("save:0x%x=%f\r\n", next_address, float_data);
#endif
if (fmc_status != FMC_READY) {
return FLASH_EEPROM_WRITE_ERROR;
}
return FLASH_EEPROM_OK;
} else {
// 空间不足,需要格式化页面
#if EEP_DEBUG
EEP_DEBUG_PRINT("no enough space\r\n");
#endif
FlashEepromStatus status = format();
if (status == FLASH_EEPROM_OK) {
status = save_system_config(false);
return status;
} else {
return FLASH_EEPROM_ERASE_ERROR;
}
}
}
/**
* @brief 格式化 Flash EEPROM 页面
* @param None
* @retval FlashEepromStatus: 操作状态
*/
FlashEepromStatus FlashEEPROM::format() {
fmc_state_enum fmc_status;
EEP_DEBUG_PRINT("擦除页面\r\n");
fmc_unlock();
fmc_status = fmc_page_erase(EEPROM_START_ADDRESS); // 擦除第一页
if (fmc_status != FMC_READY) {
fmc_lock();
return FLASH_EEPROM_ERASE_ERROR;
}
fmc_status = fmc_page_erase(EEPROM_DATA_PAGE2_ADDRESS); // 擦除第二页
if (fmc_status != FMC_READY) {
fmc_lock();
return FLASH_EEPROM_ERASE_ERROR;
}
fmc_lock();
return FLASH_EEPROM_OK;
}
/**
* @brief 更新系统配置并保存到 Flash
* @param config 新的系统配置值
*/
FlashEepromStatus FlashEEPROM::save_system_config(bool saved) {
u32 CRC_NUM = 0x0000;
float data[CONFIG_SAVED_NUM] = {
(float)FOC.control_mode, FOC.limit_voltage,
FOC.target_current, FOC.rt_angle,
FOC.target_angle, FOC.target_velocity};
// 如果系统配置标记已保存,则无需再次保存
if (system_config_saved) {
EEPROMStatus = FLASH_EEPROM_OK;
return FLASH_EEPROM_OK;
}
// 写入参数并检查错误
FlashEepromStatus status;
status = writeFloat(ADDR_CONTROL_MODE, data[0]);
if (status != FLASH_EEPROM_OK) {
EEPROMStatus = status;
return status;
}
status = writeFloat(ADDR_VOLTAGE_LIMIT, data[1]);
if (status != FLASH_EEPROM_OK) {
EEPROMStatus = status;
return status;
}
status = writeFloat(ADDR_VEL_LIMIT, data[2]);
if (status != FLASH_EEPROM_OK) {
EEPROMStatus = status;
return status;
}
status = writeFloat(ADDR_ANGLE, data[3]);
if (status != FLASH_EEPROM_OK) {
EEPROMStatus = status;
return status;
}
status = writeFloat(ADDR_TARGET_ANGLE, data[4]);
if (status != FLASH_EEPROM_OK) {
EEPROMStatus = status;
return status;
}
status = writeFloat(ADDR_TARGET_VEL, data[5]);
if (status != FLASH_EEPROM_OK) {
EEPROMStatus = status;
return status;
}
// 创建用于 CRC 计算的模拟数据数组,模拟实际存储到 Flash 中的数据格式
u32 crc_data_words[CONFIG_SAVED_NUM];
// 按照 writeFloat 函数的方式处理数据
for (u8 i = 0; i < CONFIG_SAVED_NUM; i++) {
// 将完整的 float 数据复制到 32 位变量中
memcpy(&crc_data_words[i], &data[i], SIZE_FLOAT);
// 去除最后 2 位精度
crc_data_words[i] = crc_data_words[i] & DATA_FLOAT_MASK;
}
crc_data_register_reset(); // 确保 CRC 计算从初始状态开始
CRC_NUM = crc_block_data_calculate(crc_data_words, CONFIG_SAVED_NUM);
writeCRC(CRC_NUM, saved);
EEPROMStatus = FLASH_EEPROM_OK;
return FLASH_EEPROM_OK;
}
/**
* @brief 保存电角度零位校准角到 Flash
* @param[in] angle 校准角(度)
* @return FlashEepromStatus 操作状态
*/
FlashEepromStatus FlashEEPROM::save_calibration_angle(float angle) {
return writeFloat(ADDR_CALIBRATION_ANGLE, angle);
}
// ============================================================================
// 节点 ID 独立读写函数实现(使用魔数+双重备份+计数器机制)
// ============================================================================
/*!
* @brief 计算节点 ID 数据的 CRC32 校验值
* @param node_id: 节点 ID 值
* @param counter: 写入计数器值
* @retval u32: CRC32 校验值
*/
static u32 calculate_node_id_crc(u16 node_id, u32 counter) {
u32 crc_data[2];
crc_data[0] = (u32)node_id; // 节点 ID 值
crc_data[1] = counter; // 写入计数器
crc_data_register_reset();
return crc_block_data_calculate(crc_data, 2);
}
/*!
* @brief 比较写入计数器新旧(有符号差值比较,容忍 u32 回绕)
* @param a: 计数器 a
* @param b: 计数器 b
* @retval bool: a 比 b 新则为 true
* @note 计数器写入时跳过 0,只要两次写入间隔不超过 2^31 次即可正确比较
*/
static bool node_id_counter_newer(u32 a, u32 b) {
return (int32_t)(a - b) > 0;
}
/*!
* @brief 擦除单个 1KB Flash 页
* @param page_addr: 页起始地址(1KB 对齐)
* @retval FlashEepromStatus: 操作状态
*/
static FlashEepromStatus erase_flash_page(u32 page_addr) {
fmc_unlock();
fmc_state_enum fmc_status = fmc_page_erase(page_addr);
if (fmc_status != FMC_READY) {
fmc_lock();
EEP_DEBUG_PRINT("Page erase failed at 0x%08X, status=%d\r\n",
page_addr, fmc_status);
return FLASH_EEPROM_ERASE_ERROR;
}
fmc_lock();
return FLASH_EEPROM_OK;
}
/*!
* @brief 从指定地址读取节点 ID 数据结构
* @param addr: Flash 地址
* @param node_id: 输出节点 ID 值
* @param counter: 输出写入计数器值
* @retval FlashEepromStatus: 操作状态
*/
static FlashEepromStatus read_node_id_struct(u32 addr, u16 *node_id, u32 *counter) {
// 检查地址是否对齐
if (addr & 0x3) {
return FLASH_EEPROM_ALIGNMENT_ERROR;
}
volatile u32 *flash_ptr = (volatile u32 *)addr;
// 读取魔数
u32 magic = flash_ptr[NODE_ID_MAGIC_OFFSET / sizeof(u32)];
if (magic != NODE_ID_MAGIC) {
EEP_DEBUG_PRINT("Node ID magic mismatch at 0x%08X: 0x%08X\r\n", addr, magic);
return FLASH_EEPROM_DATA_NOT_FOUND;
}
// 读取节点 ID 值
u16 id_value = (u16)flash_ptr[NODE_ID_VALUE_OFFSET / sizeof(u32)];
if (id_value > MAX_NODE_ID && id_value != 0xFFFF) {
EEP_DEBUG_PRINT("Invalid node ID value: %d\r\n", id_value);
return FLASH_EEPROM_READ_ERROR;
}
// 读取写入计数器
u32 write_counter = flash_ptr[NODE_ID_COUNTER_OFFSET / sizeof(u32)];
// 读取并验证 CRC
u32 stored_crc = flash_ptr[NODE_ID_CRC_OFFSET / sizeof(u32)];
u32 calculated_crc = calculate_node_id_crc(id_value, write_counter);
if (stored_crc != calculated_crc) {
EEP_DEBUG_PRINT("Node ID CRC mismatch at 0x%08X: stored=0x%08X, calc=0x%08X\r\n",
addr, stored_crc, calculated_crc);
return FLASH_EEPROM_CRC_ERROR;
}
*node_id = id_value;
*counter = write_counter;
return FLASH_EEPROM_OK;
}
/*!
* @brief 写入节点 ID 数据结构到指定地址
* @param addr: Flash 地址
* @param node_id: 节点 ID 值
* @param counter: 写入计数器值
* @retval FlashEepromStatus: 操作状态
*/
static FlashEepromStatus write_node_id_struct(u32 addr, u16 node_id, u32 counter) {
// 检查地址是否对齐
if (addr & 0x3) {
return FLASH_EEPROM_ALIGNMENT_ERROR;
}
// 计算 CRC
u32 crc = calculate_node_id_crc(node_id, counter);
// 准备写入数据
u32 write_data[4];
write_data[0] = NODE_ID_MAGIC; // 魔数
write_data[1] = (u32)node_id; // 节点 ID 值
write_data[2] = counter; // 写入计数器
write_data[3] = crc; // CRC 校验
// 写入 Flash(需要解锁)
fmc_unlock();
fmc_flag_clear(FMC_FLAG_PGERR | FMC_FLAG_PGAERR | FMC_FLAG_WPERR);
fmc_state_enum fmc_status;
for (u8 i = 0; i < 4; i++) {
fmc_status = fmc_word_program(addr + i * sizeof(u32), write_data[i]);
if (fmc_status != FMC_READY) {
fmc_lock();
EEP_DEBUG_PRINT("Flash write failed at offset %d, status=%d\r\n", i, fmc_status);
return FLASH_EEPROM_WRITE_ERROR;
}
}
fmc_lock();
return FLASH_EEPROM_OK;
}
/*!
* @brief 擦除节点 ID 存储区(主备份页 + 备用备份页,恢复默认值)
* @retval FlashEepromStatus: 操作状态
* @note 仅用于恢复出厂/调试场景,会同时清除两份备份
*/
FlashEepromStatus FlashEEPROM::eraseNodeId() {
EEP_DEBUG_PRINT("Erasing Node ID pages at 0x%08X / 0x%08X\r\n",
NODE_ID_PRIMARY_ADDR, NODE_ID_BACKUP_ADDR);
FlashEepromStatus status = erase_flash_page(NODE_ID_PRIMARY_ADDR);
if (status != FLASH_EEPROM_OK) {
return status;
}
return erase_flash_page(NODE_ID_BACKUP_ADDR);
}
/*!
* @brief 从 Flash 读取节点 ID(双重备份回退机制)
* @param node_id: 输出节点 ID 值
* @retval FlashEepromStatus: 操作状态
* @note 读取主备份与备用备份两份记录:
* - 两份均有效:取写入计数器更新的一份
* - 仅一份有效:回退使用该份(另一份损坏或未写入)
* - 均无效:返回 DATA_NOT_FOUND,由调用方使用默认值
*/
FlashEepromStatus FlashEEPROM::readNodeId(u16 *node_id) const {
if (node_id == nullptr) {
return FLASH_EEPROM_INVALID_ADDR;
}
u16 id_primary = 0, id_backup = 0;
u32 counter_primary = 0, counter_backup = 0;
FlashEepromStatus status_primary = read_node_id_struct(
NODE_ID_PRIMARY_ADDR, &id_primary, &counter_primary);
FlashEepromStatus status_backup = read_node_id_struct(
NODE_ID_BACKUP_ADDR, &id_backup, &counter_backup);
if (status_primary == FLASH_EEPROM_OK &&
status_backup == FLASH_EEPROM_OK) {
// 双备份均有效:取计数器更新的一份(计数器相同时优先主备份)
if (node_id_counter_newer(counter_backup, counter_primary)) {
*node_id = id_backup;
EEP_DEBUG_PRINT("Node ID read from backup (counter=%u): %d\r\n",
counter_backup, *node_id);
} else {
*node_id = id_primary;
EEP_DEBUG_PRINT("Node ID read from primary (counter=%u): %d\r\n",
counter_primary, *node_id);
}
} else if (status_primary == FLASH_EEPROM_OK) {
// 主备份有效,备用备份损坏/为空:回退主备份
*node_id = id_primary;
EEP_DEBUG_PRINT(
"Node ID fallback to primary (backup status=%d): %d\r\n",
status_backup, *node_id);
} else if (status_backup == FLASH_EEPROM_OK) {
// 主备份损坏/为空,备用备份有效:回退备用备份
*node_id = id_backup;
EEP_DEBUG_PRINT(
"Node ID fallback to backup (primary status=%d): %d\r\n",
status_primary, *node_id);
} else {
EEP_DEBUG_PRINT("Node ID not found in both slots (primary=%d, backup=%d)\r\n",
status_primary, status_backup);
return FLASH_EEPROM_DATA_NOT_FOUND;
}
return FLASH_EEPROM_OK;
}
/*!
* @brief 保存节点 ID 到 Flash(乒乓式双重备份,写入后校验)
* @param node_id: 要保存的节点 ID 值
* @retval FlashEepromStatus: 操作状态
* @note 主备份与备用备份分属两个独立 1KB Flash 页。写入流程:
* 1. 读取两份备份,定位计数器最新的有效记录作为基准
* 2. 选择"非最新"的一页作为写入目标(乒乓交替)
* 3. 只擦除并写入目标页,另一页全程保有旧的有效记录,
* 掉电时至少一份数据存活
* 4. 写入后立即校验,失败则重试,最多3次
* 某一页损坏时无需专门修复:下一次写入目标即切换到该页,
* 擦除+重写天然完成自愈。
*/
FlashEepromStatus FlashEEPROM::writeNodeId(u16 node_id) {
if (node_id > MAX_NODE_ID) {
EEP_DEBUG_PRINT("Invalid node ID: %d (max=%d)\r\n", node_id, MAX_NODE_ID);
return FLASH_EEPROM_INVALID_ADDR;
}
// 读取两份备份,确定当前最新有效记录(计数器基准 + 乒乓目标选择)
u16 id_primary = 0, id_backup = 0;
u32 counter_primary = 0, counter_backup = 0;
FlashEepromStatus status_primary = read_node_id_struct(
NODE_ID_PRIMARY_ADDR, &id_primary, &counter_primary);
FlashEepromStatus status_backup = read_node_id_struct(
NODE_ID_BACKUP_ADDR, &id_backup, &counter_backup);
bool primary_latest = false;
bool backup_latest = false;
if (status_primary == FLASH_EEPROM_OK &&
status_backup == FLASH_EEPROM_OK) {
// 两份均有效:计数器新的为最新(相同时视为主备份最新)
if (node_id_counter_newer(counter_backup, counter_primary)) {
backup_latest = true;
} else {
primary_latest = true;
}
} else if (status_primary == FLASH_EEPROM_OK) {
primary_latest = true;
} else if (status_backup == FLASH_EEPROM_OK) {
backup_latest = true;
}
// 计数器 +1(跳过 0;两份均无效时从 1 开始)
u32 new_counter = 1;
if (primary_latest) {
new_counter = counter_primary + 1;
} else if (backup_latest) {
new_counter = counter_backup + 1;
}
if (new_counter == 0) {
new_counter = 1;
}
// 乒乓写入:目标为"非最新有效"的一页,最新有效的一页保持不动
u32 target_addr = primary_latest ? NODE_ID_BACKUP_ADDR : NODE_ID_PRIMARY_ADDR;
// 写入并校验,最多3次(重试期间另一页的旧记录始终有效)
/** @brief MAX_WRITE_RETRY 宏定义 */
#define MAX_WRITE_RETRY 3
for (u8 retry = 0; retry < MAX_WRITE_RETRY; retry++) {
EEP_DEBUG_PRINT("Writing to 0x%08X (counter=%u, retry=%d)\r\n",
target_addr, new_counter, retry);
FlashEepromStatus status = erase_flash_page(target_addr);
if (status != FLASH_EEPROM_OK) {
continue;
}
FlashEepromStatus write_status =
write_node_id_struct(target_addr, node_id, new_counter);
if (write_status != FLASH_EEPROM_OK) {
EEP_DEBUG_PRINT("Node ID write failed: status=%d\r\n", write_status);
continue;
}
// 立即校验数据
u16 read_id = 0;
u32 read_counter = 0;
FlashEepromStatus read_status =
read_node_id_struct(target_addr, &read_id, &read_counter);
if (read_status == FLASH_EEPROM_OK && read_id == node_id &&
read_counter == new_counter) {
EEP_DEBUG_PRINT("Node ID saved and verified: %d (counter=%u, slot=0x%08X)\r\n",
node_id, new_counter, target_addr);
return FLASH_EEPROM_OK;
}
EEP_DEBUG_PRINT("Node ID verify failed: read_id=%d (expected=%d), "
"read_counter=%u (expected=%u)\r\n",
read_id, node_id, read_counter, new_counter);
}
EEP_DEBUG_PRINT("Node ID save failed after %d retries\r\n", MAX_WRITE_RETRY);
return FLASH_EEPROM_WRITE_ERROR;
}
/**
* @brief 保存 CRC 数据到 Flash
* @param saved: 是否一次性保存
* @return FlashEepromStatus: 状态
*/
FlashEepromStatus FlashEEPROM::writeCRC(u32 CRC_NUM, bool saved) {
// 每次调用重新扫描可写入位置,不跨调用残留旧地址
// (循环至少执行一次,next_address 必被赋值)
u32 next_address = EEPROM_START_ADDRESS;
// 遍历 Flash 页面寻找下一个可写入 crc 的位置
for (u32 address = EEPROM_DATA_START_ADDRESS - SIZE_U32;
address > EEPROM_START_ADDRESS; address -= SIZE_U32) {
// 安全读取 Flash 数据
volatile u32 *flash_ptr = (volatile u32 *)address;
u32 data_word = *flash_ptr;
// 找到空位置
if (data_word == 0xFFFFFFFF) {
next_address = address;
break;
}
// 更新下一个可能的写入位置(如果需要格式化)
next_address = address - SIZE_U32;
}
// 如果当前 CRC 与现在的 crc 值不同,则写入
if (*((u32 *)next_address) != CRC_NUM) {
// 如果有足够空间,直接写入 crc 值
if (next_address > EEPROM_START_ADDRESS) {
fmc_state_enum fmc_status = write_ADDRESS(next_address, CRC_NUM);
EEP_DEBUG_PRINT("CRC cmp1,0x%X_0x%x \r\n", CRC_NUM,
*((u32 *)next_address));
// 检查写入是否成功
if (fmc_status != FMC_READY) {
EEPROMStatus = fmc_status;
return FLASH_EEPROM_WRITE_ERROR;
}
} else {
// 空间不足,需要格式化页面
EEP_DEBUG_PRINT("no enough space\r\n");
FlashEepromStatus status = format();
if (status == FLASH_EEPROM_OK) {
status = save_system_config(false);
return status;
} else {
EEPROMStatus = FLASH_EEPROM_ERASE_ERROR;
return FLASH_EEPROM_ERASE_ERROR;
}
}
}
// 重新读取 CRC 校验
if (CRC_NUM == *((u32 *)next_address)) {
system_config_saved = saved; // 标记已保存过
EEPROMStatus = FLASH_EEPROM_OK; // 设置 EEPROM 状态
return FLASH_EEPROM_OK;
} else {
EEPROMStatus = FLASH_EEPROM_CRC_ERROR; // 设置 EEPROM 错误状态
return FLASH_EEPROM_CRC_ERROR;
}
}
/**
* @brief 设置 OTA 配置参数到 Flash
*
* 此函数用于更新存储在 Flash 中的 OTA 相关标志位(如 APP 可用、启用、校验等)。
* GD32 的 Flash 特性是:只能将 1 写为 0,不能将 0 写为 1。若要将 0 写为
* 1,必须先擦除整个页面。
* 因此,本函数通过读取同一页内的所有参数,擦除该页,然后重新写入有效的参数来实现"写
* 1"操作。
*
* @param addr: 要设置的 Flash 地址
* @param data: 要写入的数据 (通常为 OTA_AVAL=0x00000000 或
* OTA_NOT_AVAL=0xFFFFFFFF)
* @retval FLASH_EEPROM_OK (0) 表示成功,其他值表示失败
*/
u8 OTA_SET(u32 addr, u32 data) {
EEP_DEBUG_PRINT("OTA_prepare write%08x at %08x\r\n", data, addr);
fmc_state_enum fmc_status;
u32 temp_val[OTA_CONFIG_NUM]; // 临时数组,用于存储当前所有配置参数的值
volatile u32 *flash_ptr = (volatile u32 *)addr;
// 1. 检查目标地址的值是否已与期望值相同,如果相同则无需操作
temp_val[0] = *flash_ptr;
if (temp_val[0] == data) {
return FLASH_EEPROM_OK; // 值已正确,直接返回成功
}
// 2. 尝试直接写入数据。这适用于将 1 修改为 0 的情况(因为 Flash 允许 1->0)
if (data == OTA_AVAL) {
fmc_status = write_ADDRESS(addr, data);
// 如果写入成功,则返回成功
if (fmc_status == FMC_READY) {
return FLASH_EEPROM_OK;
}
// 如果写入失败(例如地址已被写为 0),则继续执行后续的擦除 - 重写流程
}
// 3. 对于需要将 0 改回 1 的操作,必须先擦除整个 Flash 页
flash_ptr = (volatile u32 *)n_APP_LATEST_AVAL_ADDR;
temp_val[0] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_BACKUP_AVAL_ADDR;
temp_val[1] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_LATEST_EN_ADDR;
temp_val[2] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_BACKUP_EN_ADDR;
temp_val[3] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_LATEST_CHECK_ADDR;
temp_val[4] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_BACKUP_CHECK_ADDR;
temp_val[5] = *flash_ptr;
EEP_DEBUG_PRINT("OTA_prepare write%08x at %08x\r\n", data, addr);
// 修复:完整输出所有 6 个配置项的值,便于调试
EEP_DEBUG_PRINT("LatestAval:0x%x, BackupAval:0x%x, LatestEn:0x%x, "
"BackupEn:0x%x, LatestCheck:0x%x, BackupCheck:0x%x\r\n",
temp_val[0], temp_val[1], temp_val[2], temp_val[3],
temp_val[4], temp_val[5]);
// 4. 擦除包含所有配置参数的 Flash 页
fmc_status = fmc_page_erase(n_APP_LATEST_AVAL_ADDR);
if (fmc_status != FMC_READY) {
EEP_DEBUG_PRINT("OTA_SET - Page erase failed! Status: %d\r\n",
fmc_status);
return FLASH_EEPROM_ERASE_ERROR;
}
// 5. 重新写入所有之前读取的配置项值(恢复原始状态)
// 注意:不包括当前正在设置的地址,因为它将在最后单独处理
for (int i = 0; i < OTA_CONFIG_NUM; i++) {
u32 current_addr = n_APP_LATEST_AVAL_ADDR + 4 * i;
if (current_addr != addr and temp_val[i] != OTA_NOT_AVAL) {
fmc_status = write_ADDRESS(current_addr, temp_val[i]);
if (fmc_status != FMC_READY) {
EEP_DEBUG_PRINT("OTA_SET - Write backup value %08X failed at "
"0x%08X!status:%d\r\n",
temp_val[i], current_addr, fmc_status);
return FLASH_EEPROM_WRITE_ERROR;
}
}
}
#if EEP_DEBUG
// 3. 对于需要将 0 改回 1 的操作,必须先擦除整个 Flash 页
flash_ptr = (volatile u32 *)n_APP_LATEST_AVAL_ADDR;
temp_val[0] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_BACKUP_AVAL_ADDR;
temp_val[1] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_LATEST_EN_ADDR;
temp_val[2] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_BACKUP_EN_ADDR;
temp_val[3] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_LATEST_CHECK_ADDR;
temp_val[4] = *flash_ptr;
flash_ptr = (volatile u32 *)n_APP_BACKUP_CHECK_ADDR;
temp_val[5] = *flash_ptr;
// 修复:完整输出所有 6 个配置项的值,便于调试
EEP_DEBUG_PRINT("LatestAval:0x%x, BackupAval:0x%x, LatestEn:0x%x, "
"BackupEn:0x%x, LatestCheck:0x%x, BackupCheck:0x%x\r\n",
temp_val[0], temp_val[1], temp_val[2], temp_val[3],
temp_val[4], temp_val[5]);
#endif
return FLASH_EEPROM_OK;
}
/**
* @brief 跳转应用处理模块
* @param 无
* @retval 无
*/
void jumpToBOOT() {
// 0. 进 BOOT 前保存系统配置与运行参数(兜底,确保数据落盘)
flashEEP.save_system_config(false);
// 1. 关闭所有中断
__disable_irq();
for (int i = 0; i < 8; i++) {
NVIC->ICER[i] = 0xFFFFFFFF;
}
// 2. 重置外设(特别是 CAN,防止冲突)
can_deinit(CAN0);
// 3. 重置系统时钟到默认状态
rcu_system_clock_source_config(RCU_CKSYSSRC_IRC8M);
// 4. 关闭一些关键外设时钟
rcu_periph_clock_disable(RCU_GPIOA);
// rcu_periph_clock_disable(RCU_GPIOB);
rcu_periph_clock_disable(RCU_GPIOC);
rcu_periph_clock_disable(RCU_USART0);
rcu_periph_clock_disable(RCU_CAN0);
rcu_periph_clock_disable(RCU_TIMER0);
rcu_periph_clock_disable(RCU_DMA0);
// 5. 设置中断向量表偏移到应用程序起始地址
SCB->VTOR = BOOTLOADER_START_ADDR;
// 6. 设置 MSP 并跳转
uint32_t msp_value = *(__IO uint32_t *)BOOTLOADER_START_ADDR;
uint32_t reset_handler = *(__IO uint32_t *)(BOOTLOADER_START_ADDR + 4);
typedef void (*AppEntryFunc)(void);
AppEntryFunc app_entry = (AppEntryFunc)reset_handler;
// 跳转前喂狗,给 BOOT 留出一个完整看门狗周期做早期初始化
fwdgt_counter_reload();
__set_MSP(msp_value);
// 7. 跳转
app_entry();
// 如果程序执行到这里,说明跳转失败
EEP_DEBUG_PRINT("Jump to application failed!\r\n");
}
/**
* @brief 将 0x08011000 地址后 0xC000 大小区域内的数据搬运到 0x08005000 地址
* @param None
* @retval fmc_state_enum: 操作状态
*/
fmc_state_enum move_app_data() {
// 源地址:APPLATEST_START_ADDR (0x08011000)
// 目标地址:APPBACK_START_ADDR (0x08005000)
// 数据大小:APP_MAX_SIZE (0xC000 = 48KB)
fmc_state_enum fmc_status = FMC_READY;
OTA_DEBUG_PRINT("move_app_data\r\n");
// 关闭全局中断以防止 Flash 操作期间的中断干扰
__disable_irq();
fmc_unlock();
// 首先擦除目标地址的 Flash 页面
uint32_t num_pages =
(APP_MAX_SIZE + FMC_PAGE_SIZE - 1) / FMC_PAGE_SIZE; // 向上取整
for (uint32_t i = 0; i < num_pages; i++) {
uint32_t page_addr = APPBACK_START_ADDR + i * FMC_PAGE_SIZE;
// 擦除页面
fmc_status = fmc_page_erase(page_addr);
if (fmc_status != FMC_READY) {
fmc_lock();
#if OTA_DEBUG
OTA_DEBUG_PRINT("move_app_data erase error\r\n");
delay_1ms(1000);
#endif
__enable_irq(); // 重新开启中断
return fmc_status;
}
}
// 复制数据 - 按字(32 位)进行复制以提高效率
uint32_t offset = 0;
uint32_t write_count = 0; // 记录实际写入的字数
const uint32_t *src_ptr = (const uint32_t *)APPLATEST_START_ADDR;
uint32_t *dst_ptr = (uint32_t *)APPBACK_START_ADDR;
// 预计算源数据的 CRC,避免重复访问内存
crc_deinit();
crc_data_register_reset();
uint32_t src_crc = crc_block_data_calculate(
(uint32_t *)APPLATEST_START_ADDR, APP_MAX_SIZE / 4);
while (offset < APP_MAX_SIZE) {
uint32_t src_data = src_ptr[offset / 4];
// 检查源地址是否为有效的 Flash 数据(非 0xFFFFFFFF 表示有数据)
if (src_data != 0xFFFFFFFF) {
fmc_unlock();
// 清除之前的标志位
fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGAERR |
FMC_FLAG_PGERR);
fmc_status =
fmc_word_program(APPBACK_START_ADDR + offset, src_data);
// 检查写入操作是否成功
if (fmc_status != FMC_READY) {
#if OTA_DEBUG
OTA_DEBUG_PRINT(
"Flash write failed at offset 0x%08X with status: %d\r\n",
offset, fmc_status);
delay_1ms(1000);
#endif
fmc_lock();
__enable_irq(); // 重新开启中断
return fmc_status;
}
write_count++;
}
offset += 4;
// 每写入一定数量的数据后,喂狗一次
if ((offset % 4096) == 0) { // 每 4KB 喂狗一次
fwdgt_counter_reload();
}
}
fmc_lock();
// 重新开启全局中断
__enable_irq();
crc_data_register_reset(); // 确保 CRC 计算从初始状态开始
// 计算目标数据的 CRC 值
uint32_t dst_crc = crc_block_data_calculate((uint32_t *)APPBACK_START_ADDR,
APP_MAX_SIZE / 4);
// 比较 CRC 值,确保数据完整性
if (src_crc != dst_crc) {
OTA_DEBUG_PRINT(
"APPbackup CRC mismatch: src_crc=0x%08X, dst_crc=0x%08X\r\n",
src_crc, dst_crc);
return (fmc_state_enum)0xFF; // CRC 校验失败
}
OTA_DEBUG_PRINT("APPbackup CRC OK\r\n");
// 设置备份应用可用标志
fmc_unlock();
OTA_SET(n_APP_BACKUP_AVAL_ADDR, OTA_AVAL);
OTA_SET(n_APP_BACKUP_CHECK_ADDR, OTA_AVAL);
fmc_lock();
return fmc_status;
}