ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《STM32H7R7开发指南 V1.1 》第四十章 FDCAN通讯实验

《STM32H7R7开发指南 V1.1 》第四十章 FDCAN通讯实验

原创 嵌入式/内核开发 作者:正点原子 时间:2025-09-09 09:59:46 0 删除 编辑

第四十章 FDCAN通讯实验


       本章我们将向大家介绍如何使用 STM32H7R7自带的FDCAN控制器,相比于STM32以前自带的CAN,FDCAN性能更为强大, FDCAN每帧数据最大可以到64字节,FDCAN速度远超CAN的1Mbps,能够达到8Mbps甚至更高。本章我们就来实现两个开发板之间的FDCAN通讯,并将结果显示在TFTLCD模块上。本章分为如下几个部分:

       40.1 CAN/FDCAN简介

       40.2 硬件设计

       40.3 程序设计

       40.4 下载验证


        40.1 CAN/FDCAN总线简介


       40.1.1 CAN简介

       CAN是Controller Area Network的缩写(以下称为CAN),是ISO国际标准化的串行通信协议。在当前的汽车产业中,出于对安全性、舒适性、方便性、低公害、低成本的要求,各种各样的电子控制系统被开发了出来。由于这些系统之间通信所用的数据类型及对可靠性的要求不尽相同,由多条总线构成的情况很多,线束的数量也随之增加。为适应“减少线束的数量”、“通过多个LAN,进行大量数据的高速通信”的需要,1986年德国电气商博世公司开发出面向汽车的CAN通信协议。此后,CAN通过ISO11898及ISO11519进行了标准化,现在在欧洲已是汽车网络的标准协议。

       现在,CAN的高性能和可靠性已被认同,并被广泛地应用于于工业自动化、船舶、医疗设备、工业设备等方面。现场总线是当今自动化领域技术发展的热点之一,被誉为自动领域的计算机局域网。它的出现为分布式控制系统实现各节点之间实时、可靠的数据通信供了强有力的技术支持。

       CAN 控制器根据两根线上的电位差来判断总线电平。总线电平分为显性电平和隐性电平,二者必居其一。发送方通过使总线电平发生变化,将消息发接收方。

       CAN 协议具有一下特点:


       1)多主控制。在总线空闲时,所有单元都可以发送消息(多主控制),而两个以上的单元同时开始发送消息时,根据标识符( Identifier 以下称为 ID)决定优先级。 ID 并不是表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时开始发送消息时,对各消息ID 的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。


       2)系统的柔软性。与总线相连的单元没有类似于“地址”的信息。因此在总线上增加单元时,连接在总线上的其它单元的软硬件及应用层都不需要改变。


       3)通信速度较快,通信距离远。最高 1Mbps(距离小于 40M),最远可达10KM(速率低于 5Kbps)。


       4)具有错误检测、错误通知和错误恢复功能。 所有单元都可以检测错误(错误检测功能),检测出错误的单元会立即同时通知其他所有单元(错误通知功能),正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止(错误恢复功能)。


       5)故障封闭功能。 CAN 可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。


       6)连接节点多。CAN 总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。


       正是因为 CAN 协议的这些特点,使得 CAN 特别适合工业过程监控设备的互连,因此,越来越受到工业界的重视,并已公认为最有前途的现场总线之一。

       CAN 协议经过 ISO 标准化后有两个标准:ISO11898 标准和 ISO11519-2 标准。其中 ISO11898是针对通信速率为 125Kbps~1Mbps 的高速通信标准,而 ISO11519-2 是针对通信速率为 125Kbps以下的低速通信标准。


       40.1.2 FDCAN简介

       CAN最高只能到1Mbps,但是随着汽车技术的发展,人们对于更高的数据传输速度需求越来越强烈。加上CAN与竞争对手产品的巨大差异,比如FlexRay最高可以到10Mbps,2011年Bosch发布了CAN-FD(也就是STM32H7R7自带的FDCAN),CAN-FD继承了传统CAN的主要特性,CAN-FD对带宽和数据长度做了优化,是下一代汽车总线。CAN-FD相比CAN主要有以下几个优势:。


       ①、更高的传输速率。CAN-FD可以以更高的速率传输数据,仲裁段(ID和ACK)的速率和CAN一样最高1Mbps,保持不变,这样可以保证总线可靠。但是CAN-FD帧数据段可以8Mbps甚至更高!这个技术叫做数据段波特率可变,CAN-FD中FD的全称就是Flexible Data-Rate。

       ②、更长的数据帧。CAN的一帧只能发送8字节数据,而CAN-FD一帧最高可以发送64字节的数据,大大的提高了数据传输效率,以前需要分几帧传输的帧文,现在一帧就可以传输了。

       CAN-FD向下兼容CAN,如果想要使用CAN-FD功能的话都需要哪些条件呢?


       ①、所使用的控制器或者MCU支持CAN-FD协议,比如STM32H7R7的FDCAN就支持CAN-FD协议。


       ②、所使用的CAN收发器芯片支持CAN-FD,STM32H7R7开发板上的CAN收发器为SIT1050T(TJA1050T),这两款芯片均支持CAN-FD,所以STM32H7R7开发板可以使用FDCAN的功能。FDCAN中一帧数据最大64字节,仲裁段位速率最高1 Mbps, 数据段位速率最高8 Mbps。


       本章,我们使用STM32H7R7中FDCAN功能,通信速率为5Mbps(SIT1050T(TJA1050T)收发器最高支持5Mbps),使用的是ISO11898标准,标准CAN和 CAN-FD有着相同的物理层特性,CAN的物理层特征如图40.1.2.1所示:


