【GD32 MCU 入门教程】GD32 MCU 常见外设介绍(15)CAN 模块介绍

描述

CAN是控制器局域网络(Controller Area Network)的简称,它是由研发和生产汽车电子产品著称的德国BOSCH公司开发的,并最终成为国际标准(ISO11519),是国际上应用最广泛的现场总线之一。

CAN总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线,并且拥有以CAN为底层协议专为大型货车和重工机械车辆设计的J1939协议。近年来,它具有的高可靠性和良好的错误检测能力受到重视,被广泛应用于汽车计算机控制系统和环境温度恶劣、电磁辐射强及振动大的工业环境。

15.1.CAN 基础知识

CAN 物理层

与I2C、SPI等具有时钟信号的同步通讯方式不同,CAN通讯并不是以时钟信号来进行同步的,它是一种异步通讯,只具有CAN_High和CAN_Low两条信号线,共同构成一组差分信号线,以差分信号的形式进行通讯。

闭环总线网络

CAN物理层的形式主要有两种,如下图所示CAN闭环总线通讯网络是一种遵循ISO11898标准的高速、短距离“闭环网络”,它的总线最大长度为40m,通信速度最高为1Mbps,总线的两端各要求有一个“120欧”的电阻。

mcu

开环总线网络

如下图所示CAN开环总线通讯网络是一种遵循ISO11519-2标准的低速、远距离“开环网络”,它的最大传输距离为1km,最高通讯速率为125kbps,两根总线是独立的、不形成闭环,要求每根总线上各串联有一个“2.2千欧”的电阻。

mcu

通讯节点

从CAN通讯网络图可了解到,CAN总线上可以挂载多个通讯节点,节点之间的信号经过总线传输,实现节点间通讯。由于CAN通讯协议不对节点进行地址编码,而是对数据内容进行编码的,所以网络中的节点个数理论上不受限制,只要总线的负载足够即可,可以通过中继器增强负载。

CAN通讯节点由一个CAN控制器及CAN收发器组成,控制器与收发器之间通过CAN_Tx及CAN_Rx信号线相连,收发器与CAN总线之间使用CAN_High及CAN_Low信号线相连。其中CAN_Tx及CAN_Rx使用普通的类似TTL逻辑信号,而CAN_High及CAN_Low是一对差分信号线,使用比较特别的差分信号,下一小节再详细说明。

当CAN节点需要发送数据时,控制器把要发送的二进制编码通过CAN_Tx线发送到收发器,然后由收发器把这个普通的逻辑电平信号转化成差分信号,通过差分线CAN_High和CAN_Low线输出到CAN总线网络。而通过收发器接收总线上的数据到控制器时,则是相反的过程,收发器把总线上收到的CAN_High及CAN_Low信号转化成普通的逻辑电平信号,通过CAN_Rx输出到控制器中。

例如,GD32的CAN片上外设就是通讯节点中的控制器,为了构成完整的节点,还要给它外接一个收发器,在GD32E103V-EVAL V1.0开发板上使用型号为SN65HVD230的芯片作为CAN收发器。

CAN控制器与CAN收发器的关系如同TTL串口与MAX3232电平转换芯片的关系,MAX3232芯片把TTL电平的串口信号转换成RS-232电平的串口信号,CAN收发器的作用则是把CAN控制器的TTL电平信号转换成差分信号(或者相反)。

差分信号

差分信号又称差模信号,与传统使用单根信号线电压表示逻辑的方式有区别,使用差分信号传输时,需要两根信号线,这两个信号线的振幅相等,相位相反,通过两根信号线的电压差值来表示 逻辑0和逻辑1。如下图所示差分信号使用了V+与V-信号的差值表达出了图下方的信号。

mcu

相对于单信号线传输的方式,使用差分信号传输具有如下优点:

抗干扰能力强,当外界存在噪声干扰时,几乎会同时耦合到两条信号线上,而接收端只关心两个信号的差值,所以外界的共模噪声可以被完全抵消。

能有效抑制它对外部的电磁干扰,同样的道理,由于两根信号的极性相反,他们对外辐射的电磁场可以相互抵消,耦合的越紧密,泄放到外界的电磁能量越少。

时序定位精确,由于差分信号的开关变化是位于两个信号的交点,而不像普通单端信号依靠高低两个阈值电压判断,因而受工艺,温度的影响小,能降低时序上的误差,同时也更适合于低幅度信号的电路。

由于差分信号线具有这些优点,所以在USB协议、485协议、以太网协议及CAN协议的物理层中,都使用了差分信号传输。

CAN 协议中的差分信号

CAN协议中对它使用的CAN_High及CAN_Low表示的差分信号做了规定,见下表CAN协议标准表示的信号逻辑及下图CAN差分信号(高速)所示。以高速CAN协议为例,当表示逻辑1时(隐性电平),CAN_High和CAN_Low线上的电压均为2.5v,即它们的电压差VH-VL=0V;而表示逻辑0时(显性电平),CAN_High的电平为3.5V,CAN_Low线的电平为1.5V,即它们的电压差为VHVL=2V。例如,当CAN收发器从CAN_Tx线接收到来自CAN控制器的低电平信号时(逻辑0),它会使CAN_High输出3.5V,同时CAN_Low输出1.5V,从而输出显性电平表示逻辑0。

