GD32 CAN总线驱动开发,过滤器配置、中断接收与标准帧通信实战
一、程序原理说明
CAN(Controller Area Network)总线是一种多主串行通信协议,广泛应用于工业控制和汽车电子领域。GD32系列MCU内置的CAN控制器遵循CAN 2.0A/2.0B协议规范,其核心工作机制围绕三大模块展开:发送邮箱、接收FIFO和标识符过滤器。
发送机制:GD32的CAN控制器提供3个发送邮箱(Mailbox),每个邮箱可独立装载一帧报文(包含ID、DLC和数据域)。当多个邮箱同时有待发送报文时,硬件根据报文ID的优先级(ID越小优先级越高)自动仲裁发送顺序,无需软件干预。
接收机制:CAN控制器配备2个接收FIFO,每个FIFO深度为3帧。当总线上的报文经过过滤器匹配后被存入FIFO,触发"FIFO非空"中断。软件在中断服务函数中调用can_message_receive()读取报文数据,该函数在读取的同时自动释放FIFO中的一个存储位。
过滤器机制:GD32提供28个过滤器组,支持两种工作模式——掩码模式(Mask Mode)和列表模式(List Mode)。掩码模式下,过滤器将接收到的报文ID与预设的参考ID按位进行"与"运算,掩码位为1的位必须完全匹配,为0的位忽略。列表模式下,过滤器存储两个完整的ID,接收到的报文ID必须与其中任一个完全匹配。
波特率计算原理:CAN波特率由APB1总线时钟经预分频器和位时序参数共同决定,公式为:
波特率 = F_APB1 / [预分频 × (1 + BS1 + BS2)]
其中BS1(时间段1)包含同步段和传播段,BS2(时间段2)为相位缓冲段2,采样点位置由BS1/(1+BS1+BS2)决定,通常设置在75%~87.5%之间以保证通信稳定性。
二、程序框架
整个CAN驱动采用分层架构设计,分为四层:
硬件抽象层:负责GPIO引脚配置、时钟使能和CAN外设寄存器初始化,包括波特率参数、工作模式和过滤器配置。
通信服务层:封装发送和接收接口函数,提供统一的报文构造、发送请求和接收解析API,屏蔽底层寄存器操作细节。
中断管理层:配置NVIC中断优先级,实现FIFO非空中断服务函数,在中断上下文中快速读取报文并推入环形缓冲区,确保中断执行时间最短。
应用调度层:主循环中轮询环形缓冲区取出报文,根据ID分发至不同的业务处理函数,实现通信与业务的解耦。
三、具体程序实现
以下基于GD32F30x系列MCU,以500Kbps波特率、标准帧通信为例,演示完整的驱动实现。
1. GPIO与时钟初始化
#include "gd32f30x.h"
void can_gpio_config(void)
{
/* 使能GPIO和CAN时钟 */
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_AF);
rcu_periph_clock_enable(RCU_CAN0);
/* CAN部分重映射:PB8=RX(上拉输入), PB9=TX(复用推挽) */
gpio_pin_remap_config(GPIO_CAN_PARTIAL_REMAP, ENABLE);
gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
}
2. CAN初始化与波特率配置
void can_init_config(void)
{
can_parameter_struct can_param;
can_struct_para_init(CAN_INIT_STRUCT, &can_param);
can_deinit(CAN0);
/* 工作模式与功能配置 */
can_param.working_mode = CAN_NORMAL_MODE;
can_param.auto_retrans = ENABLE; /* 发送失败自动重传 */
can_param.auto_bus_off = ENABLE; /* 自动离线恢复 */
can_param.auto_wake_up = DISABLE;
can_param.rec_fifo_overwrite = DISABLE; /* FIFO满时丢弃新报文 */
can_param.trans_fifo_order = DISABLE; /* 按ID优先级发送 */
/* 波特率:500Kbps @ 36MHz APB1
* 36MHz / (4 × (1 + 11 + 6)) = 500Kbps
* 采样点 = (1+11)/(1+11+6) = 66.7% */
can_param.prescaler = 4;
can_param.resync_jump_width = CAN_BT_SJW_1TQ;
can_param.time_segment_1 = CAN_BT_BS1_11TQ;
can_param.time_segment_2 = CAN_BT_BS2_6TQ;
can_init(CAN0, &can_param);
}
3. 过滤器配置——只接收指定ID的标准帧
void can_filter_config(void)
{
can_filter_parameter_struct filter;
/* 过滤器0:掩码模式,32位宽度,只接收ID=0x301的标准数据帧 */
filter.filter_number = 0;
filter.filter_mode = CAN_FILTERMODE_MASK;
filter.filter_bits = CAN_FILTERBITS_32BIT;
/* 32位过滤器格式:[31:21]=标准ID, [20:3]=扩展ID, [2:1]=帧类型, [0]=IDE
* 标准ID 0x301 左移21位,帧类型设为数据帧 */
filter.filter_list_high = (uint16_t)(((0x301 << 21) | (0x02 << 1)) >> 16);
filter.filter_list_low = (uint16_t)((0x301 << 21) | (0x02 << 1));
/* 掩码:高11位(标准ID)全匹配,其余位忽略 */
filter.filter_mask_high = 0xFFE0; /* [31:21]全为1,要求ID完全匹配 */
filter.filter_mask_low = 0x0000;
filter.filter_fifo_number = CAN_FIFO0;
filter.filter_enable = ENABLE;
can_filter_init(&filter);
}
4. 中断配置与接收服务函数
/* 接收报文结构体(全局) */
can_receive_message_struct can_rx_msg;
/* 环形缓冲区定义 */
#define RING_BUF_SIZE 32
static can_receive_message_struct ring_buf[RING_BUF_SIZE];
static volatile uint8_t buf_head = 0, buf_tail = 0;
void can_interrupt_config(void)
{
/* 使能FIFO0非空中断 */
can_interrupt_enable(CAN0, CAN_INTEN_RFNEIE0);
/* NVIC配置 */
nvic_irq_enable(USB_LP_CAN0_RX0_IRQn, 1, 0);
}
/* CAN0接收中断服务函数 */
void USB_LP_CAN0_RX0_IRQHandler(void)
{
if (can_receive_message_length_get(CAN0, CAN_FIFO0) > 0) {
can_message_receive(CAN0, CAN_FIFO0, &can_rx_msg);
/* 推入环形缓冲区(ISR中执行,需保证快速) */
uint8_t next = (buf_head + 1) % RING_BUF_SIZE;
if (next != buf_tail) {
ring_buf[buf_head] = can_rx_msg;
buf_head = next;
}
}
}
5. 发送函数与主循环
/* 发送标准帧 */
void can_send_std_frame(uint32_t id, uint8_t *data, uint8_t len)
{
can_trasnmit_message_struct tx_msg;
tx_msg.tx_sfid = id;
tx_msg.tx_efid = 0;
tx_msg.tx_ide = CAN_ID_STD; /* 标准帧 */
tx_msg.tx_ft = CAN_FT_DATA; /* 数据帧 */
tx_msg.tx_dlen = len;
for (uint8_t i = 0; i < len; i++) {
tx_msg.tx_data[i] = data[i];
}
can_message_transmit(CAN0, &tx_msg);
}
int main(void)
{
/* 系统初始化 */
can_gpio_config();
can_init_config();
can_filter_config();
can_interrupt_config();
uint8_t tx_data[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
while (1) {
/* 定时发送(示例:简单延时) */
can_send_std_frame(0x200, tx_data, 8);
delay_1ms(1000);
/* 轮询接收缓冲区,处理业务 */
while (buf_tail != buf_head) {
can_receive_message_struct *msg = &ring_buf[buf_tail];
buf_tail = (buf_tail + 1) % RING_BUF_SIZE;
/* 根据ID分发业务处理 */
if (msg->rx_sfid == 0x301) {
/* 处理ID=0x301的报文 */
process_command(msg->rx_data, msg->rx_dlen);
}
}
}
}
四、关键工程要点与避坑指南
过滤器位域映射:GD32的32位过滤器中,标准帧ID占据[31:21]共11位,而非直接存储在低11位。配置时必须将ID左移21位,否则过滤器无法正确匹配。
双路CAN的过滤器分配:GD32的28个过滤器由CAN0和CAN1共享,默认通过CAN_FCTL寄存器的HBC1F字段划分——CAN0使用0~13号,CAN1使用14~27号。若CAN1使用了0~13号过滤器,将导致无法接收数据。可通过can1_filter_start_bank()函数调整分配边界。
中断中避免耗时操作:CAN接收中断服务函数中仅做FIFO读取和缓冲区写入,所有业务逻辑(如UART打印、数据处理)必须下沉到主循环中执行,防止中断占用时间过长导致后续报文丢失。
波特率采样点优化:采样点位置直接影响通信的抗干扰能力。在1Mbps及以上波特率时,建议将采样点设置在80%以上(如BS1=7TQ, BS2=2TQ,采样点87.5%),以留出足够的相位裕量应对总线延迟差异。
GD32的CAN驱动开发看似简单,实则在过滤器位域映射、双路CAN资源分配和中断实时性三个环节暗藏诸多陷阱。掌握"过滤器精确匹配→中断快速搬运→主循环业务解耦"的三层设计范式,是构建稳定可靠CAN通信系统的工程基石。





