CAN 通讯实验

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协议的这些特点,使得CAN特别适合工业过程监控设备的互连,因此,越来越受到工业界的重视,并已公认为最有前途的现场总线之一。
CAN 协议经过 ISO 标准化后有两个标准:ISO11898 标准和 ISO11519-2 标准。其中 ISO11898是针对通信速率为 125Kbps~1Mbps 的高速通信标准,而 ISO11519-2 是针对通信速率为 125Kbps以下的低速通信标准。
本章,我们使用的是 500Kbps 的通信速率,使用的是 ISO11898 标准,该标准的物理层特征如图:

我们使用ISO11898标准,物理层特征如图所示

CAN 控制器根据CAN_L和CAN_H上的电位差来判断总线电平。总线电平分为显性电平和隐性电平,二者必居其一。发送方通过使总线电平发生变化,将消息发送给接收方。 显性电平对应逻辑:0 CAN_H和CAN_L之差为2V左右。 隐性电平对应逻辑:1 CAN_H和CAN_L之差为0V。显性电平具有优先权,只要有一个单元输出显性电平,总线上即为显性电平。而隐形电平则具有包容的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平(显性电平比隐性电平更强)。另外,在CAN总线的起止端都有一个120Ω的终端电阻,来做阻抗匹配,以减少回波反射。

CAN通信是以以下5种类型的帧进行的:

   其中,数据帧和遥控帧有标准格式和扩展格式两种格式。        标准格式有11 个位的标识符(ID),扩展格式有29 个位的ID 。 

数据帧由7个段组成: 

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

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

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

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

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

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

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

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

1,帧起始。标准帧和扩展帧都是由1个位的显性电平表示帧起始。2,仲裁段。表示数据优先级的段,标准帧和扩展帧格式在本段有所区别,如图所示:

 ID:高位在前,低位在后。 基本ID,禁止高7位都为隐性,即不能:ID=1111111XXXX。 RTR,远程请求位。0,数据帧;1, 远程帧; SRR,替代远程请求位。设置为1(隐性电平); IDE,标识符选择位。0,标准标识符;1,扩展标识符;

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个位的隐性位组成。标准帧和扩展帧在这个段格式完全一样。

同时多个单元发送数据时,总线仲裁过程:

规律:1,总线空闲时,最先发送的单元获得发送优先权,一但发送,其他单元无法抢占。2,如果有多个单元同时发送,则连续输出显性电平多的单元,具有较高优先级。 从ID开始比较,如果ID相同,还可能会比较RTR和SRR等位。

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

同步段(SS)

传播时间段(PTS)

相位缓冲段1(PBS1)

相位缓冲段2(PBS2) 

这些段又由可称为 Time Quantum(以下称为Tq)的最小时间单位构成。 1 位分为4 个段,每个段又由若干个Tq 构成,这称为位时序。 位时间=1/波特率,因此,知道位时间,我们就可以知道波特率。 1 位由多少个Tq 构成、每个段又由多少个Tq 构成等,可以任意设定位时序。通过设定位时序,多个单元可同时采样,也可任意设定采样点。

位时序各段的作用和 Tq 数如下表:

一个位的构成:

图中采样时间加大或减少量的最大值就是SJW 

重点掌握四个知识点:

1,标识符(ID)

2,数据帧的构成

3,总线仲裁

4,位时序

2、STM32 CAN控制器简介-bxCAN 

STM32自带了基本扩展CAN外设,又称bxCAN,bxCAN的特点如下:

支持CAN协议2.0A和2.0B主动模式

波特率最高达1Mbps

支持时间触发通信 具有3个发送邮箱

具有3级深度的2个接收FIFO

可变的筛选器组(也称过滤器组,最多28个 )

工作模式      

 ①初始化模式(INRQ=1,SLEEP=0)        

②正常模式(INRQ=0,SLEEP=0)        

③睡眠模式(SLEEP=1) 测试模式      

 ①静默模式( LBKM=0,SILM=1 )        

②环回模式( LBKM=1,SILM=0 )      

 ③环回静默模式(LBKM=1,SILM=1)

调试模式

CAN控制器框图如下:

两个CAN分别拥有自己的发送邮箱和接收FIFO,但是他们共用28个筛选器

CAN的标识符不表示目的地址而是表示发送优先级。接收节点根据标识符的值,来决定是否接收对应消息。

