CAN通信实验

描述

18.1 CAN协议

18.1.1 协议概述

CAN是Controller Area Network的缩写,最初是专门用于汽车网络的通信协议,与485协议相似,CAN也是一种2线制,采用两根线的电压差进行数据传输的协议,随着CAN协议的高性能与可靠性被认同,现在被广泛运用在工业自动化,船舶等当面,目前最热门的国六标准,J1939通信协议就是以CAN协议为基础设计的。

CAN协议最远通信距离可达10km,与485相似,CAN也有两种电平,分别为显性电平与隐性电平,当CAN_H与CAN_L电压一致的时候为隐性电平,反之为显性电平。实际上隐性电平代表逻辑电平1,显性电平代表逻辑电平0,CAN在通信的时候,也需要在每个设备输出端并联1120Ω的终端匹配电阻,用于进行阻抗匹配。

18.1.2 通信组成

CAN协议通过5种类型的帧进行数据通信:数据帧,遥控帧,错误帧,过载帧和间隔帧,其中数据帧与遥控帧具有标准格式与扩展格式两种,标准格式有11个位的标识符,扩展格式则有29个位的标识符,五种帧的功能如下所示。

(1)数据帧:用于实际数据传输

(2)遥控帧:用于接收端向具有相同ID的发送端请求数据

(3)错误帧:用于检测报错时通知其他设备

(4)过载帧:用于接收端通报尚未准备好接收准备

(5)间隔帧:用于将数据帧与遥控帧和前面的帧分割

完整的数据帧构成如下图所示。

工业自动化

数据帧一般由7段组成:

(1)帧起始:即数据帧开始的段,标准帧和扩展帧都是由1个位的显性电平表示帧起始

(2)仲裁段:表示该帧优先级,标准帧和扩展帧格式在这一段结构如下图所示。

工业自动化

其中RTR位用于标识是否是远程帧(0代表数据帧;1代表远程帧),IDE位为标识符选择位(0表示使用标准标识符;1表示使用扩展标识符),SRR位为代替远程请求位,为隐性位,代替了标准帧中的RTR位。

(3)控制段:表示数据的字节数即保留位,由6个位构成,表示数据段的字节数。标准帧和扩展帧的控制段结构如下图所示。

工业自动化

r0和r1为保留位,必须全部以显性电平发送,但是接收端可以接收显性、隐性及任意组合的电平。DLC段为数据长度表示段,高位在前,DLC段有效值为08,但是接收方接收到915的时候并不认为是错误。

(4)数据段:数据段内容,一段可以发送0~8个字节的数据,从最高位MSB开始输出

(5)CRC校验:CRC校验数据完整性

(6)应答段:表示正常接收

(7)帧结束:表示该帧结束

18.1.3 通信参数

由发送单元在非同步的情况下发送的每秒钟的位数称为位速率。一个位可分为4段。

(1)同步段(SS)

(2)传播时间段(PTS)

(3)相位缓冲段1(PBS1)

(4)相位缓冲段2(PBS2)

这些段又由可称为Tq的最小时间单位构成。1位分为4个段,每个段又由若干个Tq构成,这称为位时序。1位由多少个Tq构成、每个段又由多少个Tq构成等,可以任意设定位时序。通过设定位时序,多个单元可同时采样,也可任意设定采样点。各段的作用和Tq数如下表所示。

工业自动化

18.1.4 总线仲裁

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

工业自动化

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

18.1.5 STM32 CAN模组简介

STM32F1系列自带的是基本扩展CAN,支持CAN协议的2.0A和2.0B,支持报文发送的优先级要求可软件配置,最大通信速率1Mbps,3个发送邮箱和3级深度的2个接收缓存器FIFO,28个可变的滤波器组,STM32的CAN模组结构如下图所示。

工业自动化

从上面结构可以看出,两个CAN都分别具备自己的发送与接收邮箱,但是28个滤波器却是公用的,通过FMR寄存器可以设置滤波器的分配方式,STM32的每个滤波器组的位宽都可以独立配置,根据位宽的不同,每个滤波器组可以提供