CAN协议标准表示的信号逻辑

输入链接说明

CAN 差分信号(高速)

mcu

在 CAN 总线中,必须使它处于隐性电平(逻辑 1)或显性电平(逻辑 0)中的其中一个状态。假如有两个 CAN 通讯节点,在同一时间,一个输出隐性电平,另一个输出显性电平,类似 I2C 总线的“线 与”特性将使它处于显性电平状态,显性电平的名字就是这样来的,即可以认为显性具有优先的意味。

由于 CAN 总线协议的物理层只有 1 对差分线,在一个时刻只能表示一个信号,所以对通讯节点来说,CAN 通讯是半双工的,收发数据需要分时进行。在 CAN 的通讯网络中,因为共用总线,在整个网络中同一时刻只能有一个通讯节点发送信号,其余的节点在该时刻都只能接收。

CAN 协议层

以上是 CAN 的物理层标准,约定了电气特性,以下介绍的协议层则规定了通讯逻辑。

CAN 的波特率及位同步

由于CAN属于异步通讯,没有时钟信号线,连接在同一个总线网络中的各个节点会像串口异步通讯那样,节点间使用约定好的波特率进行通讯,特别地,CAN还会使用“位同步”的方式来抗干扰、吸收误差,实现对总线电平信号进行正确的采样,确保通讯正常。

位时序分解

为了实现位同步,CAN 协议把每一个数据位的时序分解成如下图 CAN 位时序分解图所示的 SS段、PTS 段、PBS1 段、PBS2 段,这四段的长度加起来即为一个 CAN 数据位的长度。分解后最小的时间单位是 Tq,而一个完整的位由 8~25 个 Tq 组成。为方便表示,CAN 位时序分解图中的高低电平直接代表信号逻辑 0 或逻辑 1(不是差分信号)。

mcu

该图中表示的 CAN 通讯信号每一个数据位的长度为 19Tq,其中 SS 段占 1Tq,PTS 段占 6Tq,PBS1 段占 5Tq,PBS2 段占 7Tq。信号的采样点位于 PBS1 段与 PBS2 段之间,通过控制各段的长度,可以对采样点的位置进行偏移,以便准确地采样。

各段的作用介绍如下:

SS段(SYNC SEG)

SS 译为同步段,若通讯节点检测到总线上信号的跳变沿被包含在 SS 段的范围之内,则表示节点与总线的时序是同步的,当节点与总线同步时,采样点采集到的总线电平即可被确定为该位的电 平。SS 段的大小固定为 1Tq。

PTS段(PROP SEG)

PTS 译为传播时间段,这个时间段是用于补偿网络的物理延时时间。是总线上输入比较器延时和输出驱动器延时总和的两倍。PTS 段的大小可以为 1~8Tq。

PBS1段(PHASE SEG1)

PBS1 译为相位缓冲段,主要用来补偿边沿阶段的误差,它的时间长度在重新同步的时候可以加长。PBS1 段的初始大小可以为 1~8Tq。

PBS2段(PHASE SEG2)

PBS2 这是另一个相位缓冲段,也是用来补偿边沿阶段误差的,它的时间长度在重新同步时可以缩短。PBS2 段的初始大小可以为 2~8Tq。

通讯的波特率

总线上的各个通讯节点只要约定好1个Tq的时间长度以及每一个数据位占据多少个Tq,就可以确定CAN通讯的波特率。

例如,假设图5-53中的1Tq=1us,而每个数据位由19个Tq组成,则传输一位数据需要时间T1bit =19us,从而每秒可以传输的数据位个数为:

1x106/19 = 52631.6 (bps)

这个每秒可传输的数据位的个数即为通讯中的波特率。

同步过程分析

波特率只是约定了每个数据位的长度,数据同步还涉及到相位的细节,这个时候就需要用到数据位内的SS、PTS、PBS1及PBS2段了。

根据对段的应用方式差异,CAN 的数据同步分为硬同步和重新同步。其中硬同步只是当存在“帧起始信号”时起作用,无法确保后续一连串的位时序都是同步的,而重新同步方式可解决该问题,这 两种方式具体介绍如下:

(1)硬同步

若某个 CAN 节点通过总线发送数据时,它会发送一个表示通讯起始的信号(即下一小节介绍的帧起始信号),该信号是一个由高变低的下降沿。而挂载到 CAN 总线上的通讯节点在不发送数据时, 会时刻检测总线上的信号。

下图硬同步过程图所示,可以看到当总线出现帧起始信号时,某节点检测到总线的帧起始信号不在节点内部时序的 SS 段范围,所以判断它自己的内部时序与总线不同步,因而这个状态的采样点采集得的数据是不正确的。所以节点以硬同步的方式调整,把自己的位时序中的 SS 段平移至总线出现下降沿的部分,获得同步,同步后采样点就可以采集得正确数据了。