STM32 CAN控制器,提供了28个可配置的筛选器组(F1仅互联型才有28个,其他的只有14个),可降低CPU处理CAN通信的开销。

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个标识符寄存器。接收报文标识符的每一位都必须跟筛选器标识符相同。 

通过CAN_FM1R和CAN_FS1R可配置筛选器的位宽和模式:

为了过滤出一组标识符,应该设置筛选器组工作在屏蔽位模式。

为了过滤出一个标识符,应该设置过滤器组工作在标识符列表模式。

应用程序不用的筛选器组,应该保持在禁用状态(通过CAN_FA1R设置)。

筛选器组中的每个筛选器,都被编号为(即:筛选器编号)从0开始,到某个最大数值-取决于筛选器组的模式和位宽的设置。

通过CAN_FFA1R的设置,可以将筛选器组关联到FIFO0/FIFO1        

例:设置筛选器组0工作在:1个32位筛选器-标识符屏蔽模式,然后设置CAN_F0R1=0XFFFF0000,CAN_F0R2=0XFF00FF00。其中存放到CAN_F0R1的值就是期望收到的ID,即(STID+EXTID+IDE+RTR)最好是:0XFFFF0000。而0XFF00FF00就是设置我们需要必须关心的ID,表示收到的映像,其位[31:24]和位[15:8]这16个位的必须和CAN_F0R1中对应的位一模一样,而另外的16个位则不关心,可以一样,也可以不一样,都认为是正确的ID,即收到的映像必须是0XFFxx00xx,才算是正确的(x表示不关心)。 关于标识符筛选器的其他介绍,请看《STM32中文参考手册》,CAN相关章节。 

CAN发送流程 

CAN发送流程为: 程序选择1个空置的邮箱(TME=1)设置标识符(ID),数据长度和发送数据设置CAN_TIxR的TXRQ位为1,请求发送邮箱挂号(等待成为最高优先级)预定发送(等待总线空闲)发送邮箱空置。

CAN接收流程 

CAN接收流程为: FIFO空收到有效报文挂号_1(存入FIFO的一个邮箱,这个由硬件控制,我们不需要理会)收到有效报文挂号_2收到有效报文挂号_3收到有效报文溢出。

CAN收到的有效报文,存储在3级邮箱深度的FIFO中。FIFO接收到的报文数,我们可以通过查询CAN_RFxR的FMP寄存器来得到,只要FMP不为0,我们就可以从FIFO读出收到的报文。

STM32的CAN位时序,如下图所示:

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。

CAN主控制寄存器(CAN_MCR) 

该寄存器的我们仅介绍下INRQ位,该位用来控制初始化请求。 设置INRQ=0,可使CAN从初始化模式进入正常工作模式。 设置INRQ=1,可使CAN从正常工作模式进入初始化模式 。 CAN初始化时,先设置INRQ=1 ,进入初始化模式,进行初始化(尤其是CAN_BTR的设置,该寄存器,必须在CAN正常工作之前设置),之后再设置INRQ=0,进入正常工作模式。

CAN位时序寄存器(CAN_BTR)

CAN接收FIFO寄存器(CAN_RF0R/CAN_RF1R)

CAN发送邮箱标识符寄存器(CAN_TIxR)(x=0~2)

CAN发送邮箱数据长度和时间戳寄存器 (CAN_TDTxR) (x=0~2)

 CAN发送邮箱数据寄存器(CAN_TDLxR/CAN_TDHxR) (x=0~2)

图为CAN_TDLxR寄存器的描述,用于存储低4个字节的数据。CAN_TDHxR寄存器与之类似,用于存储高4个字节的数据。要发送的数据就是存储在这两个寄存器。

CAN接收FIFO邮箱标识符寄存器(CAN_RIxR)(x=0/1)

CAN接收FIFO邮箱数据长度和时间戳寄存器(CAN_RDTxR) (x=0/1)

 

CAN接收FIFO邮箱邮箱数据寄存器(CAN_RDLxR      /CAN_RDHxR) (x=0/1)

图为CAN_RDLxR寄存器的描述,用于存储低4个字节的数据。CAN_RDHxR寄存器与之类似,用于存储高4个字节的数据。接收到的数据就存储在这两个寄存器。

