当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]CAN(Controller Area Network)总线是一种多主串行通信协议,广泛应用于工业控制和汽车电子领域。GD32系列MCU内置的CAN控制器遵循CAN 2.0A/2.0B协议规范,其核心工作机制围绕三大模块展开:发送邮箱、接收FIFO和标识符过滤器。

一、程序原理说明

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通信系统的工程基石。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
换一批
延伸阅读

在汽车电子、工业自动化、新能源电力电子等强干扰场景中,CAN总线是目前应用最广泛的现场总线之一,而CAN收发器作为CAN控制器和物理总线之间的接口核心,直接决定了整个总线网络的通信可靠性和鲁棒性。很多工程师在设计CAN通...

关键字: CAN收发器 CAN总线

在嵌入式数据存储方案中,SD卡是最常见的大容量存储介质之一。GD32系列MCU支持SDIO和SPI两种方式驱动SD卡,其中SPI模式以引脚占用少、硬件设计简单著称。但SPI模式的代价是协议需要软件模拟,且读写速度受限。本...

关键字: GD32 SD卡

在车载诊断(OBD)、电池管理系统(BMS)、车身控制模块(BCM)等汽车电子场景中,CAN总线依然是数据交互的事实标准。Linux从2.6内核开始引入SocketCAN子系统,将CAN总线抽象为网络设备,开发者可以用标...

关键字: CAN总线 驱动开发 汽车电子

在GD32平台上引入FreeRTOS后,很多开发者以为任务调度从此高枕无忧。但实际调试时,高优先级任务莫名阻塞、低优先级任务"霸占"CPU、甚至整个系统卡死的现象并不少见。这些问题的根源往往隐藏在信号...

关键字: GD32 FreeRTOS

当STM32的供应波动和成本压力持续发酵,GD32作为国产替代方案正被越来越多的团队提上日程。硬件层面的Pin-to-Pin兼容性让板级替换变得简单——GD32F103与STM32F103在引脚上完全兼容。但“焊上去能跑...

关键字: GD32 STM32

嵌入式开发,一行代码的效率差异可能在百万次循环后被放大为秒级延迟。GD32系列MCU主频最高可达120MHz(F4系列),但再快的CPU也经不起低效代码的挥霍。位操作、内联函数与编译器优化等级,是三把从指令集底层到编译策...

关键字: GD32 代码优化

GD32系列MCU的定时器体系按功能复杂度分为三类:高级定时器(TIMER0/TIMER7)、通用定时器(TIMER1~TIMER4)和基础定时器(TIMER5/TIMER6)。三者共享相同的底层时钟树结构,但外围功能逐...

关键字: GD32 定时器

GD32F303开发板摆在桌上,芯片已焊接、供电已就绪,但屏幕上只有一片空白——没有Device Pack、没有启动文件、连编译都报错。对于刚从STM32生态转入GD32的开发者,环境搭建是第一道坎,也是最容易被忽略的&...

关键字: Keil MDK GD32

在GD32系列MCU运行FreeRTOS、RT-Thread等实时操作系统时,多任务并发访问共享资源(如UART、SPI总线、全局缓冲区)是常态。信号量(Semaphore)与互斥量(Mutex)是两种最常用的同步机制,...

关键字: 信号量 互斥量 GD32

在嵌入式实时系统中,任务之间如何高效通信、如何精确管理时间,是决定系统架构质量的核心问题。裸机编程中,超级循环配合中断的模型在复杂度上升时迅速失控——全局变量满天飞、中断嵌套混乱、时间管理依赖硬件定时器资源。μC/OS-...

关键字: μCOS-III GD32
关闭