图40.1.2.1 ISO11898物理层特性


       从该特性可以看出,显性电平对应逻辑0,CAN_H和CAN_L之差为2.5 V左右。而隐性电平对应逻辑1,CAN_H和CAN_L之差为0V。在总线上显性电平具有优先权,只要有一个单元输出显性电平,总线上即为显性电平。而隐形电平则具有包容的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平(显性电平比隐性电平更强)。另外,在CAN总线的起止端都有一个120Ω的终端电阻,来做阻抗匹配,以减少回波反射。


       40.1.3 CAN-FD协议

       CAN-FD可以认为是CAN的升级版,继承了CAN的绝大多数特性,仅仅对协议进行了升级,物理特性没有改变,并且CAN-FD是兼容CAN2.0的,CAN与CAN-FD主要区别:传输速率不同、数据域长度不同、帧格式不同、ID长度不同。

       CAN协议是通过以下5种类型的帧进行的:

       l 数据帧

       l 遥控帧

       l 错误帧

       l 过载帧

       l 间隔帧

       另外,数据帧和遥控帧有标准格式和扩展格式两种格式。标准格式有11个位的标识符(ID),扩展格式有29个位的ID。各种帧的用途如表40.1.3.1所示:


表40.1.3.1 CAN协议各种帧及其用途


       由于篇幅所限,我们这里仅对数据帧进行详细介绍,CAN-FD与CAN一样,一共具有:帧起始,仲裁段,控制段,数据段,CRC段,ACK段和帧结束,7部分组成数据帧一般由7个段构成,即:

       帧起始。表示数据帧开始的段。

       仲裁段。表示该帧优先级的段。

       控制段。表示数据的字节数及保留位的段。

       数据段。数据的内容,一帧可发送0~64个字节的数据。

       CRC段。检查帧的传输错误的段。

       ACK段。表示确认正常接收的段。

       帧结束。表示数据帧结束的段。

       数据帧的构成如图40.1.3.1所示:


图40.1.3.1数据帧的构成


       帧起始,FD-CAN和CAN使用相同的SOF标志位来标志报文的起始,标准帧和扩展帧都是由1个位的显性电平表示帧起始。

       仲裁段,表示数据优先级的段,与经典CAN相比,CAN FD取消了远程帧,用RRS位替换了RTR位,为常显性,如图40.1.3.2所示:


图40.1.3.2 数据帧仲裁段构成


       标准帧和扩展帧格式在本段有所区别,如图40.1.3.1所示,标准格式的ID有11个位。这意味着标准帧的ID范围是0X000至0X7FF。扩展格式的ID有29个位。基本ID从ID28到ID18,扩展ID由ID17到ID0表示。范围0X0000 0000至0X1FFF FFFF。

       其中RRS 代替原来的 CAN 帧的 RTR,IDE位为标识符选择位(0,使用标准标识符;1,使用扩展标识符),SRR位为代替远程请求位,为隐性位,它代替了标准帧中的RTR位。

       控制段,由9个位构成,表示数据段的字节数。控制段中CANFD与CAN有着相同的IDE,res,DLC位,同时增加了三个控制bit位,FDF、BRS、ESI,如图40.1.3.3所示:


图40.1.3.3 数据帧控制段构成


       上图中,FDF为 CAN数据帧中的保留位r,FDF常为隐性,表示CAN-FD 报文,BRS是位速率转换开关,当BRS为显性位时数据段的位速率与仲裁段的位速率一致,当BRS为隐性位时数据段的位速率高于仲裁段的位速率,ESI是错误状态指示,主动错误时发送显性位,被动错误时发送隐性位,IDE为表示是否是扩展帧(IDE 为1是扩展帧),res为保留位,必须全部以显性电平发送,但是接收端可以接收显性、隐性及任意组合的电平。DLC段为数据长度表示段,高位在前,DLC的0~8线性表示数据长度, 9~15离散表示数据长度。

       数据段,该段可包含0~64个字节的数据。从最高位(MSB)开始输出,标准帧和扩展帧在这个段的定义都是一样的。如图40.1.3.4所示:


图40.1.3.4 数据帧数据段构成


       图中D表示显性电平,R表示隐性电平(下同)。

       CRC段,该段用于检查帧传输错误,因为CAN-FD的数据段是可变长度的,CRC段根据不同的数据长度使用不同长度的 CRC 校验。由17、21或25个位(CAN是15个位)的CRC顺序和1个位的CRC界定符(用于分隔的位)组成,标准帧和扩展帧在这个段的格式也是相同的。如图40.1.3.5所示:


图40.1.3.5 数据帧CRC段构成


       此段CRC的值计算范围包括:帧起始、仲裁段、控制段、数据段。接收方以同样的算法计算CRC值并进行比较,不一致时会通报错误。

       ACK段,此段用来确认是否正常接收。由ACK槽(ACKSlot)和ACK界定符2个位组成。标准帧和扩展帧在这个段的格式也是相同的。如图40.1.3.6所示:


图40.1.3.6 数据帧CRC段构成


       发送单元的ACK,发送2个位的隐性位,而接收到正确消息的单元在ACK槽(ACKSlot)发送显性位,通知发送单元正常接收结束,这个过程叫发送ACK/返回ACK。发送ACK的是在既不处于总线关闭态也不处于休眠态的所有接收单元中,接收到正常消息的单元(发送单元不发送ACK)。所谓正常消息是指不含填充错误、格式错误、CRC错误的消息。

       帧结束,这个段也比较简单,标准帧和扩展帧在这个段格式一样,由7个位的隐性位组成。

       至此,数据帧的7个段就介绍完了,其他帧的介绍,请大家参考光盘的《CAN入门书.pdf》相关章节。接下来,我们再来看看CAN的位时序。

       CAN-FD兼容CAN2.0,也就是说CAN-FD和CAN有着相似的位时序,由发送单元在非同步的情况下发送的每秒钟的位数称为位速率。一个位可分为4段。

       l 同步段(SS)

       l 传播时间段(PTS)

       l 相位缓冲段1(PBS1)

       l 相位缓冲段2(PBS2)

       这些段又由可称为Time Quantum(以下称为Tq)的最小时间单位构成。

       1位分为4个段,每个段又由若干个Tq构成,这称为位时序。

       1位由多少个Tq构成、每个段又由多少个Tq构成等,可以任意设定位时序。通过设定位时序,多个单元可同时采样,也可任意设定采样点。各段的作用和Tq数如表40.1.3.2所示:


表40.1.3.2 一个位各段及其作用


       1个位的构成如图40.1.3.7所示:


图40.1.3.7 一个位的构成


       上图的采样点,是指读取总线电平,并将读到的电平作为位值的点。位置在PBS1结束处。根据这个位时序,我们就可以计算CAN通信的波特率了。具体计算方法,我们等下再介绍,前面提到的CAN协议具有仲裁功能,下面我们来看看是如何实现的。

       在总线空闲态,最 先开始发送消息的单元获得发送权。当多个单元同时开始发送时,各发送单元从仲裁段的第一位开始进行仲裁。连续输出显性电平最多的单元可继续发送。实现过程,如图40.1.3.8所示:


图40.1.3.8 CAN总线仲裁过程


       上图中,单元1和单元2同时开始向总线发送数据,开始部分他们的数据格式是一样的,故无法区分优先级,直到T时刻,单元1输出隐性电平,而单元2输出显性电平,此时单元1仲裁失利,立刻转入接收状态工作,不再与单元2竞争,而单元2则顺利获得总线使用权,继续发送自己的数据。这就实现了仲裁,让连续发送显性电平多的单元获得总线使用权。

       通过以上介绍,我们对CAN总线有了个大概了解(详细介绍参考光盘的:《CAN入门书.pdf》),接下来我们介绍下STM32H7R7的FDCAN控制器。

       STM32H7R7自带的是FDCAN,它支持CAN协议2.0A、2.0B和CAN FD V1.0。它的设计目标是,以最小的CPU负荷来高效出来大量收到的报文。它也支持报文发送的优先级要求(优先级特性可软件配置)。对于安全紧要的应用,FDCAN1提供所有支持时间触发通信模式所需的硬件功能。

       STM32H7R7的FDCAN的主要特点有:

       l 支持CAN协议2.0A、2.0B和ISO 11898-1:2015

       l CAN FD模式下一帧数据最高可达64个字节。

       l CAN错误记录

       l AUTOSAR和J1939支持

       l 增强的接收滤波

       l 两个分别接收三个有效载荷的FIFO(每个有效载荷最多64字节)

       l 接收高优先级消息时单独发信令

       l 传输三个有效载荷的FIFO/队列(每个有效载荷最多64字节)

       l 发送事件FIFO

       l 可编程回环测试模式

       l 可屏蔽模块中断

       l 两个时钟域:APB总线接口和CAN内核时钟

       l 低功耗支持

       我们使用的STM32H7R7L8H6H带有两个FDCAN控制器,而我们本章只使用了1个FDCAN。FDCAN的框图如图40.1.3.9所示:


图40.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内核的消息传输。最多三个Tx缓冲区可用于传输。Tx缓冲区可以用作Tx FIFO或Tx队列。Tx事件FIFO存储Tx时间戳以及对应的消息ID。还支持发送取消。


       ⑤、接收处理

       Rx Handle控制从CAN内核到外部消息RAM的接收消息的传输。Rx Handle支持两个接收FIFO,用于存储通过接受过滤的所有消息。Rx时间戳与每条消息一起存储。最多可为11位ID定义28个过滤器,最多8个过滤器为29位ID。


       ⑥、 APB 接口

       连接 FDCAN 到 APB 总线上。


       ⑦、消息 RAM 接口

       下一小节详细讲解消息 RAM 接口。


       40.1.4 消息RAM

       消息RAM是FDCAN的核心部分消息 RAM 就是一段 1KB 的内存,在 HAL 库中提供了函数 FDCAN_CalcultateRamBlockAddresses 来计算每个RAM块的起始地址和大小。这段内存分成了不同的区域,不同的区域代表的含义不同。 STM32H7R7 的 FDCAN 在消息 RAM 实现了过滤器、接收 FIFO、接收 buffer、发送事件 FIFO 和发送 buffer。消息 RAM 是 FDCAN1 和 FDCAN2 共享的,消息RAM 的内存分配如图 40.1.4.1 所示:


图 40.1.4.1 消息 RAM


       消息 RAM 中一个元素的大小是 32 位,也就是 4 字节。上图中一共有212个元素,一个元素占1words(4字节),所以FDCAN一共配备了212words。

       消息 RAM 和寄存器一样的,都可以设置相应的 bit 来完成不的功能。关于消息RAM 中各个域的内容和后面设置滤波器、发送和接收收据都跟消息 RAM 的各个域有关。关于消息 RAM 的详细请参考《STM32H7Rx 参考手册_V6(英文版)》的60.3.3 节(2844 页)。


       40.1.5 位时间特性

       接下来,我们简单看看 STM32H7R7 的 FDCAN 位时间特性,因为 FDCAN 的波特率就是通过位时间来设置的。STM32H7R7 的 FDCAN 位时间有3 段:同步段(SYNC_SEG)、时间段1(BS1)和时间段 2(BS2)。 STM32H7R7 的同步段长度为 1 个时间单元 tq,BS1 段可以设置为1~16 个时间单元 tq,BS2 段可以设置1~8 个时间单元 tq。STM32H7R7 的FDCAN 位时序如图40.1.5.1所示:


图 40.1.5.1 STM32H7R7 CAN 位时序


       FDCAN 的波特率(baudrate)计算公式如下:

       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)

       上面波特率计算公式有标称位时间和数据位时间两种,我们这里使用标称位时间,波特率计算公式可以简化为:



       我们只需要确定: ffdcan_tq_ck、 FDCAN_NBTP.NTSEG1[7:0]、 FDCAN_NBTP.NTSEG2[6:0]和FDCAN_NBTP.NBRP[8:0]的值,就可以确定 CAN 的波特率是多少了。

       其中, ffdcan_tq_ck是 CAN 的时间片时钟,用于生成位时序,其来源如图 40.1.5.2:


图 40.1.5.2 CAN 时间片时钟来源图


       图中 fdcan_ker_ck 是 CAN 内核时钟,该时钟经过分频器处理后,生成 fdcan_tq_ck 时钟,输入 CAN 内核,作为 CAN 时间片时钟,用于生成位时序。

       fdcan_ker_ck 由 RCC_CCIPR2 寄存器的 FDCANSEL[1:0]位选择时钟来源:00,hse_ker_ck;01,pll1_q_ck;10,pll2_p_ck;11,保留。我们设置 FDCANSEL[1:0]的值为 01,选择pll1_q_ck(一般为 600Mhz)作为 fdcan_ker_ck 的时钟源。

       FDCAN_NBTP.NTSEG1[7:0]、FDCAN_NBTP.NTSEG2[6:0]和FDCAN_NBTP.NBRP[8:0]等三个参数由寄存器FDCAN_NBTP 设置,假设我们设置:fdcan_tq_ck=100Mhz(fdcan_ker_ck分频后),NTSEG1=11,NTSEG2=8,NBRP=10,则可以得到500 kbps的标称波特率(仲裁段波特率),数据段波特率的计算方式也一样,大家可以自己计算下,我们例程设置的数据段波特率是5 Mbps。


       40.1.6 过滤器设置

       FDCAN提供了过滤器设置,通过过滤器可以设置允许接收哪些ID的消息。前面讲解消息RAM的时候详细的讲过SIDFC.FLSSA和XIDFC.FLESA这两个域,分别为标准ID过滤器和扩展ID过滤器。我们以标准ID过滤器为例讲解如何设置FDCAN的过滤器。

       标准ID过滤器有三种过滤模式:


       1、指定范围过滤

       通过SIDFC.FLSSA的SFID1和SFID2来设置需要过滤的ID范围,其中SFID2的值要大于SFID1,这样只有ID值在SFID1~SFID2之内的消息才能被接收到。比如我们现在要设置只接收ID在0X123~0X321范围内的消息,使用标准滤波器n,SIDFC.FLSSAn(n=0~27)的各个位设置如下:

SIDFC.FLSSAn.SFT=0   //范围滤波
SDIFC.FLSSAn.SFEC=1      //如果滤波匹配成功的话将消息保存到Rx FIFO中
SDIFC.FLSSAn.SFID1=0x123 //ID1
SDIFC.FLSSAn.SFID2=0X321 //ID2


       2、指定ID过滤

       我们也可以设置只接收指定的一个或者两个ID的消息,如果只接收指定的一个ID消息的话SFID1=SFID2。比如我们要设置只接收ID为0X123的消息,设置如下:

SIDFC.FLSSAn.SFT=1   //指定ID过滤
SDIFC.FLSSAn.SFEC=1      //如果滤波匹配成功的话将消息保存到Rx FIFO中
SDIFC.FLSSAn.SFID1=0x123 //ID1
SDIFC.FLSSAn.SFID2=0X123 //ID2


       3、传统的位过滤

       第三种过滤模式就是以前STM32的CAN上存在的位过滤模式,在屏蔽位模式下,过滤消息ID和过滤掩码一起工作决定接收哪些消息,其中SFID1为过滤的消息ID,SFID2为过滤掩码。

       举个简单的例子,我们设置过滤器SIDFC.FLSSAn工作在:传统位过滤模式,然后设置如下:

SIDFC.FLSSAn.SFT=2   //传统位过滤
SDIFC.FLSSAn.SFEC=1      //如果滤波匹配成功的话将消息保存到Rx FIFO中
SDIFC.FLSSAn.SFID1=0XFF00 //ID1
SDIFC.FLSSAn.SFID2=0XF000 //掩码

       其中SFID1是我们期望接收到的消息ID,我们希望最好接收到ID=0XFF00的消息。SFID2的0XF000规定了我们必须关心的ID,也就是接收到的消息ID其位[15:12]必须和SFID1中的位[15:12]完全一样,其他的位不关心。也即是说接收到的消息ID必须是0XFFxx这样的才算正确(x表示不关心)。

       关于滤波设置的详细接收请参考《STM32H7Rx参考手册_V6(英文版)》的60.3.8节中的滤波小节(2858页)。


       40.1.7 CAN收发过程


       1、CAN接收流程

       CAN接收到消息以后存放到FIFO0或者FIFO1中,至于是放到FIFO0还是FIFO1中,我们在初始化CAN的时候会设置。如果消息满足过滤器设置的话消息就存放到FIFO中,当FIFO满了以后就不能在存任何消息了,除非有消息从FIFO中读出。读取消息就是直接从FIFO读数据,比如现在使用的是FIFO0,那就直接读取RXF0C.F0SAn(n为索引号0~64),消息的组成方式前面讲解RXF0C.F0SA的时候就详细的说过了。比如HAL库中从FIFO中读取消息的函数HAL_FDCAN_GetRxMessage处理方式如下:

/* 计算索引 */
GetIndex += ((hfdcan->Instance->RXF0S & FDCAN_RXF0S_F0GI) >> 
             FDCAN_RXF0S_F0GI_Pos);
/* 要读取的FIFO地址 */
RxAddress = (uint32_t *)(hfdcan->msgRam.RxFIFO0SA + (GetIndex * hfdcan-
      >Init.RxFifo0ElmtSize * 4U));

       上面代码我们已经得到了消息的FIFO地址,接下来就是按照RXF0C.F0SA的格式来恢复数据了,处理方法如下:

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);
pRxHeader->RxTimestamp = (*RxAddress & FDCAN_ELEMENT_MASK_TS);
……
/* 读取消息数据 */
pData = (uint8_t *)RxAddress;
for(ByteCounter = 0; ByteCounter < DLCtoBytes[pRxHeader->DataLength >> 16];
ByteCounter++)
{
      *pRxData++ = *pData++;  
}

       关于CAN的接收详细过程,可以参考HAL库中的函数HAL_FDCAN_GetRxMessage。


       2、CAN发送流程

       CAN的发送流程和CAN的接收流程刚好相反,我们需要将消息按照TXBC.TBSA格式构建好,然后写入到发送FIFO(buffer)中,写进去以后设置TXBAR寄存器的指定位为1来请求传输。HAL库中函数HAL_FDCAN_AddMessageToTxFifoQ来完成此工作,处理部分如下:

PutIndex = ((hfdcan->Instance->TXFQS & FDCAN_TXFQS_TFQPI) >> 16); /* 获取索引 */
FDCAN_CopyMessageToRAM(hfdcan, pTxHeader, pTxData, PutIndex);/* 拷贝消息到FIFO */
hfdcan->Instance->TXBAR = (1 << PutIndex);  /* 发出传输请求 */


       40.1.8 FDCAN寄存器

       接下来,我们介绍一下本章需要用到的一些比较重要的寄存器。


       l FDCAN的CC控制寄存器(FDCAN_CCCR)

       FDCAN的CC控制寄存器(FDCAN_CCCR)各位描述如图40.1.8.1所示:


图40.1.8.1 寄存器FDCAN_CCCR各位描述


       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。

       该寄存器的详细描述,请参考《STM32H7Rx参考手册_V6(英文版).pdf》60.4.6节(2864页)。


       l FDCAN标称位定时和预分频器寄存器(FDCAN_NBTP)

       FDCAN标称位定时和预分频器寄存器各位描述如图40.1.8.2所示:


图40.1.8.1 寄存器FDCAN_CCCR各位描述


       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。

       该寄存器的详细描述,请参考《STM32H7Rx参考手册_V6(英文版).pdf》60.4.6节(2864页)。


       l FDCAN标称位定时和预分频器寄存器(FDCAN_NBTP)

       FDCAN标称位定时和预分频器寄存器各位描述如图40.1.8.2所示:


图40.1.8.3 寄存器FDCAN_TEST各位描述


       该寄存器我们重点介绍一个LBCK位,此位用来使能回测模式,当此位置1的时候就会使能FDCAN的回测模式,一共有两种回环模式:外部回环模式和内部回环模式。


       外部回环模式

       要进入外部回环模式需要将FDCAN_CCCR的TEST和FDCAN_TEST的LBCK这两个位置1。在外部环回模式下,FDCAN把发送的报文当作接收的报文并保存(如果可以通过接收过滤)在接收FIFO里。也就是说外部环回模式是一个自发自收的模式,如图40.1.8.4所示:


图40.1.8.4 FDCAN外部环回模式


       外部环回模式提供了硬件自测。为了避免外部的影响,在环回模式下FDCAN内核忽略确认错误(在数据/远程帧的确认位时刻,不检测是否有显性位)。在环回模式下,FDCAN在内部把Tx输出回馈到Rx输入上,而完全忽略Rx引脚的实际状态。发送的报文可以在Tx引脚上检测到。这里的外部回环模式就是以前STM32的回环模式。


       内部回环模式

       如果要使用内部回环模式的话,需要将FDCAN_CCCR的TEST、FDCAN_TEST的LBCK和FDCAN_CCCR的MON这三个位都置1。内部回环模式如图40.1.8.5所示:


图40.1.8.5 内部回环模式


       从图40.1.8.5可以看出,内部回环模式下Tx引脚输出显性电平,因此测试的时候Tx引脚不会输出报文。外部回环模式下Tx是会输出报文的。这个模式可以用于热机自测,测试的时候不会影响到CAN总线上的正常数据收发。


       l FDCAN发送buffer请求寄存器(FDCAN_TXBAR)

       FDCAN发送buffer请求寄存器描述如图40.1.8.6所示。


图40.1.8.6 寄存器FDCAN_TXBAR各位描述


       此寄存器用来设置FDCAN的哪个发送buffer可以发送数据,FDCAN有32个发送buffer,我们发送数据的时候就需要将所要发送的数据拷贝到这些buffer中,拷贝完成以后就需要标记此buffer可以发送。


       l FDCAN中断寄存器(FDCAN_IR)

       FDCAN中断寄存器描述如图40.1.8.7所示。


图40.1.8.67寄存器FDCAN_IR各位描述


       此寄存器用来检测FDCAN的工作时的一些状态,当发生某些情况时,寄存器对应的位会只置1,直至主机清除它们。

       关于FDCAN的介绍,就到此结束了。


        40.2 硬件设计


       1. 例程功能

       通过KEY_UP按键(即WK_UP按键)选择FDCAN的操作模式(正常模式/回环模式),然后通过KEY0控制数据发送,接着查询是否有数据接收到,假如接收到数据,就将接收到的数据显示在LCD模块上。如果是回环模式,我们不需要2个开发板。如果是正常模式,我们就需要2个H7R7开发板,并且将他们的CAN接口对接起来,然后一个开发板发送数据,另外一个开发板将接收到的数据显示在LCD模块上。


       2. 硬件资源


       1) LED灯

              LED0–PD14


       2) 独立按键

              KEY0 – PE9

              KEY_UP – PC13


       3) 正点原子 2.8/3.5/4.3/7/10.1寸LCD模块


       4) HyperRAM


       5) FDCAN1

              FDCAN1_TX –PD1

              FDCAN1_RX –PD0


       3. 原理图

       STM32有FDCAN的控制器,但要实现FDCAN通讯的差分电平,我们还需要借助外围电路来实现,根据我们需要实现的程序功能,我们设计电路原理如下:


图40.2.1 FDCAN连接原理设计


       从上图可以看出:STM32H7的FDCAN通过P10的设置,连接到TJA1050收发芯片,然后通过接线端子(CAN)同外部的CAN总线连接。图中可以看出,在STM32H7R7开发板上面是带有120Ω的终端电阻的,如果我们的开发板不是作为FDCAN的终端的话,需要把这个电阻去掉,以免影响通信。

       最后,我们用2根导线将两个开发板CAN端子的CAN_L和CAN_L,CAN_H和CAN_H连接起来。这里注意不要接反了(CAN_L接CAN_H),接反了会导致通讯异常!!


        40.3 程序设计


       40.3.1 FDCAN的HAL库驱动

       FDCAN在HAL库中的驱动代码在stm32h7rsxx_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_HandleTypeDef
{
  FDCAN_GlobalTypeDef         *Instance;  /*!< 寄存器基地址 */
  FDCAN_InitTypeDef           Init;       /*!< FDCAN所需参数 */
  FDCAN_MsgRamAddressTypeDef  msgRam;     /*!< FDCAN消息RAM块  */
  uint32_t          LatestTxFifoQRequest; /*!< FDCAN最新发送FIFO */
  __IO HAL_FDCAN_StateTypeDef State;      /*!< FDCAN通信状态 */
  HAL_LockTypeDef             Lock;       /*!< FDCAN锁定对象 */
  __IO uint32_t               ErrorCode;  /*!< FDCAN错误代码 */
} FDCAN_HandleTypeDef;


       1)Instance:指向FDCAN寄存器基地址。这里我们使用FDCAN1,设置为FDCAN1即可。


       2)Init:fdcan初始化结构体,用于配置FDCAN的工作模式、波特率等等。它的定义也在stm32h7rsxx_hal_fdcan.h中有列出。

typedef struct
{
  uint32_t ClockDivider;                 /*!<时钟分频 */
  uint32_t FrameFormat;                  /*!<帧格式 */
  uint32_t Mode;                         /*!<模式 */
  FunctionalState AutoRetransmission;    /*!<自动重传模式 */
  FunctionalState TransmitPause;         /*!<启动/禁用发送 */
  FunctionalState ProtocolException;     /*!<启动/禁用协议异常处理 */
  uint32_t NominalPrescaler;             /*!<预分频器系数 */
  ……/*!<成员变量太多,这里省略不列出来 */
} FDCAN_InitTypeDef;

       调用FDCAN的初始化函数时,我们主要也是对这个结构体赋值,配置FDCAN的工作模式。实际上参数可以分为两类。前面16个参数是用来设置完成FDCAN的通用设置,比如FrameFormat用来设置FDCAN是工作在CAN FD模式还是传统的CAN模式。Mode是设置FDCAN工作模式的参数,本实验需要用到内部回环模式(FDCAN_MODE_INTERNAL_LOOPBACK)和常规模式(FDCAN_MODE_NORMAL)。

       其他设置波特率相关的参数NominalPrescaler,NominalSyncJumpWidth,NominalTimeSeg1和NominalTimeSeg2分别用来设置波特率分频器,重新同步跳跃宽度以及时间段1和时间段2占用的时间单元数。注意:NominalPrescaler,NominalTimeSeg1和NominalTimeSeg2这三个参数分别对应寄存器:FDCAN_NBTP的NTSEG1[7:0]、NTSEG2[6:0]和NBRP[8:0]的值加1,即:

NominalPrescaler = NBRP[8:0]+1

NominalTimeSeg1= NTSEG1[7:0]+1

NominalTimeSeg2= NTSEG2[6:0]+1

       因此,FDCAN通信波特率的计算公式可以简化为:



       我们使用的是FDCAN,数据段波特率才是关注的重点,FDCAN拥有更高的传输速率是因为数据段波特率是可变的,通常大于标称波特率(仲裁段波特率),可达到8Mbps甚至更高,下面我们看下数据段波特率的计算方法,它的计算公式和标称波特率(仲裁段波特率)的计算公式相似,可以简化为:



       这些值均通过FDCAN_DBTP寄存器设置,后面Tx和Rx开头的成员变量用来设置FDCAN的收发FIFO,重点是RxFifo0ElmtSize和TxElmtSize这两个成员变量,这两个成员变量设置收发FIFO的元素大小,也就是收发一帧数据大小,我们用的是CAN-FD模式,因此最大是64字节。


       l 函数返回值:

       HAL_StatusTypeDef枚举类型的值,有4个,分别是HAL_OK表示成功,HAL_ERROR表示错误,HAL_BUSY表示忙碌,HAL_TIMEOUT为超时。

       调用初始化函数之后,同样我们需要重定义HAL_FDCAN_MspInit来初始化跟底层硬件相关的配置,我们后面编写初始化函数时用到。


       2. HAL_FDCAN_Start函数

       使能FDCAN控制器以接入总线进行数据收发处理。

HAL_StatusTypeDef HAL_FDCAN_Start(FDCAN_HandleTypeDef *hfdcan)


       l 函数描述:

       按需要配置完FDCAN总线后,使能FDCAN控制器以接入总线进行数据收发处理。


       l 函数形参:

       形参1是FDCAN的控制句柄指针,初始化函数已经介绍过它的结构了,这里不重复了。


       l 函数返回值:

       我们只关注HAL_OK的情况。


       3. HAL_FDCAN_AddMessageToTxFifoQ函数

       发送报文函数。

HAL_StatusTypeDef HAL_FDCAN_AddMessageToTxFifoQ(FDCAN_HandleTypeDef *hfdcan, FDCAN_TxHeaderTypeDef *pTxHeader, uint8_t *pTxData)


       l 函数描述:

       该函数用于向发送邮箱添加发送报文,并激活发送请求


       l 函数形参:

       形参1是FDCAN的控制句柄指针,初始化函数已经介绍过它的结构了,这里不重复了。

       形参2是FDCAN发送的结构体,它的结构如下:

typedef struct
{
   uint32_t Identifier;          /*!<消息ID */
   uint32_t IdType;              /*!<ID类型 */
   uint32_t TxFrameType;         /*!<帧类型 */
   uint32_t DataLength;          /*!<数据长度 */
   uint32_t ErrorStateIndicator; /*!<错误状态指示 */
   uint32_t BitRateSwitch;       /*!<比特率转换 */
   uint32_t FDFormat;            /*!<FD格式 */
   uint32_t TxEventFifoControl;  /*!<事件FIFO控制 */
   uint32_t MessageMarker;       /*!<消息标记 */
} FDCAN_TxHeaderTypeDef;


       4. HAL_FDCAN_GetRxMessage函数

       接收消息函数。

