STM32学习心得三十二:CAN通信基础知识、原理、配置及实验

记录一下,方便以后翻阅~
主要内容:
1) CAN通信基础知识;
2) STM32 CAN控制器简介;
3) 相关实验代码解读。
参考资料:《STM32中文参考手册_V10》第22章——控制器局域网(bxCAN)
实验功能:CAN实验需要两个开发板,系统启动后,主开发板可以让STM32F1的CAN工作在环回模式/普通模式下,通过KEY_UP按键切换模式。默认是环回模式,在环回模式下,按下KEY0,则可以在串口调试助手上面看到自发自收的消息。如果是普通模式,按下次开发板的SW4,可以在主开发板对应的串口调试助手上看到收到的信息。
硬件连接:
实验需要两个开发版,将它们的CAN_H相连,CAN_L相连。具体的,主开发板选择PA11和PA12作为CAN_RX和CAN_TX,次开发板选择PB8和PB9作为CAN_RX和CAN_TX。
1. CAN基础知识
1.1 什么是CAN(Controller Area Network)
CAN是ISO国际标准化的串行通信协议。由德国电气商博世公司在1986 年率先提出。此后,CAN 通过ISO11898 及ISO11519 进行了标准化。现在在欧洲已是汽车网络的标准协议。
CAN协议经过ISO标准化后有两个标准:ISO11898标准和ISO11519-2标准。其中ISO11898是针对通信速率为125Kbps~1Mbps的高速通信标准,而ISO11519-2是针对通信速率为125Kbps以下的低速通信标准。
CAN具有很高的可靠性,广泛应用于:汽车电子、工业自动化、船舶、医疗设备、工业设备等方面。
1.2 CAN的特点
1) 多主控制。总线空闲时,所有单元都可发送消息,而两个以上的单元同时开始发送消息时,根据标识符(ID,非目的地址)决定优先级。两个以上的单元同时开始发送消息时,对各消息ID 的每个位进行逐个仲裁比较。仲裁获胜(优先级最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作;
2) 系统柔软性。连接总线的单元,没有类似“地址”的信息,因此,在总线上添加单元时,已连接的其他单元的软硬件和应用层都不需要做改变;
3) 速度快,距离远。最高1Mbps(距离<40M),最远可达10KM(速率<5Kbps);
4) 具有错误检测、错误通知和错误恢复功能。所有单元都可以检测错误(错误检测功能),检测出错误的单元会立即同时通知其他所有单元(错误通知功能),正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止(错误恢复功能);
5) 故障封闭功能。CAN 可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去;
6) 连接节点多。CAN 总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。
正是因为CAN协议的这些特点,使得CAN特别适合工业过程监控设备的互连,因此,越来越受到工业界的重视,并已公认为最有前途的现场总线之一。
1.3 ISO11898标准物理层特征
物理层特征如图所示:
在这里插入图片描述
上图中,CAN 控制器根据CAN_L和CAN_H上的电位差来判断总线电平。总线电平分为显性电平(CAN_H约3.5V,CAN_L约1.5V)和隐性电平(CAN_H和CAN_L都为2.5V时)。发送方通过使总线电平发生变化,将消息发送给接收方:
1) 显性电平对应逻辑:0,CAN_H和CAN_L之差为2V左右;
2) 隐性电平对应逻辑:1,CAN_H和CAN_L之差为0V。
逻辑电平值0和1对应STM32芯片上的I/O口的电平值。
逻辑电平值对应CAN_H和CAN_L,中间会经过CAN收发芯片TJA1050,连接如下图所示:
在这里插入图片描述
显性电平具有优先权,只要有一个单元输出显性电平,总线上即为显性电平。而隐形电平则具有包容的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平(显性电平比隐性电平更强)。另外,在CAN总线的起止端都有一个120Ω的终端电阻,来做阻抗匹配,以减少回波反射。
1.4 帧种类型
1.4.1 5种类型
1) 数据帧(最重要):用于发送单元向接收单元传输数据的帧;
2) 遥控帧:用于接收单元向具有相同ID的发送单元请求数据的帧;
3) 错误帧:用于当检测出错误时向其他单元通知错误的帧;
4) 过载帧:用于接收单元通知其尚未做好接收准备的帧;
5) 间隔帧:用于将数据帧及遥控帧与前面的帧分离开来的帧。
其中,数据帧和遥控帧有标准格式和扩展格式两种格式:
标准格式有11个位的标识符(ID),扩展格式有29 个位的ID 。
1.4.2 数据帧介绍
1.4.2.1 数据帧由7段组成:
1) 帧起始(SOF):表示数据帧开始的段;
2) 仲裁段:表示该帧优先级的段;
3) 控制段:表示数据的字节数及保留位的段;
4) 数据段:数据的内容,一帧可发送0~8个字节的数据;
5) CRC段:检查帧的传输错误的段;
6) ACK段:表示确认正常接收的段;
7) 帧结束(EOF):表示数据帧结束的段。
1.4.4.2 数据帧的构成:
在这里插入图片描述
上图中,D表示显性电平,R表示隐形电平(下同)。
1.4.2.3 数据帧解析
1) 帧起始:标准帧和扩展帧都是由1个位的显性电平,值为0表示帧起始;
2) 仲裁段:表示数据优先级的段,标准帧和扩展帧格式在本段有所区别,如图所示:
在这里插入图片描述
ID:高位在前,低位在后。基本ID,禁止高7位都为隐性,即不能:ID=1111111XXXX
RTR:远程请求位。0,数据帧;1, 远程帧;
SRR:替代远程请求位(RTR)。设置为1(隐性电平);
IDE:标识符选择位。0,标准标识符;1,扩展标识符。
标准格式里也有IDE,只是放在了控制段里。
3) 控制段:由6个位构成,表示数据段的字节数。标准帧和扩展帧的控制段稍有不同,如图所示:
在这里插入图片描述
r0,r1:保留位,必须以显现电平发送,但是接收可以是隐性电平;
DLC:数据长度码,0~8,表示发送/接收的数据长度(字节);
IDE:标识符选择位。0,标准标识符;1,扩展标识符。
4) 数据段:该段可包含0~8个字节的数据,从最高位(MSB)开始输出。标准帧和扩展帧在这个段的格式完全一样:
在这里插入图片描述
5) CRC段:该段用于检查帧传输错误。由15个位的CRC顺序和1个位的CRC界定符(用于分隔的位)组成,标准帧和扩展帧在这个段的格式也是相同的:
在这里插入图片描述
CRC的值计算范围包括:帧起始、仲裁段、控制段、数据段。
接收方以同样的算法计算 CRC 值并进行比较,不一致时会通报错误。
6) ACK段:此段用来确认是否正常接收。由ACK槽(ACK Slot)和ACK界定符2个位组成。标准帧和扩展帧在这个段的格式也是相同的:
在这里插入图片描述
发送单元ACK段:发送2个隐性位。
接收单元ACK段:接收到正确消息的单元,在ACK槽发送显性位,通知发送单元,正常接收结束。称之为发送ACK/返回ACK。
注意:发送 ACK 的是既不处于总线关闭态也不处于休眠态的所有接收单元中,接收到正常消息的单元(发送单元不发送ACK)。
正常消息是指:不含填充错误、格式错误、CRC 错误的消息。
7)帧结束:由7个位的隐性位组成(即7个1)。标准帧和扩展帧在这个段格式完全一样。
1.5 总线仲裁
同时多个单元发送数据时,总线仲裁过程:
在这里插入图片描述
规律:1,总线空闲时,最先发送的单元获得发送优先权,一但发送,其他单元无法抢占。2,如果有多个单元同时发送,则连续输出显性电平多的单元,具有较高优先级。
从ID开始比较,如果ID相同,还可能会比较RTR和SRR等位。
1.6 位时序
1.6.1 位速率:由发送单元在非同步的情况下发送的每秒钟的位数称为位速率。一个位一般可以分为如下四段:
1) 同步段(SS);
2) 传播时间段(PTS);
3) 相位缓冲段1(PBS1);
4) 相位缓冲段2(PBS2)。
这些段又由可称为 Time Quantum(以下称为Tq)的最小时间单位构成。1 位分为4 个段,每个段又由若干个Tq 构成,这称为位时序。
位时间=1/波特率,因此,知道位时间,我们就可以知道波特率。
1 位由多少个Tq 构成、每个段又由多少个Tq 构成等,可以任意设定位时序。通过设定位时序,多个单元可同时采样,也可任意设定采样点。
1.6.2 位时序各段的作用和 Tq 数如下表:
在这里插入图片描述
备注:在STM32上,传播时间段和相位缓冲段的值是加在一起的,则对应Tq数为2~16。
1.6.2 一个位的构成:
在这里插入图片描述
图中采样时间加大或减少量的最大值就是SJW。
举例:假设设置1M的波特率,则Tq设为0.1μs。
2. STM32 CAN控制器简介
2.1 STM32自带了基本扩展CAN外设,又称bxCAN。bxCAN的特点如下:
2.1.1 支持CAN协议2.0A和2.0B主动模式;
2.1.2 波特率最高达1Mbps;
2.1.3 支持时间触发通信;
2.1.4 具有3个发送邮箱
2.1.5 具有3级深度的2个接收FIFO
2.1.6 可变的筛选器组(也称过滤器组,普通的F1有14个,F1互联型和F4有28个)。
2.2 模式
2.2.1 工作模式(通过CAN_MCR寄存器控制INRQ和SLEEP)
2.2.1.1 初始化模式(INRQ=1,SLEEP=0);
2.2.1.2 正常模式(INRQ=0,SLEEP=0);
2.2.1.3 睡眠模式(SLEEP=1)。
2.2.2 测试模式(通过CAN_BTR寄存器控制LBKM和SILM)
2.2.2.1 静默模式( LBKM=0,SILM=1 ):
接收总线上CANRX的数据,不发送数据至CANTX上(一直发1,即隐形电平);
在这里插入图片描述
2.2.2.2 环回模式( LBKM=1,SILM=0 ):
发送数据到总线的CANTX上,且通过环路传至接收端,但不接受CANRX的数据;
在这里插入图片描述
2.2.2.3 环回静默模式(LBKM=1,SILM=1):自己给自己发数据,测试用。
在这里插入图片描述
2.2.3 调试模式
2.3 bxCAN框图
在这里插入图片描述
2.3.1 普通F1系列只有1个主CAN,互联型F1和F4还有一个从CAN,即两个CAN;
2.3.2 两个CAN分别拥有自己的发送邮箱和接收FIFO,但是他们共用28个筛选器。
2.4 标识符删选器
2.4.1 CAN的标识符不表示目的地址而是表示发送优先级。接收节点根据标识符的值,来决定是否接收对应消息;
2.4.2 STM32 CAN控制器,提供了28个可配置的筛选器组(F1仅互联型才有28个,其他的只有14个),可降低CPU处理CAN通信的开销;
2.4.3 STM32 CAN控制器每个筛选器组由2个32位寄存器组成(CAN_FxR1和CAN_FxR2,x=0~27)。根据位宽不同,每个筛选器组可提供:
1个32位筛选器,包括:STDID[10:0]、EXTID[17:0]、IDE和RTR位;
或者2个16位筛选器,包括:STDID[10:0]、IDE、RTR和EXTID[17:15]位。
2.4.4 筛选器可配置为:屏蔽位模式和标识符列表模式。在屏蔽位模式下,标识符寄存器和屏蔽寄存器一起,指定报文标识符的任何一位,应该按照“必须匹配”或“不用关心”处理。而在标识符列表模式下,屏蔽寄存器也被当作标识符寄存器用。因此,不是采用一个标识符加一个屏蔽位的方式,而是使用2个标识符寄存器。接收报文标识符的每一位都必须跟筛选器标识符相同。
通过CAN_FM1R和CAN_FS1R寄存器可配置筛选器的位宽和模式:
在这里插入图片描述
筛选器的用途:
1) 为了过滤出一组标识符,应该设置筛选器组工作在屏蔽位模式(标识符掩码);
2) 为了过滤出一个标识符,应该设置过滤器组工作在标识符列表模式;
3) 应用程序不用的筛选器组,应该保持在禁用状态(通过CAN_FA1R设置);
4) 筛选器组中的每个筛选器,都被编号为(即:筛选器编号)从0开始,到某个最大数值-取决于筛选器组的模式和位宽的设置;
5) 通过CAN_FFA1R寄存器的设置,可以将筛选器组关联到FIFO0/FIFO1。
举例:
设置筛选器组0工作在:1个32位筛选器-标识符屏蔽模式,然后设置CAN_F0R1=0XFFFF0000,CAN_F0R2=0XFF00FF00。
则CAN_F0R1的值是期望收到的ID,即(STID+EXTID+IDE+RTR)最好是:0XFFFF0000;
而CAN_F0R2=0XFF00FF00是我们需要关心的ID,表示收到的映像,其位[31:24]和位[15:8]这16个位的必须和CAN_F0R1中对应的位一模一样,而另外的16个位则不关心,即收到的映像必须是0XFFxx00xx,才算是正确的(x表示不关心)。
2.5 STM32 CAN的发送流程
CAN的发送流程为:
程序选择1个空置的邮箱(TME=1)->设置标识符(ID),数据长度和发送数据à设置CAN_TIxR的TXRQ位为1,请求发送->邮箱挂号(等待成为最高优先级)->预定发送(等待总线空闲)->发送->邮箱空置。
在这里插入图片描述
APRQ=1意思是强制退出;若设置NART重复发送状态,则发送失败会回到预定状态,尝试再次发送,待达到所设的最大次数仍为成功,则也会发送失败。
2.6 接收流程
CAN接收流程为:
FIFO空->收到有效报文->挂号_1(存入FIFO的一个邮箱,这个由硬件控制,我们不需要理会)->收到有效报文->挂号_2->收到有效报文->挂号_3->收到有效报文->溢出。
CAN收到的有效报文,存储在3级邮箱深度的FIFO中。FIFO接收到的报文数,我们可以通过查询CAN_RFxR的FMP寄存器来得到,只要FMP不为0,我们就可以从FIFO读出收到的报文。
在这里插入图片描述
报文FIFO具有锁定功能(由CAN_MCR,RFLM位控制),锁定后,新数据将丢弃,不锁定则新数据将替代老数据。
2.7 位时序
STM32的CAN位时序,如下图所示:
在这里插入图片描述
提示:STM32的CAN将传播时间段(PTS)和相位缓冲时间段1(PBS1)合并成时间段1(BS1);
STM32F103,设TS1=8、TS2=7、BRP=3,波特率=36000/[(9+8+1)*4]=500Kbps;
STM32F407,设TS1=6、TS2=5、BRP=5,波特率=42000/[(7+6+1)*6]=500Kbps。
2.8 寄存器简介
2.8.1 CAN主控制寄存器(CAN_MCR)
在这里插入图片描述
INRQ位用来控制初始化请求:
设置INRQ=0,可使CAN从初始化模式进入正常工作模式;
设置INRQ=1,可使CAN从正常工作模式进入初始化模式 。
CAN初始化时,先设置INRQ=1,进入初始化模式,进行初始化(尤其是CAN_BTR的设置,该寄存器,必须在CAN正常工作之前设置),之后再设置INRQ=0,进入正常工作模式。
2.8.2 CAN位时序寄存器(CAN_BTR)
在这里插入图片描述
备注:tCAN就是tq。
2.8.3 CAN接收FIFO寄存器(CAN_RF0R/CAN_RF1R)
在这里插入图片描述
CAN_RF0R用于FIFO0控制(寄存器描述如上图);
CAN_RF1R用于FIFO1控制。
2.8.4 CAN发送邮箱标识符寄存器(CAN_TIxR)(x=0~2)