mcu

(2)重新同步

前面的硬同步只是当存在帧起始信号时才起作用,如果在一帧很长的数据内,节点信号与总线信号相位有偏移时,这种同步方式就无能为力了。因而需要引入重新同步方式,它利用普通数据位的高至低电平的跳变沿来同步(帧起始信号是特殊的跳变沿)。重新同步与硬同步方式相似的地方是它们都使用 SS 段来进行检测,同步的目的都是使节点内的 SS 段把跳变沿包含起来。

重新同步的方式分为超前和滞后两种情况,以总线跳变沿与 SS 段的相对位置进行区分。第一种相位超前的情况如下图所示,相位超前时的重新同步,节点从总线的边沿跳变中,检测到它内部的时序比总线的时序相对超前2Tq,这时控制器在下一个位时序中的PBS1段增加2Tq的时间长度,使得节点与总线时序重新同步。

相位超前时的重新同步

mcu

第二种相位滞后的情况如下图所示,相位滞后时的重新同步,节点从总线的边沿跳变中,检测到它的时序比总线的时序相对滞后2Tq,这时控制器在前一个位时序中的 PBS2 段减少 2Tq 的时间长 度,获得同步。

相位滞后时的重新同步

mcu

在重新同步的时候,PBS1 和 PBS2 中增加或减少的这段时间长度被定义为“重新同步补偿宽度SJW (reSynchronization Jump Width)”。一般来说 CAN 控制器会限定 SJW 的最大值,如限定了 最大 SJW=3Tq 时,单次同步调整的时候不能增加或减少超过 3Tq 的时间长度,若有需要,控制器会通过多次小幅度调整来实现同步。当控制器设置的 SJW 极限值较大时,可以吸收的误差加大,但通讯的速度会下降。

CAN 的报文种类及结构

在 SPI 通讯中,片选、时钟信号、数据输入及数据输出这 4 个信号都有单独的信号线,I2C 协议包含有时钟信号及数据信号 2 条信号线,异步串口包含接收与发送 2 条信号线,这些协议包含的 信号都比 CAN 协议要丰富,它们能轻易进行数据同步或区分数据传输方向。而 CAN 使用的是两条差分信号线,只能表达一个信号,简洁的物理层决定了 CAN 必然要配上一套更复杂的协议,如 何用一个信号通道实现同样、甚至更强大的功能呢?CAN 协议给出的解决方案是对数据、操作命令(如读/写)以及同步信号进行打包,打包后的这些内容称为报文。

在原始数据段的前面加上传输起始标签、片选(识别)标签和控制标签,在数据的尾段加上 CRC 校验标签、应答标签和传输结束标签,把这些内容按特定的格式打包好,就可以用一个通道表达各种信号了,各种各样的标签就如同 SPI 中各种通道上的信号,起到了协同传输的作用。当整个数据包被传输到其它设备时,只要这些设备按格式去解读,就能还原出原始数据,这样的报文就被称为 CAN 的“数据帧”。

为了更有效地控制通讯,CAN 一共规定了 5 种类型的帧,它们的类型及用途说明如下表帧的种类及其用途所示。

mcu

数据帧的结构

数据帧是在 CAN 通讯中最主要、最复杂的报文,我们来了解它的结构,如下图数据帧的结构所示。

mcu

数据帧以一个显性位(逻辑 0)开始,以 7 个连续的隐性位(逻辑 1)结束,在它们之间,分别有仲裁段、控制段、数据段、CRC 段和 ACK 段。

帧起始

SOF 段(Start Of Frame),译为帧起始,帧起始信号只有一个数据位,是一个显性电平,它用于通知各个节点将有数据传输,其它节点通过帧起始信号的电平跳变沿来进行硬同步。

仲裁段

当同时有两个报文被发送时,总线会根据仲裁段的内容决定哪个数据包能被传输,这也是它名称的由来。

仲裁段的内容主要为本数据帧的 ID 信息(标识符),数据帧具有标准格式和扩展格式两种,区别就在于 ID 信息的长度,标准格式的 ID 为 11 位,扩展格式的 ID 为 29 位,它在标准 ID 的基础上多 出 18 位。在 CAN 协议中,ID 起着重要的作用,它决定着数据帧发送的优先级,也决定着其它节点是否会接收这个数据帧。CAN 协议不对挂载在它之上的节点分配优先级和地址,对总线的占有权是由信息的重要性决定的,即对于重要的信息,我们会给它打包上一个优先级高的 ID,使它能够及时地发送出去。也正因为它这样的优先级分配原则,使得 CAN 的扩展性大大加强,在总线上增加或减少节点并不影响其它设备。

报文的优先级,是通过对 ID 的仲裁来确定的。根据前面对物理层的分析我们知道如果总线上同时出现显性电平和隐性电平,总线的状态会被置为显性电平,CAN 正是利用这个特性进行仲裁。

