1. 项目概述
最近在调试一个基于GD32F303红枫派开发板的工业数据采集节点,核心任务之一是实现与上位机控制器之间的CAN总线通信。对于很多从STM32或其他平台转过来的嵌入式开发者来说,虽然CAN协议本身是标准化的,但具体到GD32这款国产MCU的CAN外设配置、过滤器设置以及调试技巧,还是有不少细节需要摸索。这次实验,我们就以红枫派开发板为平台,手把手实现一个CAN回环通信的完整例程,不仅把代码跑通,更要把GD32F303的CAN控制器工作原理、配置要点和调试中容易踩的“坑”讲清楚。
CAN总线以其高可靠性、多主架构和强大的错误处理机制,在汽车电子和工业控制领域几乎是“标配”。但在单片机层面初次接触时,面对仲裁、位时序、过滤器这些概念,很容易感到一头雾水。本实验将通过“自发自收”的回环模式,让你在不依赖外部CAN节点的情况下,快速验证GD32F303的CAN外设是否工作正常,并深入理解数据收发、ID过滤、中断处理的整个流程。无论你是正在评估GD32芯片,还是需要在项目中集成CAN功能,这篇内容都能提供从理论到实践的完整参考。
2. CAN核心原理与GD32F303实现机制拆解
2.1 CAN总线基础与GD32外设架构
CAN(Controller Area Network)本质上是一种串行的、广播式的差分信号通信协议。它的两根信号线CAN_H和CAN_L,通过两者之间的电压差来传递逻辑“0”(显性电平,约2.5V差)和逻辑“1”(隐性电平,约0V差)。这种差分传输方式赋予了CAN极强的抗共模干扰能力,也是其能在恶劣电气环境中稳定运行的基础。
GD32F303系列芯片内部集成了CAN 2.0B协议控制器,支持标准帧(11位ID)和扩展帧(29位ID)。其外设架构可以清晰地分为几个核心部分:协议引擎负责处理CAN帧的打包、解包、CRC校验、应答和错误管理;发送处理单元包含3个独立的发送邮箱,用于缓存待发送的报文;接收处理单元则包含2个独立的接收FIFO(每个深度为3个邮箱),用于缓存接收到的报文;而过滤器组则是GD32 CAN模块的“守门人”,它决定了哪些ID的报文可以被放入接收FIFO,是高效管理多节点通信的关键。
与简单的UART不同,CAN通信是事件驱动的。发送方将组装好的报文(含ID、数据长度、数据场等)放入一个空闲的发送邮箱,硬件便会自动在总线空闲时启动发送流程,期间包括仲裁、CRC场生成、ACK场等待等全部由硬件完成。接收方则通过过滤器对总线上的所有报文进行筛选,匹配成功的报文会被硬件自动存入指定的接收FIFO,并可选地产生接收中断通知CPU。这种硬件高度自动化的设计,极大地减轻了CPU的负担。
2.2 位时序与波特率配置:通信稳定的基石
CAN通信的稳定性,很大程度上取决于位时序配置的准确性。所有CAN节点必须使用相同的波特率,但波特率本身是由更底层的“位时间”划分决定的。在GD32中,一个位时间被划分为三个段:
- 同步段(SYNC_SEG):固定为1个时间单元(Tq),用于同步总线上的边沿。
- 位段1(BS1):包含传播时间段和相位缓冲段1,用于补偿信号在总线上的物理传输延迟。可配置为1到16个Tq。
- 位段2(BS2):即相位缓冲段2,用于在采样点后提供缓冲。可配置为1到8个Tq。
波特率计算公式为:波特率 = APB1时钟频率 / (分频系数 * (1 + BS1 + BS2))。
以本实验常见的1Mbps配置为例,假设APB1时钟为60MHz。我们选择分频系数为6,BS1设置为5个Tq(寄存器值填4),BS2设置为4个Tq(寄存器值填3)。那么位时间Tq数 = 1(SYNC_SEG) + 5(BS1) + 4(BS2) = 10 Tq。每个Tq的时长 = 分频系数 / APB1时钟 = 6 / 60MHz = 100ns。因此,一个位的时间 = 10 * 100ns = 1us,对应的波特率就是1 / 1us = 1Mbps。
关键细节:GD32库函数中
can_parameter.time_segment_1和time_segment_2的参数,如CAN_BT_BS1_5TQ,其数值(5)代表的是实际的Tq个数,而非需要填入寄存器的值(4)。这一点在直接操作寄存器时需要特别注意,否则会导致波特率计算错误。
采样点的选择也至关重要。标准CAN协议规定采样点位于BS1结束、BS2开始的位置。GD32F303为了增强抗干扰能力,实际上在标准采样点前增加了两个提前采样点,形成“三取二”的容错机制。通常,为了兼顾稳定性和数据吞吐量,建议将采样点设置在位时间的75%-80%处。对于上述1Mbps配置(BS1=5, BS2=4),采样点就在第6个Tq末尾((1+5)/10 = 60%),这是一个比较保守且稳定的设置。在高速或长距离通信时,可能需要适当增加BS1,将采样点后移。
2.3 过滤器配置详解:数据接收的智能筛选器
GD32F303提供了14个(互联型为28个)独立的过滤器组,这是管理复杂CAN网络的核心。每个过滤器组可以独立配置为掩码模式或列表模式,并可以选择32位或16位位宽。
掩码模式更像是一个“模糊匹配”规则。它包含一个“标识符(ID)”值和一个“掩码(Mask)”值。掩码位为1表示对应的ID位必须严格匹配,为0则表示该位不关心(可以是0或1)。例如,设置ID=0x18FF0000,Mask=0x1FFF0000。这意味着我们只关心ID的高13位(29位扩展帧的前13位),要求它们必须是0x18FF0000的高13位(即0x18D),而低16位可以是任意值。这种模式常用于接收一组具有共同特征(如功能码相同)的报文。
列表模式则是“精确匹配”模式。在32位模式下,一个过滤器组可以存放2个完整的扩展帧ID(或4个标准帧ID,因为标准帧ID只占高11位)。只有总线上报文的ID与列表中某个ID完全一致时,才能通过过滤。这种模式用于接收少数几个特定的、已知的报文ID。
实操心得:在项目初期,如果为了快速调试,可以将过滤器配置为“全通”模式。对于32位掩码模式,设置ID=0,Mask=0,即所有位都不关心,这样就能接收到总线上的所有报文,方便用逻辑分析仪或CAN分析仪观察通信情况。但在最终产品中,必须根据通信矩阵精确配置过滤器,以减轻CPU处理无关报文的中断负担。
过滤器还需要关联到一个接收FIFO(FIFO0或FIFO1)。报文通过过滤后,会被存入对应的FIFO。你可以为不同优先级或不同类型的报文分配不同的FIFO,并在中断服务程序中分别处理,实现简单的数据分类。
3. 红枫派开发板CAN回环实验全流程解析
3.1 硬件连接与工程环境搭建
红枫派开发板通常将GD32F303的CAN接口引至特定的引脚。以本实验为例,CAN0的TX和RX分别对应GPIOB的Pin9和Pin8,并且使用了部分重映射功能(GPIO_CAN_PARTIAL_REMAP)。在硬件上,你需要确保:
- 开发板的CAN接口(可能是一个CAN收发器芯片如TJA1050)已正确供电。
- 对于回环测试,CAN_H和CAN_L之间通常不需要连接120欧姆的终端电阻,因为信号在芯片内部环回。但如果你的板载收发器电路已经包含了终端电阻,也不影响。
- 使用USB转串口线连接开发板的调试串口到电脑,用于打印调试信息。
在软件层面,你需要准备好GD32的固件库(GigaDevice.GD32F30x_DFP.3.2.0.pack或类似)和开发环境(Keil MDK、IAR或VS Code+GCC)。创建一个新的工程,包含必要的启动文件、GD32标准外设库(特别是gd32f30x_can.c,gd32f30x_gpio.c,gd32f30x_rcu.c等),以及我们即将编写的应用层代码。
3.2 代码逐层剖析与配置要点
实验的代码结构分为底层驱动driver_can.c/.h、板级支持包bsp_can.c/.h和应用层main.c。我们逐层深入。
首先是底层驱动配置(driver_can_config函数):这个函数完成了CAN外设的初始化骨架。流程如下:
- 时钟使能:使能CAN控制器所在的总线时钟(APB1)和所用GPIO端口的时钟。如果引脚有重映射,还需使能AFIO时钟并配置重映射。
- GPIO初始化:将TX引脚(PB9)配置为复用推挽输出(GPIO_MODE_AF_PP),RX引脚(PB8)配置为上拉输入(GPIO_MODE_IPU)。推挽输出能提供较强的驱动能力,上拉输入则能保证总线空闲时为稳定的隐性电平。
- CAN工作参数初始化:
working_mode = CAN_LOOPBACK_MODE:这是关键,设置为回环模式,发送的数据会被内部接收,无需外部节点。time_segment_1和time_segment_2:如前所述,设置为5和4个Tq。prescaler:分频系数,根据目标波特率和APB1时钟计算得出。代码中通过宏CAN_BAUDRATE来选择,对应1Mbps时值为6。auto_wake_up,auto_bus_off_recovery等:根据应用需求设置。在调试阶段,no_auto_retrans(自动重发)建议先禁用(DISABLE),以便在发送出错时能立刻发现,而不是无限重试。
- 过滤器初始化:本实验配置了两个过滤器组(0和1)。均采用32位掩码模式。
- 过滤器0:ID = 0x3000 << 1, Mask = 0x3000 << 1。这里
<<1是因为标准帧ID在寄存器中默认左移对齐。这个配置意味着只接收标准帧ID恰好为0x300的报文。 - 过滤器1:ID = 0x5000 << 1, Mask = 0x5000 << 1。只接收标准帧ID恰好为0x500的报文。
- 它们分别关联到FIFO0和FIFO1。
- 过滤器0:ID = 0x3000 << 1, Mask = 0x3000 << 1。这里
- 中断配置:如果使能了接收中断(
can_rx_use_interrupt = SET),则使能CAN的接收FIFO非空中断(CAN_INT_RFNE0和CAN_INT_RFNE1)。
其次是数据发送函数(driver_can_transmit):这个函数是对库函数can_message_transmit的简单封装。发送前,你需要填充一个can_trasnmit_message_struct结构体:
typedef struct { uint32_t tx_sfid; // 标准帧ID (11位) uint32_t tx_efid; // 扩展帧ID (18位),标准帧时忽略 uint8_t tx_ff; // 帧格式:CAN_FF_STANDARD 或 CAN_FF_EXTENDED uint8_t tx_ft; // 帧类型:CAN_FT_DATA 或 CAN_FT_REMOTE uint8_t tx_dlen; // 数据长度 (0-8) uint8_t tx_data[8]; // 数据场 } can_trasnmit_message_struct;填充好后,调用发送函数即可。硬件会自动寻找空闲的发送邮箱,装入数据并启动发送流程。
最后是中断接收处理(can0_rx0_interrupt_handler等):当FIFO接收到新报文并产生中断后,在中断服务程序(ISR)中,首要任务是调用can_message_receive函数,将数据从硬件FIFO搬运到用户定义的消息结构体变量中。这是一个关键步骤,必须执行,否则FIFO会一直被认为非空,无法接收新报文。搬运完成后,我们可以根据消息结构体中的rx_sfid(接收到的标准帧ID)、rx_ff(帧格式)、rx_dlen(数据长度)等字段进行判断,并设置相应的软件标志位。主循环通过轮询这些标志位来处理接收到的数据。本实验中,中断服务程序只接收ID为0x300或0x500且数据长度为2的报文,并设置对应的can0_receive_fifo0_flag或can0_receive_fifo1_flag。
3.3 main函数逻辑与实验现象分析
在main函数中,程序流程非常清晰:
- 初始化系统时钟、延时函数、调试串口和CAN外设。
- 使能CAN接收中断的NVIC。
- 进入主循环,每隔1秒依次发送三帧报文:
- 帧1: ID=0x300, 数据={0x55, 0xAA}
- 帧2: ID=0x500, 数据={0x01, 0x02}
- 帧3: ID=0x400, 数据={0x02, 0x01}
- 在发送间隙,轮询检查两个接收标志位。如果标志位被置起,则通过串口打印接收到的ID和数据,并清除标志位。
预期的串口打印结果应该是:
can0 transmit data:55, AA can0_fifo0 receive ID = 300 data:55,AA can0 transmit data:1, 2 can0_fifo1 receive ID = 500 data:1,2 can0 transmit data:2, 1 (没有关于ID 0x400的接收打印)这个结果完美验证了:
- CAN控制器在回环模式下工作正常,能够自发自收。
- 过滤器0(关联FIFO0)正确过滤并只接收了ID为0x300的报文。
- 过滤器1(关联FIFO1)正确过滤并只接收了ID为0x500的报文。
- ID为0x400的报文由于不匹配任何过滤器,被硬件静默丢弃,没有进入接收FIFO,也没有触发中断。
4. 从回环到实战:进阶配置与深度调试指南
4.1 切换至正常模式与双机通信
回环模式通过了,下一步就是真正的双节点或多节点通信。你需要将working_mode从CAN_LOOPBACK_MODE改为CAN_NORMAL_MODE。硬件上,需要将两个或多个CAN节点的CAN_H与CAN_H相连,CAN_L与CAN_L相连,并在总线两端(最远的两个节点处)各并联一个120欧姆的终端电阻,以消除信号反射。
在软件上,通信双方需要配置完全相同的波特率和位时序参数。过滤器则根据通信协议进行配置。例如,节点A负责发送控制命令(ID 0x100),节点B负责发送状态数据(ID 0x200)。那么:
- 节点A的发送ID配置为0x100,接收过滤器可以设置为接收ID 0x200的报文。
- 节点B的发送ID配置为0x200,接收过滤器设置为接收ID 0x100的报文。
注意事项:在正常模式下首次上电,建议先用CAN分析仪(如PCAN, USB-CAN适配器)监听总线。确保单个节点先能正确发送报文到总线,再测试接收。避免多个节点同时上电且配置错误导致总线持续错误,进入“Bus Off”状态。
4.2 错误处理与状态监控
CAN的强大之处在于其硬件错误管理。GD32F303的CAN控制器提供了丰富的错误状态标志,在CAN_STAT寄存器中:
ERR:错误标志,任何错误发生都会置位。BOFF:总线关闭标志。当发送错误计数器(TEC)超过255时,节点会进入“Bus Off”状态,与总线隔离。需要软件干预(或配置auto_bus_off_recovery)才能恢复。EPERR:被动错误标志。当接收或发送错误计数器超过127时进入错误被动状态,此时节点能正常通信,但发生错误时发送的错误帧延迟更大。
在正式应用中,建议在主循环或定时中断中定期检查can_error_get()函数返回的错误类型,以及can_receive_error_count_get()和can_transmit_error_count_get()获取的错误计数值。一旦发现错误计数器持续增长,就需要排查物理层问题(如终端电阻缺失、线缆接触不良、地线噪声等)或波特率不匹配问题。
4.3 性能优化与高级功能探索
发送优先级与调度:当多个发送邮箱待发送时,可以通过
can_parameter.trans_fifo_order选择调度策略。设置为DISABLE(默认)时,按照报文ID优先级发送(ID值越小,优先级越高)。设置为ENABLE时,则按照邮箱写入顺序(FIFO)发送。在实时控制系统中,通常采用ID优先级调度,确保关键指令优先发送。接收FIFO溢出处理:通过
can_parameter.rec_fifo_overwrite可以配置FIFO溢出时的行为。DISABLE时,新报文在FIFO满时会被丢弃。ENABLE时,新报文会覆盖最旧的报文。在数据流较大的应用中,建议使能覆盖模式,并提高中断处理频率,确保不错过最新数据。时间触发通信(TTCAN):对于需要高精度时间同步的应用(如分布式运动控制),可以研究GD32对TTCAN的支持。通过使能
time_triggered模式,并结合定时器,可以实现基于时间窗的确定性通信。使用DMA搬运数据:对于高速、大数据量的CAN通信,频繁的中断可能成为瓶颈。GD32F303的CAN模块支持与DMA控制器联动,可以将接收FIFO的数据直接搬运到指定的内存区域,或者从内存区域直接发送,极大减轻CPU负担。配置涉及CAN的DMA发送/接收请求使能,以及DMA通道的配置。
5. 常见问题排查与实战调试技巧
5.1 通信失败问题快速定位表
| 现象 | 可能原因 | 排查步骤与解决方法 |
|---|---|---|
| 完全无收发,无中断 | 1. CAN外设时钟未使能。 2. GPIO引脚模式配置错误(非AF模式)。 3. 工作模式配置错误(如想用正常模式却配成了静默模式)。 4. 过滤器配置过于严格,所有报文被过滤掉。 | 1. 检查RCU_APB1EN寄存器中CAN时钟使能位,或库函数rcu_periph_clock_enable(RCU_CANx)是否调用。2. 用万用表或示波器检查TX引脚在发送时是否有电平变化。无变化则检查GPIO初始化代码,TX必须为 GPIO_MODE_AF_PP。3. 确认 can_parameter.working_mode设置为CAN_NORMAL_MODE(正常模式)或CAN_LOOPBACK_MODE(回环测试)。4. 调试阶段,可将过滤器临时改为全通模式(掩码全0),看是否能收到数据。 |
| 能发送,不能接收 | 1. 接收中断未使能或NVIC未配置。 2. 接收FIFO溢出,新数据被丢弃。 3. 过滤器ID/掩码配置错误,目标报文被过滤。 4. 中断服务程序中未调用 can_message_receive释放邮箱。 | 1. 检查can_interrupt_enable和nvic_irq_enable是否调用,中断函数名是否与启动文件向量表一致。2. 检查 CAN_RFIFOx寄存器的FULL位或通过can_receive_message_length_get()获取FIFO中报文数。考虑使能溢出覆盖或提高处理速度。3. 使用CAN分析仪监听总线,确认发送的报文ID、格式(标准/扩展)与过滤器设置完全匹配。注意ID在寄存器中的对齐方式(通常左移)。 4.这是最常见原因!确保在接收中断服务程序中,第一时间调用 can_message_receive,并将数据读到一个用户变量中。 |
| 通信不稳定,偶发错误 | 1. 波特率或位时序不匹配。 2. 总线物理层问题(终端电阻、线缆、共地)。 3. 电磁干扰严重。 4. 采样点设置不合理。 | 1.确保所有节点波特率、BS1、BS2、分频系数完全一致。用示波器测量一个位的实际时长,反推计算是否符合预期。 2. 测量总线两端CAN_H与CAN_L之间的电阻,应为60欧姆左右(两个120欧姆并联)。检查所有节点电源共地。 3. 使用双绞线,远离强干扰源。可在总线两端增加共模电感。 4. 尝试调整BS1和BS2,将采样点设置在位的75%-80%位置,通常更稳定。 |
| 进入Bus Off状态 | 1. 总线持续出现错误(如显性位毛刺、ACK错误)。 2. 波特率严重不匹配。 3. 硬件故障(收发器损坏)。 | 1. 检查CAN_STAT寄存器的BOFF位。使能auto_bus_off_recovery功能,或软件在检测到BOFF后,执行can_works_mode_set(CAN_MODE_INITIALIZE)再重新进入正常工作模式。2. 这是导致Bus Off的常见原因。用已知良好的节点或分析仪校准波特率。 3. 更换CAN收发器芯片测试。 |
5.2 调试工具与技巧
- 串口打印:最基础的调试手段。在关键位置(如初始化完成、发送函数调用后、中断入口)打印信息,可以帮助梳理程序流程。
- 逻辑分析仪:连接MCU的CAN_TX引脚,可以直观看到发送的原始位波形,测量位时间,验证波特率设置是否正确。这是排查硬件层问题的利器。
- 专业CAN分析仪:如PEAK-System的PCAN-USB,ZLG的USBCAN等。它们可以连接到真实CAN总线,以高层协议视角捕获、解析、发送CAN报文,并能显示错误帧,是开发复杂CAN网络不可或缺的工具。
- GD32的CAN调试技巧:
- 回环静默模式:在怀疑是自身节点发送导致总线问题时,可切换到
CAN_LOOPBACK_SILENT_MODE。此模式下,节点内部回环,但对外不干扰总线,可以安全地自检发送功能。 - 监听模式:通过
can_parameter.working_mode设置为CAN_SILENT_MODE,节点可以监听总线所有流量而不发送任何报文(包括ACK位),用于网络分析而不干扰现有通信。 - 寄存器查看:在调试器(Keil/IAR)中实时查看
CAN_STAT、CAN_ERR、CAN_TSTAT、CAN_RFIFO0/1等关键寄存器,可以获取最直接的控制器状态信息。
- 回环静默模式:在怀疑是自身节点发送导致总线问题时,可切换到
5.3 软件设计建议
- 抽象通信层:不要将CAN发送/接收代码与具体业务逻辑强耦合。建议封装一个
can_bus.c/h模块,提供如CAN_SendMsg(uint32_t id, uint8_t* data, uint8_t len)和CAN_RegisterRxCallback(uint32_t id_filter, void (*callback)(can_msg_t* msg))这样的接口。业务层只需调用发送接口和注册接收回调函数,提高代码可移植性和可维护性。 - 超时与重发机制:对于重要的命令或数据,在应用层实现基于应答的超时重发机制。发送方在发送后启动一个定时器,如果在规定时间内未收到接收方的应答报文(可以是特定ID的ACK帧),则进行重发,并记录重发次数。
- 数据打包与解析:CAN一帧最多8字节,对于复杂数据,需要定义应用层协议进行分包、组包。常用的有“长度+序号+数据+校验”的格式。务必处理好字节序(大端/小端)问题,建议统一使用网络字节序(大端)。
- 中断处理要快进快出:CAN接收中断服务程序中,只做最必要的操作:读取数据、释放邮箱、设置标志位或放入环形缓冲区。复杂的数据处理应放到主循环或低优先级任务中。避免在中断中调用
printf等耗时函数。
通过本实验,你不仅能在GD32F303红枫派上跑通CAN回环通信,更重要的是理解了从信号电平、位时序、帧结构到过滤器、邮箱、中断的完整知识链。在实际项目中,结合这里提到的调试方法和设计建议,你就能从容地应对更复杂的多节点CAN网络应用。