CAN筛选器模式寄存器(CAN_FM1R)

 该寄存器设置筛选器的工作模式,必须在CAN_FMR寄存器FINIT=1时配置,对于STM32F103ZET6,只有[13:0]位有效,对于互联性STM32F1或者STM32F407,则全部有效。

CAN筛选器FIFO关联寄存器(CAN_FFA1R)

CAN筛选器FIFO关联寄存器(CAN_FFA1R) 

该寄存器设置报文通过筛选器组之后,被存入的FIFO,如果对应位为0,则存放到FIFO0;如果为1,则存放到FIFO1。该寄存器也只能在过滤器处于初始化模式( CAN_FMR 寄存器的FINIT=1 )下配置。

CAN筛选器激活寄存器(CAN_FA1R)

该寄存器用于设置筛选器组的开启和关闭。对对应位置1,即开启对应的筛选器组;置0则关闭该筛选器组。

CAN筛选器组i寄存器x(CAN_FiRx)(i=0~27,x=1/2)

每个筛选器组的CAN_FiRx都由2个32位寄存器构成,即:CAN_FiR1和CAN_FiR2。根据过滤器位宽和模式的不同设置,这两个寄存器的功能也不尽相同。关于过筛选器的映射,功能描述和屏蔽寄存器的关联,在前面已经介绍过了。

2、STM32 CAN控制器简介-初始化流程 

①配置相关引脚的复用功能,使能CAN时钟。        

要用CAN,先要使能CAN的时钟,CAN的时钟通过APB1ENR的第25位来设置。其次要设置CAN的相关引脚为复用输出,这里我们需要设置PA11为上拉输入(CAN_RX引脚)PA12为复用输出(CAN_TX引脚),并使能PA口的时钟

②设置CAN工作模式及波特率等。        

通过先设置CAN_MCR寄存器的INRQ位,让CAN进入初始化模式,然后设置CAN_MCR的其他相关控制位。再通过CAN_BTR设置波特率和工作模式(正常模式/环回模式)等信息。 最后设置INRQ为0,退出初始化模式。

③设置滤波器。         本例程,我们将使用筛选器组0,并工作在32位标识符屏蔽位模式下。先设置CAN_FMR的FINIT位,进入初始化模式,然后设置筛选器组0的工作模式以及标识符ID和屏蔽位。最后激活筛选器,并退出初始化模式。

重点掌握以下内容:

1,标识符筛选器 2,数据收发流程 3,位时序(波特率设置) 4,了解相关寄存器及其作用 5,了解CAN初始化流程