HAL_StatusTypeDef HAL_FDCAN_GetRxMessage(FDCAN_HandleTypeDef *hfdcan, uint32_t
RxLocation, FDCAN_RxHeaderTypeDef *pRxHeader, uint8_t *pRxData)


       l 函数描述:

       该函数可从接收FIFO里面的输出邮箱获取到消息报文。


       l 函数形参:

       形参1是FDCAN的控制句柄指针,初始化函数已经介绍过它的结构了,这里不重复了。

       形参2是接收FIFO,具体是FIFO0/1 ,得看过滤器组关联的FIFO。

       形参3是FDCAN接收的结构体,它的结构如下:

typedef struct
{
  uint32_t Identifier;            /*!<标识 */
  uint32_t IdType;                /*!<ID类型*/
  uint32_t RxFrameType;           /*!<接收到的帧类型 */
  uint32_t DataLength;            /*!<帧长度*/
  uint32_t ErrorStateIndicator;   /*!<错误状态指示 */
  uint32_t BitRateSwitch;         /*!<比特率转换 */
  uint32_t FDFormat;              /*!<FD格式 */
  uint32_t RxTimestamp;           /*!<帧捕获 */
  uint32_t FilterIndex;           /*!<过滤器索引 */
  uint32_t IsFilterMatchingFrame; /*!<是否匹配过滤器*/
} FDCAN_RxHeaderTypeDef;

       在发送结构体中,同样的,也是通过IDE位确认该消息报文的标识符类型,该结构体不同于发送结构体还有一个过滤器匹配序号成员,可以查看到是此报文是通过哪里过滤器到达接收FIFO。其他成员可以对照发送邮箱寄存器相关位进行理解。

       形参4是接收报文的内容。


       FDCAN的初始化配置步骤


       1) FDCAN参数初始化(工作模式、波特率等)

       HAL库通过调用FDCAN初始化函数HAL_FDCAN_Init完成对FDCAN参数初始化,详见例程源码。

       注意:该函数会调用:HAL_FDCAN_MspInit函数来完成对FDCAN底层的初始化,包括:FDCAN以及GPIO时钟使能、GPIO模式设置、中断设置等。


       2) 开启FDCAN和对应管脚时钟,配置FDCAN_TX和FDCAN_RX的复用功能输出

       首先开启FDCAN的时钟,然后配置FDCAN相关引脚为复用功能(对应的引脚可查看STM32H7R7L8H6H.pdf的P132)。本实验中FDCAN_TX对应的是PD1,FDCAN_RX对应的是PD0。他们的时钟开启方法如下:

__HAL_RCC_FDCAN_CLK_ENABLE();           /* 使能FDCAN1 */
__HAL_RCC_GPIOD_CLK_ENABLE();           /* 开启GPIOD时钟 */

       IO口复用功能是通过函数HAL_GPIO_Init来配置的。


       3) CAN数据接收和发送

       HAL库通过调用HAL_FDCAN_AddMessageToTxFifoQ完成数据发送。

       通过调用HAL_FDCAN_GetRxMessage函数进行接收数据。

       至此,FDCAN就可以开始正常工作了。如果用到中断,就还需要进行中断相关的配置。详看例程源码,这里就不作介绍了。


       42.3.2 程序解析

       我们要使用LED、LCD、按键这些功能,直接复制上一个485实验的代码,把rs485的代码从工程中移除,并在Drivers/BSP目录下新建一个《FDCAN》文件夹,与之前一样,新建fdcan.c/fdcan.h文件并把它们加入到工程中。


       1. fdcan.c函数

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。FDCAN驱动相关源码包括两个文件:fdcan.c和fdcan.h。

       我们根据《STM32H7Rx参考手册_V6(英文版).pdf》第60章的内容,我们利用前面介绍的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内核时钟频率为 fdcan_clk = PLL1Q / ckdiv= 100Mhz
 *              波特率 = fdcan_clk / ((ntsg1 + ntsg2 + 1) * presc);
 *              我们设置 can_init(10, 8, 11, 8, 1), 则CAN波特率为:
 *              100M / ((11 + 8 + 1) * 10) = 500Kbps
 * @retval      0,  初始化成功; 其他, 初始化失败;
 */
uint8_t fdcan_init(uint32_t presc, uint32_t ntsjw, uint32_t ntsg1, 
uint32_t ntsg2, uint32_t mode)
{
    FDCAN_FilterTypeDef canx_rxfilter = {0};
    HAL_FDCAN_DeInit(&g_fdcanx_handler);             /* 先清除以前的设置 */
    
    /* 初始化FDCAN */
    g_fdcanx_handler.Instance = FDCAN_FDCANX;        /* FDCAN1 */
g_fdcanx_handler.Init.ClockDivider = FDCAN_CLOCK_DIV6;              
/* FDCAN时钟6分频,即100MHz,芯片手册标明最大不超过125MHz */
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
(开发板的FD CAN收发器最高支持5Mbit/s) */
/* 数据段通信速率(仅FDCAN模式需配置)
= fdcan_clk / 分频系数 / (1 + dseg1 + dseg2)=25M/(3 + 1 + 1)=5Mbit/s */
    g_fdcanx_handler.Init.DataPrescaler = 4;      /* 数据段分频系数范围:1~32  */
    g_fdcanx_handler.Init.DataSyncJumpWidth = 4;  /* 数据段重新同步跳跃宽度1~16 */
    g_fdcanx_handler.Init.DataTimeSeg1 = 3;       /* 数据段dsg1范围:1~32 */
    g_fdcanx_handler.Init.DataTimeSeg2 = 1;       /* 数据段dsg2范围:1~16 */
    g_fdcanx_handler.Init.StdFiltersNbr = 28;     /* 标准信息ID滤波器编号 */
    g_fdcanx_handler.Init.ExtFiltersNbr = 8;      /* 扩展信息ID滤波器编号 */
/* 仲裁段通信速率(FDCAN与传统CAN均需配置)
 = fdcan_clk / presc / (1 + ntsg1 + ntsg2) = 10M/(11+8+1)=500Kbit/s */
    g_fdcanx_handler.Init.NominalPrescaler = presc;      /* 分频系数 */
g_fdcanx_handler.Init.NominalSyncJumpWidth = ntsjw;  
/* 重新同步跳跃宽度1~128 */
    g_fdcanx_handler.Init.NominalTimeSeg1 = ntsg1;       /* ntsg1范围:2~256 */
    g_fdcanx_handler.Init.NominalTimeSeg2 = ntsg2;       /* ntsg2范围:2~128 */
g_fdcanx_handler.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;    
/* 发送FIFO序列模式 */
    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 */
canx_rxfilter.FilterID2 = 0x0000;                                   
/* 第二个可被接受的32位ID,如果FDCAN配置为传统模式的话,这里是32位掩码 */
    /* 过滤器配置 */
    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;
    }
    /* 激活RX FIFO0的MESSAGE通知中断,位开启Tx Buffer中断*/
