Files

522 lines
18 KiB
C
Raw Permalink 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 can.c
\brief CAN统一接口实现(整合 CAN0/CAN1/CAN2
*/
#include "gd32h7xx.h"
#include "can.h"
#include <string.h>
/* RX strategy: TX on MB0, RX on MB1 (validated on all 3 CAN ports). */
/* 支持的 CAN 端口数量(CAN0/CAN1/CAN2 */
#define CAN_PORT_COUNT 3U
/* 每个端口的软件接收环形缓存深度(单位:帧) */
/* 单帧最大数据长度(Classic CAN,8 字节) */
#define CAN_DATA_MAX_LEN 8U
/**
* @brief 单帧接收缓存条目
*/
typedef struct {
uint32_t id; /* 帧 ID(标准/扩展均可) */
uint8_t data[CAN_DATA_MAX_LEN]; /* 帧数据 */
uint8_t dlc; /* 数据长度 */
uint8_t ide; /* 帧类型:0 标准帧,1 扩展帧 */
uint8_t rtr; /* 帧类型:0 数据帧,1 远程帧 */
uint8_t used; /* 条目是否有效:0 无效,1 有效 */
} can_rx_item_t;
/**
* @brief 单端口运行态(软件接收环形缓存)
*/
typedef struct {
can_rx_item_t rx_buffer[CAN_RX_BUFFER_SIZE]; /* 缓存数组 */
volatile uint8_t head; /* 写指针(中断侧推进) */
volatile uint8_t tail; /* 读指针(任务侧推进) */
} can_port_state_t;
/**
* @brief 端口硬件静态配置表项
*/
typedef struct {
uint32_t canx; /* CAN 外设基址(CAN0/CAN1/CAN2 */
can_idx_enum idx; /* 时钟源索引(IDX_CANx */
rcu_periph_enum can_clock; /* CAN 外设时钟 */
rcu_periph_enum gpio_clock; /* GPIO 端口时钟 */
uint32_t gpio_port; /* GPIO 端口 */
uint32_t gpio_af; /* 复用功能编号 */
uint32_t tx_pin; /* TX 引脚 */
uint32_t rx_pin; /* RX 引脚 */
IRQn_Type irqn; /* 对应中断号 */
} can_port_cfg_t;
/* 各端口运行态(接收缓存和读写索引) */
static can_port_state_t g_can_state[CAN_PORT_COUNT];
static uint8_t g_can_initialized = 0U;
/* 各端口硬件配置映射表 */
static const can_port_cfg_t g_can_cfg[CAN_PORT_COUNT] = {
{ CAN0, IDX_CAN0, RCU_CAN0, RCU_GPIOB, GPIOB, GPIO_AF_9, GPIO_PIN_9, GPIO_PIN_8, CAN0_Message_IRQn },
{ CAN1, IDX_CAN1, RCU_CAN1, RCU_GPIOB, GPIOB, GPIO_AF_9, GPIO_PIN_6, GPIO_PIN_5, CAN1_Message_IRQn },
{ CAN2, IDX_CAN2, RCU_CAN2, RCU_GPIOF, GPIOF, GPIO_AF_2, GPIO_PIN_7, GPIO_PIN_6, CAN2_Message_IRQn }
};
/**
* @brief 获取指定端口的配置项
* @param port 端口号(0/1/2
* @return 配置项指针,非法端口返回 NULL
*/
static const can_port_cfg_t *can_get_cfg(uint8_t port)
{
if (port >= CAN_PORT_COUNT) {
return NULL;
}
return &g_can_cfg[port];
}
/**
* @brief 初始化指定端口的软件接收缓存
* @param port 端口号(0/1/2
*/
static void can_buffer_init(uint8_t port)
{
memset(&g_can_state[port], 0, sizeof(g_can_state[port]));
}
/**
* @brief 配置指定端口 CAN GPIO 和时钟
* @param port 端口号(0/1/2
*/
static void can_gpio_config(uint8_t port)
{
const can_port_cfg_t *cfg = can_get_cfg(port);
if (cfg == NULL) {
return;
}
rcu_can_clock_config(cfg->idx, RCU_CANSRC_APB2);
rcu_periph_clock_enable(cfg->can_clock);
rcu_periph_clock_enable(cfg->gpio_clock);
gpio_af_set(cfg->gpio_port, cfg->gpio_af, cfg->rx_pin);
gpio_mode_set(cfg->gpio_port, GPIO_MODE_AF, GPIO_PUPD_NONE, cfg->rx_pin);
gpio_output_options_set(cfg->gpio_port, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, cfg->rx_pin);
gpio_af_set(cfg->gpio_port, cfg->gpio_af, cfg->tx_pin);
gpio_mode_set(cfg->gpio_port, GPIO_MODE_AF, GPIO_PUPD_PULLUP, cfg->tx_pin);
gpio_output_options_set(cfg->gpio_port, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, cfg->tx_pin);
}
/**
* @brief 配置指定端口 CAN 协议参数和中断
* @param port 端口号(0/1/2
* @note CAN0/CAN1: 250kbps, CAN2: 125kbps,采样点约 87.5%
*/
static void can_parameter_config(uint8_t port)
{
const can_port_cfg_t *cfg = can_get_cfg(port);
can_parameter_struct can_parameter;
uint32_t prescaler;
if (cfg == NULL) {
return;
}
/* 根据端口选择预分频器:CAN2使用125kbps,其他使用250kbps */
if (port == 2) { /* CAN2 */
prescaler = 150U; /* 125 kbps */
} else {
prescaler = 75U; /* 250 kbps */
}
can_deinit(cfg->canx);
can_struct_para_init(CAN_INIT_STRUCT, &can_parameter);
can_parameter.internal_counter_source = CAN_TIMER_SOURCE_BIT_CLOCK;
can_parameter.self_reception = DISABLE;
can_parameter.mb_tx_order = CAN_TX_HIGH_PRIORITY_MB_FIRST;
can_parameter.mb_tx_abort_enable = ENABLE;
can_parameter.local_priority_enable = DISABLE;
can_parameter.mb_rx_ide_rtr_type = CAN_IDE_RTR_FILTERED;
can_parameter.mb_remote_frame = CAN_STORE_REMOTE_REQUEST_FRAME;
can_parameter.rx_private_filter_queue_enable = DISABLE;
can_parameter.edge_filter_enable = DISABLE;
can_parameter.protocol_exception_enable = DISABLE;
can_parameter.rx_filter_order = CAN_RX_FILTER_ORDER_MAILBOX_FIRST;
can_parameter.memory_size = CAN_MEMSIZE_32_UNIT;
/* Follow official examples mailbox filter setting. */
can_parameter.mb_public_filter = 0U;
can_parameter.resync_jump_width = 1U;
can_parameter.prop_time_segment = 2U;
can_parameter.time_segment_1 = 11U;
can_parameter.time_segment_2 = 2U;
can_parameter.prescaler = prescaler;
can_init(cfg->canx, &can_parameter);
// nvic_irq_enable(cfg->irqn, 0U, 0U); /* 已迁移到 sys_drv_init.c:nvic_configuration() */
/* Mailbox path: TX on MB0, RX on MB1. */
can_interrupt_enable(cfg->canx, CAN_INT_MB0); /* TX complete/abort events */
can_interrupt_enable(cfg->canx, CAN_INT_MB1);
/* Enable error-related interrupts to avoid "silent dead" and help recovery. */
can_interrupt_enable(cfg->canx, CAN_INT_ERR_SUMMARY);
can_interrupt_enable(cfg->canx, CAN_INT_BUSOFF);
can_interrupt_enable(cfg->canx, CAN_INT_BUSOFF_RECOVERY);
can_interrupt_enable(cfg->canx, CAN_INT_RX_WARNING);
can_interrupt_enable(cfg->canx, CAN_INT_TX_WARNING);
/* Real bus communication mode. */
can_operation_mode_enter(cfg->canx, CAN_NORMAL_MODE);
}
/**
* @brief 配置指定端口接收过滤器(当前放行标准帧和扩展帧)
* @param port 端口号(0/1/2
*/
static void can_filter_config(uint8_t port)
{
if (can_get_cfg(port) == NULL) {
return;
}
/* Mailbox RX uses mb_public_filter configured in can_parameter_config(). */
}
/**
* @brief 指定端口发送一帧 CAN 数据
* @param port 端口号(0/1/2
* @param id 帧 ID
* @param data 数据指针
* @param dlc 数据长度(1~8
* @param ide 帧类型:0 标准帧,1 扩展帧
* @param rtr 帧类型:0 数据帧,1 远程帧
* @return 0 成功,1 参数错误
*/
static uint8_t can_port_send_data(uint8_t port, uint32_t id, const uint8_t *data, uint8_t dlc, uint8_t ide, uint8_t rtr)
{
can_mailbox_descriptor_struct tx_mailbox; /* 发送邮箱描述符 */
uint32_t tx_buffer[CAN_DATA_MAX_LEN] = {0}; /* 局部发送缓冲(驱动要求 32bit 对齐) */
const can_port_cfg_t *cfg = can_get_cfg(port); /* 端口配置 */
if ((cfg == NULL) || (data == NULL) || (dlc == 0U) || (dlc > CAN_DATA_MAX_LEN)) {
return 1U;
}
can_struct_para_init(CAN_MDSC_STRUCT, &tx_mailbox);
tx_mailbox.id = id;
tx_mailbox.dlc = dlc;
tx_mailbox.ide = ide;
tx_mailbox.rtr = rtr;
tx_mailbox.srr = (ide == 1U) ? 1U : 0U;
tx_mailbox.code = CAN_MB_TX_STATUS_DATA;
tx_mailbox.data_bytes = dlc;
memcpy(tx_buffer, data, dlc);
tx_mailbox.data = tx_buffer;
can_mailbox_config(cfg->canx, 0U, &tx_mailbox);
return 0U;
}
/**
* @brief 从指定端口的软件接收缓存读取一帧数据
* @param port 端口号(0/1/2
* @param id 输出:帧 ID
* @param data 输出:帧数据
* @param dlc 输出:数据长度
* @param ide 输出:帧类型(标准/扩展)
* @param rtr 输出:帧类型(数据/远程)
* @return 0 成功,1 无数据,2 参数错误
*/
static uint8_t can_port_receive_data(uint8_t port, uint32_t *id, uint8_t *data, uint8_t *dlc, uint8_t *ide, uint8_t *rtr)
{
can_port_state_t *state; /* 指向端口运行态 */
if ((port >= CAN_PORT_COUNT) || (id == NULL) || (data == NULL) || (dlc == NULL) || (ide == NULL) || (rtr == NULL)) {
return 2U;
}
state = &g_can_state[port];
if (state->rx_buffer[state->tail].used == 0U) {
return 1U;
}
*id = state->rx_buffer[state->tail].id;
*dlc = state->rx_buffer[state->tail].dlc;
*ide = state->rx_buffer[state->tail].ide;
*rtr = state->rx_buffer[state->tail].rtr;
memcpy(data, state->rx_buffer[state->tail].data, *dlc);
state->rx_buffer[state->tail].used = 0U;
state->tail = (uint8_t)((state->tail + 1U) % CAN_RX_BUFFER_SIZE);
return 0U;
}
/**
* @brief 统一中断处理逻辑(按端口复用)
* @param port 端口号(0/1/2
* @note 在中断上下文执行:从硬件邮箱读帧并入软件环形缓存
*/
static void can_irq_handler_common(uint8_t port)
{
can_port_state_t *state; /* 端口运行态 */
const can_port_cfg_t *cfg = can_get_cfg(port); /* 端口配置 */
uint8_t next_head; /* 计算后的下一写指针 */
if (cfg == NULL) {
return;
}
{
can_mailbox_descriptor_struct rx_mb;
uint32_t rx_data_u32[2] = {0U, 0U}; /* 8 bytes aligned buffer */
if (RESET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_MB1)) {
return;
}
can_struct_para_init(CAN_MDSC_STRUCT, &rx_mb);
rx_mb.data = rx_data_u32;
rx_mb.data_bytes = 8U;
if (SUCCESS != can_mailbox_receive_data_read(cfg->canx, 1U, &rx_mb)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_MB1);
return;
}
/* Only accept: extended + data frame */
if ((rx_mb.ide == 0U) || (rx_mb.rtr != 0U)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_MB1);
return;
}
state = &g_can_state[port];
next_head = (uint8_t)((state->head + 1U) % CAN_RX_BUFFER_SIZE);
if ((next_head != state->tail) && (state->rx_buffer[state->head].used == 0U)) {
uint8_t dlc = (uint8_t)rx_mb.dlc;
if (dlc > CAN_DATA_MAX_LEN) {
dlc = CAN_DATA_MAX_LEN;
}
state->rx_buffer[state->head].id = rx_mb.id;
state->rx_buffer[state->head].dlc = dlc;
state->rx_buffer[state->head].ide = (uint8_t)rx_mb.ide;
state->rx_buffer[state->head].rtr = (uint8_t)rx_mb.rtr;
memcpy(state->rx_buffer[state->head].data, (uint8_t *)rx_data_u32, dlc);
state->rx_buffer[state->head].used = 1U;
state->head = next_head;
}
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_MB1);
}
}
/**
* @brief CAN 中断统一入口(消息/错误/BusOff/告警等均可调用)
* @param can_port 端口号(0/1/2
*
* 说明:
* - GD32H7 的 CAN 中断线有 Message/Busoff/Error/TEC/REC 多条,
* 不同中断源触发哪一条与库/配置有关。这里统一收敛到一个入口,保证不中断“漏接”。
*/
void v_can_irq_handler(uint8_t can_port)
{
const can_port_cfg_t *cfg = can_get_cfg(can_port);
if (cfg == NULL) {
return;
}
/* Message path dispatch (MB1 RX mailbox). */
if (SET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_MB1)) {
can_irq_handler_common(can_port);
}
/* MB0 indicates TX mailbox event, clear it to avoid stale status. */
if (SET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_MB0)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_MB0);
}
/* 清理可能导致反复进入的错误/BusOff 标志(不依赖业务侧处理) */
if (SET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_BUSOFF)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_BUSOFF);
}
if (SET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_BUSOFF_RECOVERY)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_BUSOFF_RECOVERY);
}
if (SET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_ERR_SUMMARY)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_ERR_SUMMARY);
}
if (SET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_RX_WARNING)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_RX_WARNING);
}
if (SET == can_interrupt_flag_get(cfg->canx, CAN_INT_FLAG_TX_WARNING)) {
can_interrupt_flag_clear(cfg->canx, CAN_INT_FLAG_TX_WARNING);
}
}
/**
* @brief 初始化全部 CAN 端口(GPIO/参数/过滤器/缓存)
*/
void v_can_interface_init(void)
{
uint8_t port; /* 端口遍历索引 */
for (port = 0U; port < CAN_PORT_COUNT; port++) {
can_mailbox_descriptor_struct rx_mb;
uint32_t dummy_data[2] = {0U, 0U};
can_gpio_config(port);
can_parameter_config(port);
can_filter_config(port);
can_buffer_init(port);
/* Configure MB1 as RX mailbox. */
can_struct_para_init(CAN_MDSC_STRUCT, &rx_mb);
rx_mb.rtr = 0U;
rx_mb.ide = 1U; /* expect extended */
rx_mb.code = CAN_MB_RX_STATUS_EMPTY;
/* No ID filtering in mailbox setup; extension/data filtering is done in software path. */
rx_mb.id = 0U;
rx_mb.data = dummy_data;
rx_mb.data_bytes = 8U;
can_mailbox_config(g_can_cfg[port].canx, 1U, &rx_mb);
}
g_can_initialized = 1U;
}
uint8_t u8_can_is_initialized(void)
{
return g_can_initialized;
}
/**
* @brief 统一 CAN 发送接口(兼容旧接口)
* @param can_port 端口号(0/1/2
* @param id 帧 ID
* @param data 数据指针
* @param dlc 数据长度(1~8
* @param ide 0 标准帧,1 扩展帧
* @param rtr 0 数据帧,1 远程帧
* @return 0 成功,1 参数错误
*/
uint8_t can_send_data(uint8_t can_port, uint32_t id, uint8_t *data, uint8_t dlc, uint8_t ide, uint8_t rtr)
{
return can_port_send_data(can_port, id, data, dlc, ide, rtr);
}
/**
* @brief 统一 CAN 接收接口(兼容旧接口)
* @param can_port 端口号(0/1/2
* @param id 输出:帧 ID
* @param data 输出:帧数据
* @param dlc 输出:数据长度
* @param ide 输出:帧类型(标准/扩展)
* @param rtr 输出:帧类型(数据/远程)
* @return 0 成功,1 无数据,2 参数错误
*/
uint8_t can_receive_data(uint8_t can_port, uint32_t *id, uint8_t *data, uint8_t *dlc, uint8_t *ide, uint8_t *rtr)
{
return can_port_receive_data(can_port, id, data, dlc, ide, rtr);
}
/**
* @brief 对外统一发送接口(与参考工程接口风格一致)
* @param can_id 端口枚举
* @param can_data 发送数据结构
*/
void v_can_tx(E_CAN_ID can_id, const CAN_DATA *can_data)
{
if ((can_data == NULL) || (can_data->Len == 0U) || (can_data->Len > CAN_DATA_MAX_LEN)) {
return;
}
(void)can_send_data((uint8_t)can_id,
can_data->ID,
(uint8_t *)can_data->Data,
can_data->Len,
1U,
0U);
}
/**
* @brief 对外统一接收接口(仿照参考工程:一次取出当前软件缓存内全部CAN报文)
* @param can_id 端口枚举
* @param can_data 输出:接收数据结构体数组
* @return 返回本次拷出的报文数量;0 表示无数据或参数错误
*/
uint8_t u8_can_rx(E_CAN_ID can_id, CAN_DATA *can_data)
{
can_port_state_t *state;
const can_port_cfg_t *cfg;
uint8_t count = 0U;
if ((can_id >= E_CAN_0 + CAN_PORT_COUNT) || (can_data == NULL)) {
return 0U;
}
cfg = can_get_cfg((uint8_t)can_id);
if (cfg == NULL) {
return 0U;
}
state = &g_can_state[(uint8_t)can_id];
NVIC_DisableIRQ(cfg->irqn);
while ((count < CAN_RX_BUFFER_SIZE) && (state->rx_buffer[state->tail].used != 0U)) {
can_data[count].ID = state->rx_buffer[state->tail].id;
can_data[count].Len = state->rx_buffer[state->tail].dlc;
memcpy(can_data[count].Data, state->rx_buffer[state->tail].data, can_data[count].Len);
state->rx_buffer[state->tail].used = 0U;
state->tail = (uint8_t)((state->tail + 1U) % CAN_RX_BUFFER_SIZE);
count++;
}
if (state->head == state->tail) {
state->head = 0U;
state->tail = 0U;
}
NVIC_EnableIRQ(cfg->irqn);
return count;
}
/**
* @brief 打包扩展帧 ID(协议字段组装)
* @param prior 优先级字段
* @param cmd 命令字段
* @param desAddr 目的地址字段
* @param oriAddr 源地址字段
* @return 29 位扩展帧 ID
*/
uint32_t u32_get_can_ID(uint8_t prior, uint8_t cmd, uint8_t desAddr, uint8_t oriAddr)
{
return ((uint32_t)prior << 26)
| ((uint32_t)cmd << 16)
| ((uint32_t)desAddr << 8)
| (uint32_t)oriAddr;
}
/**
* @brief 解析扩展帧 ID(协议字段拆解)
* @param u32_id 待解析 ID
* @param prior 输出:优先级(可为 NULL)
* @param cmd 输出:命令(可为 NULL
* @param desAddr 输出:目的地址(可为 NULL)
* @param oriAddr 输出:源地址(可为 NULL)
* @return 1 成功,0 参数 ID 非法
*/
uint8_t u8_can_ID_unpack(uint32_t u32_id, uint8_t *prior, uint8_t *cmd, uint8_t *desAddr, uint8_t *oriAddr)
{
if ((u32_id == 0U) || (u32_id > 0x1FFFFFFFU)) {
return 0U;
}
if (prior != NULL) {
*prior = (uint8_t)((u32_id >> 26) & 0xFFU);
}
if (cmd != NULL) {
*cmd = (uint8_t)((u32_id >> 16) & 0xFFU);
}
if (desAddr != NULL) {
*desAddr = (uint8_t)((u32_id >> 8) & 0xFFU);
}
if (oriAddr != NULL) {
*oriAddr = (uint8_t)(u32_id & 0xFFU);
}
return 1U;
}