(1)1个32位过滤器:包含STDID[10:0],EXTID[17:0],18位扩展ID,IDE和RTR位

(2)1个16位过滤器:包含STDID[10:0],IDE,RTR和EXTID[17:15]

此外过滤器可以配置为屏蔽位模式与标识符列表模式。

(1)在屏蔽位模式下,标识符寄存器和屏蔽寄存器一起,指定报文标识符的任何一位,应该按照“必须匹配”或“不用关心”处理

(2)标识符列表模式下,屏蔽寄存器也被当作标识符寄存器用。因此,不是采用一个标识符加一个屏蔽位的方式,而是使用2个标识符寄存器。接收报文标识符的每一位都必须跟过滤器标识符相同。

18.1.6 CAN的发送流程

第1步:程序选择1个空置的邮箱(TME=1),设置标识符(ID),数据长度和发送数据

第2步:设置CAN_TIxR的TXRQ位为1,请求发送

第3步:邮箱挂号(等待成为最高优先级)

第4步:预定发送(等待总线空闲)

第5步:发送

第6步:邮箱空置

整个发送流程如下图所示。

工业自动化

18.1.7 CAN的接收流程

CAN接收到的有效报文,被存储在3级邮箱深度的FIFO中。FIFO完全由硬件来管理,从而节省了CPU的处理负荷,简化了软件并保证了数据的一致性。应用程序只能通过读取FIFO输出邮箱,来读取FIFO中最先收到的报文。这里的有效报文是指那些正确被接收的(直到EOF都没有错误)且通过了标识符过滤的报文。前面我们知道CAN的接收有2个FIFO,我们每个滤波器组都可以设置其关联的FIFO,通过CAN_FFA1R的设置,可以将滤波器组关联到FIFO0/FIFO1。CAN接收流程为:

FIFO空->收到有效报文->挂号1(存入FIFO的一个邮箱,这个由硬件控制,我们不需要理会)->收到有效报文->挂号2->收到有效报文->挂号3->收到有效报文->溢出

这个流程里面,我们没有考虑从FIFO读出报文的情况,实际情况是:我们必须在FIFO溢出之前,读出至少1个报文,否则下个报文到来,将导致FIFO溢出,从而出现报文丢失。每读出1个报文,相应的挂号就减1,直到FIFO空,完整的接收流程图如下图所示。

工业自动化

FIFO接收到的报文数,我们可以通过查询CAN_RFxR的FMP寄存器来得到,只要FMP不为0,我们就可以从FIFO读出收到的报文。

18.1.8 通信速率计算

根据通信参数小节我们可以知道1个位是由4个段组成,分别为同步段(SS),传播时间段(PTS),相位缓冲段1(PBS1),相位缓冲段2(PBS2),STM32内部将传播时间段与相位缓冲段1合并为时间段1,这样1个位就是有3个段组成,即同步段SS,时间段1和时间段2(即相位缓冲段),由于波特率的定义是1秒内发送二进制位的个数,所以,CAN波特率的计算公式为

工业自动化

18.1.9 STM32F1系列CAN测试模式

(1)静默模式

通过对CAN_BTR寄存器的SILM位置1,来选择静默模式。在静默模式下,CAN可以正常地接收数据帧和远程帧,但只能发出隐性位,而不能真正发送报文。如果bxCAN需要发出显性位(确认位、过载标志、主动错误标志),那么这样的显性位在内部被接回来从而可以被CAN内核检测到,同时CAN总线不会受到影响而仍然维持在隐性位状态。因此,静默模式通常用于分析CAN总线的活动,而不会对总线造成影响-显性位(确认位、错误帧)不会真正发送到总线上,静默模式等效图如下图所示。

工业自动化

(2)环回模式

通过对CAN_BTR寄存器的LBKM位置1,来选择环回模式。在环回模式下,CAN把发送的报文当作接收的报文并保存(如果可以通过接收过滤)在接收邮箱里,环回模式等效图如下图所示。

工业自动化

(3)环回静默模式

该模式可用于“热自测试”,即可以像环回模式那样测试CAN,但却不会影响CANTX和CANRX所连接的整个CAN系统。在环回静默模式下,CANRX引脚与CAN总线断开,同时CANTX引脚被驱动到隐性位状态,环回静默模式等效图如下图所示。

