feat(FOC): 添加快速电压检测功能和PWM调试模式

新增基于跌幅检测的快速电压保护机制,在100us周期内响应电压异常,
当电压跌幅超过阈值或低于绝对下限时立即关机保护。同时添加PWM输出
调试模式便于硬件测试。

BREAKING CHANGE: 重构了电压检测逻辑,采用峰值跟踪算法替代固
定阈值检测,提高在动态供电环境下的可靠性
This commit is contained in:
cbb
2026-04-26 03:36:21 +08:00
parent bdf03517b3
commit f4fb23d640
9 changed files with 586 additions and 127 deletions
+179 -112
View File
@@ -38,8 +38,25 @@ extern u32 motor_runtime_50us; // 电机驱动计算轮次(用于静默模式
// 添加 DMA 相关变量
dma_parameter_struct dma_rx_parameter;
dma_parameter_struct dma_tx_parameter;
uint8_t can_rx_buffer[8]; // CAN 接收缓冲区
uint8_t can_tx_buffer[8]; // CAN 发送缓冲区
uint8_t can_rx_buffer[64]; // CAN 接收缓冲区(支持CAN FD最大64字节)
uint8_t can_tx_buffer[64]; // CAN 发送缓冲区(支持CAN FD最大64字节)
// CAN发送队列结构
#define CAN_TX_QUEUE_SIZE 8
typedef struct {
u32 id;
u8 length;
u8 data[64];
u8 valid;
} can_tx_queue_item_t;
static can_tx_queue_item_t tx_queue[CAN_TX_QUEUE_SIZE];
static volatile u8 tx_queue_head = 0;
static volatile u8 tx_queue_tail = 0;
static volatile u8 tx_queue_count = 0;
// 发送队列使用的独立消息结构体
static can_trasnmit_message_struct queue_transmit_message;
CAN_transmiter CAN0_transmiter; // CAN 初始化
@@ -100,28 +117,24 @@ void CAN_transmiter::set_node_id(u16 new_id) {
}
/*!
\brief 从 Flash 加载节点 ID
\brief 从 Flash 加载节点 ID(使用魔数+双重备份+计数器机制)
\param[in] none
\param[out] none
\retval FLASH_EEPROM_OK: 加载成功
其他:加载失败,使用默认值
\note Flash 擦除状态为 0xFFFF,需要特殊处理避免误判
注意:0 是有效的节点 ID,不应被视为无效值
\note 使用独立的节点 ID 存储区域,带魔数验证和 CRC 校验
防止位翻转导致错读
*/
u8 CAN_transmiter::load_node_id_from_flash() {
u16 node_id_temp = 0xFFFF; // 初始化为 Flash 擦除值
FlashEepromStatus status =
flashEEP.read(FlashEEPROM::ADDR_NODE_ID, &node_id_temp);
u16 node_id_temp = 0;
FlashEepromStatus status = flashEEP.readNodeId(&node_id_temp);
// 检查 Flash 是否为擦除状态 (0xFFFF) 或读取失败
// 注意:Flash 未写入时读到的值是 0xFFFF
// 0 是有效的节点 ID,不应被视为无效值
if (status != FLASH_EEPROM_OK || node_id_temp == 0xFFFF) {
// 检查读取是否成功
if (status != FLASH_EEPROM_OK) {
node_id = DEFAULT_NODE_ID;
CAN_DEBUG_PRINT(
"Node ID not found in Flash (status=%d, val=0x%04X), using "
"default: %d\r\n",
status, node_id_temp, node_id);
"Node ID not found in Flash (status=%d), using default: %d\r\n",
status, node_id);
return FLASH_EEPROM_DATA_NOT_FOUND;
}
@@ -139,25 +152,26 @@ u8 CAN_transmiter::load_node_id_from_flash() {
}
/*!
\brief 保存节点 ID 到 Flash
\brief 保存节点 ID 到 Flash(使用魔数+双重备份+计数器机制)
\param[in] new_id 要保存的节点 ID
\param[out] none
\retval FLASH_EEPROM_OK: 保存成功
其他:保存失败
\note 写入主备份和备用备份,确保数据可靠性
*/
u8 CAN_transmiter::save_node_id_to_flash(u16 new_id) {
if (new_id > MAX_NODE_ID) {
CAN_DEBUG_PRINT("Invalid Node ID: %d\r\n", new_id);
return FLASH_EEPROM_INVALID_ADDR;
}
FlashEepromStatus status =
flashEEP.write(FlashEEPROM::ADDR_NODE_ID, new_id);
FlashEepromStatus status = flashEEP.writeNodeId(new_id);
if (status == FLASH_EEPROM_OK) {
node_id = new_id;
CAN_DEBUG_PRINT("Node ID saved to Flash: %d\r\n", node_id);
return FLASH_EEPROM_OK;
} else {
CAN_DEBUG_PRINT("Failed to save Node ID to Flash\r\n");
CAN_DEBUG_PRINT("Failed to save Node ID to Flash (status=%d)\r\n", status);
return (u8)status;
}
}
@@ -746,10 +760,6 @@ void CAN_transmiter::communication_check() {
// 处理所有接收到的 CAN 消息(从环形缓冲区中)
can_receive_message_struct current_message;
while (dequeue_rx_message(current_message)) {
// CAN_DEBUG_PRINT("\r\n can0 %X receive data:",
// current_message.rx_efid); for (i = 0; i < current_message.rx_dlen;
// i++) { CAN_DEBUG_PRINT(" %02x", current_message.rx_data[i]);
// }
// 提取基础 ID (低 16 位,去除高 16 位节点 ID 部分)
u32 base_id = current_message.rx_efid & 0xFFFF;
@@ -776,6 +786,74 @@ void CAN_transmiter::communication_check() {
continue; // BOOT 命令处理完跳过后续逻辑
}
// 设置节点 ID 命令 (基础 ID: 0x0400)
// 注意:此命令在 is_message_for_me 检查之前处理,所有节点都会响应
// 这样即使节点ID已经改变,仍然可以用旧节点ID或广播ID来修改
else if (base_id == CANFD_SET_NODE_ID) {
// 打印接收到的原始数据
CAN_DEBUG_PRINT(
"SET_NODE_ID: rx_dlen=%d, data[0]=%02X data[1]=%02X\r\n",
current_message.rx_dlen, current_message.rx_data[0],
current_message.rx_data[1]);
// 解析新节点 ID - 支持 1 字节或 2 字节格式
u16 new_node_id;
if (current_message.rx_dlen >= 2) {
new_node_id = unpack_bytes_to_u16(current_message.rx_data, 0);
} else {
new_node_id =
current_message.rx_data[0]; // 只发送 1 字节时直接使用
}
u16 old_node_id = node_id; // 保存旧节点 ID 用于构建响应 ID
CAN_DEBUG_PRINT(
"SET_NODE_ID: recv new_id=%d (0x%04X), old_id=%d\r\n",
new_node_id, new_node_id, old_node_id);
if (new_node_id <= MAX_NODE_ID && new_node_id != BROADCAST_NODE_ID) {
// 先发送响应(使用旧节点ID构建CAN ID)
// 响应数据格式:[status:1byte][new_node_id:2byte] = 3 字节
transmit_data[0] = 0x01; // 成功
transmit_data[1] = (u8)(new_node_id & 0xFF); // 新 ID 低字节
transmit_data[2] =
(u8)((new_node_id >> 8) & 0xFF); // 新 ID 高字节
// 使用旧节点 ID 发送响应
node_id = old_node_id;
u8 send_result = CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3,
transmit_data);
// 然后保存新节点 ID 到 Flash
u8 save_status = save_node_id_to_flash(new_node_id);
CAN_DEBUG_PRINT("SET_NODE_ID: save_status=%d, send_result=%d\r\n",
save_status, send_result);
if (save_status == FLASH_EEPROM_OK) {
CAN_DEBUG_PRINT("SET_NODE_ID: SUCCESS, node_id updated from %d to %d\r\n",
old_node_id, new_node_id);
} else {
CAN_DEBUG_PRINT("SET_NODE_ID: Flash write FAILED, status=%d\r\n",
save_status);
// 发送错误响应
transmit_data[0] = 0x00; // 失败
node_id = old_node_id;
CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3,
transmit_data);
}
} else {
// 发送错误响应 - 使用旧节点 ID 构建响应 ID
CAN_DEBUG_PRINT("SET_NODE_ID: invalid new_id=%d (max=%d)\r\n",
new_node_id, MAX_NODE_ID);
transmit_data[0] = 0x00; // 失败
transmit_data[1] = (u8)(new_node_id & 0xFF);
transmit_data[2] = (u8)((new_node_id >> 8) & 0xFF);
node_id = old_node_id; // 临时恢复旧 ID 用于构建响应
CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3,
transmit_data);
// 保持原 ID 不变
}
continue; // SET_NODE_ID 命令处理完跳过后续逻辑
}
// 其他 base_id 的消息,检查是否是发给本节点的
if (!is_message_for_me(current_message.rx_efid)) {
continue; // 不是发给本节点的消息,跳过
@@ -864,75 +942,6 @@ void CAN_transmiter::communication_check() {
transmit_data);
}
}
// 设置节点 ID 命令 (基础 ID: 0x0400)
else if (base_id == CANFD_SET_NODE_ID) {
// 打印接收到的原始数据
CAN_DEBUG_PRINT(
"SET_NODE_ID: rx_dlen=%d, data[0]=%02X data[1]=%02X\r\n",
current_message.rx_dlen, current_message.rx_data[0],
current_message.rx_data[1]);
// 解析新节点 ID - 支持 1 字节或 2 字节格式
u16 new_node_id;
if (current_message.rx_dlen >= 2) {
new_node_id = unpack_bytes_to_u16(current_message.rx_data, 0);
} else {
new_node_id =
current_message.rx_data[0]; // 只发送 1 字节时直接使用
}
u16 old_node_id = node_id; // 保存旧节点 ID 用于构建响应 ID
CAN_DEBUG_PRINT(
"SET_NODE_ID: recv new_id=%d (0x%04X), old_id=%d\r\n",
new_node_id, new_node_id, old_node_id);
if (new_node_id <= MAX_NODE_ID) {
u8 save_status = save_node_id_to_flash(new_node_id);
CAN_DEBUG_PRINT(
"SET_NODE_ID: save_status=%d, current node_id=%d\r\n",
save_status, node_id);
if (save_status == FLASH_EEPROM_OK) {
// 发送确认响应 - 使用旧节点 ID 构建响应 ID
// 响应数据格式:[status:1byte][new_node_id:2byte] = 3 字节
transmit_data[0] = 0x01; // 成功
transmit_data[1] = (u8)(new_node_id & 0xFF); // 新 ID 低字节
transmit_data[2] =
(u8)((new_node_id >> 8) & 0xFF); // 新 ID 高字节
CAN_DEBUG_PRINT("SET_NODE_ID: SUCCESS, send response with "
"old_id=%d, new_id_bytes=%02X %02X\r\n",
old_node_id, transmit_data[1],
transmit_data[2]);
// 注意:这里不修改 node_id,因为 save_node_id_to_flash
// 已经更新了 但构建响应时需要用旧 ID,所以临时修改
node_id = old_node_id;
CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3,
transmit_data); // 发送 3 字节
node_id = new_node_id; // 恢复新 ID
CAN_DEBUG_PRINT("SET_NODE_ID: node_id updated to %d\r\n",
node_id);
} else {
// Flash 写入失败,发送错误响应
CAN_DEBUG_PRINT(
"SET_NODE_ID: Flash write FAILED, status=%d\r\n",
save_status);
transmit_data[0] = 0x00; // 失败
transmit_data[1] = (u8)(new_node_id & 0xFF);
transmit_data[2] = (u8)((new_node_id >> 8) & 0xFF);
node_id = old_node_id;
CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3,
transmit_data);
}
} else {
// 发送错误响应 - 使用旧节点 ID 构建响应 ID
CAN_DEBUG_PRINT("SET_NODE_ID: invalid new_id=%d (max=%d)\r\n",
new_node_id, MAX_NODE_ID);
transmit_data[0] = 0x00; // 失败
transmit_data[1] = (u8)(new_node_id & 0xFF);
transmit_data[2] = (u8)((new_node_id >> 8) & 0xFF);
node_id = old_node_id; // 临时恢复旧 ID 用于构建响应
CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3,
transmit_data);
// 保持原 ID 不变
}
}
// 查询节点 ID 命令 (基础 ID: 0x0402)
else if (base_id == CANFD_GET_NODE_ID) {
pack_u16_to_bytes(transmit_data, 0, get_node_id());
@@ -969,7 +978,7 @@ u8 CAN_transmiter::CAN_Wakeup() {
}
/**
* @brief CAN0 中断服务程序
* @brief CAN0 中断服务程序(优化版:直接读取数据,减少DMA竞态条件)
* @retval 无
*/
void CAN0_RX0_IRQHandler(void) {
@@ -978,14 +987,7 @@ void CAN0_RX0_IRQHandler(void) {
// 清除中断标志
can_interrupt_flag_clear(CAN0, CAN_INT_FLAG_RFL0);
// 使用 DMA 读取接收到的数据
dma_channel_disable(DMA0, DMA_CH4);
dma_rx_parameter.number =
can_receive_message_length_get(CAN0, CAN_FIFO0);
dma_init(DMA0, DMA_CH4, &dma_rx_parameter);
dma_channel_enable(DMA0, DMA_CH4);
// 读取接收到的数据
// 直接读取消息(包括数据),不依赖DMA
can_receive_message_struct temp_message;
can_message_receive(CAN0, CAN_FIFO0, &temp_message);
@@ -1086,7 +1088,51 @@ u8 CAN_transmiter::CAN_AsyncSend(u32 id, u8 length, u8 *data) {
}
/**
* @brief 异步 CANFD 数据发送 (使用 DMA 和中断机制)
* @brief 向发送队列添加消息
* @param id CAN消息ID
* @param length CAN消息长度
* @param data CAN消息数据
* @retval 1=成功, 0=队列满
*/
static u8 CAN_TX_QueuePush(u32 id, u8 length, u8 *data) {
if (tx_queue_count >= CAN_TX_QUEUE_SIZE) {
CAN_DEBUG_PRINT("TX queue full, message dropped\r\n");
return 0; // 队列满,丢弃消息
}
tx_queue[tx_queue_tail].id = id;
tx_queue[tx_queue_tail].length = length;
for (int i = 0; i < length && i < 64; i++) {
tx_queue[tx_queue_tail].data[i] = data[i];
}
tx_queue[tx_queue_tail].valid = 1;
tx_queue_tail = (tx_queue_tail + 1) % CAN_TX_QUEUE_SIZE;
tx_queue_count++;
return 1;
}
/**
* @brief 从发送队列取出消息
* @param item 指向队列项的指针
* @retval 1=成功, 0=队列空
*/
static u8 CAN_TX_QueuePop(can_tx_queue_item_t *item) {
if (tx_queue_count == 0) {
return 0; // 队列空
}
*item = tx_queue[tx_queue_head];
tx_queue[tx_queue_head].valid = 0;
tx_queue_head = (tx_queue_head + 1) % CAN_TX_QUEUE_SIZE;
tx_queue_count--;
return 1;
}
/**
* @brief 异步 CANFD 数据发送 (使用队列机制防止丢帧)
* @param id CAN 消息 ID
* @param length CAN 消息长度
* @param data CAN 消息数据
@@ -1151,29 +1197,50 @@ u8 CAN_transmiter::CANFD_AsyncSend(u32 id, u8 length, u8 *data) {
// 检查是否成功获取 mailbox
if (transmit_mailbox == CAN_NOMAILBOX) {
// 没有可用的 mailbox,启用发送中断以便在邮箱可用时重试
CAN_DEBUG_PRINT(
"No available mailbox for CANFD transmission, enabling TX "
"interrupt\r\n");
return 0;
// 没有可用的 mailbox,将消息加入发送队列
CAN_DEBUG_PRINT("No available mailbox, adding to TX queue\r\n");
return CAN_TX_QueuePush(id, valid_length, data);
}
// 启用 CAN 发送中断以检测传输完成
// 启用 CAN 发送中断以检测传输完成(用于队列发送)
can_interrupt_enable(CAN0, CAN_INTEN_TMEIE);
return 1;
}
// 添加 CAN 传输中断处理函数
/**
* @brief CAN 传输中断处理函数(支持队列发送)
*/
void CAN0_TX_IRQHandler(void) {
// 检查是否有发送邮箱空闲
if (can_flag_get(CAN0, CAN_FLAG_TME0) != RESET ||
can_flag_get(CAN0, CAN_FLAG_TME1) != RESET ||
can_flag_get(CAN0, CAN_FLAG_TME2) != RESET) {
// 邮箱已空闲,可以发送新消息
// 可以在这里处理待发送队列中的消息
can_tx_queue_item_t next_msg;
// 尝试从队列中取出下一条消息发送
if (CAN_TX_QueuePop(&next_msg)) {
// 配置并发送队列中的消息(使用独立的队列消息结构体)
queue_transmit_message.tx_ft = CAN_FT_DATA;
queue_transmit_message.tx_ff = CAN_FF_EXTENDED;
queue_transmit_message.tx_efid = next_msg.id;
queue_transmit_message.tx_dlen = next_msg.length;
queue_transmit_message.fd_flag = CAN_FDF_FDFRAME;
queue_transmit_message.fd_brs = ENABLE;
queue_transmit_message.fd_esi = DISABLE;
for (int i = 0; i < next_msg.length; i++) {
queue_transmit_message.tx_data[i] = next_msg.data[i];
}
can_message_transmit(CAN0, &queue_transmit_message);
} else {
// 队列为空,禁用发送中断
can_interrupt_disable(CAN0, CAN_INTEN_TMEIE);
}
// 清除发送中断标志
can_flag_clear(CAN0, CAN_FLAG_TME0);
can_flag_clear(CAN0, CAN_FLAG_TME1);
can_flag_clear(CAN0, CAN_FLAG_TME2);
}
}
}