/** * @file device/Can.h * @brief 底层设备驱动与外设封装源文件 * @note 本文件仅补充 Doxygen 文档注释,不改变代码逻辑。 */ #ifndef _CAN_H #define _CAN_H #include #ifdef __cplusplus extern "C" { #endif #include #include "gd32c10x_can.h" #include "config.h" // 定义一个联合体类型用于 float 和 byte 数组之间的转换 /** * @brief 匿名联合体 联合体定义 */ typedef union { float f; u8 b[4]; } float_union_t; // 添加辅助函数用于将 u16 类型数据打包到 u8 数组中 /** * @brief pack_u16_to_bytes 函数 * @param[in] buffer 参数 * @param[in] offset 参数 * @param[in] value 参数 * @return void */ static inline void pack_u16_to_bytes(u8 *buffer, u16 offset, u16 value) { buffer[offset] = (u8)(value & 0xFF); buffer[offset + 1] = (u8)((value >> 8) & 0xFF); } /** * @brief pack_u32_to_bytes 函数(小端) * @param[in] buffer 参数 * @param[in] offset 参数 * @param[in] value 参数 * @return void */ static inline void pack_u32_to_bytes(u8 *buffer, u16 offset, u32 value) { pack_u16_to_bytes(buffer, offset, (u16)(value & 0xFFFF)); pack_u16_to_bytes(buffer, offset + 2, (u16)((value >> 16) & 0xFFFF)); } // 将 mA 电流值饱和转换为 int16 后按小端打包。 // DBC 中 U/V/W current 定义为 signed int16([-32768,32767] mA); // 相电流有正有负,此前用 u16() 打包负值被削成 0,上位机波形下半周丢失。 /** * @brief pack_current_s16_to_bytes 函数 * @param[in] buffer 参数 * @param[in] offset 参数 * @param[in] value_mA 参数 * @return void */ static inline void pack_current_s16_to_bytes(u8 *buffer, u16 offset, float value_mA) { short value; if (value_mA > 32767.0f) { value = 32767; // 饱和,避免 float→int 超范围截断环绕 } else if (value_mA < -32768.0f) { value = -32768; } else { value = (short)value_mA; } pack_u16_to_bytes(buffer, offset, (u16)value); } // 添加辅助函数用于将 float 类型数据打包到 u8 数组中 /** * @brief pack_float_to_bytes 函数 * @param[in] buffer 参数 * @param[in] offset 参数 * @param[in] float_union 参数 * @return void */ static inline void pack_float_to_bytes(u8 *buffer, u16 offset, float_union_t &float_union) { buffer[offset] = float_union.b[0]; buffer[offset + 1] = float_union.b[1]; buffer[offset + 2] = float_union.b[2]; buffer[offset + 3] = float_union.b[3]; } // 添加辅助函数用于从 u8 数组中解包 short 类型数据 /** * @brief unpack_bytes_to_short 函数 * @param[in] buffer 参数 * @param[in] offset 参数 * @return short 返回值 */ static inline short unpack_bytes_to_short(u8 *buffer, u16 offset) { short value = (short)(buffer[offset] | (buffer[offset + 1] << 8)); return value; } // 添加辅助函数用于从 u8 数组中解包 u16 类型数据 /** * @brief unpack_bytes_to_u16 函数 * @param[in] buffer 参数 * @param[in] offset 参数 * @return u16 返回值 */ static inline u16 unpack_bytes_to_u16(u8 *buffer, u16 offset) { u16 value = (u16)(buffer[offset] | (buffer[offset + 1] << 8)); return value; } // 添加辅助函数用于从 u8 数组中解包 float 类型数据 /** * @brief unpack_bytes_to_float 函数 * @param[in] buffer 参数 * @param[in] offset 参数 * @return float_union_t 返回值 */ static inline float_union_t unpack_bytes_to_float(u8 *buffer, u16 offset) { float_union_t float_union; float_union.b[0] = buffer[offset]; float_union.b[1] = buffer[offset + 1]; float_union.b[2] = buffer[offset + 2]; float_union.b[3] = buffer[offset + 3]; return float_union; } /** * @brief 初始化 CAN 引脚 * @return u8 返回值 */ u8 can_gpio_init(void); // CAN 引脚初始化 /** * @brief 配置 CAN FD 外设 * @return void */ void canfd_config(void); // CANFD 初始化 /** * @brief 初始化 CAN DMA 通道 * @return void */ void can_dma_init(void); // CAN DMA 初始化 /** * @brief CAN0 发送中断处理函数 * @return void */ void CAN0_TX_IRQHandler(void); // CAN 发送中断处理函数 /** * @brief CAN0 接收中断处理函数 * @return void */ void CAN0_RX0_IRQHandler(void); // CAN 接收中断处理函数 /** * @brief DMA0 通道 4 中断处理函数 * @return void */ void DMA0_Channel4_IRQHandler(void); // DMA 通道 4 中断处理函数 (CAN 接收) /** * @brief DMA0 通道 5 中断处理函数 * @return void */ void DMA0_Channel5_IRQHandler(void); // DMA 通道 5 中断处理函数 (CAN 发送) /** * @brief jumpToBOOT 函数 * @return void */ void jumpToBOOT(); /*! \brief CAN 通信类 \note 封装 CAN 发送和接收功能,支持多电机节点 ID 隔离 */ class CAN_transmiter { private: static const uint8_t CAN_RX_BUFFER_SIZE = 8; // CAN 接收缓冲区大小 static const u32 CAN_SILENT_DURATION_MS = 10000; // CAN 静默时长 10 秒 bool is_sleep = false; // 是否休眠 bool is_silent = false; // 是否静默(收到 base_id=0x0000 后进入静默) u32 silent_start_time = 0; // 静默开始时间(ms) u16 node_id; // 本节点 ID(0-254),使用 CAN ID 高 8 位存储 (在 init() 中初始化为 DEFAULT_NODE_ID) u8 Target_Control_Mode = 0; // 目标控制模式 u8 controller_Mode = 0; // 当前模式 short mcu_temp = 0; // MCU 温度 short motor_temp = 0; // 电机温度 u16 Power_Voltage = 0; // 电源电压 u16 limit_voltage = 0; // 限制电压 u8 cpu_usage = 0; // CPU 使用率(%) float_union_t rt_angle_union = {0}; // 实时角度 float_union_t rt_velocity_union = {0}; // 实时速度 float_union_t rt_current_union = {0}; // 实时电流 float_union_t target_angle_union = {0}; // 目标角度 float_union_t target_velocity_union = {0}; // 目标速度 float_union_t target_current_union = {0}; // 目标电流 float_union_t u_current_union = {0}; // U 相电流 float_union_t w_current_union = {0}; // W 相电流 float_union_t v_current_union = {0}; // V 相电流 u8 EEPROMStatus = 0; // EEPROM 状态码 // MotorStatus 不在此处:判据与上报都收在 health 的 1ms nFAULT 轮询里, // 声明见 public 段(与 MagStatus/ThermalStatus 同处),避免出现第二个写手。 can_trasnmit_message_struct transmit_message; // 发送消息结构体 u8 transmit_mailbox = CAN_NOMAILBOX; // 初始化为无 mailbox 状态 u8 transmit_data[64]; // CANFD 最大 64 字节 // 周期反馈帧独立刷新率(ms,0=停发)与 1ms 节拍分频计数器。 // init() 从 flashEEP 的 RAM 副本加载,SET 命令(0x0400 扩展格式)运行时修改 u16 status_period_ms = CANFD_TX_PERIOD_STATUS_DEFAULT_MS; u16 status_period_counter = 0; u16 config_period_ms = CANFD_TX_PERIOD_CONFIG_DEFAULT_MS; u16 config_period_counter = 0; // CAN 接收消息队列 can_receive_message_struct rx_message_buffer[CAN_RX_BUFFER_SIZE]; volatile uint8_t rx_buffer_head = 0; volatile uint8_t rx_buffer_tail = 0; /** * @brief 更新对象内部状态或采样数据 * @return void */ void update(); // 更新数据 /** * @brief 打包并发送 CONFIG 反馈帧(0x0101,控制模式/目标设定回显) * @return void */ void send_config_frame(); // CONFIG 反馈帧发送 /** * @brief CAN_Receive 函数 * @return u8 返回值 */ u8 CAN_Receive(); // CAN 接收 /** * @brief CANFD_Receive 函数 * @return u8 返回值 */ u8 CANFD_Receive(); // CANFD 接收 /** * @brief is_rx_buffer_full 函数 * @return bool 返回值 */ bool is_rx_buffer_full(); /** * @brief is_rx_buffer_empty 函数 * @return bool 返回值 */ bool is_rx_buffer_empty(); /** * @brief dequeue_rx_message 函数 * @param[in] message 参数 * @return bool 返回值 */ bool dequeue_rx_message(can_receive_message_struct& message); /** * @brief check_silent_mode 函数 * @return void */ void check_silent_mode(); // 检查静默模式是否结束 /** * @brief 处理周期性发送请求,静默模式下只检查是否退出静默 * @return void */ void handle_periodic_send_tick(); /** * @brief 将底层接收队列项转换为 CAN 消息并按协议分发 * @param[in] id 接收到的扩展 CAN ID * @param[in] length 接收数据长度 * @param[in] data 接收数据指针 * @return void */ void handle_received_queue_item(u32 id, u8 length, const u8 *data); /** * @brief 处理不受本节点过滤限制的特殊消息 * @param[in,out] current_message 当前 CAN 消息 * @param[in] base_id 低 16 位基础 ID * @param[in] sender_node_id 发送方节点 ID * @return bool true 表示消息已处理,false 表示继续按普通节点消息处理 */ bool handle_special_message(can_receive_message_struct ¤t_message, u32 base_id, u16 sender_node_id); /** * @brief 处理 BOOT 命令或其他节点 BOOT 广播导致的静默逻辑 * @param[in] sender_node_id 发送方节点 ID * @return void */ void handle_boot_message(u16 sender_node_id); /** * @brief 处理节点 ID 设置命令,发送应答并保存新 ID 到 Flash * @param[in,out] current_message 当前 CAN 消息 * @return void */ void handle_set_node_id_message(can_receive_message_struct ¤t_message); /** * @brief 处理 0x0400 扩展格式参数设置(DLC=4:[选择器 u16][值 u16]), * 选择器 0x0000=节点 ID、反馈帧基础 ID=对应帧刷新周期(ms) * @param[in,out] current_message 当前 CAN 消息 * @return void */ void handle_set_param_message(can_receive_message_struct ¤t_message); /** * @brief 校验并保存新节点 ID 到 Flash(不修改应答帧) * @param[in] new_id 新节点 ID * @return u8 1=成功,0=非法 ID 或 Flash 写入失败 */ u8 apply_new_node_id(u16 new_id); /** * @brief 设置反馈帧刷新周期并持久化到 Flash * @param[in] base_id 反馈帧基础 ID(CANFD_STATUS_ID/CANFD_CONFIG_ID) * @param[in] period_ms 刷新周期 ms(0=停发) * @return u8 1=成功,0=未知帧或 Flash 写入失败 */ u8 set_tx_period(u16 base_id, u16 period_ms); /** * @brief 处理已经确认发给本节点的普通协议消息 * @param[in,out] current_message 当前 CAN 消息 * @param[in] base_id 低 16 位基础 ID * @return void */ void handle_node_message(can_receive_message_struct ¤t_message, u32 base_id); /** * @brief 解析控制命令并更新 FOC 目标值和 PID 参数 * @param[in,out] current_message 当前 CAN 消息 * @return void */ void handle_control_command(can_receive_message_struct ¤t_message); /** * @brief 解析复位命令各 bit 位并执行对应动作 * @param[in,out] current_message 当前 CAN 消息 * @return void */ void handle_reset_command(can_receive_message_struct ¤t_message); /** * @brief 处理 OTA ACK 查询,APP 有效时回发确认帧 * @return void */ void handle_ota_ack_message(); public: // 节点 ID 相关辅助函数 (使用高 16 位存储节点 ID) /** * @brief build_can_id 函数 * @param[in] base_id 参数 * @return u32 返回值 */ u32 build_can_id(u32 base_id); // 根据节点 ID 构建 CAN ID,格式:[节点 ID(16 位)][基础 ID(16 位)] /** * @brief is_message_for_me 函数 * @param[in] received_id 参数 * @return bool 返回值 */ bool is_message_for_me(u32 received_id); // 判断消息是否发给本节点 public: // 添加构造函数声明 /** * @brief 初始化对象或模块 * @return u8 返回值 */ u8 init(void); can_receive_message_struct receive_message; // 接收消息结构体 FlagStatus can0_send_flag; // CAN0 发送标志 float_union_t testfloat_union = {0}; // 测试用 u16 test_u16 = 0; bool MagStatus = 0; // 磁状态码 bool ThermalStatus = 0; // 热能状态码 // nFAULT 状态位(STATUS 帧 motStatus)。唯一写手 = health.cpp 的 1ms nFAULT // 轮询,值与"是否允许运行"的判据同源;CAN update() 不再自行读引脚。 bool MotorStatus = 0; // 门驱动故障状态码 /** * @brief communication_check 函数 * @return void */ void communication_check(); // 通信检查 /** * @brief 打包并发送 STATUS 反馈帧(0x0100,实时状态与错误位图)。 * 不做分频与静默检查,初始化失败上报路径直接调用 * @return u8 1=已提交发送,0=发送失败 */ u8 send_status_frame(); // STATUS 反馈帧发送 /** * @brief CAN_Sleep 函数 * @return u8 返回值 */ u8 CAN_Sleep(); // CAN 进入睡眠 /** * @brief CAN_Wakeup 函数 * @return u8 返回值 */ u8 CAN_Wakeup(); // CAN 唤醒 /** * @brief CAN_AsyncSend 函数 * @param[in] ID 参数 * @param[in] length 参数 * @param[in] data 参数 * @return u8 返回值 */ u8 CAN_AsyncSend(u32 ID, u8 length, u8 *data); // CAN 异步发送 (使用 DMA) /** * @brief CANFD_AsyncSend 函数 * @param[in] ID 参数 * @param[in] length 参数 * @param[in] data 参数 * @return u8 返回值 */ u8 CANFD_AsyncSend(u32 ID, u8 length, u8 *data); // CANFD 异步发送 (使用 DMA) /** * @brief enter_silent_mode 函数 * @return void */ void enter_silent_mode(); // 进入静默模式(停发 10 秒) /** * @brief is_in_silent_mode 函数 * @return bool 返回值 */ bool is_in_silent_mode(); // 检查是否在静默模式中 // 节点 ID 管理接口 /** * @brief 获取 get_node_id 对应的数据或状态 * @return u16 返回值 */ u16 get_node_id(); // 获取当前节点 ID /** * @brief 设置 set_node_id 对应的参数或状态 * @param[in] new_id 参数 * @return void */ void set_node_id(u16 new_id); // 设置节点 ID /** * @brief load_node_id_from_flash 函数 * @return u8 返回值 */ u8 load_node_id_from_flash(); // 从 Flash 加载节点 ID /** * @brief save_node_id_to_flash 函数 * @param[in] new_id 参数 * @return u8 返回值 */ u8 save_node_id_to_flash(u16 new_id); // 保存节点 ID 到 Flash // 中断处理函数需要访问的公共接口 /** * @brief enqueue_rx_message_from_isr 函数 * @param[in] message 参数 * @return void */ void enqueue_rx_message_from_isr(const can_receive_message_struct& message) { if (!is_rx_buffer_full()) { rx_message_buffer[rx_buffer_head] = message; rx_buffer_head = (rx_buffer_head + 1) % CAN_RX_BUFFER_SIZE; } // 如果缓冲区满,丢弃新消息 } }; #ifdef __cplusplus } #endif #endif