工业自动化

18.2 相关寄存器

18.2.1 CAN主控制器:CAN_MCR

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
- DBF
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
RESET - TTCM ABOM AWUM NART RFLM TXFP SLEEP INRQ

Bit 16:DBF:调试冻结

0:在调试时,CAN照常工作

1:在调试时,冻结CAN的接收/发送。仍然可以正常地读写和控制接收FIFO

Bit 15:bxCAN软件复位

0:本外设正常工作

1:对bxCAN进行强行复位,复位后bxCAN进入睡眠模式。此后硬件自动对该位清0

Bit 7:时间触发通信模式

0:禁止时间触发通信模式

1:允许时间触发通信模式

Bit 6:自动离线管理

0:离线状态的退出过程是,软件对INRQ位进行置1随后清0后,一旦硬件检测到128次11位连续的隐性位,则退出离线状态

1:一旦硬件检测到128次11位连续的隐性位,则自动退出离线状态

Bit 5:自动唤醒模式

0:睡眠模式通过清除CAN_MCR寄存器的SLEEP位,由软件唤醒

1:睡眠模式通过检测CAN报文,由硬件自动唤醒。唤醒的同时,硬件自动对SLEEP和SLAK位清0

Bit 4:禁止报文自动重传

0:按照CAN标准,CAN硬件在发送报文失败时会一直自动重传直到发送成功

1:CAN报文只被发送1次,不管发送的结果如何

Bit 3:接收FIFO锁定模式

0:在接收溢出时FIFO未被锁定,当接收FIFO的报文未被读出,下一个收到的报文会覆盖原有的报文

1:在接收溢出时FIFO被锁定,当接收FIFO的报文未被读出,下一个收到的报文会被丢弃

Bit 2:发送FIFO优先级

0:优先级由报文的标识符来决定

1:优先级由发送请求的顺序来决定

Bit 1:睡眠模式请求(在复位后该位被置1)

1:可以请求CAN进入睡眠模式,一旦当前的CAN活动结束,CAN就进入睡眠

0:使CAN退出睡眠模式

Bit 0:初始化请求

0:当CAN在接收引脚检测到连续的11个隐性位后,CAN就达到同步,并为接收和发送数据作好准备了。为此,硬件相应地对INAK位清0

1:一旦当前的CAN活动结束,CAN就进入初始化模式。相应地,硬件对INAK位置1

18.2.2 CAN主状态寄存器:CAN_MSR

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
- RX SAMP RXM TXM - SLAKI WKUI ERRI SLAK INAK

Bit 11:CAN接收电平,该位反映CAN接收引脚的实际电平

Bit 10:上次采样值,CAN接收引脚的上次采样值(对应于当前接收位的值)

Bit 9:接收模式,该位为1表示CAN当前为接收器

Bit 8:发送模式,该位为1表示CAN当前为发送器

Bit 4:睡眠确认中断,当SLKIE=1,一旦CAN进入睡眠模式硬件就对该位置1,紧接着相应的中断被触发软件可对该位清0,当SLAK位被清0时硬件也对该位清0

Bit 3:唤醒中断挂号,当CAN处于睡眠状态,一旦检测到帧起始位,硬件就置该位为1

Bit 2:出错中断挂号,当检测到错误时,CAN_ESR寄存器的某位被置1,如果CAN_IER寄存器的相应中断使能位也被置1时,则硬件对该位置1

Bit 1:睡眠模式确认

0:CAN退出睡眠模式

1:CAN模块正处于睡眠模式

Bit 0:初始化确认

0:CAN退出初始化模式时

1:CAN模块正处于初始化模式

18.2.3 CAN位时序寄存器:CAN_BTR

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
SILM LBKM - SJW[1:0] - TS2[2:0] TS1[3:0]
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
- BRP[9:0]

Bit 31:静默模式(用于调试)

0:正常状态

1:静默模式

Bit 30:环回模式(用于调试)

0:禁止环回模式

1:允许环回模式