HAL_FDCAN_ActivateNotification(&g_fdcanx_handler, 
FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);
    return 0;
}

       调用HAL_FDCAN_Init后会调用HAL_FDCAN_MspInit,我们重定义这个函数,在函数中初始化我们用于控制FDCAN的收发引脚:

/**
 * @brief   HAL库FDCAN初始化MSP函数
 * @param   无
 * @retval  无
 */
void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef *hfdcan)
{
    GPIO_InitTypeDef gpio_init_struct = {0};
    RCC_PeriphCLKInitTypeDef fdcan_clk_init = {0};
    
    if (hfdcan->Instance == FDCAN_FDCANX)
    {
        /* 配置时钟 */
        fdcan_clk_init.PeriphClockSelection = RCC_PERIPHCLK_FDCAN;
        fdcan_clk_init.FdcanClockSelection = RCC_FDCANCLKSOURCE_PLL1Q;      
/* 选择PLL1Q作为fdcan的时钟120MHz */
        HAL_RCCEx_PeriphCLKConfig(&fdcan_clk_init);
        
        /* 使能时钟 */
        FDCAN_FDCANX_CLK_ENABLE();
        FDCAN_FDCANX_TX_GPIO_CLK_ENABLE();
        FDCAN_FDCANX_RX_GPIO_CLK_ENABLE();
        
        /* 初始化TX引脚 */
        gpio_init_struct.Pin = FDCAN_FDCANX_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 = FDCAN_FDCANX_TX_GPIO_AF;
        HAL_GPIO_Init(FDCAN_FDCANX_TX_GPIO_PORT, &gpio_init_struct);
        
        /* 初始化RX引脚 */
        gpio_init_struct.Pin = FDCAN_FDCANX_RX_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 = FDCAN_FDCANX_RX_GPIO_AF;
        HAL_GPIO_Init(FDCAN_FDCANX_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(10, 8, 11, 8, FDCAN_MODE_INTERNAL_LOOPBACK);

       要与其它的FDCAN节点设备通讯,我们需要编写FDCAN相关的收发函数。首先是发送函数,发送报文是有ID,所以我们在发送时需要设定ID,故需要设计一个形参为ID号,我们利用HAL库的发送函数封装一个更方便我们使用的函数,代码如下:

/**
 * @brief       FDCAN 发送一组数据
 * @note        发送格式固定为: 标准ID, 数据帧
 * @param       len     :数据长度,取值范围:FDCAN_DLC_BYTES_0 ~ FDCAN_DLC_BYTES_64
 * @param       msg     :数据指针,最大为64个字节
 * @retval      发送状态 0, 成功; 1, 失败;
 */
uint8_t fdcan_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初始化时,我们对于过滤器的配置是不过滤任何报文ID,也就是说可以接收全部报文。但是我们可以编写接收函数时,使用软件的方式过滤报文ID,通过形参来跟接收到的报文ID进行匹配。接收函数代码具体如下:

/**
 * @brief       FDCAN接收数据
 * @note        接收数据格式固定为: 标准ID, 数据帧
 * @param       buf     : 数据缓存区
 * @retval      接收结果
 * @arg         0   , 无数据被接收到;
 * @arg         其他, 接收的数据长度
 */
uint8_t fdcan_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;
}


       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;                   /* FDCAN工作模式: 0,普通模式; 1,环回模式 */
    
    sys_mpu_config();                   /* 配置MPU */
    sys_cache_enable();                 /* 使能Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
    delay_init(600);                    /* 初始化延时 */
    usart_init(115200);                 /* 初始化串口 */
    led_init();                         /* 初始化LED */
    key_init();                         /* 初始化按键 */
    hyperram_init();                    /* 初始化HyperRAM */
    lcd_init();                         /* 初始化LCD */
fdcan_init(10, 8, 11, 8, FDCAN_MODE_INTERNAL_LOOPBACK);  
/* FDCAN初始化, 环回模式, 波特率500Kbps */
    
    /* 显示提示信息 */
    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 WKUP: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 = fdcan_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 == WKUP_PRES)     /* KEY2按下, 改变FDCAN的工作模式 */
        {
            mode = !mode;
            /* FDCAN初始化, 普通(0)/回环(1)模式, 波特率500Kbps */
            fdcan_init(12, 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 = fdcan_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函数的执行过程与程序流程图是一致的,这里需要注意:在选择正常模式的情况下,要使两个开发板通信成功,必须保持一致的波特率。其它细节,参考光盘资料中的源码即可。


        40.4 下载验证

       在代码编译成功之后,我们通过下载代码到开发板上,得到如图40.4.1所示:


图40.4.1 程序运行效果图


       伴随LED0的不停闪烁,提示程序在运行。默认设置的回环模式,按下KEY0就可以在LCD模块上面看到自发自收的数据(如上图所示),如果我们选择正常模式(KEY_UP按键切换),就必须连接两个开发板的FDCAN接口,然后就可以互发数据了。如图40.4.2和图40.4.3所示:


图40.4.2 FDCAN正常模式发送数据


图40.4.3 FDCAN正常模式接收数据


       图40.4.2来自开发板A,发送了8个数据,图40.4.3来自开发板B,收到了来自开发板A的个数据。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3095221/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    764725