CAN 的初始化配置步骤:
1)配置相关引脚的复用功能( AF9),使能 CAN 时钟。
我们要用 CAN,第一步就要使能 CAN 的时钟, CAN 的时钟通过 APB1ENR 的第 25 位来
设置。其次要设置 CAN 的相关引脚为复用输出, 这里我们需要设置 PA11( CAN1_RX)和 PA12
( CAN1_TX)为复用功能( AF9),并使能 PA 口的时钟。 具体配置过程如下:
//使能相关时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能 PORTA 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//使能 CAN1 时钟
//初始化 GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11| GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化 PA11,PA12
//引脚复用映射配置
GPIO_PinAFConfig(GPIOA,GPIO_PinSource11,GPIO_AF_CAN1);//PA11 复用为 CAN1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource12,GPIO_AF_CAN1);//PA12 复用为 CAN1
这里需要提醒一下, CAN 发送接受引脚是哪些 IO 口,可以在中文参考手册引脚表里面查找。
2)设置 CAN 工作模式及波特率等。
这一步通过先设置 CAN_MCR 寄存器的 INRQ 位,让 CAN 进入初始化模式,然后设置
CAN_MCR 的其他相关控制位。再通过 CAN_BTR 设置波特率和工作模式(正常模式/环回模式)
等信息。 最后设置 INRQ 为 0,退出初始化模式。
在库函数中,提供了函数 CAN_Init()用来初始化 CAN 的工作模式以及波特率, CAN_Init()
函数体中,在初始化之前,会设置 CAN_MCR 寄存器的 INRQ 为 1 让其进入初始化模式,然后
初始化 CAN_MCR 寄存器和 CRN_BTR 寄存器之后,会设置 CAN_MCR 寄存器的 INRQ 为 0
让其退出初始化模式。所以我们在调用这个函数的前后不需要再进行初始化模式设置。下面我
们来看看 CAN_Init()函数的定义:
uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct);
第一个参数就是 CAN 标号,这里我们的芯片只有一个 CAN,所以就是 CAN1。
第二个参数是 CAN 初始化结构体指针,结构体类型是 CAN_InitTypeDef,下面我们来看看这个
结构体的定义:
typedef struct
{
uint16_t CAN_Prescaler;
uint8_t CAN_Mode;
uint8_t CAN_SJW;
uint8_t CAN_BS1;
uint8_t CAN_BS2;
FunctionalState CAN_TTCM;
FunctionalState CAN_ABOM;
FunctionalState CAN_AWUM;
FunctionalState CAN_NART;
FunctionalState CAN_RFLM;
FunctionalState CAN_TXFP;
} CAN_InitTypeDef;
这个结构体看起来成员变量比较多,实际上参数可以分为两类。前面 5 个参数是用来设置寄存
器 CAN_BTR,用来设置模式以及波特率相关的参数,这在前面有讲解过,设置模式的参数是
CAN_Mode,我们实验中用到回环模式 CAN_Mode_LoopBack和常规模式 CAN_Mode_Normal,
大家还可以选择静默模式以及静默回环模式测试。其他设置波特率相关的参数 CAN_Prescaler,
CAN_SJW, CAN_BS1 和 CAN_BS2 分别用来设置波特率分频器,重新同步跳跃宽度以及时间
段 1 和时间段 2 占用的时间单元数。后面 6 个成员变量用来设置寄存器 CAN_MCR,也就是设
置 CAN 通信相关的控制位。大家可以去翻翻中文参考手册中这两个寄存器的描述,非常详细,
我们在前面也有讲解到。初始化实例为:
CAN_InitStructure.CAN_TTCM=DISABLE; //非时间触发通信模式
CAN_InitStructure.CAN_ABOM=DISABLE; //软件自动离线管理
CAN_InitStructure.CAN_AWUM=DISABLE; //睡眠模式通过软件唤醒
CAN_InitStructure.CAN_NART=ENABLE; //禁止报文自动传送
CAN_InitStructure.CAN_RFLM=DISABLE; //报文不锁定,新的覆盖旧的
CAN_InitStructure.CAN_TXFP=DISABLE; //优先级由报文标识符决定
CAN_InitStructure.CAN_Mode= CAN_Mode_LoopBack; //模式设置 1,回环模式;
CAN_InitStructure.CAN_SJW=CAN_SJW_1tq;//重新同步跳跃宽度为个时间单位
CAN_InitStructure.CAN_BS1=CAN_BS1_8tq; //时间段 1 占用 8 个时间单位
CAN_InitStructure.CAN_BS2=CAN_BS2_7tq;//时间段 2 占用 7 个时间单位
CAN_InitStructure.CAN_Prescaler=5; //分频系数(Fdiv)
CAN_Init(CAN1, &CAN_InitStructure); // 初始化 CAN1
3) 设置滤波器。
本章,我们将使用滤波器组 0,并工作在 32 位标识符屏蔽位模式下。先设置 CAN_FMR
的 FINIT 位,让过滤器组工作在初始化模式下,然后设置滤波器组 0 的工作模式以及标识符 ID
和屏蔽位。最后激活滤波器,并退出滤波器初始化模式。
在库函数中,提供了函数 CAN_FilterInit ()用来初始化 CAN 的滤波器相关参数, CAN_Init()
函数体中,在初始化之前,会设置 CAN_FMR 寄存器的 INRQ 为 INIT 让其进入初始化模式,
然后初始化 CAN 滤波器相关的寄存器之后,会设置 CAN_FMR 寄存器的 FINIT 为 0 让其退出
初始化模式。所以我们在调用这个函数的前后不需要再进行初始化模式设置。下面我们来看看
CAN_FilterInit ()函数的定义:
void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);
这 个 函 数 只 有 一 个 入 口 参 数 就 是 CAN 滤 波 器 初 始 化 结 构 体 指 针 , 结 构 体 类 型 为
CAN_FilterInitTypeDef,下面我们看看类型定义:
typedef struct
{
uint16_t CAN_FilterIdHigh;
uint16_t CAN_FilterIdLow;
uint16_t CAN_FilterMaskIdHigh;
uint16_t CAN_FilterMaskIdLow;
uint16_t CAN_FilterFIFOAssignment;
uint8_t CAN_FilterNumber;
uint8_t CAN_FilterMode;
uint8_t CAN_FilterScale;
FunctionalState CAN_FilterActivation;
} CAN_FilterInitTypeDef;
结构体一共有 9 个成员变量,第 1 个至第 4 个是用来设置过滤器的 32 位 id 以及 32 位 mask id,
分别通过 2 个 16 位来组合的,这个在前面有讲解过它们的意义。
第 5 个成员变量 CAN_FilterFIFOAssignment 用来设置 FIFO 和过滤器的关联关系,我们的实验
是关联的过滤器 0 到 FIFO0,值为 CAN_Filter_FIFO0。
第 6 个成员变量 CAN_FilterNumber 用来设置初始化的过滤器组,取值范围为 0~13。
第 7 个成员变量 FilterMode 用来设置过滤器组的模式,取值为标识符列表模式
CAN_FilterMode_IdList 和标识符屏蔽位模式 CAN_FilterMode_IdMask。
第 8 个成员变量 FilterScale 用来设置过滤器的位宽为 2 个 16 位 CAN_FilterScale_16bit 还是 1 个
32 位 CAN_FilterScale_32bit。
第 9 个成员变量 CAN_FilterActivation 就很明了了,用来激活该过滤器。
过滤器初始化参考实例代码:
CAN_FilterInitStructure.CAN_FilterNumber=0; //过滤器 0
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
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;// FIFO0
CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //激活过滤器 0
CAN_FilterInit(&CAN_FilterInitStructure);//滤波器初始化
4)发送接受消息
在初始化 CAN 相关参数以及过滤器之后,接下来就是发送和接收消息了。库函数中提供
了发送和接受消息的函数。发送消息的函数是:
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage);
这个函数比较好理解,第一个参数是 CAN 标号,我们使用 CAN1。第二个参数是相关消息结构
体 CanTxMsg 指针类型, CanTxMsg 结构体的成员变量用来设置标准标识符,扩展标识符,消
息类型和消息帧长度等信息。
接受消息的函数是:
void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage);
前面两个参数也比较好理解, CAN 标号和 FIFO 号。第二个参数 RxMessage 是用来存放接受到
的消息信息。结构体 CanRxMsg 和结构体 CanTxMsg 比较接近,分别用来定义发送消息和描述
接受消息,大家可以对照看一下,也比较好理解。
5) CAN 状态获取
对于 CAN 发送消息的状态,挂起消息数目等等之类的传输状态信息,库函数提供了一些
列的函数,包括 CAN_TransmitStatus()函数, CAN_MessagePending()函数, CAN_GetFlagStatus()
函数等等,大家可以根据需要来调用。
至此, CAN 就可以开始正常工作了。如果用到中断,就还需要进行中断相关的配置,本章
因为没用到中断,所以就不作介绍了


