GD32F103C8T6 CAN通讯
前言:
我正在做两个GD32之间的CAN通讯,软件写好,硬件连接好后一上电发现怎么都发不了接收不了,于是开始了长达五天多的问题排查,我也是第一次学习CAN通讯,为此分享下CAN问题排查的一些思路和经历。
软件配置:
整体过程:
GD32F103C8T6是一款体积小,性能不错的国产芯片,是STM32F103的很好平替,价格也有优势,官方的标准库也支持keil5,所以很适合进行学习和开发小项目。
CAN通讯主要有两根线CANH CANL ,因为CAN通讯是通过两条线的电压差来进行数据的传输,也叫差分电压,这种比较方式比较稳定,不会因为一些电磁影响而数据出错,速度也很快,几百k到1M。主要配置过程为:配置GPIO,CAN ,配置过滤器, 实现收发函数。
配置GPIO
首先要做的就是查看官方给的数据手册,官网就有GD32F103C8T6 | MCU选择器 | 兆易创新 GigaDevice | 官方网站

我们先要知道这款芯片的CAN口 搭载在哪个总线上,然后他有几个CAN口,分别是复用了哪些GPIO口,还要记得开启引脚重映射。


我们可以看到搭载在APB1总线上的,然后分频后是108M/2=54MHz,然后使用的是CAN0,对应PB8,PB9 因此GPIO配置就是
static void CAN_GPIO_Init(void){
//使能时钟
rcu_periph_clock_enable(RCU_GPIOB); //GPIOB使能
rcu_periph_clock_enable(RCU_CAN0); // CAN0
rcu_periph_clock_enable(RCU_AF); // 复用时钟
//配置GPIO PA8:RX PA9:TX
gpio_init(GPIOB,GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CAN_TX);//复用推挽输出
gpio_init(GPIOB,GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, CAN_RX);//上拉输入
gpio_pin_remap_config(GPIO_CAN_PARTIAL_REMAP,ENABLE); //开启引脚重映射
}
配置CAN参数
这个主要就是通过查看官方给的函数接口,一般都是配置结构体,一般有配置时间段1,2,预分频系数、同步段、以及帧的格式(标准帧还是扩展帧)、数据格式等等。
static void CAN_Struct_Init(void){
can_flag_clear(CAN0, CAN_FLAG_ERRIF); // 清除错误中断
can_flag_clear(CAN0, CAN_FLAG_WERR); // 清除警告
can_flag_clear(CAN0, CAN_FLAG_PERR); // 清除校验错
can_flag_clear(CAN0, CAN_FLAG_BOERR); // 清除总线关闭
can_flag_clear(CAN0, CAN_FLAG_RFF1); // 强制清锁死的FIFO1满标志
can_flag_clear(CAN0,CAN_FLAG_SLPIF); //清除睡眠标志位
can_parameter_struct can_init_struct; //CAN参数结构体初始化
//复位CAN0 初始化配置结构体
can_deinit(CAN0);
can_struct_para_init(CAN_INIT_STRUCT,&can_init_struct);
//核心配置
can_init_struct.working_mode = CAN_NORMAL_MODE; // 正常模式(硬件通讯)
//can_init_struct.working_mode = CAN_LOOPBACK_MODE; // 回环模式(自测用)
can_init_struct.prescaler = 24; // 预分频
can_init_struct.resync_jump_width = CAN_BT_SJW_1TQ; // SJW=1
can_init_struct.time_segment_1 = CAN_BT_BS1_5TQ ; // BS1=8
can_init_struct.time_segment_2 = CAN_BT_BS1_3TQ ; // BS2=3 → 250kbps
// 波特率 = APB1时钟(54MHz) / [ prescaler × ( 1 + BS1 + BS2 ) ]
// 自动功能 重传
can_init_struct.auto_bus_off_recovery = ENABLE;
can_init_struct.auto_retrans = ENABLE;
// 其他功能
can_init_struct.time_triggered = DISABLE;
can_init_struct.auto_wake_up = ENABLE;
can_init_struct.rec_fifo_overwrite = DISABLE;
can_init_struct.trans_fifo_order = DISABLE;
//初始化CAN
can_init(CAN0,&can_init_struct);
can_interrupt_enable(CAN0, CAN_INT_RFNE1);//这里是中断使能
nvic_irq_enable(CAN0_RX1_IRQn,0, 0); //依旧使能中断NVIC 这里使用的是RX1 FIFO1
}
配置过滤器参数
过滤器的作用就是过滤接收的CAN报文,进行筛选,比如我想要哪些ID的报文,我要从哪个接收邮箱里面取FIFO0 还是FIFO1 怎么检查?掩码模式 多少位16/32 等等 也是结构体的形式
static void CAN_Struct_Init(void){
can_filter_parameter_struct can_filter_init_struct;
//初始化过滤器结构体
can_struct_para_init(CAN_FILTER_STRUCT,&can_filter_init_struct);
// 核心:掩码全0 → 所有ID都放行
can_filter_init_struct.filter_list_high = 0x0000;
can_filter_init_struct.filter_list_low = 0x0000 ;//过滤器ID 即目标ID 全0就是接收所有ID的报文
can_filter_init_struct.filter_mask_high = 0x0000;
can_filter_init_struct.filter_mask_low = 0x0000;// 掩码 该位为1必须一样 该位为0 则不管
// 固定配置
can_filter_init_struct.filter_fifo_number = CAN_FIFO1; //从FIFO1邮箱取
can_filter_init_struct.filter_number = 0;
can_filter_init_struct.filter_mode = CAN_FILTERMODE_MASK; // 掩码模式
can_filter_init_struct.filter_bits = CAN_FILTERBITS_32BIT; //32位
can_filter_init_struct.filter_enable = ENABLE;
can_filter_init(&can_filter_init_struct);
}
收发函数
发送函数
msg是需要自己定义的消息结构体,简单定义下就行
typedef struct {
uint32_t id; // CAN ID(标准ID:0~0x7FF)
uint8_t data_len; // 数据长度(0~8)
uint8_t data[8]; // 数据内容
} can_msg_t;
uint8_t can_send_msg(can_msg_t* msg){
//获取CAN错误状态
can_error_enum err = can_error_get(CAN0);
if(err != CAN_ERROR_NONE){
printf("CAN ERROR!\r\n");
printf("%d\r\n",err);
return 1;
} //打印什么参数 就从官方给的错误里面找
uint8_t mail;
can_trasnmit_message_struct can_msg_struct;
//初始化默认结构体
can_struct_para_init(CAN_TX_MESSAGE_STRUCT,&can_msg_struct);
//核心配置
can_msg_struct.tx_dlen = msg->data_len; //数据长度
can_msg_struct.tx_sfid = msg->id ; // ID
can_msg_struct.tx_ff =CAN_FF_STANDARD; // 强制标准帧
can_msg_struct.tx_ft =CAN_FT_DATA; // 强制数据帧
for(uint8_t i=0;i<msg->data_len;i++){
can_msg_struct.tx_data[i] = msg->data[i];
}
mail = can_message_transmit(CAN0,&can_msg_struct); //主要是这个函数 返回邮箱的值
if (mail != CAN_MAILBOX0 && mail != CAN_MAILBOX1 && mail != CAN_MAILBOX2) {
printf("Transmit request failed\n");
}
else {
/* 等待发送完成并检查硬件寄存器 */
//while (can_flag_get(CAN0, CAN_FLAG_MTF0 + mail) == RESET);
/* 清除标志 */
can_flag_clear(CAN0, (can_flag_enum)(CAN_FLAG_MTF1));
}
printf("SEND FINISH!!!\r\n");
return 0;
}
接收函数
还是大差不差 初始化消息的结构体,然后接收函数 ,记得接收完一定要手动释放FIFO1邮箱,以及清除好标志位
uint8_t can_msg_receive(can_msg_t* msg){
//获取CAN错误状态
can_error_enum err = can_error_get(CAN0);
if(err != CAN_ERROR_NONE){
printf("CAN ERROR!\r\n");
return 1;
}
can_receive_message_struct can_rx_struct;
//初始化
can_struct_para_init(CAN_RX_MESSAGE_STRUCT,&can_rx_struct);
//接收数据
can_message_receive(CAN0,CAN_FIFO1,&can_rx_struct);
//释放
can_fifo_release(CAN0,CAN_FIFO1);
can_flag_clear(CAN0, CAN_FLAG_RFF1);
//将数据给定义的结构体
msg->data_len = can_rx_struct.rx_dlen ;
msg->id = can_rx_struct.rx_sfid ;
for(uint8_t i=0;i<can_rx_struct.rx_dlen;i++){
msg->data[i] = can_rx_struct.rx_data[i] ;
}
return 0;
}
下面是使用了中断接收的方式,通过重写中断服务函数来实现。
void CAN0_RX1_IRQHandler(void){
can_receive_message_struct can_rx_struct1;
can_struct_para_init(CAN_RX_MESSAGE_STRUCT,&can_rx_struct1);
can_message_receive(CAN0,CAN_FIFO1,&can_rx_struct1);
//printf("receive sfid:%d ff:%d\r\n",can_rx_struct1.rx_sfid ,can_rx_struct1.rx_ff);
can_fifo_release(CAN0, CAN_FIFO1);
if(can_rx_struct1.rx_sfid == 0x321 && can_rx_struct1.rx_ff == CAN_FF_STANDARD)
{
//验证收到了报文
}
can_fifo_release(CAN0,CAN_FIFO1);
can_flag_clear(CAN0, CAN_FLAG_RFF1);
}
硬件部分
GD32F103内置了CAN控制器,但是CAN通讯还需要能够将电平转换为差分信号的电路器件,也就是CAN收发器。

