一、介绍. T H: t2 [4 i) T \5 q+ d3 R' d FDCAN(Flexible Data-Rate CAN)是CAN的升级版。特点包括: 6 d* C8 ~6 `5 A6 k1 z 1、每帧数据段最大长度由8字节上升到64字节。% i: _& r* L" ~/ P5 c; W3 L 2 [ r$ s. x; v3 h; y4 _ 2、速度由1Mbps上升到5Mbps,甚至还可以更高。在一个数据帧中仲裁段(ID和ACK)的速率和CAN一样最高1Mbps,这样可以保证总线的健壮可靠,但是数据段可以5Mbps甚至更高,一个数据帧中使用不同的波特率,这就是FD(Flexible Data-Rate)的由来。( F# K f& o! i 3、向下兼容CAN。 H7的FDCAN包含2个可配置接收FIFO。多达64个专用接收buffer,多达32个专用发送buffer。可配置发送FIFO/队列,可配置发送事件FIFO。 二、结构 先看结构框图:# e) E: p! i+ B) c ( \4 G) m+ N( A" D- B 0 p) c8 y& t8 ^9 F3 K3 ? t 1、两条中断线:fdcan_intr0_it和fdcan_intr1_it。可以通过FDCAN_ILE寄存器的 EINT0和 EINT1这两个位来使能或者关闭。 可以通过FDCAN_ILS寄存器来选择FDCAN的中断是在fdcan_intr0_it上触发,还是在fdcan_intr1_it上触发,默认所有中断都在fdcan_intr0_it上触发,没有特殊的要求,只需要用一条中断线就可以了。 9 @/ y! h6 J$ q8 d9 G# A0 o 2、TX Handler:负责将消息RAM中的数据发送到CAN内核,最多可配置32个发送buffer进行发送。发送buffer可用作专用发送buffer、发送FIFO(发送队列的组成部分)或二者的组合。发送事件FIFO会将发送时间戳与相应的消息ID存储在一起,另外还支持取消发送。 # J: E( B8 u2 o: J# c% q+ H 3、RX Handler:负责将CAN内核的数据传输到消息RAM,支持两个接收FIFO(每个FIFO的大小均可配置)以及最多64个专用接收buffer(用于存储所有通过验收过滤的消息)。专用接收buffer仅用于存储具有特定标识符的消息,与接收FIFO有所不同。每条消息均与其接收时间戳存储在一起。: ]8 ?4 h" ?7 v7 V+ y$ o9 r R 4、CAN core:CAN内核,读RX引脚数据处理后给RX Handler,接收TX Handler处理后控制TX引脚。/ a' J) N& i9 E/ ~: _ ! L/ Y) d9 u4 X7 I 5、Message RAM interface:消息RAM接口,外面连接着消息RAM。消息RAM是FDCAN的核心,本质是一段最大10KB的内存,把这段内存分成不同的区域,每个区域的作用不同,可以实现:过滤器、接收FIFO、接收buffer、发送事件FIFO、发送buffer。内存分配如下: 7 K& B& b0 L& A7 } 10KB的消息RAM中,是以32位为最小单位来操作的(因此消息RAM的起始地址要32位对齐),即字(word),因此10KB共有10*1024/4=2560个字。2560个字被分成了8个功能区,从上到下为:SIDFC.FLSSA、XIDFC.FLESA、RXF0C.F0SA......每个功能区中的条目又定义为元素(element),每个元素可以占用多个字,但是同一个功能区中的元素大小是相同的。比如Rx FIFO0是接收FIFO0,消息通过过滤器后被保存到这里,它最多占用1152个字的空间,有64个元素,也就是可以保存64条消息,这64个元素的大小都相同,每个元素的大小最大为1152/64=18字。 ' y; e, ?# K6 {$ Z& N 1)、SIDFC.FLSSA:这个区域用来存放11位过滤ID,最多可以有128个元素。 2)、XIDFC.FLESA:这个区域用来存放29位过滤ID,最多可以有64个元素。(F1配置寄存器来设置过滤ID,H7直接划了个内存区来设置过滤ID)- [' S |; |; W+ |. l6 c 3)、RXF0C.F0SA:接收FIFO0,最多可以有64个元素,可以接收64条消息。 4)、RXF1C.F1SA:接收FIFO1,最多可以有64个元素,可以接收64条消息。 / {4 u7 j% m7 g$ b: d 5)、RXBC.RBSA:接收buffer,最多可以有64个元素。, Q- k% V; K4 z7 t" V9 p; M + V0 T+ @& K9 x) A; B# H! _% I 6)、TXEFC.EFSA:发送事件FIFO,最多可以有32个元素。(不是发送FIFO), y6 \: d& O* ]. R# T" ]! B * i: t7 ~4 w" t+ t! z+ W 7)、TXBC.TBSA:发送buffer,最多可以有32个元素。 0 J2 M2 H5 a8 D4 w 8)、TMC.TMSA:触发存储器,最多可以有64个元素。 《注:这8个区域的元素个数和元素大小都是可以配置的,但是这8个区域的地址又是连续的。因此在初始化的时候,会根据每个区域的元素个数和大小来计算下一个区域的起始地址,并保存到相应的寄存器中》 8 Q/ \+ Z) d- i+ F' N: @/ F; r/ ~ 下面来详细介绍这几个功能区域:$ z) i, T; X0 X/ E + Z) y, R [: t; S1 m% D 1)、SIDFC.FLSSA:这个区域用来存放11位过滤ID,有128个元素,每个元素占一个字,定义为: 0 c. y, g" E V' _7 A' U SFT[1:0](bit31:30):这两位用来表示过滤类型,一共有四种:" Z Z1 P; A5 e 00:范围过滤,过滤到SFID1到SFID2中的所有信息。3 x( g4 T: h, {$ m5 W% f: ^/ n# \ 01:双过滤,过滤到SFID1和SFID2中的信息。 10:传统位过滤,SFID1 是过滤值,SFID2 是掩码。 11:禁止过滤器元素+ X1 E- Y% z `4 l SFEC[2:0](bit29:27):标准过滤配置% x; X6 { D j7 g 000:禁止过滤器元素! ~" ?. g+ F$ F- T G 001:当过滤匹配以后存储在Rx FIFO0中。, }7 r$ n4 B: i W# W' o 010:当过滤匹配以后存储在Rx FIFO1中。( J: y3 r& B; l) F0 ] 011:当过滤匹配以后丢弃。2 ^6 A! _) C. E8 e! P 100:当过滤匹配以后设置IR寄存器的HPM位,触发高优先级中断。, g! Q1 A4 k) v' N$ U 101:当过滤匹配以后存储在Rx FIFO0中并设置IR寄存器的HPM位,触发高优先级中断。$ a$ l1 S! i: n, @1 D* H3 l 110:当过滤匹配以后存储在Rx FIFO1中并设置IR寄存器的HPM位,触发高优先级中断。 111:存储到Rx buffer中或者作为debug消息,FDCAN SFT[1:0]忽略。 SFID1[10:0](bit26:16):标准过滤ID1,第一个要过滤的ID。 SFID2[15:10](bit15:10):标准过滤ID2,此位根据SFEC的配置不同而不同,当SFEC=001~110的时候此位域表示标准过滤ID。当SFEC=111的时候此位域表示Rx buffer或者debug消息。 SFID2[10:9](bit10:9):设置接收到的消息是存放在Rxbuffer中还是处理为debug消息的消息A、B或C。$ @! h9 w3 N$ y: _3 `( ^7 ]5 ` 00:将消息存储在Rx Buffer中。; q1 C- ?( v2 y3 F 4 p: n4 r0 A$ H: [- e+ D, |" y 01:debug消息A。 10:debug消息B。% B1 P% k) \+ K- f* y9 w 11:debug消息C。 SFID2[8:6](bit8:6):确定是否将滤波器事件引脚作为外部接口。* s9 `2 Y* R1 c( g4 g) z SFID2[5:0](bit5:0):定义当匹配以后存储消息的区域相对于Rx buffer 的开始地址RXBC.RBSA的偏移。 " O5 C3 A P6 k 2)、XIDFC.FLESA:这个区域用来存放29位过滤ID,有64个元素,每个元素占2个字,定义为: - y0 V; @4 e2 ^ m4 P' U( v F0 EFEC[2:0](bit31:29):扩展过滤配置6 G! F( c7 R+ [3 w" G |/ R0 n" u 000:禁止过滤器元素 001:当过滤匹配以后存储在Rx FIFO0中。9 [/ h$ f/ K4 t" e 010:当过滤匹配以后存储在Rx FIFO1中3 d( L9 Q$ P/ ^0 c; g6 e! i) { 011:当过滤匹配以后丢弃。 100:当过滤匹配以后设置IR寄存器的HPM位,触发高优先级中断。& u/ x1 u; N' C( A6 B/ C/ s7 A4 D% V 101:当过滤匹配以后存储在Rx FIFO0中并设置IR寄存器的HPM位,触发高优先级中断。/ V6 q1 f# B8 \3 p 110:当过滤匹配以后存储在Rx FIFO1中并设置IR寄存器的HPM位,触发高优先级中断。 111:存储到Rx buffer中,EFT[1:0]忽略。 F0 EFID1[28:0](bit28:0):扩展过滤ID1。0 B, C+ M5 R! \6 m. A F1 EFTI[1:0](bit31:30):扩展过滤类型 00:范围过滤,过滤到EF1ID到EF2ID中的所有信息。* T" h* @( Q( y7 i8 D 01:双过滤,过滤到EF1ID和EF2ID中的信息。 }% r& n7 c! L' {+ J 10:传统位过滤,EF1ID 是过滤值,EF2ID 是掩码。 11:范围过滤,过滤到ED1ID到ED2ID中的所有信息,没有使用FDCAN XIDAM的掩码 F1 EFID2[10:9](bit10:9):设置接收到的消息是存放在Rx buffer中还是处理为debug消息的消息A、B或C。 00将消息存储在Rx Buffer中。 01:debug消息A。 10:debug消息B。6 X" ?) g1 i2 x 11:debug消息C. F1 EFD2[8:6]bit8:6):确定是否将滤波器事件引脚作为外部接口。- n' m% d+ e* C% p8 B; Z9 Z0 I F1EDIF2[5:0](bit5:0):定义当匹配以后存储消息的区域相对于Rx buffer的开始地址RXBC.RBSA的偏移。 3)、RXF0C.F0SA、RXF1C.F1SA和 RXBC.RBSA分别为Rx FIFO0、Rx FIFO1、Rx buffer,它们都有64个元素,元素的大小根据数据长度不同而不同,范围为4-18字,它们的位定义相同: 9 B' k" l, Q0 [' j \ R0 ESI(bit31):错误状态标志 0:传输节点显性错误。2 Y- ~5 c' t- Q7 E: Y1 h: f 1:传输节点隐形错误 R0 XTD(bit30):标记是标准ID还是扩展ID5 N# Q& a+ |: A+ p4 e5 j3 ^ 0:11位标准ID 3 B" f- z+ T! T 1:29位扩展ID R0 RTR(bit29):遥控传输请求,标记当前帧是数据帧还是遥控帧。/ C4 i, F! {- j5 [% `" Z 0:接收到的帧是数据帧/ o) G' Y# i/ f+ d 1 n- [3 V( I3 G& w 1:接收到的帧是遥控帧 R0 ID[28:0](bit28:0):帧ID,根据XTD位决定是11位ID还是29位ID。 R1 ANMF(bit31):! v9 @' E7 {" ^$ t/ | 0:接收到的帧和FIDX中的过滤帧匹配 . {6 T+ ?4 u2 f/ i( i 1:接收到的帧和Rx滤波器中的任何元素都不匹配* z+ E8 i& T/ ^& c6 z: a R1 FIDX[6:0]bit30:24):过滤器索引,0~127,表示和Rx滤波器中的哪个元素匹配。# H% y3 S0 y/ L% T R1 FDF(bit21):帧格式 0:标准帧格式4 a( L q8 @1 ^# }2 e 1:FDCAN帧格式。( Q$ `. U- L, X8 A3 u* | ~* K1 ` R1 BRS(bit20):比特率切换 0:接收到的帧没有比特率切换3 U/ K$ j+ c7 M4 u. z$ i" G7 u * z" C* S9 M4 h% j. q0 Q+ S 1:接收到的帧带有比特率切换- D1 u* H7 E. W3 M1 g i6 ]* _; Q R1 DLC[3:0](bit):数据长度。6 N# n8 o4 U Z 0-8:传统CAN+CAN FD,接收帧长度为0~8字节。 9-15:如果是传统CAN,接收帧长度为8字节。8 r2 u: Y0 [9 q y9 B" N 9-15:如果是 CAN FD模式的话表示接收到的长度12/16/20/24/32/48/64 字节。 R1 RXTS[15:0](bit15:0):接收时间戳。 R2-Rn:接收到的数据。$ s' \7 l7 H6 \7 H1 \* [ 4)、TXEFC.EFSA:发送事件FIFO,有32个元素,每个元素两个字。 b D1 K2 c- ], E5 T " q; H {1 \$ l2 H) y4 u 5 Q: ^. c: ]# j6 o9 H6 X8 K1 W3 ` E0 ESI (bit31): 错误状态标志 0:传输节点显性错误8 T/ y% w! w u0 `* U. j6 T7 O7 K 1:传输节点隐形错误。 E0 XTD(bit30):标记是标准D还是扩展ID 0:11位标准ID.( Y5 ~/ d/ v; [0 d+ s; a6 n 1:29位扩展ID E0RTR(bit29):遥控传输请求,标记当前帧是数据帧还是遥控帧。$ @4 O- D, x! J' z F6 ?4 U2 ]3 T 0:发送的帧是数据帧2 Q( O) X% _0 ~: `) b 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):事件类型* }; z- L: T) t( c/ v 00:保留。 01:发送事件。* J7 Z( N X4 q4 K 10:发送取消。 11 :保留。 E1 EDL(bit21):扩展数据长度 0:标准帧格式( S/ X- c4 [# _) U 1:FDCAN帧格式。% \/ j$ B* {0 |4 a! e K E1 BRS(bit20):比特率切换; C+ [" D8 o& m" n- _ 0:发送的帧没有比特率切换 d; U. _# [$ }) R 1:发送的帧带有比特率切换- e' [9 s( e3 D; P E1 DLC[19:16](bit):数据长度。 0-8:传统CAN+CANFD,长度为0~8字节。 9-15:长度为8字节。 E1 TXTS[15:0]bit15:0):时间戳。3 s# K1 |4 a1 ?4 h8 K9 m 5)、TXBC.TBSA:发送buffer,有32个元素。9 W0 {2 s* a0 S* G" c$ } 4 u0 k/ C3 I8 R! X* [4 q1 A T0 ESI(bit31):错误状态标志 0:CANFD帧中取决于被动错误标志。 1:CANFD帧中隐形错误。9 D& G1 x# D9 B+ i T0 XTD(bit30):标记是标准ID还是扩展ID 0:11位标准ID7 e9 b$ {3 l8 x: z7 h4 i 1:29位扩展ID ' }- X3 e$ S; R( A2 ?1 O+ N T0RTR(bit29):遥控传输请求,标记当前帧是数据帧还是遥控帧。 0:发送的帧是数据帧 1:发送到的帧是遥控帧" H" f+ m @/ N5 e4 f) M T0 ID[28:0](bit28:0):帧ID,根据XTD位决定是11位ID还是29位ID。8 E" _" V- A4 ~" Z" K2 H T1 MM[7:0](bit31:24):消息掩码,配置Tx buffer的时候由CPU写入。3 \. L* Y1 Y5 R1 {. H! y T1 EFC(bit23):事件FIFO控制; i' L; B* j/ G5 h. X& p8 E) | 0:不存储发送事件。' O/ I) a: N6 L+ @' k 1:存储发送事件。' b/ x# `2 Y6 ~, X T1 FDF(bit1):帧格式! {2 V+ U+ ]4 E7 t7 F' e7 a: n 0:标准帧格式 1:FDCAN帧格式。 T1 BRS(bit20):比特率切换, Y4 U" ]; T/ s8 i' t; K! {' ` 0:发送的帧没有比特率切换 1:发送的帧带有比特率切换 T1 DLC[19:16](bit):数据长度。 0-8:传统CAN+CAN FD,接收帧长度为0~8字节。) I7 E3 Y. W/ Y8 _. u! ?; s2 E, r 9-15:传统CAN,接收帧长度为8字节。 x0 q/ w0 o& s9 e 9-15:CAN FD模式的话表示接收到的长度12/16/20/24/32/48/64字节。: J, C/ ~4 \# |- g b T2-Tn:发送的数据。 三、过滤器设置5 J2 Y5 Y4 p4 ` 标准帧和扩展帧过滤器的设置分别往SIDFC.FLSSA和 XIDFC.FLESA区域写数据即可,下面以标准帧为例,标准帧过滤器共有3种过滤模式:$ @, z: U2 f0 l, Q - S- T3 S* G! A. P+ t 1、指定范围过滤* r$ _4 |& U# T- M( r2 ?) `+ ~) H3 o 通过SIDFC.FLSSA的SFID1和SFID2来设置需要过滤的ID范围,其中SFID2的值要大于SFID1,这样只有ID值在SFID1~SFID2之内的消息才能被接收到。比如我们现在要设置只接收ID在0X123~0X321范围内的消息,使用标准滤波器n,SIDFC.FLSSAn(n=0~128)的各个位设置如下: 0 L. N2 a8 L% ?
2、指定ID过滤 我们也可以设置只接收指定的一个或者两个ID的消息,如果只接收指定的一个ID消息的话SFID1=SFID2。比如我们要设置只接收ID为0X123的消息,设置如下:
3、传统的位过滤 第3种过滤模式就是以前STM32的CAN上存在的位过滤模式,在屏蔽位模式下,过滤消息D和过滤掩码一起工作决定接收哪些消息,其中SFID1为过滤的消息ID,SFID2 为过滤掩码。举个简单的例子,我们设置过滤器SIDFC.FLSSAn工作在:传统位过滤模式,然后设置如下:( r, K2 m" V1 F; _7 W 6 v! P$ O; u. q# M
其中SFID1是我们期望接收到的消息ID,我们希望最好接收到ID=0xFF00的消息。SFID2的0xF000规定了我们必须关心的ID,也就是接收到的消息ID其位[15:12]必须和SFID1中的位[15:12]完全一样,其他的位不关心。也即是说接收到的消息ID必须是0XxFxx这样的才算正确(x表示不关心)。 . X1 ]8 O8 p/ L) s 四、CAN的收发过程; d+ [6 |) N0 `: b. p' G 1、CAN接收过程2 V+ T5 I: ~$ O, H4 N% g( _ # l! r0 u) c1 h/ J1 \ CAN接收到消息后会进行过滤,过滤时会从SIDFC.FLSSA或XIDFC.FLESA区域的第0个元素开始匹配,直至符合过滤器中的某个元素的规则,则过滤完成,过滤完成的消息会按照过滤器元素的配置进行处理。比如过滤器元素的SFEC/EFEC位决定过滤后的消息是放入接收FIFO0、接收FIFO1还是专用的接收buffer中。0 U. Q# _8 S/ e R. ]- v 接收FIFO 0 和接收FIFO 1 最多可分别保存64个元素。两个接收FIFO的配置是通过寄存器RXF0C和RXF1C完成的。 0 m2 b q5 A" S# L- c8 _7 l 当IR寄存器的RFnF(n为0或1,代表接收FIFO0或接收FIFO1)指示接收FIFO已满条件时,在至少已读取一条消息且接收FIFO 获取索引递增之前,不会继续向相应的接收 FIFO 写入消息。如果在相应的接收 FIFO 已满时收到消息, 此消息会被丢弃,中断标志IR[RFnL]会置 1。也就是说,接收FIFO满了后会触发RFnF中断,如果继续接收,消息会被丢弃,并触发RFnL中断。 , D% Q& f# b: _3 N7 y1 I 1 } |2 p$ Y0 F0 y0 z4 q+ t$ p 为了避免接收FIFO溢出,可使用接收FIFO水印。当接收FIFO填充级别达到由RXFnC[FnWM] 配置的接收FIFO水印时,中断标志IR[RFnW]会置 1。比如接收FIFO0大小配置为64,水印值配置为60,当接收了60条消息时会触发水印中断,提前进行处理,这样就避免了FIFO溢出。- b6 E. c5 A5 e; E& b 读取消息就是直接从RXF0C.F0SAn或RXF1C.F1SAn(n为索引号 0~64)中读,消息的组成结构在上面已经说过了,比如HAL库HAL_FDCAN_GetRxMessage函数读数据过程(假定从FIFO0中读): 6 C$ ^' t4 ]9 `0 B7 x
1)、((hfdcan->Instance->RXF0S & FDCAN_RXF0S_F0GI) >> 8):作用是获得FIFO0状态寄存器FDCAN_RXF0S的bit[13:8],读这里可以知道接收到的数据保存在FIFO0中的第几个元素中,GetIndex的范围是0-63,刚好对应64个元素。当FIFO接收到一条消息,GetIndex会加一,当从FIFO中取出一条消息,GetIndex会减一。. R/ g8 e$ p. \4 y. C F 6 q+ @ U8 j$ G8 W$ y $ z; h D3 J r: H5 n- D. y 2)、hfdcan->msgRam.RxFIFO0SA:是RXF0C.F0SA区域的开始地址。 3)、hfdcan->Init.RxFifo0ElmtSize:是接收FIFO0的元素大小,根据数据长度不同而不同 1 S+ e7 k8 @2 t: J1 D/ `: Q$ }" W# G * K- _" Q- y* ~. K$ O1 ] 如果消息长度是8个字节,那么算上固定的R0、R1,元素大小就是4个字。 如果消息长度是64个字节,那么元素大小就是64/4+2=18个字。 计算出了元素在FIFO0中的地址,直接按数据结构读数据就可以了:( Z* n; x) i9 a( U) S4 L
2、CAN发送流程 在消息RAM中TXBC.TBSA是发送buffer,最多有32个元素。发送buffer可以配置为专用发送buffer和发送FIFO/队列。发送FIFO/队列是指要么是发送FIFO,要么是发送队列,即发送FIFO模式或发送队列模式,由TXBC[TFQM]决定: 因此发送buffer可以有三种组合:全部是专用发送buffer、专用发送buffer加发送FIFO、专用发送buffer加发送队列。; _+ @2 o) W( ~1 Q 下面来介绍一下专用发送buffer、发送FIFO、发送队列的情况:/ G+ d8 g, f) j! N$ a2 I3 z & G7 t8 ?. d( x* m- V 1)、专用发送buffer 8 ~; Y5 X5 ?/ y4 k 专用发送buffer可完全在CPU的控制下发送消息。每个专用发送buffer都配置了特定的消息ID。如果多个发送buffer配置为使用同一消息ID,则会先发送buffer编号最小的发送buffer中的消息。% Q7 z6 Y' V h; @( ]1 q 如果数据部分已更新,则会通过添加请求的TXBAR[ARn] 位请求发送。请求的消息在内部会与发送FIFO/队列中的消息进行仲裁,在外部会与CAN总线上的消息进行仲裁, 并会根据其消息ID发送出去。- g: X5 w8 x) s/ x, l + X2 M& W! x" z" q5 X* U, h6 T A, } ) i7 M( J" B, b" A 专用发送buffer会在消息RAM中分配四个32位字(元素大小为4个字)。因此消息RAM中专用发送buffer的起始地址是:0 r" A$ A5 o* }6 T4 j- ? ) D1 h+ j5 W+ ~; R, \9 j 发送buffer索引 (0…31) *4+发送buffer起始地址。 . D1 ]: s% h; C# q7 Q" a! N 9 S, j' F# n9 e( E 2)、发送FIFO / n0 M1 G% G4 R$ k( b5 Y* g1 A0 m+ m 发送FIFO中存储的消息是先从获取索引TXFQS[TFGI] 引用的消息开始发送的。每次发送后,获取索引会循环递增,直至发送FIFO为空。发送FIFO可按消息写入发送FIFO的顺序发送来自不同发送buffer但消息ID相同的消息。FDCAN会计算获取索引和放入索引之差作为发送FIFO空闲级别TXFQS[TFFL],用于指示可用(空闲)的发送 FIFO 元素数。, ^8 p# ?& H/ K% s. J, W: T 新的发送消息必须写入以放入索引 TXFQS[TFQPI] 引用的发送buffer开始的发送FIFO中。 添加请求会将放入索引增加到下一空闲发送FIFO元素。放入索引达到获取索引后,会指示发送FIFO已满(TXFQS[TFQF]=“1”)。在这种情况下,下一条消息已发送且获取索引已递增之前,不应继续向发送FIFO写入消息。 3 W; x9 e a" b4 t, j, T, C D ! M: F$ Z6 ]- n" n0 l9 } 发送FIFO其实就是个环形队列,放入索引是队头,获取索引是队尾,队头和队尾之间保存着消息,相减当然就是待发送消息个数。当从发送FIFO取出数据,获取索引会自加,自加到等于放入索引时,表示发送FIFO为空。当放入数据到发送FIFO,放入索引自加,当绕了一圈自加到等于获取索引时,表示发送FIFO满了。 : v$ |% @ L h& G; k+ r 如果有一条消息添加到发送FIFO,则会通过向与发送FIFO放入索引引用的发送buffer相关的TXBAR位写入“1”来请求消息的发送。 r& K3 k7 j0 Y' K; C 如果有多条 (n条) 消息添加到发送FIFO,则会写入以放入索引开始的n个连续发送buffer中。 随后会通过TXBAR请求发送。放入索引随后会循环递增n。请求的发送buffer数不应超过发送FIFO空闲级别指示的空闲发送buffer数。+ m0 V; R6 ?! q) f5 X7 S 如果获取索引引用的发送buffer的发送请求取消,获取索引会增加到下一个具有挂起发送请求的发送buffer,并会重新计算发送FIFO空闲级别。如果取消对其他任何发送buffer的发送,获取索引和FIFO空闲级别保持不变。0 d7 f9 m( u h; p4 [4 Y4 L3 ^ 发送FIFO元素会在消息RAM中分配4个32位字。因此下一可用(空闲)发送FIFO 缓冲区的起始地址是:: n$ S( B; J. z u9 G6 \5 F ) R4 ]0 Z- l) @- R. u 放入索引 TXFQS[TFQPI] (0…31)的值*4+发送buffer起始地址。+ E8 q9 \9 Y" |. V" ~; i8 i 3)、发送队列 发送队列中存储的消息是先从消 息ID最小(优先级最高)的消息开始发送的。如果多个队列缓冲区配置为使用同一消息 ID,则会先发送缓冲区编号最小的队列缓冲区。0 [+ ?0 O4 q# W1 a% R1 r3 t$ f3 ~ 新消息必须写入放入索引 TXFQS[TFQPI] 引用的发送buffer中(放入索引 TXFQS[TFQPI] 只是队列头的数字)。添加请求会将放入索引循环增加到下一空闲发送buffer。如果发送队列已满(TXFQS[TFQF]=“1”),则放入索引无效,并且在至少有一个请求的消息已发出或挂起的发送请求已取消之前,不应继续向发送队列写入消息。- m3 q5 x& h9 l( B; { 应用可使用寄存器 TXBRP来代替放入索引,并可将消息放入任何没有挂起传输请求的发送缓冲区中。3 y$ W& B2 [& c( T. d 发送队列缓冲区会在消息 RAM 中分配四个 32 位字。因此下一可用(空闲)发送队列缓冲区 的起始地址是: R; v8 s& M! R$ m" r# I6 L 发送队列放入索引 TXFQS[TFQPI] (0…31) 的值*4+发送buffer的起始地址。1 v6 K' v. n8 P 6 H2 ^6 I) ]: t3 E9 B+ x 因此可以知道专用发送buffer、发送FIFO、发送队列的区别是对放入其中的多条消息的发送顺序不同:4 n9 X8 T4 \, H( j0 o7 S# {3 Z% V 4 Q% B( J4 b3 v5 e6 P 1)、发送buffer全部配置为专用发送buffer' S; Q( M2 H8 D2 `. R0 p3 Z! [ 每个专用发送buffer都配置了特定的消息ID。如果多个发送buffer配置为使用同一消息ID,则会先发送buffer编号最小的发送buffer中的消息。6 C% B' g' u$ L' y2 ^: O( j 2)、混合专用发送buffer和发送FIFO 在这种情况下,消息RAM中的发送buffer部分会被划分为一组专用发送buffer和一个发送FIFO。专用发送buffer的数量是通过 TXBC[NDTB] 配置的。分配给发送FIFO的发送buffer数量是通过 TXBC[TFQS] 配置的。如果 TXBC[TFQS] 编程为 0,则仅会使用专用发送buffer。6 E* C( ]3 Z4 n, _ . Q5 Z3 Y: R1 F* N $ U& Z) Z8 r/ p& c9 ]& Z! t6 } 发送优先次序:8 B+ H% V; C4 w% A5 r) } A、扫描专用发送缓冲区和时间最久的挂起发送FIFO缓冲区(TXFS[TFGI] 引用的缓冲区) B、消息ID最小的缓冲区优先级最高,下次将发送该缓冲区的数据' c; E5 `" }, L& L- _1 ^ 3)、混合专用发送buffer和发送队列9 D" X7 \7 P, _ 在这种情况下,消息 RAM 中的发送缓冲区会被划分为一组专用发送缓冲区和一个发送队 列。专用发送缓冲区的数量是通过 TXBC[NDTB] 配置的。发送队列缓冲区的数量是通过 TXBC[TFQS] 配置的。如果 TXBC[TFQS] 编程为 0,则仅会使用专用发送缓冲区。 发送优先级设置:# G. A, `+ i' B X; y A、扫描所有激活了发送请求的发送缓冲区 B、消息 ID 最小的发送缓冲区优先级最高,下次将发送该缓冲区的数据, P. l: t$ V! S. F, U/ B+ I! K 再来介绍一下发送事件FIFO,它并不是发送FIFO,不是用来存储发送消息的,而是用来存储发送消息的状态的。" } v! S9 I# ^ FDCAN 在 CAN 总线上发送消息 后,消息 ID 和时间戳会存储在发送事件 FIFO 元素中。为了将发送事件关联到发送事件 FIFO 元素,已发送的发送缓冲区中的消息标志会被复制到发送事件 FIFO 元素中。 发送事件 FIFO 最多可配置为 32 个元素。发送 FIFO 中介绍了发送事件 FIFO 元素。根据元素 大小 (TXESC) 的配置,会使用 2 到 16 个 32 位字 (Tn = 3 ..17) 来存储 CAN 消息数据字段。, F0 O# k; @/ S& x/ l 发送事件 FIFO 的用途是将处理发送状态信息与处理发送消息分开,也就是让发送缓冲区仅 保存要发送的消息,而将发送状态单独存储在发送事件 FIFO 中。这样做有很大的优势,尤 其是在处理动态管理的发送队列时,发送缓冲区可在发送成功后立即用于新消息。覆盖发送缓冲区之前,不需要保存该发送缓冲区的发送状态信息。 为了防止发送事件FIFO溢出,发送事件FIFO仍然支持水印功能。 , D, D2 @9 j7 L5 I' x( n CAN发送消息的代码实现: V6 c3 G+ `7 a5 D : f1 h" _3 L! ]5 K 发送流程与接收流程刚好相反,只需要把消息按照TXBC.TBSA格式构建好,然后写入发送buffer,写进去后设置FDCAN_TXBAR寄存器的指定位为1来请求传输即可。如HAL库函数HAL_FDCAN_AddMessageToTxFifoQ:' Y/ H/ _0 C( G# R. n! Y0 l* x6 |0 ~
发送buffer请求寄存器FDCAN_TXBAR,描述为: 8 @6 I" X4 p4 k8 ]3 ?. p5 r 此寄存器用来设置FDCAN的哪个发送buffer可以发送数据,FDCAN有32个发送buffer,当把消息复制到这些buffer中后,就需要设置此寄存器来标记此buffer可以发送。' {; A* ?* v4 R0 N0 O- e % c1 x9 O1 z3 l( o3 Y0 r. \ # {2 y H5 D, G0 G& ? 五、初始化$ z3 r3 j* {/ n2 [& x* X( w4 ^ / s9 t" t& W6 N 初始化可以直接使用HAL库, - w& {' Y7 P. I" @
六、CAN发送示例(来自正点原子)5 j1 m! e0 I4 [$ I% _, ?9 ` # V4 F$ ^2 J$ r+ K
七、CAN接收示例(来自正点原子,因为滤波器被关联到FIFO0,因此消息只会放入到FIFO0)# h* s6 B {( ^4 N
参考:《正点原子》 STM32H7开发指南-HAL库版本_V1.0 (文档中有很多错误的地方,笔者被误导了好久,阅读的时候还是要以H7官方手册为准) : n" u3 w' s+ c, X2 g# Z |
stm32使用定时器触发dma传输,启动dma没反应的几种情况的解决方法
【Wio Lite AI视觉开发套件】+cube.ai与食物识别
【STM32H7S78-DK】汽车仪表系统
【STM32H7S78-DK】基于 rtthread 适配 lcd 驱动移植 lvgl
【STM32H7S78-DK评测】TouchGFX (QR Code)二维码生成器
【STM32H7S78-DK】rtthread 增加 psram 内存管理
【STM32H7S78-DK】开箱与rtthread工程初体验
【STM32H7S78-DK评测】-5 LVGL&DMA2D DEMO测试
【STM32H7S78-DK评测】-4 LTDC&DMA2D 基本测试
【STM32H7S78-DK评测】CoreMark移植和优化--兼记printf重定向实现方法及常见问题