第三十章 FDCAN通讯实验
本章我们将向大家介绍如何使用 STM32G474自带的FDCAN控制器,相比于STM32以前自带的CAN,FDCAN性能更为强大,FDCAN每帧数据最大可以到64字节,FDCAN速度远超CAN的1Mbps,能够达到5Mbps甚至更高。本章我们将有两个实验,分别是《传统CAN模式实验》和《FDCAN模式实验》,通过这两个实验我们来实现两个开发板之间的FDCAN通讯,并将结果显示在LCD模块上。本章分为如下几个部分:
30.1 CAN/FDCAN简介
30.2 硬件设计
30.3 程序设计
30.4 下载验证
30.1 CAN/FDCAN简介
30.1.1 CAN简介
CAN是Controller Area Network的缩写(以下称为CAN),是ISO国际标准化的串行通信协议。在当前的汽车产业中,出于对安全性、舒适性、方便性、低公害、低成本的要求,各种各样的电子控制系统被开发了出来。由于这些系统之间通信所用的数据类型及对可靠性的要求不尽相同,由多条总线构成的情况很多,线束的数量也随之增加。为适应“减少线束的数量”、“通过多个LAN,进行大量数据的高速通信”的需要,1986年德国电气商博世公司开发出面向汽车的CAN通信协议。此后,CAN通过ISO11898及ISO11519进行了标准化,现在在欧洲已是汽车网络的标准协议。
现在,CAN的高性能和可靠性已被认同,并被广泛地应用于工业自动化、船舶、医疗设备、工业设备等方面。现场总线是当今自动化领域技术发展的热点之一,被誉为自动化领域的计算机局域网。它的出现为分布式控制系统实现各节点之间实时、可靠的数据通信提供了强有力的技术支持。
CAN协议具有一下特点:
① 多主控制。在总线空闲时,所有单元都可以发送消息(多主控制),而两个以上的单元同时开始发送消息时,根据标识符(Identifier以下称为ID)决定优先级。ID并不是表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时开始发送消息时,对各消息ID的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。
② 系统的柔软性。与总线相连的单元没有类似于“地址”的信息。因此在总线上增加单元时,连接在总线上的其它单元的软硬件及应用层都不需要改变。
③ 通信速度较快,通信距离远。最高1Mbps(距离小于40M),最远可达10KM(速率低于5Kbps)。
④ 具有错误检测、错误通知和错误恢复功能。所有单元都可以检测错误(错误检测功能),检测出错误的单元会立即同时通知其他所有单元(错误通知功能),正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止(错误恢复功能)。
⑤ 故障封闭功能。CAN可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。
⑥ 连接节点多。CAN总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。
正是因为CAN协议的这些特点,使得CAN特别适合工业过程监控设备的互连,因此,越来越受到工业界的重视,并已公认为最有前途的现场总线之一。
CAN协议经过ISO标准化后有两个标准:ISO11898标准(高速CAN)和ISO11519-2标准(低速CAN)。其中ISO11898是针对通信速率为125Kbps~1Mbps的高速通信标准,而ISO11519-2是针对通信速率为125Kbps以下的低速通信标准。
30.1.2 FDCAN简介
CAN最高只能到1Mbps,但是随着汽车技术的发展,人们对于更高的数据传输速度需求越来越强烈。再加上CAN与竞争对手产品的巨大差异,比如FlexRay最高可以到10Mbps,2011年Bosch发布了CAN-FD(也就是STM32G474自带的FDCAN),CAN-FD继承了传统CAN的主要特性,CAN-FD对带宽和数据长度做了优化,是下一代汽车总线。CAN-FD相比CAN主要有以下几个优势:
① 更高的传输速率
CAN-FD可以以更高的速率传输数据,仲裁段(ID和ACK)的速率和CAN一样最高1Mbps,保持不变,这样可以保证总线健壮可靠。但是CAN-FD帧数据段可以5Mbps甚至更高!这个技术叫做数据段波特率可变,CAN-FD中FD的全称就是Flexible Data-Rate。
② 更长的数据帧
CAN的一帧只能发送8字节数据,而CAN-FD一帧最高可以发送64字节的数据,大大的提高了数据传输效率,以前需要分几帧传输的报文,现在一帧就可以传输了。
CAN-FD向下兼容CAN,如果要想使用CAN-FD功能的话都需要哪些条件呢?
1、所使用的控制器或者MCU支持CAN-FD协议,比如STM32G474的FDCAN就支持CAN-FD协议。
2、所使用的CAN收发器支持CAN-FD,DMG474开发板上的CAN收发器为SIT1042T或者TPT1051V,这两款收发器均支持CAN-FD协议。因此,在DMG474开发板上,我们将使用FDCAN协议,其最高仲裁段速率为1Mbps,数据段速率至少为5Mbit/s,每个数据帧最大可达64字节。
在本章中,我们将使用STM32G474的FDCAN功能实现通信,将通信波特率设置为500Kbps,并将数据段速率设置为5.3Mbit/s(仅FDCAN模式支持)。我们采用ISO11898标准。本章将首先介绍CAN的相关内容,然后再深入讲解CAN-FD的相关知识。CAN的物理层特征如图30.1.2.1所示。

图30.1.2.1 ISO11898物理层特性
从该特性可以看出,显性电平对应逻辑0,CAN_High和CAN_Low之差为2V左右。而隐性电平对应逻辑1,CAN_High和CAN_Low之差为0V。在总线上显性电平具有优先权,只要有一个单元输出显性电平,总线上即为显性电平。而隐性电平则具有包容的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平(显性电平比隐性电平更强)。另外,在CAN总线的起止端都有一个120Ω的终端电阻,来做阻抗匹配,以减少回波反射。
30.1.3 CAN协议
CAN协议是通过以下5种类型的帧进行的:
l 数据帧
l 遥控帧
l 错误帧
l 过载帧
l 间隔帧
另外,数据帧和遥控帧有标准格式和扩展格式两种格式。标准格式有11个位的标识符(ID),扩展格式有29个位的ID。各种帧的用途如表30.1.3.1所示:

表30.1.3.1 CAN协议各种帧及其用途
由于篇幅所限,我们这里仅对数据帧进行详细介绍,数据帧一般由7个段构成,即:
帧起始。表示数据帧开始的段。
仲裁段。表示该帧优先级的段。
控制段。表示数据的字节数及保留位的段。
数据段。数据的内容,一帧可发送0~8个字节的数据。
CRC段。检查帧的传输错误的段。
ACK段。表示确认正常接收的段。
帧结束。表示数据帧结束的段。
数据帧的构成如图30.1.3.1所示:

图30.1.3.1数据帧的构成图中D表示显性电平,R表示隐形电平(下同)。
帧起始,这个比较简单,标准帧和扩展帧都是由1个位的显性电平表示帧起始。
仲裁段,表示数据优先级的段,标准帧和扩展帧格式在本段有所区别,如图30.1.3.2所示:

图30.1.3.2 数据帧仲裁段构成
标准格式的ID有11个位。禁止高7位都为隐性(禁止设定:ID=1111111XXXX)。扩展格式的ID有29个位。基本ID从ID28到ID18,扩展ID由ID17到ID0表示。基本ID和标准格式的ID相同。禁止高7位都为隐性(禁止设定:基本ID=1111111XXXX)。
其中RTR位用于标识是否是远程帧(0,数据帧;1,远程帧),IDE位为标识符选择位(0:使用标准标识符;1:使用扩展标识符),SRR位为代替远程请求位,为隐性位,它代替了标准帧中的RTR位。
控制段,由6个位构成,表示数据段的字节数。标准帧和扩展帧的控制段稍有不同,如图30.1.3.3所示:

图30.1.3.3 数据帧控制段构成
上图中,r0和r1为保留位,必须全部以显性电平发送,但是接收端可以接收显性、隐性及任意组合的电平。DLC段为数据长度表示段,高位在前,DLC段有效值为0~8,但是接收方接收到9~15的时候并不认为是错误。
数据段,该段可包含0~8个字节的数据。从最高位(MSB)开始输出,标准帧和扩展帧在这个段的定义都是一样的。如图30.1.3.4所示:

图30.1.3.4 数据帧数据段构成
CRC段,该段用于检查帧传输错误。由15个位的CRC顺序和1个位的CRC界定符(用于分隔的位)组成,标准帧和扩展帧在这个段的格式也是相同的。如图30.1.3.5所示:

图30.1.3.5 数据帧CRC段构成
此段CRC的值计算范围包括:帧起始、仲裁段、控制段、数据段。接收方以同样的算法计算CRC值并进行比较,不一致时会通报错误。
ACK段,此段用来确认是否正常接收。由ACK槽(ACKSlot)和ACK界定符2个位组成。标准帧和扩展帧在这个段的格式也是相同的。如图30.1.3.6所示:

图30.1.3.6 数据帧CRC段构成
发送单元的ACK,发送2个位的隐性位,而接收到正确消息的单元在ACK槽(ACKSlot)发送显性位,通知发送单元正常接收结束,这个过程叫发送ACK/返回ACK。发送ACK的是在既不处于总线关闭态也不处于休眠态的所有接收单元中,接收到正常消息的单元(发送单元不发送ACK)。所谓正常消息是指不含填充错误、格式错误、CRC错误的消息。
帧结束,这个段也比较简单,标准帧和扩展帧在这个段格式一样,由7个位的隐性位组成。至此,数据帧的7个段就介绍完了,其他帧的介绍,请大家参考光盘的《CAN入门书.pdf》相关章节。接下来,我们再来看看CAN的位时序。
由发送单元在非同步的情况下发送的每秒钟的位数称为位速率。一个位可分为4段。
l 同步段(SS)
l 传播时间段(PTS)
l 相位缓冲段1(PBS1)
l 相位缓冲段2(PBS2)
这些段又由可称为Time Quantum(以下称为Tq)的最小时间单位构成。
1位分为4个段,每个段又由若干个Tq构成,这称为位时序。
1位由多少个Tq构成、每个段又由多少个Tq构成等,可以任意设定位时序。通过设定位时序,多个单元可同时采样,也可任意设定采样点。各段的作用和Tq数如表30.1.3.2所示:

表30.1.3.2 一个位各段及其作用
1个位的构成如图30.1.3.7所示:

图30.1.3.7 一个位的构成
上图的采样点,是指读取总线电平,并将读到的电平作为位值的点。位置在PBS1结束处。根据这个位时序,我们就可以计算CAN通信的波特率了。具体计算方法,我们等下再介绍,前面提到的CAN协议具有仲裁功能,下面我们来看看是如何实现的。
在总线空闲态,最先开始发送消息的单元获得发送权。当多个单元同时开始发送时,各发送单元从仲裁段的第一位开始进行仲裁。连续输出显性电平最多的单元可继续发送。实现过程,如图30.1.3.8所示:

图30.1.3.8 CAN总线仲裁过程
上图中,单元1和单元2同时开始向总线发送数据,开始部分他们的数据格式是一样的,故无法区分优先级,直到T时刻,单元1输出隐性电平,而单元2输出显性电平,此时单元1仲裁失利,立刻转入接收状态工作,不再与单元2竞争,而单元2则顺利获得总线使用权,继续发送自己的数据。这就实现了仲裁,让连续发送显性电平多的单元获得总线使用权。
通过以上介绍,我们对CAN总线有了个大概了解(详细介绍参考A盘的:《CAN入门书.pdf》。),接下来我们介绍下FDCAN协议。
30.1.4 FDCAN协议
FDCAN发送的数据包由以下三个部分组成:
l 第一仲裁段 first arbitration phase,下图左蓝色部分
l 数据段 data phase,下图中红色部分
l 第二仲裁段 second arbitration phase,下图右蓝色部分

图30.1.4.1 标准的FDCAN数据帧
由上图,可以看出FDCAN协议与传统的CAN协议非常相似。下面简要概述一个标准的FDCAN数据帧的构成,其中包括:
1、第一个仲裁阶段包含以下内容:
l 帧起始SOF,这个比较简单,与传统CAN协议一样由1个位的显性电平表示帧起始。
l ID号和其他位,表示消息的目的 (提供或请求数据)以及速度和格式配置
2、数据段包含以下内容:
l 数据长度码 (DLC) ,指示消息包含多少字节,该位有4bit,因此有16种不同的数据长度可供选择,分别为0~8、12、16、20、24、32、48和64,这些固定的字节数选择可以用来确定数据包中消息的长度。注意传统CAN模式下最大支持8字节。FDCAN模式下最大支持64字节。
l 用户希望发的数据。
l 循环冗余序列校验(CRC), 通过以下方式确保数据传输的完整性:17级多项式用于对16字节内的有效载荷进行CRC校验,,21级多项式用于对16字节与64字节之间的有效载荷进行校验。
l 显性位。
3、第二个仲裁阶段包含以下内容:
l 总线上其他节点发送的确认接收器(ACK)(如果至少有一个已成功接收到消息);
l 帧结束(EOF),在IFS期间不发送消息, 目的是将当前帧与下一帧分开。
与标准FDCAN帧格式相比,29位扩展帧的格式在标准帧的第一个仲裁阶段的IDE位之后会添加一个长度为18位的标识符。下图展示了扩展帧的格式。

图30.1.4.2 FDCAN扩展帧格式
扩展帧我们这里不多做介绍,下面我们以标准帧为例,来讲解下传统CAN2.0与FDCAN的区别:

图30.1.4.3 CAN和FDCAN的区别
CAN 2.0 中RTR为显性(dominant, 逻辑0)表示数据帧,为隐性(recessive,逻辑1)表示远程帧。
FDCAN仅支持数据帧,RTR始终为显性数据帧,可以理解为保留不用。
IDE 位保持不变,用于区分标准帧还是扩展帧。FDCAN在控制字段中新增了3个位:
l EDL,Extend data length,扩展数据长度位,隐性(逻辑1)表示FDCAN帧,显性(逻辑0,R0)表示CAN2.0帧
l BRS,Bit rate switching,位速率切换,指是否切换高速率传输,如从500K切换到2M。
l ESI,Error state indicator,错误状态指示器,指示节点处于 error-active模式还是 error-passive模式。
DLC(Data Length Code)是数据长度代码的缩写,用于指示数据包中数据的长度。在CAN 2.0和FDCAN中,DLC处于相同的位置,并且均为4bit长度,但CAN2.0和FDCAN的最大数据有效载荷不同,如下表所示:

表30.1.4.1 CAN和FDCAN数据长度
由表30.1.4.1可知FDCAN的有效载荷从CAN 2.0的最大8字节提升到最大64字节从而改善了网络带宽,减少了对多包处理的需求。因此,通过增加CRC字段的位数以增强消息完整性成为必要措施:
l 有效载荷在16字节及以内,CRC以17-bit编码;
l 有效载荷在20字节及以上,CRC以21-bit编码。
下表汇总了FDCAN和CAN2.0之间的主要差异。相较于CAN2.0,FDCAN的主要改进特点在于其增加了数据有效载荷,并且通过可用的BRS、EDL和ESI位等功能,实现了更高的传输速率。

表30.1.4.2 CAN2.0和FDCAN的主要差异
介绍完CAN2.0与FDCAN的区别后,接下来我们将重点介绍STM32的FDCAN外设的特点。
STM32G474自带的是FDCAN,它支持CAN协议2.0A、2.0B和CAN-FD V1.0。它的设计目标是,以最小的CPU负荷来高效处理大量收到的报文。它也支持报文发送的优先级要求(优先级特性可软件配置)。对于安全紧要的应用,FDCAN提供所有支持时间触发通信模式所需的硬件功能。
STM32G474的FDCAN的主要特点有:
l 符合CAN协议版本2.0A/B和ISO 11898-1
l 最多支持64个字节数据
l 支持错误记录
l 更好的接收过滤水平
l 支持AUTOSAR和J1939
l 两个可配置的接收FIFO
l 支持回环模式
l 仲裁段位速率最高1Mbit/s, 数据段位速率最高8Mbit/s
在STM32G474VET6中,带有3个FDCAN控制器,而我们本章只用了一个FDCAN,即FDCAN1。FDCAN的框图如图30.1.3.9所示:

图30.1.3.9 FDCAN框图
① 双中断线
从图中可以看出,FDCAN提供了两个中断线:fdcan_intr0_it和fdcan_intr1_it。可以通过寄存器FDCAN_ILE的EINT0和EINT1这两个位来使能或者关闭这两个中断。
② CAN内核
CAN Core包含协议控制器和收发移位寄存器,它支持ISO 11898-1:2015的所有协议功能,支持11位和29位ID。
③ 同步
Sync同步单元用于同步APB时钟信号和CAN内核时钟。
④ 发送处理
TX Handler负责将消息RAM中的数据发送到CAN内核,最多可以给发送单元配置32个发送buffer。
⑤ 接收处理
RX Handler负责将CAN内核的数据传输到外部消息RAM中,RX Handler支持两个接收FIFO,每个FIFO可以配置64个专用的buffer。
⑥ APB接口
连接FDCAN到APB总线上。
⑦ 消息RAM接口
下一小节详细讲解消息RAM的接口。
30.1.5 消息RAM
消息RAM是FDCAN的核心部分,消息RAM就是一段1KB的内存,在HAL库中提供了函数FDCAN_CalcultateRamBlockAddress来设置这段内存,这段内存分成了不同的区域,不同的区域代表的含义不同。STM32G474的FDCAN在消息RAM实现了过滤器、接收FIFO、接收buffer、发送事件和发送buffer。消息RAM的内存分配如图30.1.5.1所示:

图30.1.5.1 消息RAM
消息RAM中一个元素的大小是32位,也就是4字节。我们来看一下这些内存都是怎么被分配的:
FLSSA:这个区域用来存放11位过滤ID,后面“28 elements/28 words”中的“28 elements”表示这个区域占用了28个字,“28 words”表示SIDFC.FLSSA这个区域一共有28个字,下面的其他区域同理。在以前的STM32中(如STM32F7),过滤是通过寄存器来配置的,比如设置过滤ID等,在STM32G474中是通过设置FLSSA和FLESA这两个区域。
FLESA: 这个区域用来存放29位过滤ID,这个区域用了8字,一共有16个字。
F0SA:接收FIFO0,这个区域用了3个字,一共有54个字。
F1SA:接收FIFO1,这个区域用了3个字,一共有54个字。
EFSA:发送事件FIFO,这个区域用了3个字,一共有6个字。
TBSA:发送buffer,这个区域用了3个字,一共有54个字。
接下来我们详细的分析一下消息RAM中这些区域的具体含义:
1、FLSSA
FLSSA是标准消息ID(11位)滤波区域,一共有28个元素,每个元素的具体bit含义如下图30.1.5.2所示:

图30.1.5.2 FLSSA
SFT[1:0](bit31:30):这两位用来表示过滤类型,一共有四种:
00 范围过滤,过滤到SFID1到SFID2中的所有信息。
01 双过滤,过滤到SFID1和SFID2中的所有信息。
10 传统位过滤,SFID1是过滤值,SFID2是掩码。
11 关闭过滤机制。
SFEC[2:0](bit29:27):标准过滤配置
000 关闭过滤机制。
001 当过滤匹配以后存储在Rx FIFO0中。
010 当过滤匹配以后存储在Rx FIFO1中。
011 当过滤匹配以后丢弃。
100 当过滤匹配以后设置优先级。
101 当过滤匹配以后存储在Rx FIFO0中并设置优先级。
110 当过滤匹配以后存储在Rx FIFO1中并设置优先级。
111 保留。
SFID1[10:0](bit26:16):标准过滤ID1,第一个要过滤的ID。
SFID2[10:0](bit10:0):标准过滤ID2,第二个要过滤的ID。
2、FLESA
FLESA是扩展消息ID(29位)滤波区域,一共有8个元素,每个元素的具体bit含义如下图30.1.5.3所示:

图30.1.5.3 FLESA
F0 EFEC[2:0](bit31:29):扩展过滤配置
000 关闭过滤机制
001 当过滤匹配以后存储在Rx FIFO0中。
010 当过滤匹配以后存储在Rx FIFO1中。
011 当过滤匹配以后丢弃。
100 当过滤匹配以后设置优先级。
101 当过滤匹配以后存储在Rx FIFO0中并设置优先级。
110 当过滤匹配以后存储在Rx FIFO1中并设置优先级。
111 保留。
F0 EFID1[28:0](bit28:0):扩展过滤ID1。
F1 EFT[1:0](bit31:30):扩展过滤类型
00 范围过滤,过滤到EF1ID到EF2ID中的所有信息。
01 双过滤,过滤到EF1ID和EF2ID中的信息。
10 传统位过滤,EF1ID是过滤值,EF2ID是掩码。
11 范围过滤,过滤到ED1ID到ED2ID中的所有信息,没有使用FDCAN_XIDAM的掩码。
F1 EFID2[28:0](bit28:0): 扩展过滤ID2。
3、F0SA和F1SA
这两个分别为Rx FIFO0和Rx FIFO1,它们的bit含义都是一样的,见图30.1.5.4所示:

图30.1.5.4 Rx FIFO
R0 ESI(bit31):错误状态标志
0 传输节点显性错误
1 传输节点隐性错误
R0 XTD(bit30):标记是标准ID还是扩展ID
0 11位标准ID
1 29位扩展ID
R0 RTR(bit29):遥控传输请求,标记当前帧是数据帧还是遥控帧
0 接收到的帧是数据帧
1 接收到的帧是遥控帧
R0 ID[28:0](bit28:0):帧ID,根据XTD位决定是11位ID还是29位ID。
R1 ANMF(bit31):0 接收到的帧和FIDX中的过滤帧匹配
1 接收到的帧和Rx滤波器中的任何元素都不匹配
R1 FIDX[6:0](bit30:24):过滤器索引,0~127,表示和Rx滤波器中的哪个元素匹配。
R1 FDF(bit21):帧格式
0 标准帧格式
FDCAN帧格式
R1 BRS(bit20):比特率切换
0 接收到的帧没有比特率切换
1 接收到的帧有比特率切换
R1 DLC[3:0](bit):数据长度。
0-8 传统CAN+CAN FD,接收帧长度为0~8字节。
9-15 传统CAN,接收帧长度为8字节。
9-15 CAN FD模式的话表示接收到的长度12/16/20/24/32/48/64字节。
R1 RXTS[15:0](bit15:0):接收时间戳。
R2-Rn:接收到的数据
4、EFSA
这个域是Tx event FIFO,一共是3个元素,每个元素的具体含义如下图30.1.5.6所示:

图30.1.5.6 Tx event FIFO
E0 ESI(bit31):错误状态标志。
0 传输节点显性错误
1 传输节点隐性错误
E0 XTD(bit30):标记是标准ID还是扩展ID。
0 11位标准ID
1 29位扩展ID
E0 RTR(bit29):遥控传输请求,标记当前帧是数据帧还是遥控帧。
0 发送的帧是数据帧
1 发送的帧是遥控帧
E0 ID[28:0](bit28:0):帧ID,根据XTD位决定是11 位ID还是29位ID。
E1 MM[7:0](bit31:24):消息掩码,从Tx buffer拷贝到Tx Event FIFO中。
E1 EFC(bit23:22):事件类型。
00 保留。
01 发送事件。
10 发送取消(仍然在传输)。
11 保留。
E1 EDL(bit21):扩展数据长度。
0 标准帧格式
1 FDCAN帧格式
E1 BRS(bit20):比特率切换。
0 发送的帧没有比特率切换
1 发送的帧带有比特率切换
E1 DLC[19:16](bit):数据长度。
0-8 传统CAN+CAN FD,长度为0~8字节。
9-15 长度为8字节。
E1 TXTS[15:0](bit15:0):时间戳。
可以看出消息RAM和寄存器一样的,都可以设置相应的bit来完成不同的功能。关于消息RAM中各个域就讲到这里,后面设置滤波器、发送和接收数据都跟消息RAM的各个域有关。
30.1.6 位时间特性
接下来,我们简单看看STM32G474的FDCAN位时间特性,因为FDCAN的波特率就是通过位时间来设置的。STM32G474的FDCAN位时间有3段:同步段(SYNC_SEG)、时间段1(BS1)和时间段2(BS2)。STM32G474的同步段长度为1个时间单元tq,BS1段可以设置为1~16个时间单元tq,BS2段可以设置1~8个时间单元tq。STM32G474的CAN位时序如图30.1.6.1所示:

图30.1.6.1 STM32G474 CAN时序
FDCAN的波特率计算公式如下:
baudrate = 1/bit time。
bit time = tSyncSeg+tBS1+tBS2。
对于标称位时间
tq = (FDCAN_NBTP.NBRP[8:0]+1) * tfdcan_tq_ck
tSyncSeg = 1 * tq
tBS1 = tq * (FDCAN_NBTP.NTSEG1[7:0]+1)
tBS2 = tq * (FDCAN_NBTP.NTSEG2[6:0]+1)
对于数据位时间
tq = (FDCAN_DBTP.DBRP[4:0]+1) * tfdcan_tq_ck
tSyncSeg = 1 * tq
tBS1 = tq * (FDCAN_DBTP.DTSEG1[4:0]+1)
tBS2 = tq * (FDCAN_DBTP.DTSEG2[3:0]+1)
上面波特率计算公式有标称位时间和数据位时间两种。我们这里使用标称位时间,波特率计算公式可以简化为:

当使用可变位速率的FDCAN模式才需要用到数据位时间的计算,计算公式可以简化为:

我们只需要确定:ffdcan_tq_ck、FDCAN_NBTP.NTSEG1[7:0]、FDCAN_NBTP.NTSEG2[6:0]、FDCAN_NBTP.NBRP[8:0]、、的值,就可以确定CAN的波特率和数据段位速率是多少了。
其中,ffdcan_tq_ck是CAN的时间片时钟,用于生成位时序,其来源如图30.1.6.2:

图30.1.6.2 CAN时间片时钟来源图
图中fdcan_ck是CAN内核时钟,该时钟经过处理后,生成fdcan_tq_ck时钟,输入CAN内核,作为CAN时间片时钟,用于生成位时序。关于位时间特性的详细内容,可以参考《STM32G4xx参考手册_V7(英文版).pdf》。
30.1.7 过滤器设置
FDCAN提供了过滤器设置,通过过滤器可以设置允许接收哪些ID的消息。前面讲解消息RAM的时候详细的讲过FLSSA和FLSSA这两个域,分别为标准ID过滤器和扩展ID过滤器。我们以标准ID过滤器为例讲解如何设置FDCAN的过滤器。
标准ID过滤器有三种过滤模式:
1、 指定范围过滤
通过SIDFC.FLSSA的SFID1和SFID2来设置需要过滤的ID范围,其中SFID2的值要大于SFID1,这样只有ID值在SFID1~SFID2之内的消息才能被接收到。比如我们现在要设置只接收ID在0X123~0X321范围内的消息,使用标准滤波器n,SIDFC.FLSSAn(n=0~128)的各个位设置如下:
SIDFC.FLSSAn.SFT=0 /* 范围滤波 */ SIDFC.FLSSAn.SFEC=1 /* 如果滤波匹配成功的话将消息保存到Rx FIFO中 */ SIDFC.FLSSAn.SFID1=0x123 /* ID1 */ SIDFC.FLSSAn.SFID2=0x321 /* ID2 */
2、指定ID过滤
我们也可以设置只接收指定的一个或者两个ID的消息,如果只接收指定的一个ID消息的话SFID1=SFID2.比如我们要设置只接收ID为0X123的消息,设置如下:
SIDFC.FLSSAn.SFT=1 /* 特定ID滤波 */ SIDFC.FLSSAn.SFEC=1 /* 如果滤波匹配成功的话将消息保存到Rx FIFO中 */ SIDFC.FLSSAn.SFID1=0x123 /* ID1 */ SIDFC.FLSSAn.SFID2=0x123 /* ID2 */
3、传统的位过滤
第三种过滤模式就是以前STM32的CAN上存在的过滤模式,在屏蔽位模式下,过滤消息ID和过滤掩码一起工作决定接收哪些消息,其中SFID1为过滤的消息ID,SFID2为过滤掩码。
举个简单的例子,我们设置过滤器SIDFC.FLSSAn工作在:传统过滤模式,然后设置如下:
SIDFC.FLSSAn.SFT=2 /* 传统位过滤 */ SIDFC.FLSSAn.SFEC=1 /* 如果滤波匹配成功的话将消息保存到Rx FIFO中 */ SIDFC.FLSSAn.SFID1=0xFF00 /* ID1 */ SIDFC.FLSSAn.SFID2=0xF000 /* 掩码 */
其中SFID1是我们期望接收到的消息ID,我们希望最好接收到ID=0XFF00的消息。SFID2的0XF000规定了我们必须关心的ID,也就是接收到的消息ID其位[15:12]必须和SFID1中的位[15:12]完全一样,其他的位不关心。也即是说接收到的消息ID必须是0XFFxx这样的才算正确(x表示不关心)。
关于滤波设置的详细接收请参考《STM32G4xx参考手册_V7(英文版).pdf》。
30.1.8 FDCAN/CAN收发工程
1、FDCAN/CAN接收流程
CAN接收到消息以后存放到FIFO0或者FIFO1中,至于是放到FIFO0还是FIFO1中,我们在初始化CAN的时候会设置。如果消息满足过滤器设置的话消息就存放到FIFO中,当FIFO满了以后就不能再存任何消息了,除非有消息从FIFO中读出,读出消息就是直接从FIFO读数据。比如HAL库中从FIFO中读取消息的函数HAL_FDCAN_GetRxMessage处理方式如下:
GetIndex+=((hfdcan->Instance->RXF0S&FDCAN_RXF0S_F0GI>> FDCAN_RXF0S_F0GI_Pos); /* 计算索引 */ RxAddress = (uint32_t *)(hfdcan->msgRam.RxFIFO0SA + (GetIndex * SRAMCAN_RF0_SIZE)); /* 要读取的FIFO地址 */
上面代码我们已经得到了消息的FIFO地址,接下来就是恢复数据了,处理方法如下:
pRxHeader->IdType = *RxAddress & FDCAN_ELEMENT_MASK_XTD; pRxHeader->Identifier = ((*RxAddress & FDCAN_ELEMENT_MASK_STDID) >> 18); pRxHeader->RxFrameType = (*RxAddress & FDCAN_ELEMENT_MASK_RTR); pRxHeader->ErrorStateIndicator = (*RxAddress & FDCAN_ELEMENT_MASK_ESI); …… /* 读取消息数据 */ pData = (uint8_t *)RxAddress; for (ByteCounter=0;ByteCounterDataLength >> 16]; ByteCounter++) { pRxData[ByteCounter] = pData[ByteCounter]; }
关于CAN的接收详细过程,可以参考HAL库中的函数HAL_FDCAN_GetRxMessage。
2、CAN发送流程
CAN的发送流程和CAN的接收流程刚好相反,我们需要将消息按照TBSA格式构建好,然后写入到发送FIFO(buffer)中,写进去以后设置TXBAR寄存器的指定位为1来请求传输。HAL库中函数HAL_FDCAN_AddMessageToTxFifoQ来完成此工作,处理部分如下:
PutIndex=((hfdcan->Instance->TXFQS&FDCAN_TXFQS_TFQPI)>>FDCAN_TXFQS_TFQPI_Pos); /* 获取索引 */ FDCAN_CopyMessageToRAM(hfdcan, pTxHeader, pTxData, PutIndex); /* 拷贝消息到FIFO */ hfdcan->Instance->TXBAR = ((uint32_t)1 << PutIndex);
30.1.9 FDCAN寄存器
接下来,我们介绍一下本章需要用到的一些比较重要的寄存器。
l FDCAN的控制寄存器(FDCAN_CCCR)
FDCAN的控制寄存器(FDCAN_CCCR)各位描述如图30.1.9.1所示:

图30.1.9.1 寄存器FDCAN_CCCR各位描述
该寄存器的详细描述,请参考《STM32G4xx参考手册_V7(英文版).pdf》,这里我们仅介绍几个重要的位:
INIT位,该位用来控制初始化请求。软件对该位清0,可使CAN从初始化模式进入正常工作模式。软件对该位置1可使CAN从正常工作模式进入初始化模式。所以我们在CAN初始化的时候,先要设置该位为1,然后进行初始化,之后再设置该位为0,让CAN进入正常工作模式。
FDOE位,此位用来设置是否使用CAN FD功能,如果使用CAN FD功能的话就将此位置1,此位清零的话就关闭CAN FD功能。
DAR位,此位用来设置是否使能自动重传机制,如果要使用自动重传的话就将此位置1,此位清零就关闭自动重传,经过作者测试,如果FDCAN要工作在传统CAN模式的话一定要关闭自动重传机制,否则通信会失败!!!
MON位,此位设置是否使能总线监视模式,此位置1的话就会使能总线监视模式,此位清零关闭总线监视模式。总线监视模式用来检测CAN总线的通信状况。
TEST位,此位用来设置FDCAN工作在正常模式还是测试模式,如果要工作在正常模式的话此位清零,如果要使用模式的话就将此位置1。
l FDCAN标称位时序寄存器(FDCAN_NBTP)
该寄存器用于设置分频、tBS1、tBS2以及tSJW等非常重要的参数,直接决定了CAN的波特率,该寄存器各位描述如图30.1.9.2所示:

图30.1.9.2 寄存器FDCAN_NBTP各位描述
l FDCAN数据位时序寄存器(FDCAN_DBTP)
该寄存器用于设置数据位速率的分频、tBS1、tBS2以及tSJW等非常重要的参数,直接决定了FDCAN位速率,注意STM32G474的FDCAN数据段位速率最高8Mbit/s。该寄存器只有在设置了CCCR寄存器的CCE和INT位时,才可以写,并且该寄存器仅在使用可变位速率的FDCAN模式时,才有作用。该寄存器各位描述如图30.1.9.3所示:

图30.1.9.3 寄存器FDCAN_DBTP各位描述
l FDCAN测试寄存器(FDCAN_TEST)
FDCAN测试寄存器(FDCAN_TEST),该寄存器各位描述如图30.1.9.4所示:

图30.1.9.4 FDCAN测试模式寄存器
该寄存器我们重点介绍一个LBCK位,此位用来使能回测模式,此位置1的时候就会使能FDCAN的回测模式,一共有两种回环模式:外部回环模式和内部回环模式。
外部回环模式
要进入外部回环模式需要将FDCAN_CCCR的TEST和FDCAN_TEST的LBCK这两个位置1。在外部回环模式下,FDCAN把发送的报文当做接收的报文并保存(如果可以通过接收过滤)在接收FIFO里。也就是说外部回环模式是一个自发自收的模式,如图30.1.9.5所示:

图30.1.9.5 FDCAN外部回环模式
外部回环模式提供了硬件自测。为了避免外部的影响,在回环模式下FDCAN内核忽略确认错误(在数据/远程帧的确认位时刻,不检测是否有显性位)。在回环模式下,FDCAN在内部把Tx输出回馈到Rx输入上,而完全忽略Rx引脚的实际状态。发送的报文可以在Tx引脚上检测到。这里的外部回环模式就是以前STM32的回环模式。
内部回环模式
如果要使用内部回环模式的话,需要将FDCAN_CCCR的TEST、FDCAN_TEST的LBCK和FDCAN_CCCR的MON这三个位都置1。内部回环模式如图30.1.9.6所示:

图30.1.9.6 内部回环模式
从图30.1.9.6可以看出,内部回环模式下Tx引脚输出显性电平,因此测试的时候Tx引脚不会输出报文。外部回环模式下Tx是会输出报文的。这个模式可以用于热机自测,测试的时候不会影响到CAN总线上的正常数据收发。
l FDCAN发送buffer请求寄存器(FDCAN_TXBAR)
该寄存器各位描述如图30.1.9.7所示:

图30.1.9.7 寄存器FDCAN_TXBAR各位描述
此寄存器用来设置FDCAN的哪个发送buffer可以发送数据,FDCAN有3个发送buffer,我们发送数据的时候就需要将所要发送的数据拷贝到这些buffer中,拷贝完成以后就需要标记此buffer可以发送。
30.2 硬件设计
1. 例程功能
本章包括两个实验,分别为《传统CAN模式实验》的《FDCAN模式实验》。这两个实验的功能和现象基本相同,唯一的区别在于使用的通信模式不同。在实验中,通过KEY2按键选择CAN的工作模式(正常模式/回环模式),然后通过KEY0控制数据发送,接着查询是否有数据接收到,假如接收到数据,就将接收到的数据显示在LCD模块上。如果是回环模式,我们不需要2个开发板。如果是正常模式,我们就需要2个开发板,并且将他们的CAN接口对接起来,然后一个开发板发送数据,另外一个开发板将接收到的数据显示在LCD模块上。
2. 硬件资源
1) LED灯
LED0–PE0
2) KEY0和KEY2按键
KEY0–PE12
KEY2–PE14
3) 1.3寸TFTLCD模块(SPI接口)
4) STM32自带CAN控制器
5) CAN收发芯片SIT1042T/TPT1051V
3. 原理图
STM32有CAN的控制器,但要实现CAN通讯的差分电平,我们还需要借助外围电路来实现,根据我们需要实现的程序功能,我们设计电路原理如下:

图30.2.1 CAN连接原理设计
从上图可以看出:STM32G474的CAN连接到SIT1042T/TPT1051V收发芯片(支持FDCAN),然后通过接线端子(CAN)同外部的CAN总线连接,在G474电机开发板上带有120Ω终端电阻,如果开发板不作为CAN的终端的话,需要把这个电阻去掉,以免影响通信。
最后,我们用2根导线将两个开发板CAN端子的CAN_L和CAN_L,CAN_H和CAN_H连接起来。这里注意不要接反了(CAN_L接CAN_H是错误接法),接反了会导致通讯异常!!
30.3 程序设计
30.3.1 FDCAN的HAL库驱动
CAN在HAL库中的驱动代码在stm32g4xx_hal_fdcan.c文件(及其头文件)中。
1. HAL_FDCAN_Init函数
要使用一个外设首先要对它进行初始化,所以先看FDCAN的初始化函数,其声明如下:
HAL_StatusTypeDef HAL_FDCAN_Init(FDCAN_HandleTypeDef *hfdcan);
l 函数描述:
用于FDCAN控制器的初始化。
l 函数形参:
形参1是FDCAN的控制句柄,结构体类型是FDCAN_HandleTypeDef,其定义如下:
typedef struct
{
FDCAN_GlobalTypeDef *Instance; /* FDCAN控制寄存器基地址 */
FDCAN_InitTypeDef Init; /* 初始化所需参数 */
FDCAN_MsgRamAddressTypeDef msgRam; /* 消息内存块 */
uint32_t LatestTxFifoQRequest; /* 缓冲区索引 */
__IO HAL_FDCAN_StateTypeDef State; /* CAN通讯状态 */
HAL_LockTypeDef Lock; /* 锁定对象 */
__IO uint32_t ErrorCode; /* CAN通讯结果编码 */
} FDCAN_HandleTypeDef;
该结构体除了State,Lock,和ErrorCode三个HAL库处理状态过程变量之外,只有3个成员变量需要我们外部设置。
1)Instance:指向FDCAN寄存器基地址。这里我们使用FDCAN1,设置为FDCAN1即可。
2)msgRam:这个用来保存消息RAM的地址。
3)Init:它是FDCAN_InitTypeDef结构体类型,该结构体定义为:
typedef struct
{
uint32_t ClockDivider; /* 时钟分配 */
uint32_t FrameFormat; /* 帧格式 */
uint32_t Mode; /* FDcan操作模式 */
FunctionalState AutoRetransmission; /* 禁止/使能自动重传模式 */
FunctionalState TransmitPause; /* 禁止/使能传输暂停功能 */
FunctionalState ProtocolException; /* 禁止/使能协议异常处理 */
uint32_t NominalPrescaler; /* 指定振荡器频率的值 */
uint32_t NominalSyncJumpWidth; /* 指定FDCAN最大时间量 */
uint32_t NominalTimeSeg1; /* 比特段1中的时间量子数 */
uint32_t NominalTimeSeg2; /* 比特段2中的时间量子数 */
uint32_t DataPrescaler; /* 指定振荡器频率的值 */
uint32_t DataSyncJumpWidth; /* 指定FDCAN最大时间量 */
uint32_t DataTimeSeg1; /* 比特段1中的时间量子数 */
uint32_t DataTimeSeg2; /* 比特段2中的时间量子数 */
uint32_t StdFiltersNbr; /* 标准消息ID筛选器的数目 */
uint32_t ExtFiltersNbr; /* 扩展消息ID筛选器的数目 */
uint32_t TxFifoQueueMode; /* Tx FIFO队列模式选择 */
} FDCAN_InitTypeDef;
这个结构体看起来成员变量比较多,实际上参数可以分为两类。前面16个参数是用来设置完成FDCAN的通用设置,比如FrameFormat用来设置FDCAN是工作在CAN FD模式还是传统的CAN模式。Mode是设置FDCAN工作模式的参数,本实验需要用到内部回环模式(FDCAN_MODE_INTERNAL_LOOPBACK,数值3)和常规模式(FDCAN_MODE_NORMAL数值是0)。
其他设置波特率相关的参数NominalPrescaler,NominalSyncJumpWidth,NominalTimeSeg1和NominalTimeSeg2分别用来设置波特率分频器,重新同步跳跃宽度以及时间段1和时间段2占用的时间单元数。注意:NominaPrescaler,NominalTimeSeg1和NominalTimeSeg2这三个参数分别对应寄存器:FDCAN_NBTP的NTSEG1[7:0]、NTSEG2[6:0]和NBRP[8:0]的值加1,即:
NominalPrescler = NBRP[8:0] + 1
NominalTimeSeg1 = NTSEG1[7:0] + 1
NominalTimeSeg2 = NTSEG2[6:0] + 1
因此,CAN通信波特率的计算公式可以简化为:

与数据段通信速率相关的参数DataPrescaler,DataSyncJumpWidth,DataTimeSeg1和DataTimeSeg2分别用来设置数据段速率分频器,重新同步跳跃宽度以及时间段1和时间段2占用的时间单元数。注意:DataTimeSeg1和DataTimeSeg2,DataPrescaler这三个参数分别对应寄存器:FDCAN_DBTP的DTSEG1[4:0]、DTSEG2[3:0]和DBRP[4:0]的值加1,即:
DataPrescaler= DBRP[4:0] + 1
DataTimeSeg1= DTSEG1[4:0] + 1
DataTimeSeg2= DTSEG2[3:0]+ 1
因此,FDCAN的数据段通信速率的计算公式可以简化为:

后面Tx和Rx开头的成员变量用来设置FDCAN的收发FIFO,重点是RxFifo0ElmtSize和TxElmtSize这两个成员变量,这两个成员变量设置收发FIFO的元素大小,也就是收发一帧数据大小,当我们使用的是传统CAN模式时,最大只能是8字节,当我们使用的是FDCAN模式时,最大能到64字节。
l 函数返回值:
HAL_StatusTypeDef枚举类型的值,有4个,分别是HAL_OK表示成功,HAL_ERROR表示错误,HAL_BUSY表示忙碌,HAL_TIMEOUT为超时。
调用初始化函数之后,同样我们需要重定义HAL_FDCAN_MspInit来初始化跟底层硬件相关的配置,我们后面编写初始化函数时用到。
2. HAL_FDCAN_ConfigFilter函数
FDCAN的接收过滤器是属于硬件,可以根据软件的设置,在接收报文的时候,可以过滤出符合过滤器配置条件的报文ID,大大节省了CPU的开销,过滤器配置函数定义如下:
HAL_StatusTypeDef HAL_FDCAN_ConfigFilter(FDCAN_HandleTypeDef *hfdcan,FDCAN_FilterTypeDef *sFilterConfig)
l 函数描述:
用于配置FDCAN的接收过滤器。
l 函数形参:
形参1是FDCAN的控制句柄指针,初始化函数已经介绍过它的结构了,这里不重复了。
形参2是过滤器的结构体,这个是根据STM32的FDCAN过滤器模式设置的一些配置参数,它的结构如下:
typedef struct
{
uint32_t IdType; /* 过滤器标识符类型 */
uint32_t FilterIndex; /* 指定要初始化的筛选器 */
uint32_t FilterType; /* 过滤类型*/
uint32_t FilterConfig; /* 过滤配置 */
uint32_t FilterID1; /* 筛选器标识1 */
uint32_t FilterID2; /* 筛选器标识2 */
} FDCAN_FilterTypeDef;
结构体一共有6个成员变量,第一个参数IdType设置ID类型,有两种:FDCAN_STANDARD_ID和FDCAN_EXTENDED_ID。本实验中使用FDCAN_STANDARD_ID,即标准ID。第2个至第6个是用来设置过滤器类型和过滤器的32位id以及32位msak id。
l 函数返回值:
我们只关注HAL_OK的情况。
3. HAL_FDCAN_AddMessageToTxFifoQ函数
使能CAN控制器以接入总线进行数据发送处理。
HAL_StatusTypeDef HAL_FDCAN_AddMessageToTxFifoQ(FDCAN_HandleTypeDef *hfdcan, FDCAN_TxHeaderTypeDef *pTxHeader,uint8_t *pTxData)
l 函数描述:
按需要配置完FDCAN相关参数后,使能FDCAN控制器进行发送消息。
l 函数形参:
形参1为hfdcan,指定要发送数据的FDCAN句柄。
形参2为FDCAN_TxHeaderTypeDef类型,这是个结构体,结构体描述如下:
typedef struct
{
uint32_t Identifier; /* 标识符 */
uint32_t IdType; /* 标识符类型 */
uint32_t TxFrameType; /* 要传输的消息帧类型 */
uint32_t DataLength; /* 数据长度 */
uint32_t ErrorStateIndicator; /* 错误状态指示器 */
uint32_t BitRateSwitch; /* 传输的Tx帧带/不带比特率交换 */
uint32_t FDFormat; /* Tx帧将以经典方式还是FD格式传输 */
uint32_t TxEventFifoControl; /* 事件FIFO控制 */
uint32_t MessageMarker; /* 消息标记 */
} FDCAN_TxHeaderTypeDef;
我们来看一下其中一些重要的成员变量:
Identifier是用来设置消息ID。
IdType用来设置ID类型,有FDCAN_STANDARD_ID和FDCAN_EXTENDED_ID两种可选。
TxFrameType用于设置要发送的数据帧类型,有FDCAN_DATA_FRAME和FDCAN_REMOTE_FRAME这两种类型。
DataLength设置要发送的数据长度。
BitRateSwitch参数用于设置位速率转换开关,该参数仅在FDCAN模式下有用,可选择可变速率FDCAN_BRS_ON或者不变速率FDCAN_BRS_OFF。
FDFormat参数用于设置发送数据帧的帧格式,可设置为传统的CAN格式或FDCAN格式。当发送传统的CAN帧时,应选择FDCAN_CLASSIC_CAN;当发送FDCAN帧时,则应选择FDCAN_FD_CAN。
l 函数返回值:
我们只关注HAL_OK的情况。
FDCAN的初始化配置步骤
1) FDCAN参数初始化(工作模式、波特率等)
HAL库通过调用FDCAN初始化函数HAL_FDCAN_Init完成对FDCAN参数初始化,详见例程源码。
注意:该函数会调用:HAL_FDCAN_MspInit函数来完成对FDCAN底层的初始化,包括:FDCAN以及GPIO时钟使能、GPIO模式设置、中断设置等。
2) 开启FDCAN和对应管脚时钟,配置FDCAN_TX和FDCAN_RX的复用功能输出
首先开启FDCAN的时钟,然后配置FDCAN相关引脚为复用功能。本实验中FDCAN_TX对应的是PA12,FDCAN_RX对应的是PA11。他们的时钟开启方法如下:
__HAL_RCC_FDCAN_CLK_ENABLE(); /* 使能FDCAN */ __HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA时钟 */
IO口复用功能是通过函数HAL_GPIO_Init来配置的。
3) 设置滤波器
HAL库通过调用HAL_FDCAN_ConfigFilter完成FDCAN的滤波器相关参数初始化。
4) FDCAN数据接收和发送
通过调用HAL_FDCAN_AddMessageToTxFifoQ函数进行发送消息。
通过调用HAL_FDCAN_GetRxMessage函数进行接收数据。
至此,FDCAN就可以开始正常工作了。如果用到中断,就还需要进行中断相关的配置。本实验也提供FDCAN接收中断,详看例程源码,这里就不作介绍了。
30.3.2 程序流程图