若两个节点同时竞争CAN总线的占有权,当它们发送报文时,若首先出现隐性电平,则会失去对总线的占有权,进入接收状态。如下图过程所示,在开始阶段,两个设备发送的电平一样,所以它们一直继续发送数据。到了图中箭头所指的时序处,节点单元1发送的为隐性电平,而此时节点单元2发送的为显性电平,由于总线的“线与”特性使它表达出显示电平,因此单元2竞争总线成功,这个报文得以被继续发送出去。

mcu

仲裁段 ID 的优先级也影响着接收设备对报文的反应。因为在 CAN 总线上数据是以广播的形式发送的,所有连接在 CAN 总线的节点都会收到所有其它节点发出的有效数据,因而我们的 CAN 控制器大多具有根据 ID 过滤报文的功能,它可以控制自己只接收某些 ID 的报文。

回看数据帧的结构图中的数据帧格式,可看到仲裁段除了报文 ID 外,还有 RTR、IDE 和SRR 位。

(1) RTR 位(Remote Transmission Request Bit),译作远程传输请求位,它是用于区分数据帧和遥控帧的,当它为显性电平时表示数据帧,隐性电平时表示遥控帧。

(2) IDE 位(Identifier Extension Bit),译作标识符扩展位,它是用于区分标准格式与扩展格式,当它为显性电平时表示标准格式,隐性电平时表示扩展格式。

(3) SRR 位(Substitute Remote Request Bit),只存在于扩展格式,它用于替代标准格式中的 RTR位。由于扩展帧中的 SRR 位为隐性位,RTR 在数据帧为显性位,所以在两个 ID 相同的标准格式 报文与扩展格式报文中,标准格式的优先级较高。

控制段

在控制段中的 r1 和 r0 为保留位,默认设置为显性位。它最主要的是 DLC 段(Data Length Code),译为数据长度码,它由 4 个数据位组成,用于表示本报文中的数据段含有多少个字节,DLC 段表 示的数字为 0~8。

数据段

数据段为数据帧的核心内容,它是节点要发送的原始信息,由 0~8 个字节组成,MSB 先行。

CRC 段

为了保证报文的正确传输,CAN 的报文包含了一段 15 位的 CRC 校验码,一旦接收节点算出的CRC 码跟接收到的 CRC 码不同,则它会向发送节点反馈出错信息,利用错误帧请求它重新发送。CRC 部分的计算一般由 CAN 控制器硬件完成,出错时的处理则由软件控制最大重发数。

在 CRC 校验码之后,有一个 CRC 界定符,它为隐性位,主要作用是把 CRC 校验码与后面的 ACK 段间隔起来。

ACK 段

ACK 段包括一个 ACK 槽位,和 ACK 界定符位。类似 I2C 总线,在 ACK 槽位中,发送节点发送的是隐性位,而接收节点则在这一位中发送显性位以示应答。在 ACK 槽和帧结束之间由 ACK 界定符间隔开。

帧结束

EOF 段(End Of Frame),译为帧结束,帧结束段由发送节点发送的 7 个隐性位表示结束。

其它报文的结构

关于其它的 CAN 报文结构,不再展开讲解,其主要内容见下图 各种 CAN 报文的结构。

mcu

15.2.GD32 CAN 外设原理简介

因篇幅有限,本文无法详细介绍GD32所有系列CAN外设接口,下面以GD32E103为例,着重介绍下GD32E103的CAN外设简介和结构框图,后介绍下各个系列的差异。

GD32 CAN 主要特性

◼ 支持CAN总线协议2.0A和2.0B;

◼ 支持CAN-FD帧(ISO11898-1和CAN-FD规范V1.0);

◼ 常规帧:通信波特率最大为1Mbit/s;

◼ CAN-FD帧:通信最大波特率为6Mbit/s;

◼ 支持传输延迟补偿;

◼ 支持时间触发通信(Time-triggered communication);

◼ 中断使能和清除;

发送功能

◼ 3个发送邮箱;

◼ 支持发送优先级;

◼ 支持发送时间戳。

接收功能

◼ 2个深度为3的接收FIFO;

◼ 具有28个标识符过滤器;

◼ FIFO锁定功能。

时间触发通信

◼ 在时间触发通信模式下禁用自动重传;

◼ 16位定时器;

◼ 接收时间戳;

◼ 发送时间戳。

CAN 结构框图介绍

CAN 模块结构框图

mcu

GD32E103一共有两个CAN控制器,分别为CAN0和CAN1。每个CAN控制器有3个发送邮箱,2个深度为3的接收FIFO。

CAN控制器包含多个寄存器,下面主要讲解其中的控制寄存CAN_CTL和位时序寄存器CAN_BT。

控制寄存器 CAN_CTL

控制寄存器CAN_CTL负责管理CAN的工作模式,它使用以下寄存器位实现控制。

(1) DFZ调试冻结功能

