Files
Motor_v2/Robot_V2/device/Can.h
T
cbb 40fea328b0 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 两种板型均编译通过
2026-09-12 19:00:06 +08:00

471 lines
14 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 device/Can.h
* @brief 底层设备驱动与外设封装源文件
* @note 本文件仅补充 Doxygen 文档注释,不改变代码逻辑。
*/
#ifndef _CAN_H
#define _CAN_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <cstdint>
#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 相电流
bool MotorStatus = 0; // 电机状态码
u8 EEPROMStatus = 0; // EEPROM 状态码
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 &current_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 &current_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 &current_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 &current_message,
u32 base_id);
/**
* @brief 解析控制命令并更新 FOC 目标值和 PID 参数
* @param[in,out] current_message 当前 CAN 消息
* @return void
*/
void handle_control_command(can_receive_message_struct &current_message);
/**
* @brief 解析复位命令各 bit 位并执行对应动作
* @param[in,out] current_message 当前 CAN 消息
* @return void
*/
void handle_reset_command(can_receive_message_struct &current_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; // 热能状态码
/**
* @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