这里引用其他人的图,可以清晰的看到CAN多主节点通讯的硬件连接结构,在CANH CANL总线的两端分别都需要一个120欧姆的电阻来提供足够的阻抗来防止反射,使得末端能够完全吸收信号,以及提供稳定的差分电压,这个值不是算出来的,是测出来的。我是从网上买的TJA1050 CAN收发器模块,这个芯片耐压好,性能高。
其中这个TJA1050芯片也是很有讲究,我详读了他的数据手册,它的工作电压一般在4.75-5.出头这样子,它比较敏感,因此最好用独立的外接电源或者芯片的5V引脚来提供给供电,要用万用表测的精确的5V,要不然过大的电压有可能击穿烧毁芯片(当然 这芯片耐压)

这是官方的手册介绍图,可以看到有八个引脚,TXD RXD就是接GD32的TX RX,注意和UART的串口不一样 是TX - TX RX - RX GND和GD32共地 VCC接5V 然后就是 Vref 这个是参考输出电压,CANH - CANH CANL - CANL ,最后是S引脚, 用来选择高速输出还是静默模式,一般来说S引脚都是接GND 为0V ,如果通讯异常,它可能就是一个错误点。
CANH CANL,在空闲的时候为隐形状态,都为2.5V左右,没有差分电压。在发送接收数据的时候为闲性状态, CANH 3.5V CANL 1.5V 一般有2V左右的差分电压。
这个是手册里面高速CAN模式下的状态电平参考。可以参考来通过逻辑分析仪来分析波形,看看是不是正确的关系。