在这里插入图片描述
2.8.5 CAN发送邮箱数据长度和时间戳寄存器 (CAN_TDTxR) (x=0~2)
在这里插入图片描述
TIME[15:0]和TGT位,用于时间戳相关设置,可参考《STM32中文参考手册》相关章节。
2.8.6 CAN发送邮箱数据寄存器(CAN_TDLxR/CAN_TDHxR)(x=0~2)
在这里插入图片描述
图为CAN_TDLxR寄存器的描述,用于存储低4个字节的数据。CAN_TDHxR寄存器与之类似,用于存储高4个字节的数据。要发送的数据就是存储在这两个寄存器。
2.8.7 CAN接收FIFO邮箱标识符寄存器(CAN_RIxR)(x=0/1)
在这里插入图片描述
2.8.8 CAN接收FIFO邮箱数据长度和时间戳寄存器(CAN_RDTxR)(x=0/1)
在这里插入图片描述
TIME[15:0],用于时间戳相关设置,FMI用于存储筛选器匹配索引,可参考《STM32中文参考手册》相关章节。
2.8.9 CAN接收FIFO邮箱邮箱数据寄存器(CAN_RDLxR/CAN_RDHxR)(x=0/1)
在这里插入图片描述
图为CAN_RDLxR寄存器的描述,用于存储低4个字节的数据。CAN_RDHxR寄存器与之类似,用于存储高4个字节的数据。接收到的数据就存储在这两个寄存器。
2.8.10 CAN筛选器模式寄存器(CAN_FM1R)
在这里插入图片描述
该寄存器设置筛选器的工作模式,必须在CAN_FMR寄存器FINIT=1时配置,对于STM32F103ZET6,只有[13:0]位有效,对于互联性STM32F1或者STM32F407,则全部有效。
2.8.11 CAN筛选器尺度寄存器(CAN_FS1R)
在这里插入图片描述
该寄存器用于设置筛选器的位宽,必须在CAN_FMR寄存器FINIT=1时配置,对于STM32F103ZET6,只有[13:0]位有效,对于互联性STM32F1或者STM32F407,则全部有效。
2.8.12 CAN筛选器FIFO关联寄存器(CAN_FFA1R)
在这里插入图片描述
该寄存器设置报文通过筛选器组之后,被存入的FIFO,如果对应位为0,则存放到FIFO0;如果为1,则存放到FIFO1。该寄存器也只能在过滤器处于初始化模式( CAN_FMR 寄存器的FINIT=1 )下配置。
2.8.13 CAN筛选器激活寄存器(CAN_FA1R)
在这里插入图片描述
该寄存器用于设置筛选器组的开启和关闭。对对应位置1,即开启对应的筛选器组;置0则关闭该筛选器组。
2.8.14 CAN筛选器组i寄存器x(CAN_FiRx)(i=0~27,x=1/2)
在这里插入图片描述
提示:STM32F103ZET6只有0~13个i。
每个筛选器组的CAN_FiRx都由2个32位寄存器构成,即:CAN_FiR1和CAN_FiR2。根据过滤器位宽和模式的不同设置,这两个寄存器的功能也不尽相同。
2.9 初始化流程
2.9.1 配置相关引脚的复用功能,使能CAN时钟:
要用CAN,先要使能CAN的时钟,CAN的时钟通过APB1ENR的第25位来设置;
其次要设置CAN的相关引脚为复用输出:设置PA11为上拉输入(CAN_RX引脚),PA12为复用输出(CAN_TX引脚);
并使能PA口的时钟。
2.9.2 设置CAN工作模式及波特率等:
通过先设置CAN_MCR寄存器的INRQ位,让CAN进入初始化模式,然后设置CAN_MCR的其他相关控制位。再通过CAN_BTR设置波特率和工作模式(正常模式/环回模式)等信息。 最后设置INRQ为0,退出初始化模式。
2.9.3 设置滤波器:
本例程将使用筛选器组0,并工作在32位标识符屏蔽位模式下。先设置CAN_FMR的FINIT位,进入初始化模式,然后设置筛选器组0的工作模式以及标识符ID和屏蔽位。最后激活筛选器,并退出初始化模式。
3. 相关实验代码解读
这里仅给出主开发板的实验代码,次开发板的实验代码跟主开发板的代码基本差不多。
3.1 can.h头文件代码解读