Bit 25~Bit 24:重新同步跳跃宽度,该位域定义了CAN硬件在每位中可以延长或缩短多少个时间单元的上限,t RJW =t CAN ×(SJW[1:0]+1)

Bit 22~Bit 20:时间段2,该位域定义了时间段2占用了多少个时间单元,t BS2 =t CAN ×(TS2[2:0]+1)

Bit 19~Bit 16:时间段1,该位域定义了时间段1占用了多少个时间单元,t BS1 =t CAN ×(TS1[3:0]+1)

Bit 9Bit 0:波特率分频器,该位域定义了时间单元tq的时间长度,t q =(BRP[9:0]+1)×tPCLK~

18.2.4 CAN发送状态寄存器:CAN_TSR

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
LOW2 LOW1 LOW0 TME2 TME1 TME0 CODE[1:0] ABRQ2 - TERR2 ALST2 TXOK2 RQCP2
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
ABRQ1 - TERR1 ALST1 TXOK1 RQCP1 ABRQ0 - TERR0 ALST0 TXOK0 RQCP0

Bit 31,Bit30,Bit 29:邮箱x最低优先级标志

当多个邮箱在等待发送报文,且邮箱x的优先级最低时,硬件对该位置1

Bit 28,Bit 27,Bit 26:发送邮箱x空

Bit 25~Bit 24:邮箱号

当有至少1个发送邮箱为空时,表示下一个空的发送邮箱号。

当所有的发送邮箱都为空时,表示优先级最低的那个发送邮箱号

Bit 23,Bit 15,Bit 7:邮箱x终止发送

Bit 19,Bit 11,Bit 3:邮箱x发送失败

Bit 18,Bit 10,Bit 2:邮箱x仲裁丢失

Bit 17,Bit 9,Bit 1:邮箱x发送成功

Bit 16,Bit 8,Bit 0:邮箱x请求完成

18.2.5 CAN接收FIFOx寄存器:CAN_RFxR

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
- ROFM FOVR FULL - FMP[1:0]

Bit 5:释放接收FIFO,输出邮箱软件通过对该位置1来释放接收FIFO的输出邮箱。如果接收FIFO为空,那么对该位置1没有任何效果,即只有当FIFO中有报文时对该位置1才有意义。如果FIFO中有2个以上的报文,由于FIFO的特点,软件需要释放输出邮箱才能访问第2个报文,当输出邮箱被释放时,硬件对该位清0

Bit 4:FIFO溢出,当FIFO0已满,又收到新的报文且报文符合过滤条件,硬件对该位置1,该位由软件清0

Bit 3:FIFO满,当FIFO0中有3个报文时,硬件对该位置1,该位由软件清0

Bit 1~Bit 0:FIFO报文数目,表示当前接收FIFO0中存放的报文数目

每当1个新的报文被存入接收FIFO0,硬件就对FMP0加1

每当软件对RFOM位写1来释放输出邮箱,FMP就被减1,直到其为0

18.2.6 CAN过滤器主控寄存器:CAN_FMR

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
- FINIT

Bit 0:过滤器初始化模式

0:过滤器组工作在正常模式

1:过滤器组工作在初始化模式

18.2.7 CAN过滤器模式寄存器:CAN_FM1R

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
- FBM[27:16]
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
FBM[15:0]

Bit 29~Bit 0:过滤器模式

0:过滤器组x的2个32位寄存器工作在标识符屏蔽位模式

1:过滤器组x的2个32位寄存器工作在标识符列表模式

18.2.8 CAN过滤器位宽寄存器:CAN_FS1R

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
- FSC[27:16]
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
FSC[15:0]

Bit 29~Bit 0:过滤器位宽设置

0:过滤器位宽为2个16位

1:过滤器位宽为单个32位

18.2.9 CAN过滤器FIFO关联寄存器:CAN_FFA1R

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
FFA[27:16]
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
FFA[15:0]

Bit 29~Bit 0:过滤器关联设置

0:过滤器被关联到FIFO0

1:过滤器被关联到FIFO1

18.2.10 CAN过滤器激活寄存器:CAN_FA1R

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
FACT[27:16]
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
FACT[15:0]

