feat(can): 反馈报文刷新率逐帧可配置并持久化
STATUS/CONFIG 反馈帧各自独立按 period_ms(1ms 节拍分频,0=停发)发送, 默认 STATUS 1kHz、CONFIG 250Hz,可运行时修改: - 0x0400 扩展格式(DLC=4):[选择器 u16][值 u16],选择器 0x0000=节点 ID (仅单播),0x0100/0x0101=对应帧刷新周期;回 5B ACK,广播只应用不应答 - 0x0401 扩展格式(DLC=2):查询选择器对应值,回 [sel][val](4B); DLC<=1 及 SET DLC 1~2 保持旧格式行为完全不变 - 刷新率经 FlashEEPROM 独立 u16 记录(虚拟地址 0x08/0x09)持久化, 不并入 6 参数 CRC 组,避免老设备升级后整组参数回退默认值 - save_system_config 追加从 RAM 副本回写两条记录,页满 format() 重建后配置不丢失;read() 增加保留字节校验防 CRC 头部字误匹配 - CANFD_all_Send 拆分为 send_status_frame/send_config_frame, 初始化失败上报路径直调 STATUS 帧不受分频影响 GATE_DRIVER_DRV8323 0/1 两种板型均编译通过
This commit is contained in:
+170
-36
@@ -448,6 +448,13 @@ u8 CAN_transmiter::init() {
|
||||
|
||||
// 从 Flash 加载节点 ID (如果 Flash 中有有效值则覆盖默认值)
|
||||
load_node_id_from_flash();
|
||||
|
||||
// 加载周期反馈帧刷新率(flashEEP.init() 已从 Flash 读入 RAM 副本,
|
||||
// 无记录时为其默认值),注意 hardware_init 保证 flashEEP.init() 先执行
|
||||
status_period_ms = flashEEP.tx_period_status_ms;
|
||||
config_period_ms = flashEEP.tx_period_config_ms;
|
||||
status_period_counter = 0;
|
||||
config_period_counter = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -772,19 +779,13 @@ void CAN_transmiter::update(void) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief CANFD 发送函数
|
||||
* @retval 0: 发送失败(或处于静默模式)
|
||||
* @brief 打包并发送 STATUS 反馈帧(实时状态 + 初始化错误位图)
|
||||
* @note 不做分频与静默检查,由周期任务按 status_period_ms 分频调用;
|
||||
* 初始化失败上报路径(main.cpp)也直接调用
|
||||
* @retval 0: 发送失败
|
||||
* 1: 发送成功
|
||||
*/
|
||||
u8 CAN_transmiter::CANFD_all_Send() {
|
||||
// 检查是否在静默模式中,如果是则不发送
|
||||
if (is_in_silent_mode()) {
|
||||
check_silent_mode(); // 检查是否已退出静默
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 success_flag = 0;
|
||||
|
||||
u8 CAN_transmiter::send_status_frame() {
|
||||
// 打包 status 数据 - 只清零实际使用的 48 字节
|
||||
// v0.74:字节 32~35 新增 InitStatus(初始化错误码位图,0=全部正常,
|
||||
// 定义见 config.h INIT_ERR_*,含电流检测配平超限/无采样帧),36~47 预留
|
||||
@@ -808,9 +809,14 @@ u8 CAN_transmiter::CANFD_all_Send() {
|
||||
transmit_data[31] = cpu_usage;
|
||||
pack_u32_to_bytes(transmit_data, 32, (u32)init_error_flags);
|
||||
// 使用节点 ID 构建发送 ID (高 16 位为节点 ID)
|
||||
success_flag +=
|
||||
CANFD_AsyncSend(build_can_id(CANFD_STATUS_ID), 48, transmit_data);
|
||||
return CANFD_AsyncSend(build_can_id(CANFD_STATUS_ID), 48, transmit_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 打包并发送 CONFIG 反馈帧(控制模式与目标设定回显)
|
||||
* @note 不做分频与静默检查,由周期任务按 config_period_ms 分频调用
|
||||
*/
|
||||
void CAN_transmiter::send_config_frame() {
|
||||
// 打包 config 数据 - 只清零实际使用的 24 字节
|
||||
memset(transmit_data, 0, 24);
|
||||
transmit_data[0] = controller_Mode;
|
||||
@@ -819,10 +825,7 @@ u8 CAN_transmiter::CANFD_all_Send() {
|
||||
pack_float_to_bytes(transmit_data, 7, target_velocity_union);
|
||||
pack_float_to_bytes(transmit_data, 11, target_angle_union);
|
||||
// 使用节点 ID 构建发送 ID (高 16 位为节点 ID)
|
||||
success_flag +=
|
||||
CANFD_AsyncSend(build_can_id(CANFD_CONFIG_ID), 24, transmit_data);
|
||||
|
||||
return success_flag;
|
||||
CANFD_AsyncSend(build_can_id(CANFD_CONFIG_ID), 24, transmit_data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -846,7 +849,19 @@ void CAN_transmiter::handle_periodic_send_tick() {
|
||||
|
||||
if (!is_in_silent_mode()) {
|
||||
update();
|
||||
CANFD_all_Send();
|
||||
// STATUS 与 CONFIG 各自独立分频(period_ms=0 停发),节拍仍为
|
||||
// 1ms(Timer.cpp task_schedule_100us[9] = TASK_CAN_TRANSMIT),
|
||||
// 修改周期经 SET 命令(0x0400 扩展格式)即时生效并清零计数器
|
||||
if (status_period_ms != 0U &&
|
||||
++status_period_counter >= status_period_ms) {
|
||||
status_period_counter = 0;
|
||||
send_status_frame();
|
||||
}
|
||||
if (config_period_ms != 0U &&
|
||||
++config_period_counter >= config_period_ms) {
|
||||
config_period_counter = 0;
|
||||
send_config_frame();
|
||||
}
|
||||
} else {
|
||||
check_silent_mode();
|
||||
}
|
||||
@@ -903,6 +918,27 @@ void CAN_transmiter::handle_boot_message(u16 sender_node_id) {
|
||||
jumpToBOOT();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 校验并保存新节点 ID 到 Flash(不组应答帧)
|
||||
* @param new_id: 新节点 ID
|
||||
* @retval 1: 成功(save_node_id_to_flash 内部已更新 node_id)
|
||||
* 0: 非法 ID 或 Flash 写入失败(node_id 保持不变)
|
||||
*/
|
||||
u8 CAN_transmiter::apply_new_node_id(u16 new_id) {
|
||||
if (new_id < 1 || new_id > SETTABLE_NODE_ID_MAX) {
|
||||
CAN_DEBUG_PRINT("SET_NODE_ID: invalid new_id=%d (range 1~%d)\r\n",
|
||||
new_id, SETTABLE_NODE_ID_MAX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 先写 Flash,成功后再由调用方回确认,避免 Flash 失败时上位机已收到 OK。
|
||||
u8 save_status = save_node_id_to_flash(new_id);
|
||||
|
||||
CAN_DEBUG_PRINT("SET_NODE_ID: save_status=%d\r\n", save_status);
|
||||
|
||||
return (save_status == FLASH_EEPROM_OK) ? 1U : 0U;
|
||||
}
|
||||
|
||||
void CAN_transmiter::handle_set_node_id_message(can_receive_message_struct ¤t_message) {
|
||||
// ── 防护:只处理发给本节点(或广播)的 SET_NODE_ID ──
|
||||
u16 target_node = (u16)((current_message.rx_efid >> 16) & 0xFFFF);
|
||||
@@ -910,6 +946,13 @@ void CAN_transmiter::handle_set_node_id_message(can_receive_message_struct &curr
|
||||
return;
|
||||
}
|
||||
|
||||
// DLC=4 为扩展格式参数设置:[选择器 u16][值 u16],
|
||||
// 选择器 0x0000=节点 ID,反馈帧基础 ID=刷新周期(ms)
|
||||
if (current_message.rx_dlen == 4) {
|
||||
handle_set_param_message(current_message);
|
||||
return;
|
||||
}
|
||||
|
||||
// 支持 1 字节和 2 字节节点 ID 命令,兼容旧上位机。
|
||||
CAN_DEBUG_PRINT(
|
||||
"SET_NODE_ID: rx_dlen=%d, data[0]=%02X data[1]=%02X\r\n",
|
||||
@@ -928,20 +971,7 @@ void CAN_transmiter::handle_set_node_id_message(can_receive_message_struct &curr
|
||||
// 响应帧使用旧节点 ID 构造,确保上位机能收到确认。
|
||||
node_id = old_node_id;
|
||||
|
||||
if (new_node_id < 1 || new_node_id > SETTABLE_NODE_ID_MAX) {
|
||||
CAN_DEBUG_PRINT("SET_NODE_ID: invalid new_id=%d (range 1~%d)\r\n",
|
||||
new_node_id, SETTABLE_NODE_ID_MAX);
|
||||
transmit_data[0] = 0x00;
|
||||
CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3, transmit_data);
|
||||
return;
|
||||
}
|
||||
|
||||
// 先写 Flash,成功后再回确认,避免 Flash 失败时上位机已收到 OK。
|
||||
u8 save_status = save_node_id_to_flash(new_node_id);
|
||||
|
||||
CAN_DEBUG_PRINT("SET_NODE_ID: save_status=%d\r\n", save_status);
|
||||
|
||||
if (save_status == FLASH_EEPROM_OK) {
|
||||
if (apply_new_node_id(new_node_id)) {
|
||||
transmit_data[0] = 0x01;
|
||||
CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3, transmit_data);
|
||||
CAN_DEBUG_PRINT("SET_NODE_ID: SUCCESS, node_id updated from %d to %d\r\n",
|
||||
@@ -949,12 +979,91 @@ void CAN_transmiter::handle_set_node_id_message(can_receive_message_struct &curr
|
||||
return;
|
||||
}
|
||||
|
||||
CAN_DEBUG_PRINT("SET_NODE_ID: Flash write FAILED, status=%d\r\n", save_status);
|
||||
// 非法 ID 或 Flash 写入失败:node_id 保持旧值,NAK
|
||||
transmit_data[0] = 0x00;
|
||||
node_id = old_node_id;
|
||||
CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 3, transmit_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理 0x0400 扩展格式参数设置(DLC=4)
|
||||
* @param current_message: [选择器 u16 LE][值 u16 LE]
|
||||
* 选择器 0x0000=节点 ID(仅单播),反馈帧基础 ID=该帧刷新周期 ms(0=停发)
|
||||
* @note 写 Flash 成功后立即生效;广播目标只应用不应答
|
||||
* (多节点同时应答会占总线冲突),单播回 5 字节应答
|
||||
* [status, sel_lo, sel_hi, val_lo, val_hi]
|
||||
*/
|
||||
void CAN_transmiter::handle_set_param_message(can_receive_message_struct ¤t_message) {
|
||||
u16 selector = unpack_bytes_to_u16(current_message.rx_data, 0);
|
||||
u16 value = unpack_bytes_to_u16(current_message.rx_data, 2);
|
||||
// 目标已由 handle_set_node_id_message 过滤为本节点或广播
|
||||
u16 target_node = (u16)((current_message.rx_efid >> 16) & 0xFFFF);
|
||||
bool is_broadcast = (target_node == BROADCAST_NODE_ID);
|
||||
u8 ok = 0;
|
||||
|
||||
if (selector == CANFD_PARAM_NODE_ID) {
|
||||
// 广播改节点 ID 会使所有节点同 ID,仅允许单播,广播时直接忽略
|
||||
if (is_broadcast) return;
|
||||
ok = apply_new_node_id(value);
|
||||
} else if (selector == CANFD_STATUS_ID || selector == CANFD_CONFIG_ID) {
|
||||
ok = set_tx_period(selector, value);
|
||||
} else {
|
||||
CAN_DEBUG_PRINT("SET_PARAM: unknown selector 0x%04X\r\n", selector);
|
||||
}
|
||||
|
||||
if (is_broadcast) return;
|
||||
|
||||
transmit_data[0] = ok ? 0x01 : 0x00;
|
||||
pack_u16_to_bytes(transmit_data, 1, selector);
|
||||
pack_u16_to_bytes(transmit_data, 3, value);
|
||||
// 节点 ID 修改成功时 node_id 已更新,应答以新节点 ID 发出(与旧格式一致)
|
||||
CANFD_AsyncSend(build_can_id(CANFD_SET_NODE_ID), 5, transmit_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置反馈帧刷新周期并持久化到 Flash
|
||||
* @param base_id: 反馈帧基础 ID(CANFD_STATUS_ID/CANFD_CONFIG_ID)
|
||||
* @param period_ms: 刷新周期 ms(0=停发,上限 1kHz 由 1ms 节拍决定)
|
||||
* @retval 1: 成功且已立即生效
|
||||
* 0: 未知帧 ID 或 Flash 写入失败(不生效)
|
||||
*/
|
||||
u8 CAN_transmiter::set_tx_period(u16 base_id, u16 period_ms) {
|
||||
u16 old_period;
|
||||
if (base_id == CANFD_STATUS_ID) {
|
||||
old_period = flashEEP.tx_period_status_ms;
|
||||
flashEEP.tx_period_status_ms = period_ms;
|
||||
} else if (base_id == CANFD_CONFIG_ID) {
|
||||
old_period = flashEEP.tx_period_config_ms;
|
||||
flashEEP.tx_period_config_ms = period_ms;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 先写 Flash 成功后再生效,与 SET_NODE_ID 的"写即存"握手一致;
|
||||
// 页满 format() 重建时 save_system_config 会从 RAM 副本恢复本次修改
|
||||
if (flashEEP.write_tx_periods() != FLASH_EEPROM_OK) {
|
||||
// 写入失败回滚 RAM 副本,保证失败不残留(否则关机保存路径
|
||||
// 会把未确认的值持久化)
|
||||
if (base_id == CANFD_STATUS_ID) {
|
||||
flashEEP.tx_period_status_ms = old_period;
|
||||
} else {
|
||||
flashEEP.tx_period_config_ms = old_period;
|
||||
}
|
||||
CAN_DEBUG_PRINT("SET_PARAM: tx period 0x%04X flash write failed\r\n",
|
||||
base_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (base_id == CANFD_STATUS_ID) {
|
||||
status_period_ms = period_ms;
|
||||
status_period_counter = 0;
|
||||
} else {
|
||||
config_period_ms = period_ms;
|
||||
config_period_counter = 0;
|
||||
}
|
||||
CAN_DEBUG_PRINT("SET_PARAM: base 0x%04X period %d ms\r\n", base_id, period_ms);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void CAN_transmiter::handle_node_message(can_receive_message_struct ¤t_message,
|
||||
u32 base_id) {
|
||||
// 这里只处理已经确认发给本节点的普通协议消息。
|
||||
@@ -967,8 +1076,33 @@ void CAN_transmiter::handle_node_message(can_receive_message_struct ¤t_mes
|
||||
} else if (base_id == CANFD_OTA_ACK_ID) {
|
||||
handle_ota_ack_message();
|
||||
} else if (base_id == CANFD_GET_NODE_ID) {
|
||||
pack_u16_to_bytes(transmit_data, 0, get_node_id());
|
||||
CANFD_AsyncSend(build_can_id(CANFD_GET_NODE_ID), 2, transmit_data);
|
||||
// 仅 DLC=2 判为扩展格式 [选择器 u16 LE],避免旧上位机带填充的
|
||||
// 长 DLC 查询被误解析;其余长度按旧格式回节点 ID(2 字节)
|
||||
if (current_message.rx_dlen == 2) {
|
||||
// 扩展格式:查询参数 [选择器 u16 LE]
|
||||
// 应答 [sel_lo, sel_hi, val_lo, val_hi](4 字节)
|
||||
u16 selector = unpack_bytes_to_u16(current_message.rx_data, 0);
|
||||
u16 value = 0;
|
||||
if (selector == CANFD_PARAM_NODE_ID) {
|
||||
value = get_node_id();
|
||||
} else if (selector == CANFD_STATUS_ID) {
|
||||
value = status_period_ms;
|
||||
} else if (selector == CANFD_CONFIG_ID) {
|
||||
value = config_period_ms;
|
||||
} else {
|
||||
// 未知选择器回 0(0 对周期含义为停发,注意与真实配置区分;
|
||||
// 权威写路径 0x0400 有显式状态字节)
|
||||
CAN_DEBUG_PRINT("GET_PARAM: unknown selector 0x%04X\r\n",
|
||||
selector);
|
||||
}
|
||||
pack_u16_to_bytes(transmit_data, 0, selector);
|
||||
pack_u16_to_bytes(transmit_data, 2, value);
|
||||
CANFD_AsyncSend(build_can_id(CANFD_GET_NODE_ID), 4, transmit_data);
|
||||
} else {
|
||||
// 旧格式:回节点 ID(2 字节),兼容旧上位机
|
||||
pack_u16_to_bytes(transmit_data, 0, get_node_id());
|
||||
CANFD_AsyncSend(build_can_id(CANFD_GET_NODE_ID), 2, transmit_data);
|
||||
}
|
||||
} else {
|
||||
CAN_DEBUG_PRINT("Unknown CAN ID: %X (node_id=%d)\n",
|
||||
current_message.rx_efid, node_id);
|
||||
|
||||
Reference in New Issue
Block a user