DFZ(Debug freeze)调试冻结,使用它可设置CAN处于工作状态或禁止收发的状态,禁止收发时仍可访问接收FIFO中的数据。这两种状态是当STM32芯片处于程序调试模式时才使用的,平时使用 并不影响。

(2) TTC时间触发通信

TTC(Time triggered communication)时间触发通信,它用于配置CAN的时间触发通信模式,在此模式下,CAN使用它内部定时器产生时间戳,并把它保存在CAN_TMxP、CAN_RFIFOMPx寄存器中。内部定时器在每个CAN位时间累加,在接收和发送的帧起始位被采样,并生成时间戳。利用它可以实现ISO 11898-4 CAN标准的分时同步通信功能。

(3) ABOR自动离线恢复

ABOR(Automatic bus-off recovery) 自动离线恢复,它用于设置是否使用自动离线管理功能。当节点检测到它发送错误或接收错误超过一定值时,会自动进入离线状态,在离线状态中,CAN不能接收或发送报文。处于离线状态的时候,可以软件控制恢复或者直接使用这个自动离线管理功能,它会在适当的时候自动恢复。

(4) AWU自动唤醒

AWU(Automatic wakeup),自动唤醒功能,CAN外设可以使用软件进入低功耗的睡眠模式,如果使能了这个自动唤醒功能,当CAN检测到总线活动的时候,会自动唤醒。

(5) AR自动重传

AR(Automatic retransmission)报文自动重传功能,设置这个功能后,当报文发送失败时会自动重传至成功为止。若不使用这个功能,无论发送结果如何,消息只发送一次。

(6) RFO接收FIFO覆盖

RFO (Receive FIFO overwrite)接收FIFO覆盖,该功能用于锁定接收FIFO。锁定后,当接收FIFO溢出时,会丢弃下一个接收的报文。若不锁定,则下一个接收到的报文会覆盖原报文。

(7) TFO发送FIFO顺序

FTO(Tx FIFO order)报文发送顺序的判定方法,当CAN外设的发送邮箱中有多个待发送报文时,本功能可以控制它是根据报文的ID优先级还是报文存进邮箱的顺序来发送。

位时序寄存器(CAN_BT)及波特率

CAN外设中的位时序寄存器CAN_BT用于配置测试模式、波特率以及各种位内的段参数。

(1) 测试模式

为方便调试,GD32的CAN提供了测试模式,配置位时序寄存器CAN_BT的SCMOD及LCMOD位可以控制使用正常模式、静默模式、回环模式及静默回环模式,见下图四种通讯模式

mcu

各个通讯模式介绍如下:

正常模式

正常模式下就是一个正常的CAN节点,可以向总线发送数据和接收数据。

静默模式

静默模式下,它自己的输出端的逻辑0数据会直接传输到它自己的输入端,逻辑1可以被发送到总线,所以它不能向总线发送显性位(逻辑0),只能发送隐性位(逻辑1)。输入端可以从总线接收内容。 由于它只可发送的隐性位不会强制影响总线的状态,所以把它称为静默模式。这种模式一般用于监测,它可以用于分析总线上的流量,但又不会因为发送显性位而影响总线。

回环模式

回环模式下,它自己的输出端的所有内容都直接传输到自己的输入端,输出端的内容同时也会被传输到总线上,即也可使用总线监测它的发送内容。输入端只接收自己发送端的内容,不接收来自总线上的内容。使用回环模式可以进行自检。

回环静默模式

回环静默模式是以上两种模式的结合,自己的输出端的所有内容都直接传输到自己的输入端,并且不会向总线发送显性位影响总线,不能通过总线监测它的发送内容。输入端只接收自己发送端的内容,不接收来自总线上的内容。这种方式可以在“热自检”时使用,即自我检查的时候,不会干扰总线。

以上说的各个模式,是不需要修改硬件接线的,如当输出直连输入时,它是在GD32芯片内部连接的,传输路径不经过STM32的CAN_Tx/Rx引脚,更不经过外部连接的CAN收发器,只有输出数据 到总线或从总线接收的情况下才会经过CAN_Tx/Rx引脚和收发器。

(2)位时序及波特率

GD32外设定义的位时序与我们前面解释的CAN标准时序有一点区别,见下图GD32中的位时序。

mcu

GD32的CAN外设位时序中只包含3段,分别是同步段SYNC_SEG、位段BS1及位段BS2,采样点位于BS1及BS2段的交界处。其中SYNC_SEG段固定长度为1Tq,而BS1及BS2段可以在位时序寄存器CAN_BT设置它们的时间长度,它们可以在重新同步期间增长或缩短,该长度DSJW也可在位时序寄存器中配置。

理解GD32的CAN外设的位时序时,可以把它的BS1段理解为是由前面介绍的CAN标准协议中PTS段与PBS1段合在一起的,而BS2段就相当于PBS2段。