硬件设计

要用到的硬件资源如下:
1) 指示灯 DS0
2) KEY0 和 KEY_UP 按键
3) TFTLCD 模块
4) CAN
5) CAN 收发芯片 JTA1050
前面 3 个之前都已经详细介绍过了,这里我们介绍 STM32F4 与 TJA1050 连接关系,如图


从上图可以看出: STM32F4 的 CAN 通过 P11 的设置,连接到 TJA1050 收发芯片,然后通过接线端子( CAN)同外部的 CAN 总线连接。图中可以看出,在探索者 STM32F4 开发板上面是带有 120Ω的终端电阻的,如果我们的开发板不是作为 CAN 的终端的话,需要把这个电阻去掉,以免影响通信。另外,需要注意: CAN1 和 USB 共用了 PA11 和 PA12,所以他们不能同时使用。
这里还要注意,我们要设置好开发板上 P11 排针的连接,通过跳线帽将 PA11 和 PA12 分别连接到 CRX( CAN_RX)和 CTX( CAN_TX)上面,

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

软件设计
打开 CAN 通信实验的工程可以看到,我们增加了文件 can.c 以及头文件 can.h,同时 CAN相关的固件库函数和定义分布在文件 stm32f4xx_can.c 和头文件 stm32f4xx_can.h 中。
打开 can.c 文件,代码如下:

//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,回环模式;
u8 CAN1_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 CAN1_RX0_INT_ENABLE
NVIC_InitTypeDef NVIC_InitStructure;
#endif
//使能相关时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能 PORTA 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//使能 CAN1 时钟
//初始化 GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11| GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化 PA11,PA12
//引脚复用映射配置
GPIO_PinAFConfig(GPIOA,GPIO_PinSource11,GPIO_AF_CAN1);//PA11 复用为 CAN1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource12,GPIO_AF_CAN1); //PA12 复用为 CAN1
//CAN 单元设置
CAN_InitStructure.CAN_TTCM=DISABLE; //非时间触发通信模式
CAN_InitStructure.CAN_ABOM=DISABLE; //软件自动离线管理
CAN_InitStructure.CAN_AWUM=DISABLE;//睡眠模式通过软件唤醒
CAN_InitStructure.CAN_NART=ENABLE;//禁止报文自动传送
CAN_InitStructure.CAN_RFLM=DISABLE; //报文不锁定,新的覆盖旧的
CAN_InitStructure.CAN_TXFP=DISABLE; //优先级由报文标识符决定
CAN_InitStructure.CAN_Mode= mode; //模式设置
CAN_InitStructure.CAN_SJW=tsjw; //重新同步跳跃宽度
CAN_InitStructure.CAN_BS1=tbs1; //Tbs1 范围 CAN_BS1_1tq ~CAN_BS1_16tq
CAN_InitStructure.CAN_BS2=tbs2;//Tbs2 范围 CAN_BS2_1tq ~ CAN_BS2_8tq
CAN_InitStructure.CAN_Prescaler=brp; //分频系数(Fdiv)为 brp+1
CAN_Init(CAN1, &CAN_InitStructure); // 初始化 CAN1
//配置过滤器
CAN_FilterInitStructure.CAN_FilterNumber=0; //过滤器 0
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
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;
CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //激活过滤器 0
CAN_FilterInit(&CAN_FilterInitStructure);//滤波器初始化
#if CAN1_RX0_INT_ENABLE
CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);//FIFO0 消息挂号中断允许.
NVIC_InitStructure.NVIC_IRQChannel = CAN1_RX0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 主优先级为 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 次优先级为 0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
#endif
return 0;
}
#if CAN1_RX0_INT_ENABLE //使能 RX0 中断
//中断服务函数
void CAN1_RX0_IRQHandler(void)
{
CanRxMsg RxMessage;
int i=0;
CAN_Receive(CAN1, 0, &RxMessage);
for(i=0;i<8;i++)
printf("rxbuf[%d]:%d\r\n",i,RxMessage.Data[i]);
}
#endif
//can 发送一组数据(固定格式:ID 为 0X12,标准帧,数据帧)
//len:数据长度(最大为 8) msg:数据指针,最大为 8 个字节.
//返回值:0,成功; 其他,失败;
u8 CAN1_Send_Msg(u8* msg,u8 len)
{
u8 mbox;
u16 i=0;
CanTxMsg TxMessage;
TxMessage.StdId=0x12; // 标准标识符为 0
TxMessage.ExtId=0x12; // 设置扩展标示符( 29 位)
TxMessage.IDE=0; // 使用扩展标识符
TxMessage.RTR=0; // 消息类型为数据帧,一帧 8 位
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 CAN1_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<RxMessage.DLC;i++)
buf[i]=RxMessage.Data[i];
return RxMessage.DLC; //返回接受到的数据长度
}
此部分代码总共 3 个函数,首先是: CAN_Mode_Init 函数。该函数用于 CAN 的初始化,该函数带有 5 个参数,可以设置 CAN 通信的波特率和工作模式等,在该函数中,我们就是按32.1 节末尾的介绍来初始化的,本章中,我们设计滤波器组 0 工作在 32 位标识符屏蔽模式,从设计值可以看出,该滤波器是不会对任何标识符进行过滤的,因为所有的标识符位都被设置成
不需要关心,这样设计,主要是方便大家实验。
第二个函数, Can_Send_Msg 函数。该函数用于 CAN 报文的发送,主要是设置标识符 ID等信息,写入数据长度和数据,并请求发送,实现一次报文的发送。
第三个函数, Can_Receive_Msg 函数。用来接受数据并且将接受到的数据存放到 buf 中。can.c 里面,还包含了中断接收的配置,通过 can.h 的 CAN1_RX0_INT_ENABLE 宏定义,来配置是否使能中断接收,本章我们不开启中断接收的。其他函数我们就不一一介绍了,都比较简单,大家自行理解即可。
can.h 头文件中, CAN1_RX0_INT_ENABLE 用于设置是否使能中断接收,本章我们不用中断接收,故设置为 0。 最后我们看看主函数,代码如下:

int main(void)
{
u8 key, i=0,t=0.cnt=0,u8 canbuf[8],res;
u8 mode=1;//CAN 工作模式;0,普通模式;1,环回模式
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组 2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为 115200
LED_Init(); //初始化 LED
LCD_Init(); //LCD 初始化
KEY_Init(); //按键初始化
CAN1_Mode_Init(CAN_SJW_1tq,CAN_BS2_6tq,CAN_BS1_7tq,6,
CAN_Mode_LoopBack); //CAN 初始化环回模式,波特率 500Kbps
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");
LCD_ShowString(30,70,200,16,16,"CAN TEST");
LCD_ShowString(30,90,200,16,16,"ALIX");
LCD_ShowString(30,110,200,16,16,"2018/8/7");
LCD_ShowString(30,130,200,16,16,"LoopBack Mode");
LCD_ShowString(30,150,200,16,16,"KEY0:Send WK_UP:Mode");//显示提示信息
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(30,170,200,16,16,"Count:"); //显示当前计数值
LCD_ShowString(30,190,200,16,16,"Send Data:"); //提示发送的数据
LCD_ShowString(30,250,200,16,16,"Receive Data:"); //提示接收到的数据
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_ShowxNum(30+i*32,210,canbuf[i],3,16,0X80); //显示数据
else LCD_ShowxNum(30+(i-4)*32,230,canbuf[i],3,16,0X80); //显示数据
}
res=CAN1_Send_Msg(canbuf,8);//发送 8 个字节
if(res)LCD_ShowString(30+80,190,200,16,16,"Failed"); //提示发送失败
else LCD_ShowString(30+80,190,200,16,16,"OK "); //提示发送成功
}else if(key==WKUP_PRES)//WK_UP 按下,改变 CAN 的工作模式
{
mode=!mode;
CAN1_Mode_Init(CAN_SJW_1tq,CAN_BS2_6tq,CAN_BS1_7tq,6,mode);
//CAN 普通模式初始化,普通模式,波特率 500Kbps
POINT_COLOR=RED;//设置字体为红色
if(mode==0)//普通模式,需要 2 个开发板
{
LCD_ShowString(30,130,200,16,16,"Nnormal Mode ");
}else //回环模式,一个开发板就可以测试了.
{
LCD_ShowString(30,130,200,16,16,"LoopBack Mode");
}
POINT_COLOR=BLUE;//设置字体为蓝色
}
key=CAN1_Receive_Msg(canbuf);
if(key)//接收到有数据
{
LCD_Fill(30,270,160,310,WHITE);//清除之前的显示
for(i=0;i<key;i++)
{
if(i<4)LCD_ShowxNum(30+i*32,270,canbuf[i],3,16,0X80); //显示数据
else LCD_ShowxNum(30+(i-4)*32,290,canbuf[i],3,16,0X80); //显示数据
}
}
t++; delay_ms(10);
if(t==20)
{
LED0=!LED0;//提示系统正在运行
t=0;cnt++;
LCD_ShowxNum(30+48,170,cnt,3,16,0X80); //显示数据
}
}
}
此部分代码,我们主要关注下 CAN1_Mode_Init 初始化代码:
CAN1_Mode_Init(CAN_SJW_1tq,CAN_BS2_6tq,CAN_BS1_7tq,6,mode);
该函数用于设置波特率和 CAN 的模式,根据前面的波特率计算公式,我们知道这里的波
特率被初始化为 500Kbps。 mode 参数用于设置 CAN 的工作模式(普通模式/环回模式),通过
KEY_UP 按键,可以随时切换模式。 cnt 是一个累加数,一旦 KEY0 按下,就以这个数位基准
连续发送 8 个数据。当 CAN 总线收到数据的时候,就将收到的数据直接显示在 LCD 屏幕上。




 

猜你喜欢

转载自blog.csdn.net/qq_37951246/article/details/81487390
CAN