图30.3.2.1 FDCAN通讯实验程序流程图
30.3.3 程序解析
本章的实验我们要使用LED、LCD、按键这些功能,所以直接复制上一个232实验的代码,把rs232的代码从工程中移除,并在Drivers/BSP目录下新建一个FDCAN文件夹,与之前一样,新建fdcan.c/fdcan.h文件并把它们加入到工程中。
30.3.3.1 传统CAN模式实验解析
1. fdcan.c函数
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。FDCAN驱动相关源码包括两个文件:fdcan.c和fdcan.h。
我们利用前面介绍的HAL库函数来配置FDCAN的接收时钟及模式等参数,配置过滤器以使能硬件自动过滤功能,最后使能FDCAN以开始FDCAN控制器的工作,编写FDCAN初始化函数。
/**
* @brief FDCAN初始化
* @param presc : 分频值,取值范围1~512;
* @param ntsjw : 重新同步跳跃时间单元.范围:1~128;
* @param ntsg1 : 时间段1的时间单元.取值范围2~256;
* @param ntsg2 : 时间段2的时间单元.取值范围2~128;
* @param mode : FDCAN_MODE_NORMAL,普通模式;
FDCAN_MODE_INTERNAL_LOOPBACK,内部回环模式;
FDCAN_MODE_EXTERNAL_LOOPBACK,外部回环模式;
FDCAN_MODE_RESTRICTED_OPERATION,限制操作模式
FDCAN_MODE_BUS_MONITORING,总线监控模式
* @note 以上5个参数, 除了模式选择其余的参数在函数内部会减1, 所以, 任何一个参数都不能等于0
* FDCAN其输入时钟频率为 Fpclk1 = PCLK1 = 170Mhz
* 波特率 = Fpclk1 / ((ntsg1 + ntsg2 + 1) * presc);
* 我们设置 can_init(17, 8, 11, 8, 1), 则CAN波特率为:
* 170M / ((11 + 8 + 1) * 17) = 500Kbps
* @retval 0, 初始化成功; 其他, 初始化失败;
*/
uint8_t fdcan_init(uint16_t presc, uint8_t ntsjw, uint16_t ntsg1, uint16_t ntsg2, uint32_t mode)
{
FDCAN_FilterTypeDef canx_rxfilter = {0};
HAL_FDCAN_DeInit(&g_fdcanx_handler); /* 先清除以前设置*/
g_fdcanx_handler.Instance = FDCAN1;
g_fdcanx_handler.Init.ClockDivider = FDCAN_CLOCK_DIV1;
g_fdcanx_handler.Init.FrameFormat = FDCAN_FRAME_CLASSIC; /* 传统模式 */
g_fdcanx_handler.Init.Mode = mode; /* 回环测试 */
/* 关闭自动重传!传统模式下一定要关闭!!! */
g_fdcanx_handler.Init.AutoRetransmission = DISABLE;
g_fdcanx_handler.Init.TransmitPause = DISABLE; /* 关闭传输暂停 */
g_fdcanx_handler.Init.ProtocolException = DISABLE; /* 关闭协议异常处理 */
/* FDCAN中仲裁段位速率最高1Mbit/s, 数据段位速率最高8Mbit/s */
/* 数据段通信速率(仅FDCAN模式需配置) = 42.5M / (1 + dseg1 + dseg2) = 42.5M / 、
(6 + 1 + 1) = 5.3 Mbit/s */
g_fdcanx_handler.Init.DataPrescaler = 4; /* 数据段分频系数范围:1~32 */
g_fdcanx_handler.Init.DataSyncJumpWidth = 16; /* 数据段重新同步跳跃宽度1~16 */
g_fdcanx_handler.Init.DataTimeSeg1 = 3; /* 数据段dsg1范围:1~32 5 */
g_fdcanx_handler.Init.DataTimeSeg2 = 1; /* 数据段dsg2范围:1~16 1 */
/* 仲裁段通信速率(FDCAN与传统CAN均需配置) = 10M / (1 + ntsg1 + ntsg2) = 10M /
(11 + 8 + 1) = 500Kbit/s */
g_fdcanx_handler.Init.NominalPrescaler = presc; /* 分频系数 */
g_fdcanx_handler.Init.NominalSyncJumpWidth = ntsjw; /* 重新同步跳跃宽度 */
g_fdcanx_handler.Init.NominalTimeSeg1 = ntsg1; /* ntsg1范围:2~256 */
g_fdcanx_handler.Init.NominalTimeSeg2 = ntsg2; /* ntsg2范围:2~128 */
g_fdcanx_handler.Init.StdFiltersNbr = 28; /* 标准ID滤波器编号0~28 */
g_fdcanx_handler.Init.ExtFiltersNbr = 8; /* 扩展ID滤波器编号0~8 */
/* 发送FIFO序列模式 */
g_fdcanx_handler.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;
if (HAL_FDCAN_Init(&g_fdcanx_handler) != HAL_OK)
{
return 1;
}
/* 配置CAN过滤器 */
canx_rxfilter.IdType = FDCAN_STANDARD_ID; /* 标准ID */
canx_rxfilter.FilterIndex = 0; /* 滤波器索引 */
canx_rxfilter.FilterType = FDCAN_FILTER_MASK; /* 滤波器类型 */
canx_rxfilter.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; /* 过滤器0关联FIFO0 */
canx_rxfilter.FilterID1 = 0x0000; /* 32位ID */
/* 如果FDCAN配置为传统模式的话,这里是32位掩码 */
canx_rxfilter.FilterID2 = 0x0000;
/* 过滤器配置 */
if (HAL_FDCAN_ConfigFilter(&g_fdcanx_handler, &canx_rxfilter) != HAL_OK)
{
return 2;
}
/* 配置全局过滤器,拒收所有不匹配的标准帧或扩展帧 */
if (HAL_FDCAN_ConfigGlobalFilter(&g_fdcanx_handler, FDCAN_REJECT,
FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE) != HAL_OK)
{
return 3;
}
/* 启动CAN外围设备 */
if (HAL_FDCAN_Start(&g_fdcanx_handler) != HAL_OK)
{
return 4;
}
HAL_FDCAN_ActivateNotification(&g_fdcanx_handler,
FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);
return 0;
}
fdcan_init该函数需要特别注意的几个点,首先,需要将g_fdcanx_handler.Init.FrameFormat设置为FDCAN_FRAME_CLASSIC,即将CAN的帧格式设置为传统CAN模式。其次,需要通过设置以下几个成员变量来设置CAN的通信波特率:
l g_fdcanx_handler.Init.NominalPrescaler
l g_fdcanx_handler.Init.NominalSyncJumpWidth
l g_fdcanx_handler.Init.NominalTimeSeg1
l g_fdcanx_handler.Init.NominalTimeSeg2
我们可以通过函数的入口参数进行传参来设置CAN的通信波特率,本实验中我们设置波特率为500Kbps,关于计算公式详见第30.1.6小节。值得注意的是,由于我们采用的是传统的CAN模式,因此在数据段速率方面的设置是无效的,我们无需理会。
过滤器的配置方面我们需要注意本实验我们使用的是位过滤模式(详见30.1.7小节)。并且FilterID2设置的是0x0000,也就是支持接收任何ID发过来的数据。
用HAL_FDCAN_Init后会调用HAL_FDCAN_MspInit,我们重定义这个函数,在函数中初始化我们用于控制FDCAN的收发引脚:
/**
* @brief FDCAN底层驱动,引脚配置,时钟配置,中断配置
此函数会被HAL_FDCAN_Init()调用
* @param hcan:FDCAN句柄
* @retval 无
*/
void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* hfdcan)
{
GPIO_InitTypeDef gpio_init_struct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
if (hfdcan->Instance == FDCAN1)
{
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN;
PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PCLK1;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
CAN_RX_GPIO_CLK_ENABLE(); /* CAN_RX脚时钟使能 */
CAN_TX_GPIO_CLK_ENABLE(); /* CAN_TX脚时钟使能 */
__HAL_RCC_FDCAN_CLK_ENABLE(); /* 使能FDCAN时钟 */
gpio_init_struct.Pin = CAN_TX_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
gpio_init_struct.Alternate = GPIO_AF9_FDCAN1;
/* CAN_TX脚 模式设置 */
HAL_GPIO_Init(CAN_TX_GPIO_PORT, &gpio_init_struct);
/* CAN_RX脚 必须设置成输入模式 */
gpio_init_struct.Pin = CAN_RX_GPIO_PIN;
HAL_GPIO_Init(CAN_RX_GPIO_PORT, &gpio_init_struct);
#if FDCAN1_RX0_INT_ENABLE
HAL_NVIC_SetPriority(FDCAN1_IT0_IRQn,1,2);
HAL_NVIC_EnableIRQ(FDCAN1_IT0_IRQn);
#endif
}
}
初始化函数就编写完成了,我们要设置它的工作波特率为500Kbps,设置工作模式为回环模式,参照各个参数的意义,我们最后用以下的配置来完成初始化设置:
fdcan_init(17, 8, 11, 8, FDCAN_MODE_INTERNAL_LOOPBACK);
要与其它的FDCAN节点设备通讯,我们需要编写FDCAN相关的收发函数。首先是发送函数,发送报文是有ID,所以我们在发送时需要设定ID,故需要设计一个形参为ID号,我们利用HAL库的发送函数封装一个更方便我们使用的函数,代码如下:
/**
* @brief CAN 发送一组数据
* @note 发送格式固定为: 标准ID, 数据帧
* @param len :数据长度,取值范围:FDCAN_DLC_BYTES_0 ~ FDCAN_DLC_BYTES_64
* @param msg :数据指针,最大为8个字节
* @retval 发送状态 0, 成功; 1, 失败;
*/
uint8_t fdcan1_send_msg(uint8_t *msg, uint32_t len)
{
g_fdcanx_txheader.Identifier = 0x12; /* 32位ID */
g_fdcanx_txheader.IdType = FDCAN_STANDARD_ID; /* 标准ID */
g_fdcanx_txheader.TxFrameType = FDCAN_DATA_FRAME; /* 数据帧 */
g_fdcanx_txheader.DataLength = len; /* 数据长度 */
g_fdcanx_txheader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
g_fdcanx_txheader.BitRateSwitch = FDCAN_BRS_OFF; /* 关闭速率切换 */
g_fdcanx_txheader.FDFormat = FDCAN_CLASSIC_CAN; /* 传统的CAN模式 */
g_fdcanx_txheader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; /* 无发送事件 */
g_fdcanx_txheader.MessageMarker = 0;
if(HAL_FDCAN_AddMessageToTxFifoQ(&g_fdcanx_handler,
&g_fdcanx_txheader, msg) != HAL_OK)
{
return 1;
}
return 0;
}
在FDCAN初始化时,我们对于过滤器的配置是不过滤任何报文ID,也就是说可以接收全部报文。但是我们可以编写接收函数时,使用软件的方式过滤报文ID,通过形参来跟接收到的报文ID进行匹配。接收函数代码具体如下:
/**
* @brief CAN 接收数据查询
* @note 接收数据格式固定为: 标准ID, 数据帧
* @param buf : 数据缓存区
* @retval 接收结果
* @arg 0 , 无数据被接收到;
* @arg 其他, 接收的数据长度
*/
uint8_t fdcan1_receive_msg(uint8_t *buf)
{
if(HAL_FDCAN_GetRxMessage(&g_fdcanx_handler, FDCAN_RX_FIFO0,
&g_fdcanx_rxheader, buf) != HAL_OK) /* 读取数据 */
{
return 0;
}
return g_fdcanx_rxheader.DataLength >> 16;
}
最后,我们可以把fdcan_send_msg函数加到USMART接口中,就可以方便地用串口来调试CAN接口。
2. main.c代码
在main.c里面编写如下代码:
int main(void)
{
uint8_t key;
uint8_t i = 0, t = 0;
uint8_t cnt = 0;
uint8_t canbuf[8];
uint8_t rxlen = 0;
uint8_t res;
uint8_t mode = 1; /* CAN工作模式: 0,普通模式; 1,环回模式 */
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(85, 2, 2, 4, 8); /* 设置时钟,170Mhz */
delay_init(170); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
usmart_dev.init(170); /* 初始化USMART */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
lcd_init(); /* 初始化LCD */
/* CAN初始化, 环回模式, 波特率500Kbps */
fdcan_init(17, 8, 11, 8, FDCAN_MODE_INTERNAL_LOOPBACK);
/* 显示提示信息 */
lcd_show_string(10, 10, 140, 32, 32, "STM32", RED);
lcd_show_string(10, 42, 140, 16, 16, "FDCAN TEST", RED);
lcd_show_string(10, 60, 200, 16, 16, "LoopBack Mode ", RED);
lcd_show_string(10, 80, 239, 16, 16, "KEY0:Send KEY2:Mode", RED);
lcd_show_string(10, 100, 200, 16, 16, "Count:", RED); /* 显示当前计数值 */
lcd_show_string(10, 120, 200, 16, 16, "Send Data:", RED); /* 提示发送的数据 */
/* 提示接收到的数据 */
lcd_show_string(10, 180, 200, 16, 16, "Receive Data:", RED);
while (1)
{
key = key_scan(0);
if (key == KEY0_PRES) /* KEY0按下,发送一次数据 */
{
for (i = 0; i < 8; i++)
{
canbuf[i] = cnt + i; /* 填充发送缓冲区 */
if (i < 4)
{
/* 显示数据 */
lcd_show_xnum(10 + i * 32, 140, canbuf[i], 3, 16, 0x80, BLUE);
}
else
{
lcd_show_xnum(10 + (i - 4) * 32, 160, canbuf[i], 3, 16,
0x80, BLUE); /* 显示数据 */
}
}
res = fdcan1_send_msg(canbuf, FDCAN_DLC_BYTES_8); /* 发送8个字节 */
if (res)
{
/* 提示发送失败 */
lcd_show_string(10 + 80, 120, 200, 16, 16, "Failed", BLUE);
}
else
{
/* 提示发送成功 */
lcd_show_string(10 + 80, 120, 200, 16, 16, "OK ", BLUE);
}
}
else if (key == KEY2_PRES) /* KEY2按下, 改变CAN的工作模式 */
{
mode = !mode;
/* CAN初始化, 普通(0)/回环(1)模式, 波特率500Kbps */
fdcan_init(17, 8, 11, 8, mode ? FDCAN_MODE_INTERNAL_LOOPBACK :
FDCAN_MODE_NORMAL);
if (mode == 0) /* 普通模式,需要2个开发板 */
{
lcd_show_string(10, 60, 200, 16, 16, "Normal Mode ", RED);
}
else /* 回环模式, 一个开发板就可以测试了. */
{
lcd_show_string(10, 60, 200, 16, 16, "LoopBack Mode ", RED);
}
}
rxlen = fdcan1_receive_msg(canbuf); /* 接收数据查询 */
if (rxlen) /* 接收到有数据 */
{
lcd_fill(10, 200, 230, 239, WHITE); /* 清除之前的显示 */
for (i = 0; i < rxlen; i++)
{
if (i < 4)
{
/* 显示数据 */
lcd_show_xnum(10 + i * 32, 200, canbuf[i], 3, 16, 0x80, BLUE);
}
else
{
lcd_show_xnum(10 + (i - 4) * 32, 220, canbuf[i], 3, 16,
0x80, BLUE); /* 显示数据 */
}
}
}
t++;
delay_ms(10);
if (t == 20)
{
LED0_TOGGLE(); /* 提示系统正在运行 */
t = 0;
cnt++;
lcd_show_xnum(10 + 48, 100, cnt, 3, 16, 0x80, BLUE); /* 显示数据 */
}
}
}
main函数的执行过程与程序流程图是一致的,这里需要注意:在选择正常模式的情况下,要使两个开发板通信成功,必须保持一致的波特率。其它细节,参考光盘资料中的源码即可。由于本章的两个实验下载验证结果一致,因此在本章末尾在进行实验现象的介绍。
30.3.3.2 FDCAN模式实验解析
该实验与传统CAN模式实验相似,我们将重点讲解两者在配置上的差异及对应的代码实现。
1. fdcan.c函数
在本实验中,我们将FDCAN的帧格式设置成位速率可变的FDCAN模式,如下:
/**
* @brief FDCAN初始化
* @param presc : 分频值,取值范围1~512;
* @param ntsjw : 重新同步跳跃时间单元.范围:1~128;
* @param ntsg1 : 时间段1的时间单元.取值范围2~256;
* @param ntsg2 : 时间段2的时间单元.取值范围2~128;
* @param mode : FDCAN_MODE_NORMAL,普通模式;
FDCAN_MODE_INTERNAL_LOOPBACK,内部回环模式;
FDCAN_MODE_EXTERNAL_LOOPBACK,外部回环模式;
FDCAN_MODE_RESTRICTED_OPERATION,限制操作模式
FDCAN_MODE_BUS_MONITORING,总线监控模式
* @note 以上5个参数, 除了模式选择其余的参数在函数内部会减1, 所以, 任何一个参数都不能等于0
* FDCAN其输入时钟频率为 Fpclk1 = PCLK1 = 170Mhz
* 波特率 = Fpclk1 / ((ntsg1 + ntsg2 + 1) * presc);
* 我们设置 can_init(17, 8, 11, 8, 1), 则CAN波特率为:
* 170M / ((11 + 8 + 1) * 17) = 500Kbps
* @retval 0, 初始化成功; 其他, 初始化失败;
*/
uint8_t fdcan_init(uint16_t presc, uint8_t ntsjw, uint16_t ntsg1, uint16_t ntsg2, uint32_t mode)
{
FDCAN_FilterTypeDef canx_rxfilter = {0};
HAL_FDCAN_DeInit(&g_fdcanx_handler); /* 先清除以前的设置 */
g_fdcanx_handler.Instance = FDCAN1;
g_fdcanx_handler.Init.ClockDivider = FDCAN_CLOCK_DIV1;
g_fdcanx_handler.Init.FrameFormat = FDCAN_FRAME_FD_BRS;/* 速率变换FDCAN模式*/
g_fdcanx_handler.Init.Mode = mode; /* 回环测试 */
/* 使能自动重传!传统模式下一定要关闭!!! */
g_fdcanx_handler.Init.AutoRetransmission = ENABLE;
g_fdcanx_handler.Init.TransmitPause = ENABLE; /* 使能传输暂停 */
g_fdcanx_handler.Init.ProtocolException = DISABLE; /* 关闭协议异常处理 */
/* FDCAN中仲裁段位速率最高1Mbit/s, 数据段位速率最高8Mbit/s */
/* 数据段通信速率(仅FDCAN模式需配置) = 42.5M / (1 + dseg1 + dseg2) = 42.5M /
(6 + 1 + 1) = 5.3 Mbit/s */
g_fdcanx_handler.Init.DataPrescaler = 4; /* 数据段分频系数范围:1~32 */
g_fdcanx_handler.Init.DataSyncJumpWidth = 16; /* 数据段重新同步跳跃宽度1~16 */
g_fdcanx_handler.Init.DataTimeSeg1 = 6; /* 数据段dsg1范围:1~32 5 */
g_fdcanx_handler.Init.DataTimeSeg2 = 1; /* 数据段dsg2范围:1~16 1 */
/* 仲裁段通信速率(FDCAN与传统CAN均需配置) = 10M / (1 + ntsg1 + ntsg2) = 10M /
(11 + 8 + 1) = 500Kbit/s */
g_fdcanx_handler.Init.NominalPrescaler = presc; /* 分频系数 */
g_fdcanx_handler.Init.NominalSyncJumpWidth = ntsjw; /* 重新同步跳跃宽度 */
g_fdcanx_handler.Init.NominalTimeSeg1 = ntsg1; /* ntsg1范围:2~256 */
g_fdcanx_handler.Init.NominalTimeSeg2 = ntsg2; /* ntsg2范围:2~128 */
g_fdcanx_handler.Init.StdFiltersNbr = 28; /* 标准信息ID滤波器编号 */
g_fdcanx_handler.Init.ExtFiltersNbr = 8; /* 扩展信息ID滤波器编号 */
/* 发送FIFO序列模式 */
g_fdcanx_handler.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;
if (HAL_FDCAN_Init(&g_fdcanx_handler) != HAL_OK)
{
return 1;
}
/* 配置CAN过滤器 */
canx_rxfilter.IdType = FDCAN_STANDARD_ID; /* 标准ID */
canx_rxfilter.FilterIndex = 0; /* 滤波器索引 */
canx_rxfilter.FilterType = FDCAN_FILTER_MASK; /* 滤波器类型 */
canx_rxfilter.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; /* 过滤器0关联FIFO0 */
/* 第一个可被接受的32位ID */
canx_rxfilter.FilterID1 = 0x0000;
/* 第二个可被接受的32位ID,如果FDCAN配置为传统模式的话,这里是32位掩码 */
canx_rxfilter.FilterID2 = 0x0000;
/* 过滤器配置 */
if (HAL_FDCAN_ConfigFilter(&g_fdcanx_handler, &canx_rxfilter) != HAL_OK)
{
return 2;
}
/* 配置全局过滤器,拒收所有不匹配的标准帧或扩展帧 */
if (HAL_FDCAN_ConfigGlobalFilter(&g_fdcanx_handler, FDCAN_REJECT,
FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE) != HAL_OK)
{
return 3;
}
/* 启动CAN外围设备 */
if (HAL_FDCAN_Start(&g_fdcanx_handler) != HAL_OK)
{
return 4;
}
HAL_FDCAN_ActivateNotification(&g_fdcanx_handler,
FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);
return 0;
}
fdcan_init该函数需要特别注意的几个点,首先,需要将g_fdcanx_handler.Init.FrameFormat设置为FDCAN_FRAME_FD_BRS,即将CAN的帧格式设置为位速率变换的FDCAN模式。其次,需要通过设置以下几个成员变量来设置FDCAN的位速率:
l g_fdcanx_handler.Init.DataPrescaler = 4;
l g_fdcanx_handler.Init.DataSyncJumpWidth = 16;
l g_fdcanx_handler.Init.DataTimeSeg1 = 6;
l g_fdcanx_handler.Init.DataTimeSeg2 = 1;
通过设置以下几个成员变量来设置FDCAN的通信波特率:
l g_fdcanx_handler.Init.NominalPrescaler
l g_fdcanx_handler.Init.NominalSyncJumpWidth
l g_fdcanx_handler.Init.NominalTimeSeg1
l g_fdcanx_handler.Init.NominalTimeSeg2
本实验中我们设置通信波特率为500Kbps,位速率设置为5.3Mbit/s,关于计算公式详见第30.1.6小节。
过滤器的配置方面我们需要注意本实验我们使用的是位过滤模式(详见30.1.7小节)。并且FilterID2设置的是0x0000,也就是支持接收任何ID发过来的数据。
与传统CAN模式相比,本实验的fdcan_init函数的主要区别在于使用了可变位速率的FDCAN模式,而其他配置则基本保持一致。
接下来是FDCAN的发送和接收函数。其中,FDCAN的接收函数与传统CAN模式相同,仅有的区别在于传统CAN模式最大只能发送和接收8字节的数据,而FDCAN模式则可发送和接收最大64字节的数据。不过,在本实验中,我们均使用了8字节的数据,因此FDCAN的接收函数没有做出任何修改。而发送函数则有些许区别,下面我们来看一下FDCAN发送函数的区别说明。
/**
* @brief FDCAN 发送一组数据
* @note 发送格式固定为: 标准ID, 数据帧
* @param len :数据长度,取值范围:FDCAN_DLC_BYTES_0 ~ FDCAN_DLC_BYTES_64
* @param msg :数据指针,最大为64个字节
* @retval 发送状态 0, 成功; 1, 失败;
*/
uint8_t fdcan1_send_msg(uint8_t *msg, uint32_t len)
{
g_fdcanx_txheader.Identifier = 0x12; /* 32位ID */
g_fdcanx_txheader.IdType = FDCAN_STANDARD_ID; /* 标准ID */
g_fdcanx_txheader.TxFrameType = FDCAN_DATA_FRAME; /* 数据帧 */
g_fdcanx_txheader.DataLength = len; /* 数据长度 */
g_fdcanx_txheader.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
g_fdcanx_txheader.BitRateSwitch = FDCAN_BRS_ON; /* 开启速率切换 */
g_fdcanx_txheader.FDFormat = FDCAN_FD_CAN; /* FDCAN模式发送*/
g_fdcanx_txheader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; /* 无发送事件 */
g_fdcanx_txheader.MessageMarker = 0;
if(HAL_FDCAN_AddMessageToTxFifoQ(&g_fdcanx_handler, &g_fdcanx_txheader,
msg) != HAL_OK)
{
return 1;
}
return 0;
}
在FDCAN的发送函数中,与传统CAN模式相比较,不同之处在于开启了速率切换功能,以及将所发送的数据帧设置为位速率可变FDCAN数据帧格式。
以上是传统CAN模式与FDCAN模式的配置区别,其他配置基本保持一致。在main.c中的实现也是类似的,因此我们在此不再赘述。接下来,我们将直接观察两个实验的下载验证结果。
30.4 下载验证
本章包含两个实验,实验现象是完全相同的(仅模式以及速度有区别),所以我们在此统一进行讲解。在代码编译成功后,我们通过将代码下载到开发板上,并将其设置为回环模式。按下KEY0键后,即可在LCD模块上查看自发自收的数据,如图30.4.1所示:

图30.4.1 程序运行效果图
伴随LED0的不停闪烁,提示程序在运行。如果我们选择正常模式(KEY2按键切换),就必须连接两个开发板的CAN接口,然后就可以互发数据了。如图30.4.2和图30.4.3所示:

图30.4.2 CAN正常模式发送数据

图 30.4.3 CAN 正常模式接收数据
图30.4.2来自开发板A,发送了8个数据,图30.4.3来自开发板B,收到了来自开发板A的个数据。另外,利用USMART测试的部分,我们这里就不做介绍了,大家可自行验证下。