了解位时序后,我们就可以配置波特率了。通过配置位时序寄存器CAN_BT的BS1[6:0]及BS2[4:0]寄存器位设定BS1及BS2段的长度后,我们就可以确定每个CAN数据位的时间:

BS1段时间: TS1=Tq x (BS1[6:0] + 1)

BS2段时间: TS2= Tq x (BS2[4:0] + 1)

一个数据位的时间: T1bit =1Tq+TS1+TS2 =1+ (BS1[6:0] + 1)+ (BS2[4:0] + 1)= N Tq

其中单个时间片的长度Tq与CAN外设的所挂载的时钟总线及分频器配置有关。

CAN0和CAN1外设都是挂载在APB1总线上的,而位时序寄存器CAN_BT中的BAUDPSC[9:0]位可以设置CAN外设时钟的分频值 ,所以:

Tq = (BAUDPSC[9:0]) x TPCLK

其中的PCLK指APB1时钟,默认值为60MHz。

最终可以计算出CAN通讯的波特率:

BaudRate = 1/N Tq

例如下表一种配置波特率为1Mbps的方式说明了如何配置波特率为1Mbps。

mcu

各系列 CAN 功能差异

GD32系列MCU有关CAN外设各系列功能差异如下表GD32各系列MCU CAN外设功能差异表所示。

mcu

15.3.硬件连接说明

CAN 外设硬件连接图

mcu

如图CAN外设硬件连接图所示,为典型的CAN外设硬件连接图:SN65HVD230是收发器,其作用就是把CAN控制器的TTL电平转换成差分信号。发送数据时,控制器把要发送的二进制编码通过CAN_TX线发送到收发器,然后由收发器把这个逻辑电平转换成差分信号,通过差分线CANH、CANL线输出到总线网络。当接收数据时,收发器把总线上收到的CANH、CANL信号转换成逻辑电平,通过CAN_RX输入到控制器。

读者可以根据典型硬件连接图和相应系列的Datasheet设计出自己的硬件连接方式。

15.4.软件配置说明

本小节讲解CAN_Example历程中CAN模块的配置说明,主要包括CAN外设配置、GPIO引脚配置、主函数介绍以及运行结果。本例程主要介绍GD32 MCU各系列CAN模块的数据发送、接收,有关CAN其他功能例程可参考各系列固件库历程。

CAN 外设配置

外设配置如代码清单CAN外设配置所示,在GD32全系列MCU中CAN外设的配置基本相同。GD32标准库提供了CAN初始化结构体及初始化函数来配置CAN外设,其初始化结构体说明如下表CAN参数初始化结构体说明列表和CAN过滤器初始化结构体说明列表所示。需要注意的是本例程需要用到两个开发板,一个发送,一个接收,这可以通过打开宏定义CAN_RECEIVE或CAN_TRANSMIT来决定当前是接收还是发送。

代码清单 CAN 外设配置