USB转CAN模块 网上也有很多,我选择了一个支持PCAN驱动的 然后从PCAN官网下载驱动,里面会带有PCAN VIEW工具,也就是上位机软件。
排查错误:
回环测试
回环测试是隔离外部硬件,使数据只通过GD32内部的CAN控制器进行收发,主要是查看主控芯片以及CAN控制是否有损坏。用法很简单就是在模式下选择回环即可
//can_init_struct.working_mode = CAN_NORMAL_MODE; // 正常模式(硬件通讯)
can_init_struct.working_mode = CAN_LOOPBACK_MODE; // 回环模式(自测用)
寄存器
我觉得能够高效查看CAN是在哪个环节出问题的重要方式就是直接查看CAN相关的寄存器的值,当然, 官方也已经提供了很多封装好的接口。我这里更加推荐使用keil的debug模式,能够直接查看所有寄存器相关的值,然后就是需要去查看官方的数据手册,看看具体每个寄存器的哪个值代表着什么。

比如TSTAT寄存器里面的MTFNERR0 MTF0


还可以查看FIFO邮箱里面到底有没有进入数据,数据ID到底对不对?

抓波形
通过逻辑分析仪或者示波器,可以查看具体引脚是否有正确的波形,学生钱包有限,网上20块的24M速率的就够够的了,一般来说范围应该是需要探测波形的十倍差不多。
但是好像能看的也很少。。。 而且有些东西还会误判说是错误

最开始搞了两三天,一直发不了收不了。。。我去网上查看别人的,看官方的例程,发现自己没有引脚重映射,开启后还是不行。。就买逻辑分析仪,也查不出什么波形。。。 就是TX RX一直没波形,CANH CANL也没有 我起初以为难道是我买的CAN收发器坏的?我回环测试没问题啊,最后在确认工作电压的时候,我用万用表测S引脚,发现怎么是4.8V?欸我去,不应该是0V吗?我一看发现,GND被我接到了5V.。。。 正确接好线后,就成功发送了报文0X123 接收0X321报文


所以,我觉得还是要细心。。。 当然,因为这个虽然拖了有一个星期,但是倒逼我去了解了很多CAN协议相关的知识与其他人的经验,也算是学习了。。 为此分享出来
更多推荐


所有评论(0)