Bit 29~Bit 0:过滤器激活

0:过滤器被禁用

1:过滤器被激活

18.2.11 发送邮箱标识符寄存器:CAN_TIxR

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
STID[10:0]/EXID[28:18] EXID[17:13]
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
EXID[12:0] IDE RTR TXRQ

Bit 31~Bit 21:标准标识符或扩展标识符,依据IDE位的内容,这些位或是标准标识符,或是扩展身份标识的高字节

Bit 20~Bit 3:扩展标识符,扩展身份标识的低字节

Bit 2:标识符选择

0:使用标准标识符

1:使用扩展标识符

Bit 1:远程发送请求

0:数据帧

1:远程帧

Bit 0:发送数据请求,由软件对其置1,来请求发送邮箱的数据。当数据发送完成,邮箱为空时,硬件对其清0

18.2.12 发送邮箱数据长度和时间戳寄存器:CAN_TDTxR

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
TIME[15:0]
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
- TGT - DLC[3:0]

Bit 31~Bit 16:报文时间戳,该域包含了,在发送该报文SOF的时刻,16位定时器的值

Bit 8:发送时间戳(只有在CAN处于时间触发通信模式才有效)

0:不发送时间戳TIME[15:0]

1:发送时间戳TIME[15:0]

注:在长度为8的报文中,时间戳TIME[15:0]是最后2个发送的字节:TIME[7:0]作为第7个字节,TIME[15:8]为第8个字节,替换了写入CAN_TDHxR[31:16]的数据。为了把时间戳的2个字节发送出去,DLC必须编程为8。

Bit 3~Bit 0:发送数据长度,指定数据报文的数据长度或者远程帧请求的数据长度

18.2.13 发送邮箱低字节数据寄存器:CAN_TDLxR

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
DATA3[7:0] DATA2[7:0]
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
DATA1[7:0] DATA0[7:0]

Bit 31~Bit 24:数据字节3

Bit 23~Bit 16:数据字节2

Bit 15~Bit 8:数据字节1

Bit 7~Bit 0:数据字节0

18.2.14 发送邮箱高字节数据寄存器:CAN_TDHxR

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
DATA7[7:0] DATA6[7:0]
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
DATA5[7:0] DATA4[7:0]

Bit 31~Bit 24:数据字节7

Bit 23~Bit 16:数据字节6

Bit 15~Bit 8:数据字节5

Bit 7~Bit 0:数据字节4

18.2.15 接收邮箱标识符寄存器:CAN_RIxR

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
STID[10:0]/EXID[28:18] EXID[17:13]
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
EXID[12:0] IDE RTR -

Bit 31~Bit 21:标准标识符或扩展标识符,依据IDE位的内容,这些位或是标准标识符,或是扩展身份标识的高字节

Bit 20~Bit 3:扩展标识符,扩展身份标识的低字节

Bit 2:标识符选择

0:使用标准标识符

1:使用扩展标识符

Bit 1:远程发送请求

0:数据帧

1:远程帧

18.2.16 接收邮箱数据长度和时间戳寄存器:CAN_RDTxR

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
TIME[15:0]
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
FMI[7:0] - DLC[3:0]

Bit 31~Bit 16:报文时间戳,该域包含了,在接收该报文SOF的时刻,16位定时器的值

Bit 15~Bit 8:过滤器匹配序号

Bit 3~Bit 0:接收数据长度

18.2.17 接收邮箱低字节数据寄存器:CAN_RDLxR

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
DATA3[7:0] DATA2[7:0]
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
DATA1[7:0] DATA0[7:0]

Bit 31~Bit 24:数据字节3

Bit 23~Bit 16:数据字节2

Bit 15~Bit 8:数据字节1

Bit 7~Bit 0:数据字节0

18.2.18 接收邮箱高字节数据寄存器:CAN_RDHxR

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
DATA7[7:0] DATA6[7:0]
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
DATA5[7:0] DATA4[7:0]

Bit 31~Bit 24:数据字节7

Bit 23~Bit 16:数据字节6

Bit 15~Bit 8:数据字节5

Bit 7~Bit 0:数据字节4

18.3 实验例程

