CAN 总线介绍
CAN 是Controller Area Network 的缩写(以下称为CAN),中文意思是控制器局域网络,是ISO 国际标准化的串行通信协议。在汽车产业中,出于对安全性、舒适性、方便性、低公害、低成本的要求,各种各样的电子控制系统被开发了出来。由于这些系统之间通信所用的数据类型及对可靠性的要求不尽相同,由多条总线构成的情况很多,线束的数量也随之增加。为适应“减少线束的数量”、“通过多个LAN,进行大量数据的高速通信”的需要,1986 年德国电气商博世公司开发出面向汽车的CAN 通信协议。此后,CAN 通过ISO11898 及ISO11519 进行了标准化。CAN 是国际上应用最广泛的现场总线之一,在欧洲已是汽车网络的标准协议。CAN 的高性能和可靠性已被认同,并被广泛地应用于工业自动化、船舶、医疗设备、工业设备等方面。现场总线是当今自动化领域技术发展的热点之一,被誉为自动化领域的计算机局域网。它的出现为分布式控制系统实现各节点之间实时、可靠的数据通信提供了强有力的技术支持。
CAN 通信只具有两根信号线,分别是CAN_H 和CAN_L,CAN 控制器根据这两根线上的电位差来判断总线电平。总线电平分为显性电平和隐性电平,二者必居其一。发送方通过使总线电平发生变化,将消息发送给接收方。下面我们来看下CAN 协议具有哪些特点:
(1)多主控制。在总线空闲时,所有单元都可以发送消息(多主控制),而两个以上的单元同时开始发送消息时, 根据标识符( Identifier 以下称为ID)决定优先级。ID 并不是表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时开始发送消息时,对各消息ID 的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级最高)的单元可继续发送消息,仲裁失利
的单元则立刻停止发送而进行接收工作。
(2)系统的柔软性。与总线相连的单元没有类似于“地址”的信息。因此在总线上增加单元时,连接在总线上的其它单元的软硬件及应用层都不需要改变。
(3)通信速度较快,通信距离远。最高1Mbps(距离小于40M),最远可达10KM(速率低于5Kbps)。
(4)具有错误检测、错误通知和错误恢复功能。所有单元都可以检测错误(错误检测功能),检测出错误的单元会立即同时通知其他所有单元(错误通知功能), 正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止(错误恢复功能)。
(5)故障封闭功能。CAN 可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。
(6)连接节点多。CAN 总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。正是因为CAN 协议的这些特点,使得CAN 特别适合工业过程监控设备的互连,因此,越来越受到工业界的重视,并已公认为最有前途的现场总线之一。CAN 已通过ISO11898 及ISO11519 进行了标准化,这两种标准在物理层的主要不同点如图所示:
从这两种标准可以看出,ISO11898 标准可以组建一个高速、短距离“闭环网络”,此标准要求总线最大长度为40m,通信速度最高为1Mbps,总线的两端各要求有一个“ 120 欧”的电阻,此电阻可作为阻抗匹配功能,以减少回波反射。而ISO11519-2 标准可以组建一个低速、远距离“开环网络”,它的最大传输距离为1km,最高通讯速率为125kbps,两根总线是独立的、不形成闭环,要求每根总线上各串联有一个**“2.2 千欧”**的电阻。本章实验我们采用的是ISO11898 标准,从该标准特性图中可以看出,显性电平对应逻辑0,CAN_H 和CAN_L 之差为2.5V 左右。而隐性电平对应逻辑1,CAN_H 和CAN_L 之差为0V。在总线上显性电平具有优先权,只要有一个单元输出显性电平,总线上即为显性电平。而隐形电平则具有包容的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平(有点类似于I2C 总线SDA 的“线与”关系)。
前面我们介绍了CAN 总线具有多节点可组网特性,其网络拓扑结构如下:
从上图可以看到,CAN 总线和CAN 控制器间需要一个CAN 收发器将信号进行转换。我们开发板上使用的CAN 收发器是TJA1040。CAN 通信主要是通过5 种类型的帧进行,分别是数据帧、遥控帧、错误帧、过载帧和帧间隔。其中数据帧和遥控帧有标准格式和扩展格式两种格式。标准格式有11 个位的标识符(ID),扩展格式有29 个位的ID。各种帧的用途如图37.1.2 所示:
由于篇幅所限,我们这里仅对数据帧进行详细介绍,数据帧一般由7 个段构成,即:
(1)帧起始。表示数据帧开始的段。
(2)仲裁段。表示该帧优先级的段。
(3)控制段。表示数据的字节数及保留位的段。
(4)数据段。数据的内容,一帧可发送0~8 个字节的数据。
(5)CRC 段。检查帧的传输错误的段。
(6)ACK 段。表示确认正常接收的段。
(7)帧结束。表示数据帧结束的段。
数据帧的构成如图所示:
图中D 表示显性电平, R 表示隐形电平(下同)。
①.帧起始是由1 个位的显性电平表示,标准帧和扩展帧相同。
②.仲裁段,表示数据优先级的段,标准帧和扩展帧格式在本段有所区别,
如图所示:
从图中可以看出,标准格式的ID 有11 个位。扩展格式的ID 有29 个位。基本ID和标准格式的ID 相同。其中RTR 位用于标识是否是远程帧( 0,数据帧; 1,远程帧), IDE 位为标识符选择位( 0,使用标准标识符; 1,使用扩展标识符), SRR 位为代替远程请求位,为隐性位,它代替了标准帧中的RTR 位。
③.控制段由6 个位构成,表示数据段的字节数。标准帧和扩展帧的控制段稍有不同,如图所示:
上图中, r0 和r1 为保留位。保留位必须全部以显性电平发送,但接收方可以接收显性、隐性及其任意组合的电平。DLC 为数据长度码,数据长度码与数据的字节数的对应关系如图所示:
数据的字节数必须为0~8 字节。但接收方对DLC = 9~15 的情况并不视为错误。
④.数据段,该段可包含0~8 个字节的数据。从MSB(最高位)开始输出,标准帧和扩展帧此段相同。如图37.1.7 所示:
⑤.CRC 段,该段用于检查帧传输错误。由15 个位的CRC 顺序和1 个位的CRC 界定符(用于分隔的位)构成,标准帧和扩展帧此段也是相同的。如图所示:
CRC 顺序是根据多项式生成的CRC 值, CRC 的计算范围包括帧起始、仲裁段、控制段、数据段。接收方以同样的算法计算CRC 值并进行比较,不一致时会通报错误。
⑥.ACK 段,此段用来确认是否正常接收。由ACK 槽(ACK Slot)和ACK 界定符2 个位构成。标准帧和扩展帧此段也是相同的。如图所示:
发送单元在ACK 段发送2 个位的隐性位。接收到正确消息的单元在ACK槽(ACK Slot)发送显性位, 通知发送单元正常接收结束。这称作“发送ACK”或者“返回ACK”。发送ACK 的是在既不处于总线关闭态也不处于休眠态的所有接收单元中,接收到正常消息的单元(发送单元不发送ACK)。所谓正常消息是指不含填充错误、格式错误、CRC 错误的消息。
⑦.帧结束,它表示该帧的结束的段,由7 个位的隐性位构成。标准帧和扩展帧此帧是相同的。如图所示:
接下来,我们再来看看CAN 的位时序。由发送单元在非同步的情况下发送的每秒钟的位数称为位速率。一个位可分为4 段。
· 同步段( SS)
· 传播时间段( PTS)
· 相位缓冲段1( PBS1)
· 相位缓冲段2( PBS2)
这些段又由可称为Time Quantum(以下称为Tq)的最小时间单位构成。
1 位分为4 个段,每个段又由若干个Tq 构成,这称为位时序。1 位由多少个Tq 构成、每个段又由多少个Tq 构成等,可以任意设定位时序。通过设定位时序,多个单元可同时采样,也可任意设定采样点。各段的作用和Tq 数如图所示:
1 个位的构成如图所示:
上图中的采样点是指读取总线电平,并将读到的电平作为位值的点。位置在PBS1 结束处。根据这个位时序,我们就可以计算CAN 通信的波特率了。具体计算方法,我们等下再介绍,前面提到的CAN 协议具有仲裁功能,下面我们来看看是如何实现的。
在总线空闲态,最先开始发送消息的单元获得发送权。当多个单元同时开始发送时,各发送单元从仲裁段的第一位开始进行仲裁。连续输出显性电平最多的单元可继续发送。仲裁的过程如图所示:
从上图中可以看到,单元1 和单元2 同时开始向总线发送数据,开始部分他们的数据格式是一样的,故无法区分优先级,直到图中红色部分时刻,单元1输出隐性电平,而单元2 输出显性电平,此时单元1 仲裁失利,立刻转入接收状态工作,不再与单元2 竞争,而单元2 则顺利获得总线使用权,继续发送自己的数据。这就实现了仲裁,让连续发送显性电平多的单元获得总线使用权。
接下来我们介绍下STM32F1 的CAN 控制器。
STM32F1 芯片自带bxCAN 控制器(Basic Extended CAN),即基本扩展CAN,可与CAN 网络进行交互,它支持2.0A 和B 版本的CAN 协议。旨在以最少的CPU 负载高效管理大量的传入消息,并可按需要的优先级实现消息发送。在攸关安全性的应用中,CAN 控制器提供所有必要的硬件功能来支持CAN 时间触发通信方案。
STM32F1 的bxCAN 的主要特性有:
·支持CAN 协议2.0A 和2.0B 主动模式
·波特率最高达1Mbps
·支持时间触发通信
·具有3 个发送邮箱
·具有3 级深度的2 个接收FIFO
·可变的过滤器组( 互联网产品中有28 个, CAN1 和CAN2 共享,其他STM32F103xx 有14 个)我们使用的STM32F103ZET6 自带1 个CAN 控制器,而STM32F105/STM32F107互联网型含有2 个CAN 控制器,互联网型只是比STM32F103 多一个而已。下面我们就来看下互联网型它内部结构,如图所示:
从CAN 内部结构图可以看出,CAN1 是主设备,CAN2 是从设备。框图中的“存储访问控制器”是由CAN1 控制的,CAN2 无法直接访问存储区域,所以如果使用CAN2 的时候必须先使能CAN1 外设的时钟,我们使用的STM32F103ZET6 只含有CAN1。
框图中主要包含CAN 控制内核、发送邮箱、接收FIFO 以及验收筛选器,下面我们就依次进行简单介绍。
(1)标号1:CAN 控制内核
CAN 控制内核包含了各种控制/状态寄存器,我们主要讲解其中的主控制寄存器 CAN_MCR 及位时序寄存器 CAN_BTR。
①控制寄存器CAN_MCR
主控制寄存器CAN_MCR 负责管理CAN 的工作模式,寄存器如下:
该寄存器各位功能如下:(此部分从中文参考手册寄存器说明截取)
②位时序寄存器(CAN_BTR)
CAN 外设中的位时序寄存器CAN_BTR 用于配置测试模式、波特率以及各种位内的段参数。该寄存器如下:
该寄存器各位功能如下:(此部分从中文参考手册寄存器说明截取)
1.测试模式
为方便调试,STM32 的CAN 提供了测试模式,配置位时序寄存器CAN_BTR的SILM 及LBKM 寄存器位可以控制使用正常模式、静默模式、回环模式及静默回环模式。下面我们就来介绍下这几种工作模式:
A.正常模式
一旦初始化完成,软件必须向硬件请求进入正常模式,这样才能在CAN 总线上进行同步,并开始接收和发送。进入正常模式的请求可通过将CAN_MCR 寄存器的INRQ 位清零来发出。bxCAN 进入正常模式,并与CAN 总线上的数据传输实现同步后,即可参与总线活动。执行这一步时,需要等待出现一个由11 个连续隐性位(总线空闲状态)组成的序列。硬件通过将CAN_MSR 寄存器的INAK 位清零,来确认切换到正常模式。
B.静默模式
可以通过将CAN_BTR 寄存器的SILM 位置1,将bxCAN 置于静默模式。在静默模式下, bxCAN 可以接收有效数据帧和有效遥控帧,但仅在CAN 总线上发送隐性位,并且无法启动发送。如果bxCAN 必须发送一个显性位( ACK 位、溢出标志、活动错误标志),该位将在内部被改道发送,以便CAN 内核可以监视该显性位,但CAN 总线可以保持隐性状态。静默模式可用于分析CAN 总线上的流量,同时又不会因发送显性位(确认位、错误帧)对其造成影响。该模式下的bxCAN 如下:
C.回环模式
可以通过将CAN_BTR 寄存器的LBKM 位置1,将bxCAN 置于环回模式。在环回模式下,bxCAN 将其自身发送的消息作为接收的消息来处理并存储(如果这些消息通过了验收筛选)在接收邮箱中。该模式下的bxCAN 如下:
该模式为自检功能提供。为了不受外部事件的影响, CAN 内核在环回模式下将忽略确认错误(在数据/远程帧的确认时隙不对显性位采样)。在此模式下,bxCAN 将执行从发送输出到接收输入的内部反馈。bxCAN 将忽略CANRX 输入引脚的实际值。从CANTX 引脚可以监视发送的消息。
D.静默回环模式
可以通过将CAN_BTR 寄存器的LBKM 和SILM 位置1,将环回模式和静默模式组合起来。该模式可用于“热自检”,也就是说, bxCAN 可以像在环回模式下一样进行检测,同时又不会影响与CANTX 和CANRX 引脚相连接的运行中的CAN 系统。在此模式下, CANRX 引脚与bxCAN 断开连接, CANTX 引脚则保持隐性。该模式下的bxCAN 如下:
上述说的各个模式,是不需要修改硬件接线的,如当输出直连输入时,它是在STM32F1 芯片内部连接的,传输路径不经过STM32 的CAN_Tx/Rx 引脚,更不经过外部连接的CAN 收发器,只有输出数据到总线或从总线接收的情况下才会经过CAN_Tx/Rx 引脚和收发器。
2.位时序及波特率
STM32F1 的CAN 控制器定义的位时序与前面我们介绍的CAN 标准位时序有一点区别,STM32F1 把传播时间段和相位缓冲段1( STM32F1 称之为位段1)合并了,所以STM32F1 的CAN 一个位只有3 段。分别是:同步段(SYNC_SEG)、位段1 (BS1)、位段2 (BS2)。STM32F1 的CAN 的位时序如图所示:
这三段的工作过程如下:
● 同步段(SYNC_SEG):位变化应该在此时间段内发生。它只有一个时间片的固定长度(1 x tCAN)。
● 位段1 (BS1):定义采样点的位置。它包括CAN 标准的PROP_SEG 和PHASE_SEG1。其持续长度可以在1 到16 个时间片之间调整(刚好等于我们前面介绍的传播时间段和相位缓冲段1 之和),但也可以自动加长,以补偿不同网络节点的频率差异所导致的正相位漂移。
● 位段2 (BS2):定义发送点的位置。它代表CAN 标准的PHASE_SEG2。其持续长度可以在1 到8 个时间片之间调整,但也可以自动缩短,以补偿负相位漂移。
了解了STM32F1 的CAN 的位时序后,我们就可以配置波特率了。通过配置位时序寄存器CAN_BTR(为了避免编程错误,位时序寄存器(CAN_BTR) 只能在器件处于待机模式时进行配置) 的TS1[3:0]及TS2[2:0]寄存器位设定BS1 及BS2 段的长度后,我们就可以非常方便的计算CAN 通信波特率,计算公式如下:
其中的tPCLK 指APB1 时钟,默认值为36MHz,现在我们只要知道BS1 和BS2 的设置值就可以计算出波特率。比如设置TS1=7、TS2=8 和BRP=3,在APB1频率为36Mhz 的条件下,即可得到CAN 通信的波特率BoundRate=36000/[(8+9+1)*4]=500Kbps。
(2)标号2:发送邮箱
在STM32F1 的CAN 结构框图中,标号2 是CAN 的发送邮箱,它含有3 个发送邮箱,软件可通过三个发送邮箱设置消息。发送调度程序负责决定首先发送哪个邮箱的内容。CAN 邮箱寄存器可在中文参考手册查找。
(3)标号3:验收筛选器
互联网型产品的bxCAN 提供了28 个可调整/可配置的标识符筛选器组,我们使用的STM32F103ZET6 只有14 个,用于选择软件所需的传入消息并丢弃其余消息。每个筛选器组由2 个32 为寄存器,CAN_FxR1 和CAN_FxR2 组成。为了根据应用程序的需求来优化和调整筛选器,每个筛选器组可分别进行伸缩调整。根据筛选器尺度不同,一个筛选器组可以:
● 为STDID[10:0]、EXTID[17:0]、IDE 和RTR 位提供一个32 位筛选器。
● 为STDID[10:0]、RTR、IDE 和EXTID[17:15] 位提供两个16 位筛选器。
此外,筛选器还可配置为掩码模式或标识符列表模式。在掩码模式下,标识符寄存器与掩码寄存器关联,用以指示标识符的哪些位“必须匹配”,哪些位“无关”。而在标识符列表模式下,掩码寄存器用作标识符寄存器。这时,不会定义一个标识符和一个掩码,而是指定两个标识符,从而使单个标识符的数量加倍。传入标识符的所有位都必须与筛选器寄存器中指定的位匹配。
通过CAN_FMR 寄存器,可以配置过筛选器组的位宽和工作模式,如下图所示:
为了过滤出一组标识符,应该设置筛选器组工作在掩码模式。
为了过滤出一个标识符,应该设置筛选器组工作在标识符列表模式。
应用程序不用的过滤器组,应该保持在禁用状态。
筛选器组中的每个筛选器,都被编号为(叫做筛选器号,上图中的n)从0 开始,到某个最大数值-取决于筛选器组的模式和位宽的设置。举个简单的例子,我们设置筛选器组0 工作在: 1 个32 位筛选器-标识符掩码模式,然后设置CAN_F0R1=0XFFFF0000, CAN_F0R2=0XFF00FF00。其中存放到CAN_F0R1 的值就是期望收到的ID , 即我们希望收到的ID( STID+EXTID+IDE+RTR)最好是: 0XFFFF0000。而0XFF00FF00 就是设置我们需要必须关心的ID,表示收到的ID,其位[31:24]和位[15:8]这16 个位的必
须和CAN_F0R1 中对应的位一模一样,而另外的16 个位则不关心,可以一样,也可以不一样,都认为是正确的ID,即收到的ID 必须是0XFFxx00xx,才算是正确的( x 表示不关心)。
(4)标号4:CAN 接收FIFO
硬件使用两个接收FIFO 来存储传入消息。每个FIFO 中有三个邮箱用于存储三条完整消息。FIFO 完全由硬件管理。FIFO 开始时处于空状态,在接收的第一条有效消息存储在其中后,变为Pending_1 状态。硬件通过将CAN_RFR 寄存器的FMP[1:0] 位置为01b 来指示该事件。消息将在FIFO 输出邮箱中供读取。软件将读取邮箱内容,并通过将CAN_RFR 寄存器的RFOM 位置1,来将邮箱释放。FIFO 随即恢复空状态。如果同时接收到新的有效消息, FIFO 将保持Pending_1 状态,新消息将在输出邮箱中供读取。如果应用程序未释放邮箱,下一条有效消息将存储在FIFO 中,使其进入Pending_2 状态(FMP[1:0] = 10b)。下一条有效消息会重复该存储过程,同时将FIFO 变为Pending_3 状态(FMP[1:0] = 11b)。此时,软件必须通过将RFOM 位置1 来释放输出邮箱,从而留出一个空邮箱来存储下一条有效消息。否则,下一次接收到有效消息时,将导致消息丢失。一旦FIFO 处于Pending_3 状态(即三个邮箱均已满),则下一次接收到有效消息时,将导致上溢并丢失一条消息。硬件通过将CAN_RFR 寄存器的FOVR位置1 来指示上溢状况。丢失的消息取决于FIFO 的配置:
● 如果禁止FIFO 锁定功能( CAN_MCR 寄存器的RFLM 位清零),则新传入的消息将覆盖FIFO 中存储的最后一条消息。在这种情况下,应用程序将始终能访问到最新的消息。
● 如果使能FIFO 锁定功能( CAN_MCR 寄存器的RFLM 位置1),则将丢弃最新的消息,软件将提供FIFO 中最早的三条消息。
消息存储到FIFO 中后,FMP[1:0] 位即会更新,如果CAN_IER 寄存器的FMPIE 位置1,将产生中断请求。FIFO 存满消息(即存储了第三条消息)后,CAN_RFR 寄存器的FULL 位置1,如果CAN_IER 寄存器的FFIE 位置1,将产生中断。出现上溢时,FOVR 位将置1,如果CAN_IER 寄存器的FOVIE 位置1,将产生中断。
(5)标号5:CAN2 整体控制逻辑
CAN2 与CAN1 是一样的,它们共用验收筛选器且由于存储访问控制器由CAN1控制,所以要使用CAN2 的时候必须要使能CAN1 的时钟。由于篇幅限制,大家可以参考《STM32F10x 中文参考手册》-22 控制器区域网络(bxCAN)章节,里面有详细的讲解。CAN 总线内部还是比较复杂的,如果看不懂的可以暂时放下,因为我们使用的是库函数开发,只需简单配置下即可使用。
使用到硬件资源如下:
(1)D1 指示灯
(2)K_UP 和K_DOWN 按键
(3)串口1
(4)CAN 控制器
(5)CAN 收发器:TJA1040
D1 指示灯、K_UP 和K_DOWN 按键、串口1 电路在前面都介绍过,这里就不多说,至于CAN 控制器它属于STM32F1 芯片内部的资源,只要通过软件配置好即可使用,下面我们看下STM32F1 的CAN 与TJA1040 的连接,如图所示:
从电路图中可以看到,TJA1040 芯片的CAN_TX 和CAN_RX 管脚连接在P16 排针上,因为PA11 和PA12 是USB 和CAN1 共用的管脚,所以使用P16 排针来做切换,本章实验我们使用的是CAN1,所以将P16 排针短接到CAN 端,PCB 板上P16插针短接图如图所示:
在TJA1040 的CAN_H 和CAN_L 管脚上连接了一个120 欧的终端电阻,如果我们的开发板不是作为CAN 终端的话,需要把这个电阻去掉,以免影响通信。最后我们用两根导线将两块开发板上CAN 收发器的CAN_H 和CAN_L 对应连接,不要交叉,否则通信错误。CAN_H 和CAN_L 示意图如图所示:
连接好后,我们STM32F1 的CAN 控制器就可以通过CAN 收发器TJA1040 与外部CAN 总线进行通信了。D1 指示灯用来提示系统运行状态,K_UP 按键用来切换CAN 工作模式,K_DOWN键用来发送数据,通过串口1 将切换的模式和发送与接收到的数据打印输出。
所要实现的功能是:通过K_UP 键切换CAN 通信模式,K_DOWN 键控制数据发送,将切换的CAN 模式,发送和接收的数据通过串口打印输出,整个过程D1 指示灯不断闪烁,提示系统正常运行。本章我们使用的是STM32F1 的CAN1,
程序框架如下:
(1)初始化CAN1,包括工作模式、波特率、筛选器设置等
(2)编写CAN1 的发送和接收函数
(3)编写主函数
前面介绍CAN 配置步骤时,就已经讲解如何初始化CAN。CAN 操作的库函数都放在stm32f10x_can.c 和stm32f10x_can.h 文件中,所以使用到CAN 就必须加入stm32f10x_can.c 文件,同时还要包含对应的头文件路径。这里我们分析几个重要函数。
CAN1 初始化函数
要使用STM32F1 的CAN1,我们必须先对它进行配置。初始化代码如下:
//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+tbs2+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;
// 其他,初始化失败;
void 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_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); // 打开CAN1 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //PA 端口时钟打开
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PA11
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PA12
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO 口速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure);
//CAN 单元设置
CAN_InitStructure.CAN_TTCM=DISABLE; //非时间触发通信模式
CAN_InitStructure.CAN_ABOM=DISABLE; //软件自动离线管理
CAN_InitStructure.CAN_AWUM=DISABLE;//睡眠模式通过软件唤醒(清除CAN->MCR 的SLEEP 位)
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; // 重新同步跳跃宽度(Tsjw) 为tsjw+1 个时间单位CAN_SJW_1tq~CAN_SJW_4tq
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;//过滤器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;// 主优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;// 次优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
#endif
}
在CAN1_Mode_Init()函数中,首先使能CAN1 和GPIOA 时钟,并将管脚配置为复用功能。然后配置CAN 的工作模式、波特率及筛选器等参数。这一过程在前面步骤介绍中已经提了。CAN1_Mode_Init()函数有4 个参数,用来设置CAN1 的波特率和工作模式,方便大家修改CAN 的波特率和工作模式。参数的具体意义在函数开始处已注释。
CAN1 发送与接收函数
初始化了CAN1 后,我们就可以编写它的发送和接收函数,具体代码如下:
//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=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
CAN1_Send_Msg 函数为发送函数,用于CAN 报文的发送,主要是设置标识符ID 等信息,写入数据长度和数据,并请求发送,实现一次报文的发送。CAN1_Receive_Msg 函数为接收函数,用于接受报文数据并且将接受到的数据存放到buf 数组内。
主函数
编写好CAN1 初始化和发送接收函数后,接下来就可以编写主函数了,代码如下:
/****************************************************************
***************
* 函数名: main
* 函数功能: 主函数
* 输入: 无
* 输出: 无
*****************************************************************
**************/
int main()
{
u8 i=0,j=0;
u8 key;
u8 mode=0;
u8 res;
u8 tbuf[8],char_buf[8];
u8 rbuf[8];
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组分2 组
LED_Init();
USART1_Init(9600);
KEY_Init();
CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_Normal);//500Kbps 波特率
while(1)
{
key=KEY_Scan(0);
if(key==KEY_UP) //模式切换
{
mode=!mode;
CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,mode);
if(mode==0)
{
printf("Normal Mode\r\n");
}
else
{
printf("LoopBack Mode\r\n");
}
}
if(key==KEY_DOWN) //发送数据
{
for(j=0;j<8;j++)
{
tbuf[j]=j;
char_buf[j]=tbuf[j]+0x30;
}
res=CAN_Send_Msg(tbuf,8);
if(res)
{
printf("Send Failed!\r\n");
}
else
{
printf("发送数据:%s\r\n",char_buf);
}
}
res=CAN_Receive_Msg(rbuf);
if(res)
{
for(j=0;j
主函数实现的功能很简单,首先调用之前编写好的硬件初始化函数,包括SysTick 系统时钟,中断分组,LED 初始化等。然后调用我们前面编写的CAN1_Mode_Init 函数,我们将CAN1 波特率设置为500Kbps(波特率计算可参考前面内容),工作模式为正常模式CAN_Mode_Normal。最后进入while 循环,循环检测K_UP 和K_DOWN 按键是否按下。如果K_UP 键按下,通过mode 值变化实现CAN1 的工作模式的切换(正常/环回模式),并将切换的工作模式提示信息打印输出。如果K_DOWN 键按下,调用CAN1_Send_Msg 函数将数据发送出去,并将发送的数据内容打印输出,同时调用CAN1_Receive_Msg 函数将发送的数据内容保存在rbuf 数组内,通过串口打印输出。整个过程D1 指示灯会间隔200ms 闪烁,提示系统正常运行。