void can_config(void) { #if defined (GD32F30X_CL) ||(GD32F4XX) ||(GD32F20X_CL)||(GD32F10X_HD) can_parameter_struct can_parameter; can_filter_parameter_struct can_filter_parameter; can_struct_para_init(CAN_INIT_STRUCT, &can_parameter); can_struct_para_init(CAN_FILTER_STRUCT, &can_filter_parameter); /* initialize CAN register */ can_deinit(CAN0); /* initialize CAN parameters */ can_parameter.time_triggered = DISABLE; can_parameter.auto_bus_off_recovery = DISABLE; can_parameter.auto_wake_up = DISABLE; can_parameter.no_auto_retrans = DISABLE; can_parameter.rec_fifo_overwrite = DISABLE; can_parameter.trans_fifo_order = DISABLE; can_parameter.working_mode = CAN_NORMAL_MODE; can_parameter.resync_jump_width = CAN_BT_SJW_1TQ; can_parameter.time_segment_1 = CAN_BT_BS1_5TQ; can_parameter.time_segment_2 = CAN_BT_BS2_4TQ; can_parameter.prescaler = 12; /* initialize CAN */ can_init(CAN0, &can_parameter); /* initialize filter */ can_filter_parameter.filter_number = 0; can_filter_parameter.filter_mode = CAN_FILTERMODE_MASK; can_filter_parameter.filter_bits = CAN_FILTERBITS_32BIT; can_filter_parameter.filter_list_high = 0x0000; can_filter_parameter.filter_list_low = 0x0000; can_filter_parameter.filter_mask_high = 0x0000; can_filter_parameter.filter_mask_low = 0x0000; can_filter_parameter.filter_fifo_number = CAN_FIFO0; can_filter_parameter.filter_enable = ENABLE; can_filter_init(&can_filter_parameter); #if defined (CAN_RECEIVE) #if defined (GD32F10X_HD) nvic_irq_enable(USBD_LP_CAN0_RX0_IRQn,0,0); #else nvic_irq_enable(CAN0_RX0_IRQn,0,0); #endif /* enable can receive FIFO0 not empty interrupt */ can_interrupt_enable(CAN0, CAN_INT_RFNE0); #endif #elif defined (GD32E10X) can_parameter_struct can_parameter; can_struct_para_init(CAN_INIT_STRUCT, &can_parameter); /* initialize CAN register */ can_deinit(CAN0); /* initialize CAN parameters */ can_parameter.time_triggered = DISABLE; can_parameter.auto_bus_off_recovery = DISABLE; can_parameter.auto_wake_up = DISABLE; can_parameter.auto_retrans = DISABLE; can_parameter.rec_fifo_overwrite = DISABLE; can_parameter.trans_fifo_order = DISABLE; can_parameter.working_mode = CAN_NORMAL_MODE; /* initialize CAN */ can_init(CAN0, &can_parameter); /* config CAN0 baud rate */ can_frequency_set(CAN0, DEV_CAN_BAUD_RATE); /* initialize filter */ can_filter_mask_mode_init(DEV_CAN_ID, DEV_CAN_MASK, CAN_EXTENDED_FIFO0, 0); #if defined (CAN_RECEIVE) /* configure CAN0 NVIC */ nvic_irq_enable(CAN0_RX0_IRQn, 0, 0); /* enable can receive FIFO0 not empty interrupt */ can_interrupt_enable(CAN0, CAN_INTEN_RFNEIE0); #endif #elif defined (GD32F1X0) can_parameter_struct can_parameter; can_filter_parameter_struct can_filter_parameter; /* initialize CAN register */ can_deinit(CAN1); /* initialize CAN parameters */ can_parameter.time_triggered = DISABLE; can_parameter.auto_bus_off_recovery = DISABLE; can_parameter.auto_wake_up = DISABLE; can_parameter.no_auto_retrans = DISABLE; can_parameter.rec_fifo_overwrite = DISABLE; can_parameter.trans_fifo_order = DISABLE; can_parameter.working_mode = CAN_NORMAL_MODE; can_parameter.resync_jump_width = CAN_BT_SJW_1TQ; can_parameter.time_segment_1 = CAN_BT_BS1_4TQ; can_parameter.time_segment_2 = CAN_BT_BS2_3TQ; can_parameter.prescaler = 18; /* initialize CAN */ can_init(CAN1, &can_parameter); /* initialize filter */ can_filter_parameter.filter_number = 15; can_filter_parameter.filter_mode = CAN_FILTERMODE_MASK; can_filter_parameter.filter_bits = CAN_FILTERBITS_32BIT; can_filter_parameter.filter_list_high = 0x0000; can_filter_parameter.filter_list_low = 0x0000; can_filter_parameter.filter_mask_high = 0x0000; can_filter_parameter.filter_mask_low = 0x0000; can_filter_parameter.filter_fifo_number = CAN_FIFO0; can_filter_parameter.filter_enable = ENABLE; can_filter_init(&can_filter_parameter); #if defined (CAN_RECEIVE) /* configure CAN1 NVIC */ nvic_irq_enable(CAN1_RX0_IRQn,0,0); can_interrupt_enable(CAN1, CAN_INTEN_RFNEIE0); #endif #endif }

 

CAN 参数初始化结构体说明列表

mcu

CAN 过滤器初始化结构体说明列表

mcu

GPIO 引脚配置

GPIO引脚配置如代码清单CAN例程GPIO引脚配置所示。GD32F10X、GD32F30X、GD32F20X、GD32E10X系列用到remap功能,因此需要打开AF时钟。使用GD32F1X0时,例程中是使用CAN1来进行收发。需要注意的是,发送时,不需要打开CAN0的时钟,但是接收时需要打开CAN0的时钟。

代码清单 CAN 例程 GPIO 引脚配置

void can_gpio_config(void) { #if defined (GD32F4XX) /* enable CAN0 clock */ rcu_periph_clock_enable(RCU_CAN0); rcu_periph_clock_enable(RCU_GPIOB); /* configure CAN0 GPIO */ gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8); gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_8); gpio_af_set(GPIOB, GPIO_AF_9, GPIO_PIN_8); gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9); gpio_af_set(GPIOB, GPIO_AF_9, GPIO_PIN_9); #elif defined (GD32E10X) || (GD32F30X_CL) ||(GD32F10X_HD) /* enable CAN0 clock */ rcu_periph_clock_enable(RCU_CAN0); rcu_periph_clock_enable(RCU_GPIOD); rcu_periph_clock_enable(RCU_AF); /* configure CAN0 GPIO */ gpio_init(GPIOD,GPIO_MODE_IPU,GPIO_OSPEED_50MHZ,GPIO_PIN_0); gpio_init(GPIOD,GPIO_MODE_AF_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_1); #if defined (GD32F10X_HD) gpio_pin_remap_config(GPIO_CAN_FULL_REMAP,ENABLE); #else gpio_pin_remap_config(GPIO_CAN0_FULL_REMAP,ENABLE); #endif #elif defined (GD32F20X_CL) /* enable CAN0 clock */ rcu_periph_clock_enable(RCU_CAN0); rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_AF); /* configure CAN0 GPIO */ 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); gpio_pin_remap_config(GPIO_CAN0_PARTIAL_REMAP,ENABLE); #elif defined (GD32F1X0) //only GD32F170 and GD32F190 have CAN, GD32F130 and GD32F150 don't have /* enable CAN clock */ #if defined (CAN_RECEIVE) rcu_periph_clock_enable(RCU_CAN0); #endif rcu_periph_clock_enable(RCU_CAN1); rcu_periph_clock_enable(RCU_GPIOB); /* configure CAN1 GPIO */ gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_12); gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_12); gpio_af_set(GPIOB, GPIO_AF_9, GPIO_PIN_12); gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_13); gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_13); gpio_af_set(GPIOB, GPIO_AF_9, GPIO_PIN_13); #endif }

 

主函数配置

主函数配置如代码清单主函数配置所示。本例程需要两个开发板,一个作为发送,一个作为接收,这可以通过使用宏定义CAN_RECEIVE和CAN_TRANSMIT来实现。如果作为接收,那么主函数的开始会把can_receive_flag设置为RESET,并初始化LED2。LED2作为通讯的指示灯,如果通讯成功,就点亮LED2,否则就不点亮。can_receive_flag状态的改变在中断里实现,如果成功接收到数据,就会进入中断,在中断里把can_receive_flag设置为SET。主函数接下来对CAN的GPIO以及CAN模块进行初始化。完成后,如果是作为发送方,程序需要设置需要发送的数据。有关发送结构体参数的说明,请见下表CAN发送消息初始化结构体说明列表。

代码清单主函数配置

int main(void) { #if defined (CAN_RECEIVE) can_receive_flag = RESET; LED2_init(); #endif /* GPIO config */ can_gpio_config(); /* CAN config */ can_config(); #if defined (CAN_TRANSMIT) /* initialize transmit message */ #if defined (GD32E10X) || (GD32F30X_CL) || (GD32F10X_HD) || (GD32F4XX) || (GD32F20X_CL) can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &g_transmit_message); #endif g_transmit_message.tx_sfid = 0x00; g_transmit_message.tx_efid = DEV_CAN_ID; g_transmit_message.tx_ft = CAN_FT_DATA; g_transmit_message.tx_ff = CAN_FF_EXTENDED; g_transmit_message.tx_dlen = 4; g_transmit_message.tx_data[0] = 0xaa; g_transmit_message.tx_data[1] = 0xbb; g_transmit_message.tx_data[2] = 0xcc; g_transmit_message.tx_data[3] = 0xdd; #if defined (GD32E10X) || (GD32F30X_CL) || (GD32F10X_HD) || (GD32F4XX) || (GD32F20X_CL) can_message_transmit(CAN0, &g_transmit_message); #elif defined (GD32F1X0) can_message_transmit(CAN1, &g_transmit_message); #endif #endif #if defined (CAN_RECEIVE) while(can_receive_flag == RESET); if((g_receive_message.rx_data[0] == 0xaa) && (g_receive_message.rx_data[1] == 0xbb)&&\ (g_receive_message.rx_data[2] == 0xcc) && (g_receive_message.rx_data[3] == 0xdd)) { LED2_ON(); } else { LED2_OFF(); } #endif while(1); }

 

CAN 发送消息初始化结构体说明列表

mcu

运行结果

本例程需要用到两个开发板,用杜邦线把两个开发板的CAN_H、CAN_L分别连接起来。打开CAN_Example例程,选择好对应开发板的芯片工程后,先屏蔽掉CAN_RECEIVE,不屏蔽CAN_TRANSMIT ,编译后把程序下载到一号开发板上。然后换一块开发板,屏蔽掉CAN_TRANSMIT,不屏蔽CAN_RECEIVE,编译后把程序下载到二号开发板上。接下来按下二号开发板的复位键,然后再按下一号开发板的复位键,此时会看到二号开发板的LED2被点亮,这表明通讯成功。

15.5.CAN 使用注意事项

(1) 使用F10X、F20X、F30X、F1X0、E10X、F403接收数据时如果出现接收两帧数据会丢失一包的情况,这是由于手动多调用一次清缓存的操作导致的。因此,软件中无需调用can_fifo_release函数;

(2) 使用F10X、F20X、F1X0时,会出现CAN离线后无法自动恢复,这是由于CAN模块的离线自动恢复功能与CAN协议定义的离线恢复序列存在一定理解误差造成的。该状况可以通过使能离线中断,在离线中断内重新初始化CAN模块来规避;

(3) GD32F170和GD32F190的CAN0内置PHY,其CANH和CANL口的耐压范围为VSS-0.3到VSS+7.5,内置的PHY不支持12V和24V系统。当使用CAN0时,建议硬件上按照下图CAN0引脚连接图连接。

CAN0 引脚连接图

mcu

 

 

教程GD32 MCU方案商聚沃科技原创发布,了解更多GD32 MCU教程,关注聚沃科技官网

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分