GD32F105双CAN实战:从零配置250K波特率到收发数据(附完整代码)
当你第一次拿到GD32F105开发板时,看到双CAN接口可能会既兴奋又困惑。作为工业控制、汽车电子等领域广泛使用的通讯协议,CAN总线以其高可靠性和实时性著称。本文将带你从零开始,一步步完成双CAN接口的配置、波特率计算、数据收发全流程,并附上可直接运行的完整代码。
1. 硬件准备与基础概念
在开始编码前,我们需要了解几个关键点。GD32F105系列微控制器内置了两个独立的CAN控制器,共享相同的API接口但使用不同的外设资源。CAN总线通信需要终端电阻匹配,通常在总线的两端各接一个120Ω电阻。
CAN通信三要素:
- 波特率:决定通信速度,常见的有125Kbps、250Kbps、500Kbps等
- 标识符:用于区分不同消息的优先级
- 数据帧:包含实际传输的数据内容
开发板连接示意图:
CAN0_TX --| |-- CAN_H CAN0_RX --| GD32 |-- CAN_L CAN1_TX --| F105 |-- (第二路CAN) CAN1_RX --|________|2. 时钟配置与波特率计算
GD32F105的CAN控制器时钟来自APB1总线,默认频率为16MHz。要得到250Kbps的波特率,我们需要正确设置预分频器(prescaler)和时间段参数。
波特率计算公式:
波特率 = APB1时钟 / (Prescaler × (TimeSegment1 + TimeSegment2 + 1))对于250Kbps的目标,我们选择:
- Prescaler = 8
- TimeSegment1 = 6Tq
- TimeSegment2 = 1Tq
- SJW = 1Tq
计算验证:
16,000,000 / (8 × (6 + 1 + 1)) = 250,000 (即250Kbps)对应的初始化代码片段:
can_parameter_struct can_parameter; can_struct_para_init(CAN_INIT_STRUCT, &can_parameter); can_parameter.time_segment_1 = CAN_BT_BS1_6TQ; // 时间段1为6Tq can_parameter.time_segment_2 = CAN_BT_BS2_1TQ; // 时间段2为1Tq can_parameter.prescaler = 8; // 预分频系数 can_parameter.resync_jump_width = CAN_BT_SJW_1TQ; // 同步跳转宽度3. 双CAN接口完整初始化
下面给出完整的双CAN初始化函数,包含GPIO配置、CAN参数设置和过滤器初始化:
void CAN_Init_Config(void) { /* 使能时钟 */ rcu_periph_clock_enable(RCU_CAN0); rcu_periph_clock_enable(RCU_CAN1); /* GPIO配置 */ gpio_init(CAN0_RX_PORT, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, CAN0_RX_PIN); gpio_init(CAN0_TX_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CAN0_TX_PIN); gpio_init(CAN1_RX_PORT, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, CAN1_RX_PIN); gpio_init(CAN1_TX_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CAN1_TX_PIN); /* 复位CAN控制器 */ can_deinit(CAN0); can_deinit(CAN1); /* CAN参数配置 */ can_parameter_struct can_param; can_struct_para_init(CAN_INIT_STRUCT, &can_param); can_param.working_mode = CAN_NORMAL_MODE; can_param.resync_jump_width = CAN_BT_SJW_1TQ; can_param.time_segment_1 = CAN_BT_BS1_6TQ; can_param.time_segment_2 = CAN_BT_BS2_1TQ; can_param.prescaler = 8; /* 初始化双CAN */ can_init(CAN0, &can_param); can_init(CAN1, &can_param); /* 过滤器配置 */ can_filter_parameter_struct can_filter; can_struct_para_init(CAN_FILTER_STRUCT, &can_filter); can_filter.filter_number = 0; can_filter.filter_mode = CAN_FILTERMODE_MASK; can_filter.filter_bits = CAN_FILTERBITS_32BIT; can_filter.filter_list_high = 0x0000; can_filter.filter_list_low = 0x0000; can_filter.filter_mask_high = 0x0000; can_filter.filter_mask_low = 0x0000; can_filter.filter_fifo_number = CAN_FIFO0; can_filter.filter_enable = ENABLE; can_filter_init(&can_filter); /* 使能中断 */ can_interrupt_enable(CAN0, CAN_INT_RFNE0); can_interrupt_enable(CAN1, CAN_INT_RFNE0); nvic_irq_enable(CAN0_RX0_IRQn, 0, 0); nvic_irq_enable(CAN1_RX0_IRQn, 0, 0); }4. CAN数据收发实战
4.1 发送数据帧
发送CAN消息需要填充传输数据结构体,以下是一个标准的发送函数实现:
void CAN_Send_Message(uint32_t can_periph, uint32_t id, uint8_t *data, uint8_t len) { can_trasnmit_message_struct tx_msg; tx_msg.tx_ff = CAN_FF_STANDARD; // 标准帧 tx_msg.tx_ft = CAN_FT_DATA; // 数据帧 tx_msg.tx_sfid = id; // 标准ID tx_msg.tx_dlen = len; // 数据长度 for(uint8_t i=0; i<len; i++) { tx_msg.tx_data[i] = data[i]; // 填充数据 } can_message_transmit(can_periph, &tx_msg); }使用示例:
uint8_t test_data[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; CAN_Send_Message(CAN0, 0x123, test_data, 8);4.2 接收数据与中断处理
CAN接收通常采用中断方式,下面是完整的中断服务例程实现:
/* CAN0接收中断 */ void CAN0_RX0_IRQHandler(void) { can_receive_message_struct rx_msg; if(can_interrupt_flag_get(CAN0, CAN_INT_FLAG_RFF0)) { can_message_receive(CAN0, CAN_FIFO0, &rx_msg); // 处理接收到的数据 if(rx_msg.rx_ff == CAN_FF_STANDARD) { printf("收到标准帧, ID:0x%03X\n", rx_msg.rx_sfid); } else { printf("收到扩展帧, ID:0x%08lX\n", rx_msg.rx_efid); } // 清除中断标志 can_interrupt_flag_clear(CAN0, CAN_INT_FLAG_RFF0); } } /* CAN1接收中断 */ void CAN1_RX0_IRQHandler(void) { // 类似CAN0的处理逻辑 }5. 常见问题排查
在实际开发中,你可能会遇到以下问题:
问题1:CAN通信无反应
- 检查终端电阻是否正确连接(120Ω)
- 确认波特率设置与对端设备一致
- 使用逻辑分析仪抓取CAN总线波形
问题2:只能发送不能接收
- 检查过滤器配置是否过于严格
- 确认中断优先级和使能状态
- 验证GPIO模式设置(RX应为上拉输入)
问题3:通信不稳定
- 检查总线长度和布线质量
- 适当调整时间段参数
- 考虑增加CAN收发器的驱动能力
6. 进阶技巧与性能优化
当系统需要处理大量CAN消息时,可以考虑以下优化策略:
双缓冲接收机制
typedef struct { can_receive_message_struct buffer[2]; uint8_t active_buf; } CAN_Double_Buffer; CAN_Double_Buffer can0_rx_buf; void CAN0_RX0_IRQHandler(void) { uint8_t process_buf = 1 - can0_rx_buf.active_buf; can_message_receive(CAN0, CAN_FIFO0, &can0_rx_buf.buffer[process_buf]); can0_rx_buf.active_buf = process_buf; // 处理非活跃缓冲区的数据 }波特率自动检测
uint32_t CAN_Auto_Baudrate_Detect(uint32_t can_periph) { uint32_t test_rates[] = {1000000, 800000, 500000, 250000, 125000}; for(uint8_t i=0; i<sizeof(test_rates)/sizeof(test_rates[0]); i++) { if(CAN_Test_Baudrate(can_periph, test_rates[i])) { return test_rates[i]; } } return 0; }7. 完整项目示例
以下是一个简单的双CAN通信完整示例,实现两个CAN接口之间的数据回环:
#include "gd32f10x.h" #include <stdio.h> #define CAN0_RX_PORT GPIOA #define CAN0_RX_PIN GPIO_PIN_11 #define CAN0_TX_PORT GPIOA #define CAN0_TX_PIN GPIO_PIN_12 #define CAN1_RX_PORT GPIOB #define CAN1_RX_PIN GPIO_PIN_12 #define CAN1_TX_PORT GPIOB #define CAN1_TX_PIN GPIO_PIN_13 void CAN_Init_Config(void) { // 初始化代码如前文所示 } void CAN_Send_Message(uint32_t can_periph, uint32_t id, uint8_t *data, uint8_t len) { // 发送函数如前文所示 } void CAN0_RX0_IRQHandler(void) { can_receive_message_struct rx_msg; if(can_interrupt_flag_get(CAN0, CAN_INT_FLAG_RFF0)) { can_message_receive(CAN0, CAN_FIFO0, &rx_msg); // 将接收到的数据通过CAN1发送回去 CAN_Send_Message(CAN1, rx_msg.rx_sfid, rx_msg.rx_data, rx_msg.rx_dlen); can_interrupt_flag_clear(CAN0, CAN_INT_FLAG_RFF0); } } int main(void) { // 系统时钟初始化 rcu_clock_freq_set(RCU_CKSYSSRC_PLL, RCU_PLL_MUL9); SystemCoreClockUpdate(); // CAN初始化 CAN_Init_Config(); // 测试数据 uint8_t test_data[8] = {1,2,3,4,5,6,7,8}; while(1) { // 每隔1秒通过CAN0发送数据 CAN_Send_Message(CAN0, 0x123, test_data, 8); delay_ms(1000); // 测试数据递增 for(int i=0; i<8; i++) { test_data[i]++; } } }