feat(FOC): 添加快速电压检测功能和PWM调试模式
新增基于跌幅检测的快速电压保护机制,在100us周期内响应电压异常, 当电压跌幅超过阈值或低于绝对下限时立即关机保护。同时添加PWM输出 调试模式便于硬件测试。 BREAKING CHANGE: 重构了电压检测逻辑,采用峰值跟踪算法替代固 定阈值检测,提高在动态供电环境下的可靠性
This commit is contained in:
@@ -477,6 +477,228 @@ float FlashEEPROM::read_angle() {
|
||||
return 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 从指定地址读取节点 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: 操作状态
|
||||
*/
|
||||
FlashEepromStatus FlashEEPROM::eraseNodeId() {
|
||||
fmc_state_enum fmc_status;
|
||||
|
||||
EEP_DEBUG_PRINT("Erasing Node ID page at 0x%08X\r\n", NODE_ID_PAGE_ADDR);
|
||||
|
||||
fmc_unlock();
|
||||
fmc_status = fmc_page_erase(NODE_ID_PAGE_ADDR);
|
||||
if (fmc_status != FMC_READY) {
|
||||
fmc_lock();
|
||||
EEP_DEBUG_PRINT("Node ID page erase failed, status=%d\r\n", fmc_status);
|
||||
return FLASH_EEPROM_ERASE_ERROR;
|
||||
}
|
||||
fmc_lock();
|
||||
|
||||
return FLASH_EEPROM_OK;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief 从 Flash 读取节点 ID
|
||||
* @param node_id: 输出节点 ID 值
|
||||
* @retval FlashEepromStatus: 操作状态
|
||||
*/
|
||||
FlashEepromStatus FlashEEPROM::readNodeId(u16 *node_id) const {
|
||||
if (node_id == nullptr) {
|
||||
return FLASH_EEPROM_INVALID_ADDR;
|
||||
}
|
||||
|
||||
u16 id_value = 0;
|
||||
u32 counter = 0;
|
||||
|
||||
// 从主存储区读取
|
||||
FlashEepromStatus status = read_node_id_struct(NODE_ID_PRIMARY_ADDR, &id_value, &counter);
|
||||
|
||||
if (status == FLASH_EEPROM_OK) {
|
||||
*node_id = id_value;
|
||||
EEP_DEBUG_PRINT("Node ID read from primary (counter=%u): %d\r\n", counter, *node_id);
|
||||
return FLASH_EEPROM_OK;
|
||||
} else {
|
||||
EEP_DEBUG_PRINT("Node ID read failed from primary: status=%d\r\n", status);
|
||||
return FLASH_EEPROM_DATA_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief 保存节点 ID 到 Flash(每次写入前先擦除整个页面,写入后校验)
|
||||
* @param node_id: 要保存的节点 ID 值
|
||||
* @retval FlashEepromStatus: 操作状态
|
||||
* @note 由于Flash只能将1写为0,不能将0写为1,因此每次写入新数据前
|
||||
* 需要先擦除整个页面。简化方案:不再使用备用备份区,
|
||||
* 每次写入前擦除页面,然后写入主存储区。
|
||||
* 写入后立即校验数据,如果不对则再次写入,最多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 current_id = 0;
|
||||
u32 current_counter = 0;
|
||||
|
||||
FlashEepromStatus primary_status = read_node_id_struct(NODE_ID_PRIMARY_ADDR, ¤t_id, ¤t_counter);
|
||||
|
||||
// 计数器 +1
|
||||
u32 new_counter = current_counter + 1;
|
||||
// 如果计数器溢出(0xFFFFFFFF + 1 = 0),则使用1
|
||||
if (new_counter == 0) {
|
||||
new_counter = 1;
|
||||
}
|
||||
|
||||
// 先擦除整个页面
|
||||
EEP_DEBUG_PRINT("Erasing Node ID page before write\r\n");
|
||||
FlashEepromStatus status = eraseNodeId();
|
||||
if (status != FLASH_EEPROM_OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// 写入并校验,最多3次
|
||||
#define MAX_WRITE_RETRY 3
|
||||
for (u8 retry = 0; retry < MAX_WRITE_RETRY; retry++) {
|
||||
// 写入主存储区
|
||||
EEP_DEBUG_PRINT("Writing to primary (counter=%u, retry=%d)\r\n", new_counter, retry);
|
||||
FlashEepromStatus write_status = write_node_id_struct(NODE_ID_PRIMARY_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(NODE_ID_PRIMARY_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 successfully: %d (counter=%u)\r\n", node_id, new_counter);
|
||||
return FLASH_EEPROM_OK;
|
||||
} else {
|
||||
EEP_DEBUG_PRINT("Node ID verification failed: read_id=%d (expected=%d), read_counter=%u (expected=%u)\r\n",
|
||||
read_id, node_id, read_counter, new_counter);
|
||||
// 校验失败,重新擦除并写入
|
||||
if (retry < MAX_WRITE_RETRY - 1) {
|
||||
EEP_DEBUG_PRINT("Retrying...\r\n");
|
||||
eraseNodeId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: 是否一次性保存
|
||||
|
||||
Reference in New Issue
Block a user