#ifndef __CAN_H
#define __CAN_H  
#include "sys.h"       
//CAN接收RX0中断使能//
#define CAN_RX0_INT_ENABLE 0                               //0,不使能;1,使能,默认不使能//                                      
u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode); //CAN初始化函数// 
u8 Can_Send_Msg(u8* msg,u8 len);                           //发送数据函数//
u8 Can_Receive_Msg(u8 *buf);                               //接收数据函数//
#endif

3.2 can.c文件代码解读

#include "can.h"
#include "led.h"
#include "delay.h"
#include "usart.h"
//CAN初始化函数//
//tsjw:重新同步跳跃时间单元.范围:CAN_SJW_1tq~ CAN_SJW_4tq//
//tbs2:时间段2的时间单元.   范围:CAN_BS2_1tq~CAN_BS2_8tq//
//tbs1:时间段1的时间单元.   范围:CAN_BS1_1tq ~CAN_BS1_16tq//
//brp :波特率分频器.范围:1~1024;  tq=(brp)*tpclk1//
//波特率=Fpclk1/((tbs1+1+tbs2+1+1)*brp)//
//mode:CAN_Mode_Normal,普通模式;CAN_Mode_LoopBack,回环模式//
//Fpclk1的时钟在初始化的时候设置为36M,如果设置CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack)//
//则波特率为:36M/((8+9+1)*4)=500Kbps//
//返回值:0,初始化OK,其他,初始化失败//
u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{ 
 GPIO_InitTypeDef       GPIO_InitStructure; 
 CAN_InitTypeDef         CAN_InitStructure;
 CAN_FilterInitTypeDef   CAN_FilterInitStructure;
 #if CAN_RX0_INT_ENABLE 
  NVIC_InitTypeDef      NVIC_InitStructure;
 #endif
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //使能PORTA时钟//                                
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);    //使能CAN1时钟// 
 //配置GPIOA的引脚12,对应CAN_TX//
 GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_12;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;       //复用推挽输出//
 GPIO_Init(GPIOA, &GPIO_InitStructure);   
 //配置GPIOA的引脚11,对应CAN_RX//
 GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;
 GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPU;         //上拉输入//
 GPIO_Init(GPIOA, &GPIO_InitStructure);   
 //CAN单元设置,主要针对CAN_MCR和CAN_BTR寄存器//
 CAN_InitStructure.CAN_TTCM=DISABLE;  
 //针对CAN_MCR主控制寄存器,位[7],TTCM时间触发通信模式,设0,禁止时间触发通信模式// 
 CAN_InitStructure.CAN_ABOM=DISABLE;  //软件自动离线管理//  
 //针对CAN_MCR主控制寄存器,位[6],ABOM自动离线管理,设0,软件对INRQ位置1随后清0//
 //一旦硬件检测到128次11位连续的隐性位,则退出离线状态// 
 CAN_InitStructure.CAN_AWUM=DISABLE;  
 //针对CAN_MCR主控制寄存器,位[5],AWUM自动唤醒模式,设0,软件唤醒(清除SLEEP位)//
 CAN_InitStructure.CAN_NART=ENABLE; 
 //针对CAN_MCR主控制寄存器,位[4],NART禁止报文自动重传,设1,CAN报文只被发送1次//
 CAN_InitStructure.CAN_RFLM=DISABLE;  
 //针对CAN_MCR主控制寄存器,位[3],RFLM接收FIFO锁定模式,设0//
 //在接收溢出时FIFO未被锁定,当接收FIFO的报文未被读出,下一个收到的报文会覆盖原有的报文// 
 CAN_InitStructure.CAN_TXFP=DISABLE;  
 //针对CAN_MCR主控制寄存器,位[2],TXFP发送FIFO优先级,设0,优先级由报文的标识符来决定//
 CAN_InitStructure.CAN_Mode= mode;    
 //针对CAN_BTR位时序寄存器,位[30],LBKM回环模式,设0时禁止回环模式,设1时允许回环模式//
 //设置波特率,以下4行代码都是针对CAN_BTR位时序寄存器//
 CAN_InitStructure.CAN_SJW=tsjw;     
 //位[24~25],SJW重新同步跳跃宽度//
 CAN_InitStructure.CAN_BS1=tbs1;    
 //位[16~19],TS1时间段1//
 CAN_InitStructure.CAN_BS2=tbs2;    
 //位[20~22],TS2时间段2//
 CAN_InitStructure.CAN_Prescaler=brp; //位[0~9],BRP波特率分频器,分频系数(Fdiv)为brp+1// 
 CAN_Init(CAN1, &CAN_InitStructure);        
 //CAN过滤器配置//
 CAN_FilterInitStructure.CAN_FilterNumber=0;                       //过滤器0//
 CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;  
 //针对CAN_FM1R过滤器模式寄存器,设0,过滤器组x的2个32位寄存器工作在标识符屏蔽位模式//
 CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;    //32位宽// 
 CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;                  //32位ID//
 CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
 CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;              //32位MASK//
 CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
 CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;//过滤器0关联到FIFO0//
 CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;              //激活过滤器0//
 CAN_FilterInit(&CAN_FilterInitStructure);  
 #if CAN_RX0_INT_ENABLE 
 CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);                         //FIFO0消息挂号中断允许//      
 NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;     
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;           
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
 NVIC_Init(&NVIC_InitStructure);
 #endif
 return 0;
}   
#if CAN_RX0_INT_ENABLE 
//编写中断服务函数//       
void USB_LP_CAN1_RX0_IRQHandler(void)
{
 CanRxMsg RxMessage;
 //CanRxMsg是一个结构体,包含7个入口参数//
 //uint32_t StdId,uint32_t ExtId,uint8_t IDE,uint8_t RTR,uint8_t DLC, uint8_t Data[8],uint8_t FMI//
 int i=0;
    CAN_Receive(CAN1, 0, &RxMessage);
 for(i=0;i<8;i++)
 printf("rxbuf[%d]:%d\r\n",i,RxMessage.Data[i]);                              //把RxMessage里的Data读取//
}
#endif
//can发送一组数据(固定格式:ID为0X12,标准帧,数据帧) 
//len:数据长度(最大为8),msg:数据指针,最大为8个字节,返回值:0,成功;其他,失败//
u8 Can_Send_Msg(u8* msg,u8 len)
{ 
 u8 mbox;
 u16 i=0;
 CanTxMsg TxMessage;
 TxMessage.StdId=0x12;         //标准标识符// 
 TxMessage.ExtId=0x12;         //扩展标示符// 
 TxMessage.IDE=CAN_Id_Standard;  //标准帧//
 TxMessage.RTR=CAN_RTR_Data;    //数据帧//
 TxMessage.DLC=len;          //数据长度//
 for(i=0;i<len;i++)
 TxMessage.Data[i]=msg[i];             
 mbox= CAN_Transmit(CAN1, &TxMessage);   
 i=0; 
 while((CAN_TransmitStatus(CAN1, mbox)==CAN_TxStatus_Failed)&&(i<0XFFF))i++; //等待发送结束//
 if(i>=0XFFF)return 1;
 return 0;  
}
//can口接收数据查询
//buf:数据缓存区,返回值:0,无数据被收到;其他,接收的数据长度//
u8 Can_Receive_Msg(u8 *buf)
{          
  u32 i;
  CanRxMsg RxMessage;
  if( CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0;   //没有接收到数据,直接退出// 
  CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);             //读取数据// 
  for(i=0;i<8;i++)
    buf[i]=RxMessage.Data[i];  
  return RxMessage.DLC; 
}