实验内容:利用CAN的回环模式进行数据收发测试,通信速率500Kbps。

(1)创建can.h文件,并输入以下代码。

/*********************************************************************************************************
            CAN    通    信    驱    动    文    件
*********************************************************************************************************/
#ifndef _CAN_H_
#define _CAN_H_


#include "sys.h"                
/*********************************************************************************************************
                  函    数    列    表
*********************************************************************************************************/
u8 CAN_Init( u8 tsjw, u8 tbs2, u8 tbs1, u16 brp, u8 mode ) ;                      //CAN初始化
u8 CAN_Tx_Msg( u32 id, u8 ide, u8 rtr, u8 len, u8 *dat ) ;                        //CAN发送数据
void CAN_Rx_Msg( u8 fifox, u32 *id, u8 *ide, u8 *rtr, u8 *len, u8 *dat ) ;                //CAN接收数据


#endif

(2)创建can.c文件,并输入以下代码。

/*********************************************************************************************************
            CAN    通    信    驱    动    程    序
*********************************************************************************************************/
#include "can.h"
/*******************************************************
Name    :CAN_Mode_Init
Function  :CAN初始化
Paramater  :
      tsjw:重新同步跳跃时间单元.范围:1~3
      tbs2:时间段2的时间单元.范围:1~8
      tbs1:时间段1的时间单元.范围:1~16
      brp:波特率分频器.范围:1~1024
      mode:工作模式
        0:普通模式
        1:回环模式
Return    :
      0:成功
      其他:失败
*******************************************************/
u8 CAN_Init( u8 tsjw, u8 tbs2, u8 tbs1, u16 brp, u8 mode )
{
  u16 i=0;
   if( ( tsjw==0 )||( tbs2==0 )||( tbs1==0 )||( brp==0 ) )
    return 1 ;
  //先减去1.再用于设置
  tsjw -= 1 ;
  tbs2 -= 1 ;
  tbs1 -= 1 ;
  brp -= 1 ;


  RCC->APB2ENR |= 1<<2 ;                                        //使能PA时钟
  GPIOA->CRH &= 0xFFF00FFF ;
  GPIOA->CRH |= 0x000B8000 ;
    GPIOA->ODR |= 3<<11 ;
  RCC->APB1ENR |= 1<<25 ;                                        //使能CAN时钟
  CAN1->MCR = 0x0000 ;                                        //退出睡眠模式
  CAN1->MCR |= 1<<0 ;                                          //请求CAN进入初始化模式
  while( ( CAN1->MSR&0x01 )==0 )
  {
    i ++ ;
    if( i>100 )
      return 2 ;                                          //进入初始化模式失败
  }
  CAN1->MCR |= 0<<7 ;                                          //非时间触发通信模式
  CAN1->MCR |= 0<<6 ;                                          //软件自动离线管理
  CAN1->MCR |= 0<<5 ;                                          //睡眠模式通过软件唤醒
  CAN1->MCR |= 1<<4 ;                                          //禁止报文自动传送
  CAN1->MCR |= 0<<3 ;                                          //报文不锁定,新的覆盖旧的
  CAN1->MCR |= 0<<2 ;                                          //优先级由报文标识符决定
  CAN1->BTR = 0x00000000 ;                                      //清除原来的设置
  CAN1->BTR |= mode<<30 ;                                        //模式设置
  CAN1->BTR |= tsjw<<24 ;                                        //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位
  CAN1->BTR |= tbs2<<20 ;                                        //Tbs2=tbs2+1个时间单位
  CAN1->BTR |= tbs1<<16 ;                                        //Tbs1=tbs1+1个时间单位
  CAN1->BTR |= brp<<0 ;                                        //分频系数(Fdiv)为brp+1
  CAN1->MCR &= ~( 1<<0 ) ;                                      //请求CAN退出初始化模式
  while( ( CAN1->MSR&0x01 )!=0 )
  {
    i ++ ;
    if( i>0xFFF0 )
      return 3 ;                                          //退出初始化模式失败
  }
  //过滤器初始化
  CAN1->FMR |= 1<<0 ;                                          //过滤器组工作在初始化模式
  CAN1->FA1R &= ~( 1<<0 ) ;                                      //过滤器0不激活
  CAN1->FS1R |= 1<<0 ;                                        //过滤器位宽为32位
  CAN1->FM1R |= 0<<0 ;                                        //过滤器0工作在标识符屏蔽位模式
  CAN1->FFA1R |= 0<<0 ;                                        //过滤器0关联到FIFO0
  CAN1->sFilterRegister[ 0 ].FR1 = 0x00000000 ;                            //32位ID
  CAN1->sFilterRegister[ 0 ].FR2 = 0x00000000 ;                            //32位MASK
  CAN1->FA1R |= 1<<0 ;                                        //激活过滤器0
  CAN1->FMR &= 0<<0 ;                                          //过滤器组进入正常模式
  return 0 ;
}
/*******************************************************
Name    :CAN_Tx_Msg
Function  :CAN发送数据
Paramater  :
      id:标准ID(11位)/扩展ID(11位+18位)
      ide:ID类型
        0:标准帧
        1:扩展帧
      rtr:数据类型
        0:数据帧
        1:远程帧
      len:要发送的数据长度
      *dat:数据指针
Return    :
      0~3:邮箱编号
      0xFF:无有效邮箱
*******************************************************/
u8 CAN_Tx_Msg( u32 id, u8 ide, u8 rtr, u8 len, u8 *dat )
{
  u16 i;
  u8 mbox, sta=0 ;
  //邮箱0为空
  if( CAN1->TSR&( 1<<26 ) )
    mbox = 0 ;
  //邮箱1为空
  else if( CAN1->TSR&( 1<<27 ) )
    mbox = 1 ;
  //邮箱2为空
  else if( CAN1->TSR&( 1<<28 ) )
    mbox = 2 ;
  //无空邮箱,无法发送
  else
    return 0xFF ;
  CAN1->sTxMailBox[ mbox ].TIR = 0 ;                                  //清除之前的设置
  //标准帧
  if( ide==0 )
  {
    id &= 0x7FF ;                                          //取低11位stdid
    id <<= 21 ;
  }
  //扩展帧
  else
  {
    id &= 0x1FFFFFFF ;                                        //取低32位extid
    id <<= 3 ;
  }
  CAN1->sTxMailBox[ mbox ].TIR |= id ;
  CAN1->sTxMailBox[ mbox ].TIR |= ide<<2 ;
  CAN1->sTxMailBox[ mbox ].TIR |= rtr<<1 ;
  len &= 0x0F ;                                            //得到低四位
  CAN1->sTxMailBox[ mbox ].TDTR &= 0xFFFFFFF0 ;
  CAN1->sTxMailBox[ mbox ].TDTR |= len ;                                //设置DLC
  //待发送数据存入邮箱
  CAN1->sTxMailBox[ mbox ].TDHR = ( ( (u32)dat[7]<<24 )|( (u32)dat[6]<<16 )|( (u32)dat[5]<<8 )|( (u32)dat[4] ) ) ;
  CAN1->sTxMailBox[ mbox ].TDLR = ( ( (u32)dat[3]<<24 )|( (u32)dat[2]<<16 )|( (u32)dat[1]<<8 )|( (u32)dat[0] ) ) ;
  CAN1->sTxMailBox[ mbox ].TIR |= 1<<0 ;                                //请求发送邮箱数据
  //获取发送状态
  while( ( sta!=0x07 )&&( i<0xFFF ) )
  {
    i ++ ;
    switch( mbox )
    {
      //邮箱0
      case 0:
        sta |= CAN1->TSR&( 1<<0 ) ;                                //RQCP0
        sta |= CAN1->TSR&( 1<<1 ) ;                                //TXOK0
        sta |= CAN1->TSR&( 1<<26 )>>24 ;                            //TME0
        break;
      //邮箱1
      case 1:
        sta |= CAN1->TSR&( 1<<8 )>>8 ;                              //RQCP1
        sta |= CAN1->TSR&( 1<<9 )>>8 ;                              //TXOK1
        sta |= CAN1->TSR&( 1<<27 )>>25 ;                            //TME1
        break;
      //邮箱2
      case 2:
        sta |= CAN1->TSR&( 1<<16 )>>16 ;                            //RQCP2
        sta |= CAN1->TSR&( 1<<17 )>>16 ;                            //TXOK2
        sta |= CAN1->TSR&( 1<<28 )>>26 ;                            //TME2
        break;
      //邮箱号不对
      default:
        sta = 0x05 ;
      break ;
    }
  }
  if( i==0xFFF )
    mbox = 0xFF ;
  return mbox ;
}
/*******************************************************
Name    :CAN_Rx_Msg
Function  :CAN接收数据
Paramater  :
      fifox:邮箱号
      id:标准ID(11位)/扩展ID(11位+18位)
      ide:ID类型
        0:标准帧
        1:扩展帧
      rtr:数据类型
        0:数据帧
        1:远程帧
      len:要发送的数据长度
      *dat:数据指针
Return    :None
*******************************************************/
void CAN_Rx_Msg( u8 fifox, u32 *id, u8 *ide, u8 *rtr, u8 *len, u8 *dat )
{
  if( ( fifox==0 )&&( ( CAN1->RF0R&0x03 )==0 ) )
    len = 0 ;
  else if( ( fifox==1 )&&( ( CAN1->RF1R&0x03 )==0 ) )
    len = 0 ;
  else
  {
    //接收数据
    *ide = CAN1->sFIFOMailBox[ fifox ].RIR&0x04 ;                          //得到标识符选择位的值
    if( *ide==0 )
      *id = CAN1->sFIFOMailBox[ fifox ].RIR>>21 ;                          //标准标识符
    else
      *id = CAN1->sFIFOMailBox[ fifox ].RIR>>3 ;                          //扩展标识符
    *rtr = CAN1->sFIFOMailBox[ fifox ].RIR&0x02 ;                          //得到远程发送请求值
    *len = CAN1->sFIFOMailBox[ fifox ].RDTR&0x0F ;                          //得到DLC
    dat[ 0 ] = CAN1->sFIFOMailBox[ fifox ].RDLR&0xFF ;
    dat[ 1 ] = ( CAN1->sFIFOMailBox[ fifox ].RDLR>>8 )&0xFF ;
    dat[ 2 ] = ( CAN1->sFIFOMailBox[ fifox ].RDLR>>16 )&0xFF ;
    dat[ 3 ] = ( CAN1->sFIFOMailBox[ fifox ].RDLR>>24 )&0xFF ;
    dat[ 4 ] = CAN1->sFIFOMailBox[ fifox ].RDHR&0xFF ;
    dat[ 5 ] = ( CAN1->sFIFOMailBox[ fifox ].RDHR>>8 )&0xFF ;
    dat[ 6 ] = ( CAN1->sFIFOMailBox[ fifox ].RDHR>>16 )&0xFF ;
    dat[ 7 ] = ( CAN1->sFIFOMailBox[ fifox ].RDHR>>24 )&0xFF ;
    if( fifox==0 )
      CAN1->RF0R |= 0x20 ;                                    //释放FIFO0邮箱
    else if( fifox==1 )
      CAN1->RF1R |= 0x20 ;                                    //释放FIFO1邮箱
  }
}

(3)创建1.c文件,并输入以下代码。

#include "sys.h"
#include "delay.h"
#include "usart1.h"
#include "can.h"


int main()
{
  u32 id;
  u8 ide, rtr, len, canbuf[ 8 ] ;
  STM32_Clock_Init( 9 ) ;                                        //STM32时钟初始化
  SysTick_Init( 72 ) ;                                        //SysTick初始化
  USART1_Init( 72, 115200 ) ;                                      //初始化串口1波特率115200
  CAN_Init( 1, 8, 9, 4, 1 ) ;                                      //CAN初始化
  CAN_Tx_Msg( 0x12, 0, 0, 8, "CAN Test" ) ;                              //发送8个字节
  while( 1 )
  {
    CAN_Rx_Msg( 0, &id, &ide, &rtr, &len, canbuf ) ;                        //读取数据
  }
}
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

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

×
20
完善资料,
赚取积分