3.3 main.c文件代码解读

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"  
#include "can.h" 
 int main(void)
 {  
 u8 key;
 u8 i=0,t=0;
 u8 cnt=0;
 u8 canbuf[8];
 u8 res;
 u8 mode=CAN_Mode_LoopBack;                     //CAN工作模式;CAN_Mode_Normal(0):普通模式,CAN_Mode_LoopBack(1):环回模式//
 delay_init();                                 //延时函数初始化//   
 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级//
 uart_init(115200);                            //串口初始化为115200//
 LED_Init();                                 //初始化与LED连接的硬件接口//
 KEY_Init();                                 //按键初始化//    
 CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);  //CAN初始化环回模式,波特率500Kbps//     
 while(1)
 {
  key=KEY_Scan(0);
  if(key==KEY0_PRES)                           //KEY0按下,发送一次数据//
  {
   for(i=0;i<8;i++)
   {
    canbuf[i]=cnt+i;                         //填充发送缓冲区//
    printf("\n发送数据为:%d\r\n",canbuf[i]);
    }
   res=Can_Send_Msg(canbuf,8);                                  //发送8个字节// 
   if(res)
    printf("\n发送失败!\r\n");
   else   
    printf("\n发送成功!\r\n");   
  }else if(key==WKUP_PRES)                                       //WK_UP按下,改变CAN的工作模式
  {    
   mode=!mode;
   CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,mode); //CAN普通模式初始化, 波特率500Kbps 
   if(mode==0)                                                  
   {
    printf("\n切换至普通模式。\r\n");                          //普通模式,需要2个开发板// 
   }else                                                        
   {
    printf("\n切换至回环模式。\r\n");                          //回环模式,一个开发板就可以测试了//
   } 
  }   
  key=Can_Receive_Msg(canbuf);
  if(key)                                                        //接收到有数据//
  {   
    for(i=0;i<key;i++)
   {  
     printf("\n接收数据为:%d\r\n",canbuf[i]);    
    }
  }
  t++; 
  delay_ms(10);
  if(t==20)
  {
   LED0=!LED0;                                                  //提示系统正在运行 
   t=0;
   cnt++;
  }     
 }
}

4. 实验结果
在这里插入图片描述
旧知识点
1)复习如何新建工程模板,可参考STM32学习心得二:新建工程模板
2)复习基于库函数的初始化函数的一般格式,可参考STM32学习心得三:GPIO实验-基于库函数
3)复习寄存器地址,可参考STM32学习心得四:GPIO实验-基于寄存器
4)复习位操作,可参考STM32学习心得五:GPIO实验-基于位操作
5)复习寄存器地址名称映射,可参考STM32学习心得六:相关C语言学习及寄存器地址名称映射解读
6)复习时钟系统框图,可参考STM32学习心得七:STM32时钟系统框图解读及相关函数
7)复习延迟函数,可参考STM32学习心得九:Systick滴答定时器和延时函数解读
8)复习ST-LINK仿真器的参数配置,可参考STM32学习心得十:在Keil MDK软件中配置ST-LINK仿真器
9)复习ST-LINK调试方法,可参考STM32学习心得十一:ST-LINK调试原理+软硬件仿真调试方法
10)复习串口通信相关知识,可参考STM32学习心得十四:串口通信相关知识及配置方法

猜你喜欢

转载自blog.csdn.net/Leisure_ksj/article/details/106788852