知识来源于互联网,回馈于互联网!
1. 总体概述
1.1 基本概念
CAN 是 Controller Area Network 的缩写(以下称为 CAN),是 ISO 国际标准化的串行通信协议。在北美和西欧,CAN 总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线,并且拥有以 CAN 为底层协议专为大型货车和重工机械车辆设计的 J1939 协议。
1.2 通讯方式
回顾前面学到的 RS232 IIC SPI RS485 等通信
UART:(Universal Asynchronous Receiver Transmitter:通用异步收发器/异步串行通信口),是一种通用的串行数据总线,用于异步通信,支持全双工。它包括了RS232、RS499、RS423、RS422和RS485等接口标准规范和总线标准规范,即UART是异步串行通信口的总称。
IIC总线协议:I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
SPI总线协议:SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议。
RS232接口缺陷:
(1)接口的信号电平值较高( +/-12V),易损坏接口电路的芯片。
(2)传输速率较低,在异步传输时,波特率为20Kbps。
(3)接口使用一根信号线和一根信号返回线而构成共地的传输形式,这种共地传输容易产生共模干扰,所以抗噪声干扰性弱。
(4)传输距离有限,最大传输距离标准值为50英尺,实际上也只能用在50米左右。
RS485是对RS232的改进,特点包括:
①接口电平低,不易损坏芯片。RS485的电气特性:逻辑“1”以两线间的电压差为+(2 ~ 6)V表示,逻辑“0”以两线间的电压差为-(2~6)V表示。接口信号电平比RS232降低了,不易损坏接口电路的芯片。
②传输速率高。10米时,RS485的数据最高传输速率可达35Mbps,在1200m时,传输速度可达100Kbps。
③抗干扰能力强。RS485接口是采用平衡驱动器和差分接收器的组合,抗共模干扰能力增强,即抗噪声干扰性好。
④传输距离远,支持节点多。RS485总线最长可以传输1200m以上(速率≤100Kbps)一般最大支持32个节点,如果使用特制的485芯片,可以达到128个或者256个节点,最大的可以支持到400个节点。
CAN总线通信系统是串行通信的一种,要优于RS485总线,是目前比较常用的一种工业总线,如汽车的电气部分就采用CAN总线实现通信。与I2C、SPI等具有时钟信号的同步通讯方式不同,CAN通讯并不是以时钟信号来进行同步的,它是一种异步半双工通讯。(同步即在同一个时钟驱动下数据通信,半双工即接受与发送不能同时进行)
1.3 为什么使用CAN?
汽车工业蓬勃发展,汽车的电子控制单元逐渐增多。各电控单元之间的信号交换导致汽车线束的级数增加,复杂粗大的线束与汽车有限的布线空间之间矛盾越来越突出,繁多的线束导致电气系统可靠性下降,同时增加了重量。
CAN总线将汽车内部各电控单元之间连接成一个局域网络,实现了信息的共享,大大减少了汽车的线束,如下面的示意图:
比如上图中,每个部分的多个器件都挂载在CAN总线上(一个CAN总线上的所有器件通讯速率必须相同),各个部分再汇集到网关,由网关分配实现各个不同速率的部分之间通讯,这样就很方便轻松实现了对汽车整体电控部分的检测与控制。
在汽车、工业控制领域,数据通信的稳定性和正确性要求极高,因为设备的工作环境既有振荡、高温、辐射等各种不定因素,那不是一般的通信协议能够满足的。除此之外,CAN通信还有许多优秀的特点,比如多主控制、故障封闭功能等,非常适用于工控领域方面,将在下文再提。
1.4 CAN的协议及组成
CAN协议经过ISO标准化后有两个标准ISO11898标准和IS011519-2标准。其中ISO11898是针对通信速率为125Kbps~1Mbps的高速通信标准(闭环),而IS011519-2是针对通信速率为125Kbps以下的低速通信标准(开环)。
Kbps:总线的通信速率,指的是位速率。或称为比特率(和波特率不是一回事),表示的是:单位时间内,通信线路上传输的二进制位的数量,其基本单位是 bps 或者 b/s (bit per second)。
CAN的组成一般有两种方式:一种是CPU与CAN控制器集成到一起、再外接CAN收发器;另一种是CPU与CAN控制器分开的,使用的时候需要配置CAN接口电路,比较麻烦。STM32中就是采用第一种方式,将CAN接口集成在芯片内,使用的时候再外接CAN收发器(顾名思义,可发送,可接收),常用的有TJA1050或者82C250。
CAN收发器是用于TTL电平与差分电压信号相互转换的,TTL电平即单片机引脚直接提供的电平,逻辑0代表低电平,逻辑1代表高电平;而差分电压信号则为固定的电压值。
2. 上帝视角看CAN的通讯过程
以ISO11898 标准的高速、短距离闭环网络为例,总线最大长度为 40m,通信速度最高为 1Mbps。在 CAN 总线的起止端有一个 120Ω的终端电阻,是用来来做阻抗匹配,以减少回波反射。
2.1 数据传输原理实现
先知道CAN通信只由两根线完成的,一条称为CAN_H(CAN High),一条称为CAN_L(CAN Low),共同构成一组差分信号线。整个过程是以差分信号的形式进行通讯的,即信号的逻辑 0 和逻辑 1 由两根差分信号线的电压差来表示。
CAN_Rx和CAN_Tx分别是从MCU中接出来的引脚,比如MCU要发送一个逻辑1,则只要将CAN_Tx设置为1,经过CAN收发器转换,CAN_High和CAN_Low 线上的电压均为 2.5v,即传到总线的电压差 Vh-Vl=0V,总线上的状态则就是逻辑1。同样,当CAN_High和CAN_Low 读取到 CAN总线电压分别3.5V和1.5V,即压差为2V,经过收发器转换,MCU则可通过CAN_Rx读取到信号0。
可以想象到以差分信号形式传送,稳定性更好,因为即使环境问题导致CAN_High电压发送变化,则CAN_L也会发送同等变化,两者做差即可抵消由于这个环境引起的变化。
- 发送方通过使总线电平发生变化,将其信息传递到CAN总线上。
- 接收方通过监听总线电平,将总线上的消息读入自己的接收器。
2.2 通信的整个过程
那么是如何通过一个节点的MCU向总线上那么多节点中的某个节点进行发送信息的?一个节点又是如何知道某个信息是发给自己的或者不是发给自己的?这里或许让你想起 IIC 中以地址方式进行主从点对点的通信,但其实在CAN中并无地址的概念。
少了像IIC中的SCL地址线、SPI中的片选信号线,简洁的物理层决定了 CAN 必然要配上一套更复杂的协议。如何用一个信号通道实现同样、甚至更强大的功能呢?答案是对数据或操作命令进行打包。
2.2.1 空闲状态
先规定空闲状态,所谓的空闲状态就是指没有节点正在传输数据的时候,在CAN协议中,当总线上的上出现连续的11位隐性电平
,那么总线就处于空闲状态。也就是说对于任意一个节点而言,只要它监听到总线上连续出现了11位隐性电平,那么该节点就会认为总线当前处于空闲状态。
显/隐性电平
: 在总线上通常逻辑1表示隐性。而0表示显性。总线上的逻辑0即差分电压差为2V左右(CAN_H=3.5,CAN_L=1.5),总线上的逻辑1即差分电压差为0V(CAN_H=2,CAN_L=2)
怎么让总线连续出现11位隐形电平呢?在总线上显性电平具有优先权,只要有一个节点输出显性电平,总线上即为显性电平。而隐形电平则具有包容的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平(显性电平比隐性电平更强)。
所以,现在可以先简单地理解为,需要在总线一开始工作的时候,所有节点都输出隐性电平;已知在一次传输时该节点输出显性电平,则在传输完成后该节点再输出隐性电平即可,这样就能将总线在无数据传输时保持空闲状态。(真实的实现过程比较复杂,这里仅作为暂时理解,后面会继续提到)
2.2.2 开始数据传输
每次发送数据前,节点都会监听总线的状态,如果总线状态为空闲时,它就会立即向总线上发送自己的数据,这个数据里不仅有数据,还有本身的ID信息或者其他的控制指令,应称为数据包(数据帧),也叫做报文
。当报文被传输到其它节点时,只要这些节点按格式去解读,就能还原出原始数据。
报文
: 在原始数据段的前面加上传输起始标签、片选(识别)标签、控制标签,在数据的尾段加上 CRC 校验标签、应答标签和传输结束标签。类似这样的数据包就被称为 CAN 的数据帧。为了更有效地控制通讯,CAN 一共规定了 5 种类型的帧,帧也称为报文。
数据帧是在 CAN 通讯中最主要、最复杂的报文,它以一个显性位(逻辑 0)开始,以 7 个连续的隐性位(逻辑 1)结束。在它们之间,分为仲裁段、控制段、数据段、CRC 段和 ACK 段,以标准数据帧为例。
数据帧标准格式各个位的介绍
SOF:联系前文可知,当数据帧发出第一位时(0为显性电位),总线就由空闲状态转为传输状态,同一时间只能允许一个节点传输数据。
Identify:接下来的仲裁段有11位,即本数据帧的 ID 信息,决定着数据帧发送的优先级,也决定着其它设备是否会接收这个数据帧,禁止高 7 位都为隐性(禁止设定:ID=1111111XXXX), ID 信息的作用:① 如果同时有多个节点发送数据时,作为优先级依据(仲裁机制);② 目标节点通过 ID 信息来接受数据(验收滤波技术)。这些将在下文提出。
RTR:(Remote Transmission Request BIT) 位用于标识是否是远程帧(0,数据帧;1,远程帧),在数据帧里这一位为显性(逻辑 0)。
IDE:(Identifier Extension Bit),是用于区分标准格式与扩展格式,在标准格式中 IDE 位为显性,在扩展格式里 IDE 位为隐性。
r0:保留位,必须以显性电平发送。
DLC:由 4 位组成,MSB 先行(高位先行),它的二进制编码用于表示本报文中的数据段含有多少个字节,DLC 段表示的数字为0到8,若接收方接收到 9~15 的时候并不认为是错误。
Data:数据帧的核心内容,它由 0~8 个字节(0 ~ 64位)组成,MSB 先行。
CRC:该段用于检查帧传输错误,发送方以一定的方法计算包括:帧起始、仲裁段、控制段、数据段;接收方以同样的算法计算 CRC 值并进行比较,如果不同则会向发送端反馈出错信息,重新发送;计算和出错处理一般由 CAN 控制器硬件完成或由软件控制最大重发数。该段由 15 个位的 CRC 顺序和 1 个位的 CRC 界定符(用于分隔的位)组成,它为隐性位,主要作用是把CRC 校验码与后面的 ACK 段间隔起来。
ACK:由 ACK 槽(ACK Slot)和 ACK 界定符 2 个位组成,在 ACK 槽位中,发送端发送的为隐性位,而接收端则在这一位中发送显性位以示应答。在 ACK 槽和帧结束之间由 ACK 界定符间隔开,为隐性位。(发送 ACK/返回 ACK这个过程使用到回读机制,即发送方先在 ACK 槽发送隐性位后,回读到的总线上的电平为显性0,发送方才知道它发送成功了,不用重发)
EOF:由发送端发送 7 个隐性位表示结束。
比如总线上有3个节点,节点1设置ID为000101 00010,节点2验收滤波ID表中有节点1的ID号,而节点3中的验收滤波ID表中没有节点1的ID号,节点1向节点2发送1字节的信息。
- 报文信息:0 000101 00010 0 0 0 0 0001 0101 1000 XXXXXXXXXXXXXXX 1 1 1 1111111
- 通过总线发送时,在ID信息发送阶段,只有节点2才能收到总线上的数据,因为节点3的验收滤波ID表中没有节点1的ID号
- 在报文发送到ACK槽时,会等待并回读节点2的反馈,从节点2的角度看,此时总线为空闲状态,当验证CRC正确,则向总线发送显性电平,接着当节点1回读到显性电平,才会继续发送剩下的EOF
以上只是简单的理解,实际传输的过程比这个复杂许多,下文继续。
2.2.3 仲裁机制
运用到线与机制和回读机制
以上只是节点1主动发送数据,但是万一节点1和节点2同时向节点3发送数据的时候,如何判定先后呢?采用非破坏性位仲裁机制,即对各个消息的标识符(即ID号)进行逐位仲裁(比较),如果某个节点发送的消息仲裁获胜,那么这个节点将获取总线的发送权,仲裁失败的节点则立即停止发送并转变为监听(接收)状态。从上文可知,显性的优先级高于隐性,即仲裁比较的就是哪个ID中的0多,0最多的那个就可以获得发送权,比如 000000 00010 就比 000000 00011 的优先级要高,仲裁的过程由硬件实现;同时要注意,仲裁段除了报文 ID 外,还有 RTR、IDE、SRR 位(在拓展模式中,下文价绍),也就是说当ID全都一样时,会继续比较接下来的几位。
至于如何做到“0多即胜”,可以理解为一种回读和线与机制,即显性能够将隐性覆盖,将自己要比较的位与总线上的状态相与,只有线与的结果与本身一致时,仲裁才能够通过。
其实在报文发送上去的过程,采用的是广播的方式,在节点1和节点2总裁的同时,总线上所有的节点都能够监听到它们的ID号,只不过也在同时进行验收滤波,只有监听到的ID号存在ID表中,该节点才会选择继续监听后面的报文。
2.2.4 位时序
以上已经基本解决了CAN通信的基本问题,可以思考一下,由于 CAN 没有时钟信号线,而且它的报文中并没有包含用于同步的标志,要怎么做才能对总线的电平进行正确的采样呢?比如我节点1发送3个位出去了,节点2应该在什么时候接收才能保证此时此刻它所接收到的就是第3位或者接收到的电平是正确的? CAN中提出了位同步的方式来确保通讯时序。
位时序的主要知识点
CAN总线通讯协议的每一个数据帧可以看作一连串的电平信号,每一个电平信号代表一位(一个字节8位的位),所以一帧中包含了很多个位,由发送单元在非同步的情况下发送的每秒钟的位数称为位速率。 一位又分为4段, 同步段(SS)、传播时间段(PTS)、相位缓冲段 1(PBS1)、相位缓冲段 2(PBS2)。分解后最小的时间单位是 Tq,而一个完整的位由 8~25 个 Tq 组成。- 1 位分为 4 个段,每个段又由若干个 Tq 构成,这称为位时序。
- 1 位由多少个 Tq 构成、每个段又由多少个 Tq 构成等,可以任意设定位时序。通过设定位时序,多个单元可同时采样,也可任意设定采样点。
SS 段(SYNC SEG):同步段,比如当总线上出现帧起始信号(SOF)时,其它节点上的控制器根据总线上的这个下降沿,对自己的位时序进行调整,把该下降沿包含到 SS 段内,这样根据起始帧来进行同步的方式称为硬同步。其中 SS 段的大小为 1Tq。总线上信号的跳变沿被包含在节点的 SS 段的范围之内,则表示节点与总线的时序是同步的,采样点采集到的总线电平即可被确定为该位的电平。
PTS 段(PROP SEG):传播时间段,这个时间段是用于补偿网络的物理延时时间,包括发送单元的输出延迟、总线上信号的传播延迟、接收单元的输入延迟,这个段的时间为以上各延迟时间的和的两倍。大小可以为 1~8Tq。
PBS1 段(PHASE SEG1):相位缓冲段,主要用来补偿边沿阶段的误差,它的时间长度在重新同步的时候可以加长。 PBS1 段的初始大小可以为 1~8Tq。
PBS2 段(PHASE SEG2):另一个相位缓冲段,也是用来补偿边沿阶段误差的,它的时间长度在重新同步时可以缩短。 PBS2 段的初始大小可以为 2~8Tq。
(对于PBS段而言,当信号边沿不能被包含于 SS 段中时,可在此段进行补偿,以及可以吸收时钟误差)
SJW (reSynchronization Jump Width):重新同步补偿宽度,即在重新同步的时候,PBS1 和 PBS2 段的允许加长或缩短的时间长度,SJW 加大后允许误差加大,但通信速度下降。SJW 为补偿此误差的最大值(即每一次误差补偿都不能超过这个值,1~4Tq)。
CAN 的同步分为硬同步和重新同步:
硬同步
:在帧起始信号时同步总线上所有器件的位时序,无法确保后续一连串的位时序都是同步的。重新同步
:在检测到总线上的时序与节点使用的时序有相位差时(即总线上的跳变沿不在节点时序的 SS 段范围),通过延长 PBS1 段或缩短 PBS2 段,来获得同步。
采样点: 读取总线电平的时刻,并将读到的电平作为位值的点。位置在 PBS1 结束处。
延长/缩短PBS段来达到同步: PTS+PBS1小而PBS2加大时采样点前移,PTS+PBS1大而PBS2减小时采样点后移。
同步过程:
在硬同步阶段,当节点检测到本身SS段并不在总线电平下降沿跳变处,节点则会把自己的位时序中的 SS 段平移至总线出现下降沿的部分,后面三段也跟着上去,以获得同步。(可以理解为节点在检测到帧起始信号时才开始“设置段”)
在重新同步阶段,利用普通数据位的高至低电平的跳变沿来同步(帧起始信号是特殊的跳变沿)。重新同步与硬同步方式相似的地方是它们都使用 SS 段来进行检测,同步的目的都是使节点内的 SS 段把跳变沿包含起来。重新同步的方式分为超前和滞后两种情况,以总线跳变沿与 SS 段的相对位置进行区分,下面举例设SJW为2Tq。
① 相位超前,节点从总线的边沿跳变中,检测到它内部的时序比总线的时序相对超前 2Tq,这时控制器在下一个位时序中的 PBS1 段增加 2Tq 的时间长度,使得节点与总线时序重新同步。
② 相位相位滞后,节点从总线的边沿跳变中,检测到它的时序比总线的时序相对滞后 2Tq,这时控制器在前一个位时序中的 PBS2 段减少 2Tq 的时间长度,获得同步。
理解上面重新同步过程时,需要知道前一次SS到下一次SS之间的长度是可伸展的(暂时称之为 L ),当检测到前一次SS出现得太快(还没等到下降沿到来),这就是相位超前了,如果不把 L 缩短一点,那么下一次SS将也会超前。缩短 L 的方法就是调整 PBS 的长度,这个过程由CAN控制器完成,即(新的PBS长度 = 当前 PBS长度-SJW)。
2.2.5 一次数据传输的例子
比如总线上有3个节点,节点1设置ID为000100 00110,节点2设置ID为000100 00111,节点3验收滤波ID表中有节点1和节点2的ID号,节点1和节点2同时向节点3发送1字节的信息。
- 总线空闲,节点1和节点2同时发送帧起始信号,3个节点同时调整位时序(硬同步);
- 节点1和节点2开始仲裁,两者同时向总线发送第一位0,同时回读总线状态与本身状态相与,得0,两者第1位仲裁均通过;一直持续到第9位1,两者同时向总线发送1,同时回读总线状态,得1,两者第9位仲裁均通过;
- 直到第11位,当两个节点回读总线状态与本身状态相与时,总线得显性将隐性屏蔽,即总线状态为显性,则节点1得0(与本身状态相同),而节点2得0(与本身状态不同),此时节点1仲裁胜利,节点2放弃发送请求;
- 从第1位仲裁到第11位仲裁得同时,节点1向其他节点广播了本身的ID,当然节点1本身也接收到节点2的ID信息,因此节点2和节点3也都收到了节点1的ID信息,只不过节点2对节点1不敢兴趣,因而选择了忽略节点1后续的信息,节点3则开始接收节点1的数据;
- 从硬同步之后,每当节点1和节点2发出一个仲裁位,三个节点的CAN控制器都在检测本身的位时序与总线位时序是否一致,当有相位超前或者滞后时则自动进行位时序的重新同步。在后续的报文传送中亦是如此。
3. CAN总线协议层的详细介绍
CAN 协议经 ISO 标准化后有 ISO11898 标准和 ISO11519-2 标准两种。ISO11898 和 ISO11519-2 标准对于数据链路层的定义相同,但物理层不同。
在阅读完上文后,对CAN总线中的一个极其重要的知识点需要搞明白:回读机制。
指的是节点在向总线上发送报文的过程中,同时也对总线上的二进制位进行“回读”。通过这种机制,节点就可以判断出本节点发出的二进制位与总线上当前的二进制位是否一致。
3.1 帧的种类
帧类型 | 帧用途 |
---|---|
数据帧 | 用于发送单元向接收单元传送数据的帧。 |
遥控帧 | 用于接收单元向具有相同 ID 的发送单元请求数据的帧。 |
错误帧 | 用于当检测出错误时向其它单元通知错误的帧。 |
过载帧 | 用于接收单元通知其尚未做好接收准备的帧。 |
帧间隔 | 用于将数据帧及遥控帧与前面的帧分离开来的帧 |
另外,数据帧和遥控帧有标准格式和扩展格式两种格式。标准格式有 11 个位的标识符(Identifier: 以下称 ID),扩展格式有 29 个位的 ID。
3.1.1 数据帧
关于数据帧的标准格式各个位的介绍可以查看本文的【2.2.2节】,在数据帧的拓展格式中,与标准格式不同处在于:
- 仲裁段为 29 位;
- 多出的SRR 位(Substitute Remote Request BIT),用于替代标准格式中的 RTR 位。SRR 位为隐性位,由于 RTR 在数据帧为显性位,所以在两个 ID 相同的标准格式报文与扩展格式报文中,标准格式的优先级较高;
- 控制段中的 r1 和 r0 一样都为保留位,默认设置为显性位;
- 扩展格式的 ID 有 29 个位。基本 ID 从 ID28 到 ID18,扩展 ID 由 ID17 到 ID0 表示。基本 ID 和标准格式的 ID 相同。禁止高 7 位都为隐性。(禁止设定:基本 ID=1111111XXXX)
3.1.2 遥控帧
接收单元向发送单元请求发送数据所用的帧。遥控帧由 6 个段组成。遥控帧没有数据帧的数据段。举个例子,车钥匙需要知道车门的状态,一个远程帧过去,车门把自己的状态发回来了。
遥控帧与数据帧的不同之处:
- 遥控帧的 RTR 位为隐性位,没有数据段;
- 没有数据段的数据帧和遥控帧可通过 RTR 位区别开来;
- 遥控帧的数据长度码以所请求数据帧的数据长度码表示;
3.1.3 错误帧
用于在接收和发送消息时检测出错误通知错误的帧。错误帧由错误标志和错误界定符构成。
(1) 错误标志
错误标志包括主动错误标志和被动错误标志两种。
① 主动错误标志:6 个位的显性位。
② 被动错误标志:6 个位的隐性位。
(2) 错误界定符
错误界定符由 8 个位的隐性位构成。
(3)错误标志之后还有0~6个错误标志重叠部分
处于主动错误状态的节点检测到错误时会发送主动错误标志,6个连续显性位会违反位填充规则和位场的固定形式,进而造成其它节点也检测到错误并发送错误标志。所有节点所发送的显性序列叠加组成错误标志重叠部分,错误标志重叠部分的长度在6-12个显性位之间。
对主动错误和被动错误的通俗理解:
首先建议把广泛使用的“主动错误”和“被动错误”概念换成“主动报错”和“被动报错”。
1.主动报错站点
只要检查到错误,它立即“主动地”发出错标识。所谓“出错标识“,它本身就是一个“错误的位序列”(连续的6 个显性位,不满足CAN协议的“最多5个连续的同性位”要求),目的是“主动地”告诉大家:即使你们没有发现“刚才我已发现”的错误,现在我“以身作则”出错啦!你们该看到这个错误了吧!
2.被动报错站点
如果检查到错误,它只能干瞪眼“被动地”等别人(主动报错站点)报错,等待的时候它可不能去动总线,直到识别出由主动报错站点发出的“错误的位序列”,它才松了一口气:有人正式报错了!然后他就可以去竞争总线,该干啥干啥。
(错误帧这里看不明白没关系,后文会有详细介绍)
3.1.4 过载帧
过载帧是接收节点向总线上其它节点报告自身接收能力达到极限的帧,可以理解为:接收节点Node_A接收报文的能力达到极限了,于是Node_A就会发出过载帧来告诉总线上的其它节点(包括发送节点),我接收节点Node_A已经没有能力处理你们发来的报文了。过载帧由过载标志和过载界定符构成。
(1) 过载标志
6 个位的显性位。
过载标志的构成与主动错误标志的构成相同。
(2) 过载界定符
8 个位的隐性位。
过载界定符的构成与错误界定符的构成相同。
3.1.5 帧间隔
帧间隔是用于分隔数据帧和遥控帧的帧。数据帧和遥控帧可通过插入帧间隔将本帧与前面的任何帧(数据帧、遥控帧、错误帧、过载帧)分开。过载帧和错误帧前不能插入帧间隔。
(1) 间隔
3 个位的隐性位。
(2) 总线空闲
隐性电平,无长度限制(0 亦可)。
本状态下,可视为总线空闲,要发送的单元可开始访问总线。
(3) 延迟传送(发送暂时停止)
8 个位的隐性位。
只在处于被动错误状态的单元刚发送一个消息后的帧间隔中包含的段。
3.2 位填充
为防止突发错误而设定,CAN协议中规定,当相同极性的电平持续五位时,则添加一个极性相反的位。填充位的添加和删除是由发送节点和接收节点完成的,CAN-BUS只负责传输,不会操纵信号。
- 对于发送节点而言:
在发送数据帧和遥控帧时,对于SOF~CRC(除去CRC界定符) 之间的位流,相同极性的电平如果持续5位,那么在下一个位插入一个与之前5位反型的电平; - 对于接收节点而言:
在接收数据帧和遥控帧时,对于SOF~CRC(除去CRC界定符)之间的位流,相同极性的电平如果持续5位,那么需要删除下一位再接收。如果这个第 6 个位的电平与前 5 位相同,将被视为错误并发送错误帧。
3.3 错误的种类
位错误(Bit Check Error):节点将自己发送到总线上的电平与同时从总线上回读到的电平进行比较,如果发现二者不一致,那么这个节点就会检测出一个位错误。
有三种例外情况不属于位错误:
① 在仲裁区,节点向总线发送隐性位却回读到显性位,不认为是位错误,这种情况表示该节点仲裁失败;
② 在ACK槽,节点向总线发送隐性位却回读到显性位,不认为是位错误,这种情况表示,该节点当前发送的这一帧报文至少被一个其它节点正确接收;
③ 该节点发送被动错误标志,节点Node_A向总线发送连续六个隐性位(被动错误标志)却回读到显性位,不认为是位错误。因为被动错误标志是六个连续的隐性位,所以在总线上按照线与机制,有可能这六个连续隐性位被其它节点发送的显性电平“吃掉”;
ACK错误(Acknowledgment Error):按照CAN协议的规定,在一帧报文(数据帧或者遥控帧)发出之后,如果接收节点Node_B成功接收了该帧报文,那么接收节点Node_B就要在该帧报文ACK槽对应的时间段内向总线上发送一个显性位来应答发送节点Node_A。这样发送节点Node_A就会在ACK槽时间段内从总线上回读到一个显性位。
当发送节点Node_A在ACK槽时间段内没有回读到显性位,那么发送节点Node_A就会检测到一个ACK应答错误。这表示没有一个节点成功接收该帧报文。
填充错误(Fill Error):帧起始到CRC之间,接收节点检测到有6个连续相同的位电平时,也就是违反5位相同位插入1位相反位的“位填充”原则。
CRC错误:发送节点Node_A在发送数据帧或者遥控帧时,会计算出该帧报文的CRC序列。接收节点Node_B在接收报文时也会执行相同的CRC算法,如果接收节点Node_B计算出的CRC序列值与发送节点Node_A发来的CRC序列值不一致,那么接收节点就检测到一个CRC错误。
格式错误:在一帧报文发送时,如果在必须发送预定值的区域内检测到了非法值,即与任何一种帧格式都不符;CAN报文中,有预定值的区域包括:
- 数据帧和遥控帧的CRC界定符、ACK界定符、EOF;
- 错误帧界定符
- 过载帧界定符
3.4 错误通知
在检测到错误之后,检测到错误的节点就要发送错误帧到总线上来通知总线上的其他节点。
3.4.1 节点错误状态
对于错误界定,节点存在如下三种状态:
-
主动错误: 错误标志由6个连续的显性位组成(这种连续的6个显性位与常规的填充位和其它帧固定格式不相同,正因为如此,硬件才容易区别)
-
被动错误 :被动错误标志由6个连续的隐性位组成,当发送结束后,处于被动错误状态的节点在下一次再次发送时之前需要等待一些额外时间。
-
总线关闭: 处于总线关闭状态的节点不允许发送和接收任何形式的帧报文。且只能通过用户请求进行恢复。
3.4.2 错误状态的转换
一个CAN节点在什么情况下处于主动错误状态,什么情况下处于被动错误状态呢?
在CAN节点内,有两个计数器:发送错误计数器(TEC)和接收错误计数器(REC)
错误界定并非是依据错误的类型去界定CAN节点的错误状态,而是依据错误计数器【TEC/REC】的值来界定CAN节点的错误状态;当该节点检测到错误后,内部REC/TEC计数器会相应的增加,基于REC/TEC的值判定节点状态。
需要注意的是:这两个计数器计得不是收发报文的数量,也不是收发错误帧的数量。TEC和RCE计数值的变化,是根据下表的规定来进行的。
节点错误状态的转换就是一个 “量变”到“质变” 的过程:
- 主动错误状态:【REC<127 且TEC<127】
初步可判定该节点相对稳定可靠,该错误计数很可能是由于某个节点异常导致的,那么其他节点很可能也会触发该错误,那么允许该节点破坏CAN总线的异常报文并告知其他节点;
节点检测到一个错误就会发送带有主动错误标志的错误帧,因为主动错误标志是连续六个显性位,所以这个时候主动错误标志将会“覆盖”掉总线上其它节点的发送,而之前在CAN总线上传输的报文就被这“六个连续显性位”破坏掉了。
如果发出主动错误帧的节点是发送节点,这个情况下就相当于:刚刚发送的那一帧报文我发错了,现在我破坏掉它(发送主动错误帧),你们不管收到什么都不算数;
如果发出主动错误帧的节点是接收节点,这个情况就相当于:刚刚我收报文的时候发现了错误,不管你们有没有发现这个错误,我现在主动站出来告诉大家这个错误,并把这一帧报文破坏掉(发送主动错误帧),刚才你们收到的东西不管对错都不算数了。
- 被动错误状态:【REC>128 或TEC>128】
节点发送错误帧的次数较多,初步可判定该节点相对不可靠,该错误计数很可能是由于自身节点问题导致,即该错误很可能仅有该节点才有,对于其他节点而言是可以正常交互的,总线不信任该节点提供的错误标识,将不允许破坏总线数据,那么允许该节点发送错误帧“6个连续隐性位”至CAN总线,仅告知其他节点异常;
如果发出被动错误帧的节点为报文的发送节点,那么在发送被动错误帧之后,刚刚正在发送的报文被破坏,并且该节点不能在错误帧之后随着连续发送刚刚发送失败的那个报文。随之而来的是帧间隔,并且连带着8位隐性位的 “延迟传送” 段;这样总线电平就呈现出连续11位隐性位,总线上的其它节点就能判定总线处于空闲状态,就能参与总线竞争。
此时如果该节点能够竞争成功,那么它就能接着发送,如果竞争不能成功,那么就接着等待下一次竞争。这种机制的目的正是为了让其它正常节点(处于主动错误)优先使用总线。
- 总线关闭状态:【TEC>255】
一个处于被动错误状态的节点,仍然多次发送被动错误帧,使该节点转为总线关闭态;
该节点不能向总线上发送报文,也不能从总线上接收报文,整个节点脱离总线。等到检测到128次11个连续的隐性位时,TEC和REC置0,重新回到主动错误状态。
由于存在实现方式的不同,CAN总线关闭状态存在只允许用户请求恢复和检测到128个11位连续的隐性位时自恢复两种不同的恢复形式。
如果总线上只有一个节点,该节点发送数据帧后得不到应答,TEC最大只能计数到128,即这种情况下节点只会进入被动错误状态而不会进入总线关闭状态。
3.4.3 错误帧的发送
按照CAN协议的规定:
发生位错误、填充错误、格式错误、ACK错误时,则在错误产生的那一位的下一位开始发送错误帧。
发生CRC错误时,紧随ACK界定符后的位发送错误帧。
错误帧发送完成后,总线空闲时自动重发出错的数据帧。
3.4.4 仲裁机制与位时序
仲裁存在于当同时有多个节点竞争总线发送权时,关键词是同时,比如节点1和节点2两者同时向总线发送一个位,是同时发送的,总线只是导线(理解成自带线与功能),如果节点1发送的是1,节点2发送的是0,线与后总线的状态则为0;
与此同时,这里运用到回读机制,即:节点发送位的同时会回读总线的状态并与自身相比较,可知节点1本身状态与总线状态一致,则节点1仲裁胜利。(可查阅本文【【2.2.3节】】)
理解位时序,重点在于同步,分为硬同步和重新同步,在重新同步中,通过设置SJW来调整PBS段的长度(PBS1增长,PBS2缩短),以达到调整一个位中Tq的个数,即长度。(可查阅本文【【2.2.4节】】)
关于波特率的计算:
总线上的各个通讯节点只要约定好 1 个 Tq 的时间长度以及每一个数据位占据多少个Tq,就可以确定 CAN 通讯的波特率。例如,假设1Tq=1us,而每个数据位由 19 个 Tq 组成,则传输一位数据需要时间 T(1bit) =19us,从而每秒可以传输的数据位个数为:1x10^6/19 = 52631.6 (bps),这个每秒可传输的数据位的个数即为通讯中的波特率。
3.4.5 举个栗子
【位错误】举例(情况1):
设总线上所有节点处于主动错误状态;
当一个发送节点监控到总线上的位数值与发送的位数值不一致时,检测为位错误,并发送主动错误标志(6个连续的显性位);
接收节点接收到发送节点发送的6个连续的显性位时,会检测为位填充错误,也会发送主动错误标志;
发送节点发送完主动错误标志后,开始监控总线是否为隐性位,当总线为隐性位时,开始发送错误界定符(8个连续的隐性位);
当接收节点发送完主动错误标志后,开始向总线发送错误界定符; 等待错误帧发送完成,总线空闲后,发送节点重新发送出错的报文.
由于发送节点发送6个连续的显性位会破坏位填充规则,触发接收节点发送主动错误标志,发送节点和接收节点的结合是形成错误标志叠加部分的原因。
【位错误】举例(情况2):
假设发送节点处于被动错误状态,接收节点处于主动错误状态;
当发送节点监控到总线上的位数值与发送的位数值不一致时,检测为位错误,并发送被动错误标志(6个连续的隐性位);
接收节点接收到发送节点发送的6个连续的隐性位时,会检测为位填充错误,并会发送主动错误标志;
发送节点发送完被动错误标志后,开始监控总线是否为隐性位,当总线为隐性位时,开始发送错误界定符(8个连续的隐性位);
接收节点发送完主动错误标志后,开始监控总线是否为隐性位,当总线为隐性位时,开始发送错误界定符(8个连续的隐性位);
3.5 CAN的特性总结
1) 多主控制
在总线空闲时,所有单元都可以发送消息(多主控制),而两个以上的单元同时开始发送消息时,根据标识符(Identifier 以下称为 ID)决定优先级。ID 并不是表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时开始发送消息时,对各消息 ID 的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。
2) 系统的柔软性
与总线相连的单元没有类似于“地址”的信息。因此在总线上增加单元时,连接在总线上的其它单元的软硬件及应用层都不需要改变。
3) 通信速度较快,通信距离远。最高 1Mbps(距离小于 40M),最远可达 10KM(速率低于 5Kbps)。
4) 具有错误检测、错误通知和错误恢复功能。所有单元都可以检测错误(错误检测功能),检测出错误的单元会立即同时通知其他所有单元(错误通知功能),正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止(错误恢复功能)。
5) 故障封闭功能。CAN 可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。
6) 连接节点多。CAN 总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。
4. STM32中的CAN
STM32 的所有型号芯片中都具有 bxCAN 控制器 (Basic Extended CAN),即基本扩展 CAN,它支持 CAN 协议 2.0A(不支持扩展格式) 和2.0B 标准。
它的设计目标是,以最小的 CPU 负荷来高效处理大量收到的报文。它也支持报文发送的优先级要求(优先级特性可软件配置)。对于安全紧要的应用,bxCAN 提供所有支持时间触发通信模式所需的硬件功能。
4.1 bxCAN主要特性
- bxCAN 接口可以自动地接收和发送 CAN 报文,支持标准标识符和扩展标识符;
- 具有 3 个发送邮箱,发送报文的优先级可以使用软件,可以记录发送的时间;
- 有 2 个 3 级深度的接收 FIFO,可以使用过滤功能只接收或不接收某些 ID号的报文;
- 可以配置成自动重发;
- 不支持使用 DMA 进行数据收发;
- 可变的过滤器组(最多 28 个)。
在 STM32 互联型产品中,带有 2 个 CAN 控制器,而常用的 STM32F103ZET6 属于增强型,不是互联型,只有 1 个 CAN 控制器。
4.2 bxCAN的架构
双 CAN 的框图如下:
在双CAN中,对于CAN1(主)来说,它拥有3个发送邮箱,2个FIFO,和CAN2共用28个滤波器组,但在STM32战舰F1中,只有14个滤波器组,每个滤波器组 x 由 2 个 32 为寄存器分别为CAN_FxR1 和 CAN_FxR2 组成。
- Tx Mailboxes(发送邮箱)
STM32 的 CAN 中共有 3 个发送邮箱供软件来发送报文。发送调度器根据优先级决定哪个邮箱的报文先被发送。 - Accepttance Filters( 接收过滤器 )
STM32 的 CAN 中共有 14 个位宽可变/可配置的标识符过滤器组,软件通过对它们编程,从而在 CAN 收到的报文中选择它需要的报文,而把其它报文丢弃掉。 - Receive FIFO( 接收 FIFO )
STM32 的 CAN 中共有 2 个接收 FIFO,每个 FIFO 都可以存放 3 个完整的报文。它们完全由硬件来管理。
4.3 控制内核中的主要寄存器
CAN的三种模式:
- 工作模式:
①初始化模式(INRQ=1,SLEEP=0)
②正常模式(INRQ=0,SLEEP=0)
③睡眠模式(SLEEP-1) - 测试模式
①静默模式( LBKM=0, SILM=1 )
②环回模式( LBKM=1,SILM=0 )
③环回静默模式(LBKM=1,SILM=1) - 调试模式
4.3.1 主控制寄存器 (CAN_MCR)
负责管理CAN的工作模式(保留位由硬件强制为0)
寄存器位 |
|
---|---|
位 16 | DBF: 调试冻结 (Debug freeze) 0:在调试时,CAN照常工作 1:在调试时,冻结CAN的接收/发送。仍然可以正常地读写和控制接收FIFO。 |
位15 | RESET: bxCAN 软件复位 (bxCAN software master reset) 0:本外设正常工作; 1:对bxCAN进行强行复位,复位后bxCAN进入睡眠模式(FMP位和CAN_MCR寄存器被初始化为其复位值)。此后硬件自动对该位清’0’。 |
位7 | TTCM: 时间触发通信模式 (Time triggered communication mode) 0:禁止时间触发通信模式; 1:允许时间触发通信模式。 |
位6 | ABOM: 自动离线(Bus-Off)管理 (Automatic bus-off management) 该位决定CAN硬件在什么条件下可以退出离线状态。 0:离线状态的退出过程是,软件对CAN_MCR寄存器的INRQ位进行置’1’随后清’0’后,一旦硬件检测到128次11位连续的隐性位,则退出离线状态; 1:一旦硬件检测到128次11位连续的隐性位,则自动退出离线状态。 |
位5 | AWUM: 自动唤醒模式 (Automatic wakeup mode) 该位决定CAN处在睡眠模式时由硬件还是软件唤醒 0:睡眠模式通过清除CAN_MCR寄存器的SLEEP位,由软件唤醒; 1:睡眠模式通过检测CAN报文,由硬件自动唤醒。唤醒的同时,硬件自动对CAN_MSR寄存 器的SLEEP和SLAK位清’0’ 。 |
位4 | NART: 禁止报文自动重传 (No automatic retransmission) 0:按照CAN标准,CAN硬件在发送报文失败时会一直自动重传直到发送成功; 1:CAN报文只被发送1次,不管发送的结果如何(成功、出错或仲裁丢失)。 |
位3 | RFLM: 接收FIFO锁定模式 (Receive FIFO locked mode) 0:在接收溢出时FIFO未被锁定,当接收FIFO的报文未被读出,下一个收到的报文会覆盖原有的报文; 1:在接收溢出时FIFO被锁定,当接收FIFO的报文未被读出,下一个收到的报文会被丢弃。 |
位2 | TXFP: 发送FIFO优先级 (Transmit FIFO priority) 当有多个报文同时在等待发送时,该位决定这些报文的发送顺序 0:优先级由报文的标识符来决定; 1:优先级由发送请求的顺序来决定。 |
位1 | SLEEP: 睡眠模式请求 (Sleep mode request) 软件对该位置’1’可以请求CAN进入睡眠模式,一旦当前的CAN活动(发送或接收报文)结束,CAN就进入睡眠。 软件对该位清’0’使CAN退出睡眠模式。当设置了AWUM位且在CAN Rx信号中检测出SOF位时,硬件对该位清’0’。在复位后该位被置’1’,即CAN在复位后处于睡眠模式。 |
位0 | INRQ: 初始化请求 (Initialization request) 软件对该位清’0’可使CAN从初始化模式进入正常工作模式:当CAN在接收引脚检测到连续的11个隐性位后,CAN就达到同步,并为接收和发送数据作好准备了。为此,硬件相应地对CAN_MSR寄存器的INAK位清’0’。软件对该位置1可使CAN从正常工作模式进入初始化模式:一旦当前的CAN活动(发送或接收)结束,CAN就进入初始化模式。相应地,硬件对CAN_MSR寄存器的INAK位置’1’。 |
- TTCM时间触发模式下,CAN使用它内部定时器产生时间戳,并保存在CAN_RDTxR或CAN_TDTxR寄存器中。
- AWUM配置可以让节点在总线无数据传输时进入低功耗模式,当有报文过来得时候则自动唤醒。
- INRQ 位用来控制初始化请求,因此在 CAN 初始化的时候,先要设置该位为 1,然后进行初始化(尤其是 CAN_BTR的设置,该寄存器必须在 CAN 正常工作之前设置),之后再设置该位为 0,让 CAN 进入正常工作模式。
- ABOM、RFLM、TXFP比较重要,需要认真看上面得说明。
4.3.2 位时序寄存器 (CAN_BTR)
用于设置分频、Tbs1、Tbs2以及 Tsjw 等非常重要的参数,直接决定了 CAN 的波特率,还可以设置 CAN 的工作模式。(保留位由硬件强制为0)
寄存器位 |
|
---|---|
位31 | SILM: 静默模式(用于调试) (Silent mode (debug)) 0: 正常状态; 1: 静默模式。 |
位30 | LBKM: 环回模式(用于调试) (Loop back mode (debug)) 0: 禁止环回模式; 1: 允许环回模式。 |
位25:24 | SJW[1:0]: 重新同步跳跃宽度 (Resynchronization jump width),为了重新同步,该位域定义了CAN硬件在每位中可以延长或缩短多少个时间单元的上限。 tRJW = tCAN x (SJW[1:0] + 1)。 |
位22:20 | TS2[2:0]: 时间段2 (Time segment 2),该位域定义了时间段2占用了多少个时间单元 tBS2 = tCAN x (TS2[2:0] + 1)。 |
位19:16 | TS1[3:0]: 时间段1 (Time segment 1),该位域定义了时间段1占用了多少个时间单元 tBS1 = tCAN x (TS1[3:0] + 1) |
位9:0 | BRP[9:0]: 波特率分频器 (Baud rate prescaler),该位域定义了时间单元(tq)的时间长度 tq = (BRP[9:0]+1) x tPCLK |
理解其他模式之前,先看正常模式,此时SILM=0,LBKM=0,这种状态时节点可以正常向节点发送和接收数据。
静默模式下,节点的输出端的逻辑0数据会直接传输到自己的输入端,逻辑1可以被发送到总线,所以它不能向总线发送显
性位(逻辑0), 只能发送隐性位(逻辑1)。输入端可以从总线接收内容。
由于它只可发送的隐性位不会强制影响总线的状态,所以把它称为静默模式。这种模式一般用于监测,它可以用于分析总线上的流量,但又不会因为发送显性位而影响总线。
回环模式下,节点输出端的所有内容都直接传输到自己的输入端,输出端的内容同时也会被传输到总线上,即也可使用总线监测它的发送内容。输入端只接收自己发送端的内容,不接收来自总线上的内容。使用回环模式可以进行自检。
CAN 内核忽略确认错误(在数据/远程帧的确认位时刻,不检测是否有显性位)。在环回模式下,bxCAN 在内部把 Tx 输出回馈到 Rx 输入上,而完全忽略 CANRX 引脚的实际状态。发送的报文可以在 CANTX 引脚上检测到。
回环静默模式是以上两种模式的结合,节点的输出端的所有内容都直接传输到自己的输入端,并且不会向总线发送显性位影响总线,不能通过总线监测它的发送内容。输入端只接收自己发送端的内容,不接收来自总线上的内容。这种方式可以在“热自检”时使用,即自我检查的时候,不会干扰总线。
(回环模式下总线是可以接收到自检节点的报文的,而回环静默模式下,节点既不向总线发送数据也不接收数据)
4.3.3 bxCAN的位时序和波特率
与标准CAN中位时序有些许差异,bxCAN将PTS段与原有的PBS1段结合,并重新命名了,如下:
STM32的CAN外设位时序中只包含3段,分别是同步段SYNC_ SEG、位段BS1及位段BS2,采样点位于BS1及BS2段的交界处。其中SYNC_ SEG段固定长度为1Tq,而BS1及BS2段可以在位时序寄存器CAN_ BTR设置它们的时间长度,它们可以在重新同步期间增长或缩短,该长度SJW也可在位时序寄存器中配置。
通过前面CAN_BTR寄存器的介绍可知,计算BS1和BS2段的时间公式为:
//需要特别注意公式后的 +1
//比如要设置tBS1为3Tq,则写入TS1[3:0]的值应为2
tBS1 = tCAN x (TS1[3:0] + 1)//TS1[3:0]
tBS2 = tCAN x (TS2[2:0] + 1)//TS2[2:0]
则一个数据位的时间为:
T(1bit) = 1Tq+Ts1+Ts2 = 1 + (TS1[3:0] + 1) + (TS2[2:0]+ 1) = N Tq
那么如果要得出具体的时间长度,则只要求出Tq的值即可。Tq的值由CAN_BTR中的BRP[9:0]设定,计算公式为:
需要特别注意公式后的 +1 //比如要设置预分频系数为4,则写入BRP[9:0]的值应为3 tq = (BRP[9:0]+1) x tPCLK
由时钟树可知,bxCAN挂载在APB1时钟下,即tPCLK = APB1 = 36MHz。
最终可以计算出CAN通讯的波特率为:
BaudRate = 1/N Tq
下面是配置1Mbps波特率的例子
4.4 发送邮箱
一共有3个发送邮箱,即最多可以缓存3个待发送的报文。每个发送邮箱中包含有标识符寄存器CAN_TIxR、数据长度控制寄存器CAN_TDTxR及2个数据寄存器CAN_TDLxR、CAN_TDHxR
① 发送邮箱标识符寄存器 (CAN_TIxR) (x=0…2)
寄存器位 说明 位31:21 STID[10:0]/EXID[28:18]: 标准标识符或扩展标识符 (Standard identifier or extended identifier)
依据IDE位的内容,这些位或是标准标识符,或是扩展身份标识的高字节。位20:3 EXID[17:0]: 扩展标识符 (Extended identifier)
扩展身份标识的低字节。位2 IDE: 标识符选择 (Identifier extension)
该位决定发送邮箱中报文使用的标识符类型
0:使用标准标识符;
1:使用扩展标识符。位1 RTR: 远程发送请求 (Remote transmission request)
0:数据帧;
1:远程帧。位0 TXRQ: 发送数据请求 (Transmit mailbox request)
由软件对其置’1’,来请求发送邮箱的数据。当数据发送完成,邮箱为空时,硬件对其清’0’。② 发送邮箱数据长度和时间戳寄存器 (CAN_TDTxR) (x=0…2)
寄存器位 说明 位31:16 TIME[15:0]: 报文时间戳 (Message time stamp)
该域包含了,在发送该报文SOF的时刻,16位定时器的值。位8 TGT: 发送时间戳 (Transmit global time)
只有在CAN处于时间触发通信模式,即CAN_MCR寄存器的TTCM位为’1’时,该位才有效。
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]的数据( DATA6[7:0] 和DATA7[7:0])。为了把时间戳的2个字节发送出去,DLC必须编程为8。位3:0 DLC[15:0]: 发送数据长度 (Data length code)
该域指定了数据报文的数据长度或者远程帧请求的数据长度。1个报文包含0到8个字节数据,而这由DLC决定。③ 数据寄存器 CAN_TDLxR(低)和 CAN_TDHxR(高)
- 如果CAN_MCR寄存器的TTCM位为’1’,且该邮箱的TGT位也为’1’,那么DATA7和DATA6将被TIME时间戳代替。
通过以上的介绍可以知道,数据打包时各个数据位并不是和描述协议时那样一位紧跟着一位,当要使用CAN外设发送报文时,把报文的各个段分解,按位置写入到这些寄存器中,同时对标识符寄存器CAN_TIxR中的发送请求寄存器位TMIDxR_TXRQ置1,即可把数据发送出去。
其中标识符寄存器CAN_TIxR中 的STID寄存器位比较特别。CAN的标准标识符的总位数为11位,而扩展标识符的总位数为29位的。当报文使用扩展标识符的时候,标识符寄存器CAN_TlxR中 STID[10:0]等效于EXTID[18:28]位,它与EXTID[17:0]共同组成完整的29位扩展标识符。
4.5 接收FIFO
一共有2个接收FIFO,每个FIFO中有3个邮箱,即最多可以缓存6个接收到的报文。当接收到报文时,FIFO的报文计数器会自增,而STM32内部读取FIFO数据之后,报文计数器会自减,通过状态寄存器可获知报文计数器的值,而通过前面主控制寄存器的RFLM位,可设置锁定模式,锁定模式下FIFO溢出时会丢弃新报文,非锁定模式下FIFO溢出时新报文会覆盖旧报文。
每个接收FIFO中包含有标识符寄存器CAN_RIxR、 数据长度控制寄存器CAN_RDTxR及2个数据寄存器CAN_RDLxR、 CAN_RDHxR
① 接收FIFO邮箱标识符寄存器 (CAN_RIxR) (x=0…1)
寄存器位 说明 位31:21 STID[10:0]/EXID[28:18]: 标准标识符或扩展标识符 (Standard identifier or extended identifier)
依据IDE位的内容,这些位或是标准标识符,或是扩展身份标识的高字节。位20:3 EXID[17:0]: 扩展标识符 (Extended identifier)
扩展标识符的低字节。位2 IDE: 标识符选择 (Identifier extension)
该位决定接收邮箱中报文使用的标识符类型
0:使用标准标识符;
1:使用扩展标识符。位1 RTR: 远程发送请求 (Remote transmission request)
0:数据帧;
1:远程帧。② 接收FIFO邮箱数据长度和时间戳寄存器 (CAN_RDTxR)
寄存器位 说明 位31:16 TIME[15:0]: 报文时间戳 (Message time stamp)
该域包含了,在接收该报文SOF的时刻,16位定时器的值。位15:8 FMI[15:0]: 过滤器匹配序号 (Filter match index)
这里是存在邮箱中的信息传送的过滤器序号。位3:0 DLC[15:0]: 接收数据长度 (Data length code)
该域表明接收数据帧的数据长度(0~8)。对于远程帧请求,数据长度DLC恒为0。③ 数据寄存器 CAN_RDLxR(低)和 CAN_RDHxR(高)
CAN 接收到的有效报文,被存储在 3 级邮箱深度的 FIFO 中。FIFO 完全由硬件来管理,从而节省了 CPU 的处理负荷,简化了软件并保证了数据的一致性。
应用程序只能通过读取 FIFO输出邮箱,来读取 FIFO 中最先收到的报文。这里的有效报文是指那些正确被接收的(直到EOF都没有错误)且通过了标识符过滤的报文。所谓的3级深度,意思就是说每个FIFO由三个邮箱组成,你暂且可以将这三个邮箱一起看成一个具体三个成员的消息队列,而此时的FIFO输出邮箱就相当于这个队尾的意思,你可以将它看成是一个指向队尾的指针。
4.6 验收筛选器
以双CAN为例,一 共有28 个筛选器组,每个筛选器组有2个寄存器,CAN1和CAN2是共用的筛选器的。
在CAN协议中,消息的标识符与节点地址无关,但与消息内容有关。因此,发送节点将报文广播给所有接收器时,接收节点会根据报文标识符的值来确定软件是否需要该消息,为了简化软件的工作,STM32的CAN外设接收报文前会先使用验收筛选器检查,只接收需要的报文到FIFO中。筛选器工作的时候,可以调整筛选ID的长度及过滤模式。根据位宽的不同(ID长度不同),每个筛选器组可选择:
- 1 个 31 位过滤器,包括:STID[10:0]、EXTID[17:0]、IDE 和 RTR 位
- 2 个 16 位过滤器,包括:STID[10:0]、IDE、RTR 和 EXTID[17:15]位
- FSCx=1为31位模式,FSCx=0为16位模式
根据过滤的方法的不同又可分为以下两种模式:
- 标识符列表模式:把要接收报文的ID列成一个表,要求报文ID与列表中的某一个标识符完全相同才可以接收,可以理解为白名单管理。
- 掩码模式(屏蔽位模式):把可接收报文ID的某几位作为列表,这几位被称为掩码,可以把它理解成关键字搜索,只要掩码(关键字)相同,就符合要求,报文就会被保存到接收FIFO。
- FBMx=0为掩码模式,FBMx=1为列表模式
- 为了过滤出一组标识符,应该设置过滤器组工作在屏蔽位模式。
- 为了过滤出一个标识符,应该设置过滤器组工作在标识符列表模式。
① 31位下掩码模式:只能过滤一个ID
不是一个筛选器组有两个寄存器吗?怎么只能过滤一个?是因为掩码模式下,第一个寄存器(CAN_F0R1)用来存放要过滤的ID,第二个寄存器(CAN_F0R2)用来存放掩码,所谓的掩码其实就类似于一种标志位,如果这个掩码位为1,则表示进来参与检测的位必须和对应CAN_F0R1的这个位一样才得以通过筛选,如果这个掩码位为0,则表示不关心。
比如CAN_F0R1=0xFFFF0000,CAN_F0R2=0xFF00FF00,要筛选的信号用映像表示,则映像的ID需要为0xFF##00##才能通过筛选。(关注掩码的值为1位,#表示不关心,即不管是0是1都可)② 31位下列表模式:可以过滤两个ID
CAN_F0R1和CAN_F0R2都用来存放需要筛选的ID,此时只接收ID为这俩的报文,比如CAN_F0R1=0xFFFF0000,CAN_F0R2=0xFFFF0001,则映像的ID为0xFFFF0000或0xFFFF0001时能筛选通过。③ 16位下掩码模式:可过滤两个ID
原理就是把CAN_F0R1和CAN_F0R2分别一分为2,更多的介绍在下文③ 16位下列表模式:可过滤四个ID
原理也是把CAN_F0R1和CAN_F0R2分别一分为2,更多的介绍在下文
4.6.1 CAN过滤器寄存器
① 主控寄存器 (CAN_FMR)
② 模式寄存器 (CAN_FM1R)
③ 位宽寄存器 (CAN_FS1R)
④ FIFO关联寄存器 (CAN_FFA1R)
⑤ 激活寄存器 (CAN_FA1R)
⑥ CAN 过滤器组i的寄存器x (CAN_FiRx)
4.6.2 配置筛选器工作模式
当筛选器组寄存器长度为31位时,可为其配置标识符列表模式或者掩码模式共两种;相同,当筛选器组寄存器长度为16位时,此时也有2种配置方式。总共4种。
假如对筛选组0进行配置,则 n=0,需要在FIFO关联寄存器CAN_FFA1R寄存器中将 FFA0 为清零,关联筛选器组0;第一步,将位宽寄存器CAN_FS1R寄存器中 FSC0 位置1,即选择筛选器组0为32位位宽模式;第二步,在模式寄存器 CAN_FM1R中将FBM0清零,即选择标识符屏蔽模式(掩码模式);第三步类似,将FBM0位置1则表示使用标识符列表模式。
继续上面得情况,当处于掩码模式时
第一个寄存器存储要筛选的ID,第二个寄存器存储掩码,掩码为1的部分表示该位必须与ID中的内容一致,筛选的结果为表中第三行的ID值,它是一组包含多个的ID值,其中x表示该位可以为1可以为0。
在标识符列表模式时,则两个寄存器都存储 ID 号,只有每一位均一致才能筛选通过并接收报文存到FIFO中。
16位列表模式下,筛选的ID可放在CAN_F0R1[15:0]、CAN_F0R1[31:16]、CAN_F0R2[15:0]、CAN_F0R2[31:16];16位掩码模式下,筛选的ID可放在CAN_F0R1[15:0]、CAN_F0R2[15:0],与之对应的掩码分别放在CAN_F0R1[31:16]、CAN_F0R2[31:16]。
举个简单的例子,我们设置过滤器组 0 工作在:1 个 32 为位过滤器-标识符屏蔽模式,然后设置CAN 过滤器组0的寄存器1 即CAN_F0R1=0XFFFF0000,CAN 过滤器组0的寄存器2 即CAN_F0R2=0XFF00FF00。其中存放到 CAN_F0R1 的值就是期望收到的 ID,即我们希望收到的映像(STID+EXTID+IDE+RTR)最好是:0XFFFF0000。
而0XFF00FF00 就是设置我们需要必须关心的 ID,表示收到的映像,其位[31:24]和位[15:8]这 16个位的必须和 CAN_F0R1 中对应的位一模一样,而另外的 16 个位则不关心,可以一样,也可以不一样,都认为是正确的 ID,即收到的映像必须是 0XFFxx00xx,才算是正确的(x 表示不关心)。总结一下所用到的寄存器,分为三类,分别是主控部分、邮箱发送接收部分和筛选器部分:
5. ST库函数实现CAN通信
5.1 CAN控制相关的结构体
5.1.1 初始化结构体
CAN_InitTypeDef用于规定CAN的工作方式、通信波特率、每个位多长等这些
//CAN初始化函数
//STM32F103系列只有CAN1,因此第一个参数是CAN1
//第二个参数则是CAN_InitTypeDef
uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct);
typedef enum {DISABLE = 0, ENABLE = !DISABLE} FunctionalState;
typedef struct {
uint16_t CAN_Prescaler; /*配置CAN外设的时钟分频,可设置为1-1024*/
uint8_t CAN_Mode;/*配置CAN的工作模式,回环或正常模式*/
uint8_t CAN_SJW;/*配置SJW极限值*/
uint8_t CAN_BS1;/*配置BS1段长度*/
uint8_t CAN_BS2;/*配置BS2段长度*/
Functionalstate CAN_TTCM; /* 是否使能TTCM时间触发功能*/
Functionalstate CAN_ABOM;/* 是否使能ABOM自动离线管理功能*/
Functionalstate CAN_AWUM; /* 是否使能AWUM自动唤醒功能*/
FunctionalState CAN_NART;/*是否使能NART自动重传功能*/
FunctionalState CAN_RFLM; /*是 否使能RFLM锁定FIFO功能*/
FunctionalState CAN_TXFP; /*配置 TXFP报文优先级的判定方法*/
} CAN_InitTypeDef;
CAN_Prescaler
:时钟分频,用来确定CAN通信的波特率及控制时间片Tq的长度,只能从1-1024,不能为0,因为这个值会先被减1后才写入寄存器,计算的时候则再会被加1,因此如果要设置分频为4的话,则需要将CAN_Prescaler赋值为4。
CAN_Mode
:用于设置CAN的工作模式,可设置为正常模式(CAN_Mode_Normal)、 回环模式(CAN_Mode_LoopBack)、 静默模式(CAN_Mode_Silent)以及回环静默模式(CAN_Mode_Silent_LoopBack)。
CAN_SJW、CAN_BS1和CAN_BS2
:写入时直接使用ST提供的宏即可
5.1.2 发送及接收结构体
CanTxMsg 及CanRxMsg在发送与接收报文的时候使用到
//CAN发送报文的函数,第一个参数为CAN1
//第二个参数CanTxMsg
//返回值代表发送邮箱的编号,即0,1,2
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage);
//不能一下子就能发送成功,可以检测
//将CAN_Transmit返回的邮箱编号放进以下函数查看发送状态
//返回值为0:代表发送失败,1:代表成功
uint8_t CAN_TransmitStatus(CAN_TypeDef* CANx, uint8_t TransmitMailbox);
typedef struct {
uint32_t stdId; /*存储报文的标准标识符11位,0-0x7FF. */
uint32_t ExtId; /*存储报文的扩展标识符29位,0-0x1FFFFF. */
uint8_t IDE; /*存储IDE扩展标志*/
uint8_t RTR; /*存储RTR远程帧标志*/
uint8_t DLC; /*存储报文数据段的长度,0-8 */
uint8_t Data[8]; /*存储报文数据段的内容*/
} CanTxMsg;
//用于接收报文
//参数FIFONumber即0或者1,至于是哪个,可使用CAN_GetFlagStatus函数进行查询
void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage);
typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus;
FlagStatus CAN_GetFlagStatus(CAN_TypeDef* CANx, uint32_t CAN_FLAG)
typedef struct{
uint32_t stdId; /*存储报文的标准标识符11位,0-0x7FF. */
uint32_t ExtId; /*存储报文的扩展标识符29位,0-0x1FFFFF. */
uint8_t IDE; /*存储IDE扩展标志*/
uint8_t RTR; /*存储RTR远程帧标志*/
uint8_t DLC; /*存储报文数据段的长度,0-8 */
uint8_t Data[8]; /*存储报文数据段的内容*/
uint8_t FMI; /*存储了本报文是由经过筛选器存储进FIFO的(即筛选器的编号),0-0xFF */
} CanRxMsg;
IDE
:通过软件方式确定报文是标准格式还是扩展格式,如果是标准格式,仅需将stdId的值写入相关寄存器,如果是扩展格式,则一次性将ExtId写入相关寄存器即可。(相关寄存器即:发送邮箱标识符寄存器 CAN_TIxR)
FMI
:只存在于接收结构体,它存储了筛选器的编号,表示本报文是经过哪个筛选器存储进接收FIFO的,可以用它简化软件处理。
5.1.3 筛选器结构体
CAN_ FilterlnitTypeDef用于配置筛选器的过滤规则
//初始化筛选器,具体是哪个筛选器组,其包含在结构体中
void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct);
typedef struct {
uint16_t CAN_FilterIdHigh;/*CAN_FxR1 寄存器的高16位*/
uint16_t CAN_Filter_IdLow;/*CAN_FxR1寄存器的低16位*/
uint16_t CAN_FilterMaskIdHigh;/*CAN_FxR2 寄存器的高16位*/
uint16_t CAN_FilterMaskIdLow;/*CAN_FxR2 寄存器的低16位*/
uint16_t CAN_FilterFIFOAssignment; /*设置经过筛选后数据存储到哪个接收FIFO*/
uint8_t CAN_FilterNumber;/*筛选器编号,范围0-27*/
uint8_t CAN_FilterMode;/*筛选器模式*/
uint8_t CAN_FilterScale;/*设置筛选器的尺度*/
FunctionalState CAN_FilterActivation; /*是否使能本筛选器*/
} CAN_FilterInitTypeDef;
CAN_FilterldHigh
:用于存储要筛选的ID,若筛选器工作在32位模式,它存储的是所筛选ID的高16位;若筛选器工作在16位模式,它存储的就是一个完整的要筛选的ID。
CAN_FilterldLow
:也是用于存储要筛选的ID,若筛选器工作在32位模式,它存储的是所筛选ID的低16位;若筛选器工作在16位模式,它存储的就是一个完整的要筛选的ID。
CAN_FilterMaskldHigh
:存储的内容分两种情况,当筛选器工作在标识符列表模式时,它的功能与CAN_FilterldHigh相同,都是存储要筛选的ID;而当筛选器工作在掩码模式时,它存储的是CAN_FilterldHigh成员对应的掩码,与CAN_FilterldHigh组成一组筛选器。
CAN_FilterMaskldLow
:存储的内容也分两种情况,当筛选器工作在标识符列表模式时,它的功能与CAN_FilterldLow相同, 都是存储要筛选的ID;而当筛选器工作在掩码模式时,它存储的是CAN_FilterldLow成员对应的掩码,与CAN_FilterldLow组成一组筛选器。
模式 | FilterldHigh | FilterldLow | FilterMaskldHigh | FilterMaskldLow |
---|---|---|---|---|
32位列表模式 | ID1的高16位 | ID1的低16位 | ID2的高16位 | ID2的低16位 |
16位列表模式 | ID1的完整数值 | ID2的完整数值 | ID3的完整数值 | ID4的完整数值 |
32位掩码模式 | ID1的高16位 | ID1的低16位 | ID1掩码的高16位 | ID1掩码的低16位 |
16位掩码模式 | ID1的完整数值 | ID2的完整数值 | ID1掩码的完整数值 | ID2掩码的完整数值 |
通过前面的学习可以知道,比如CAN_F0R1最小可被分成16位,则可以用16位为一个单位表示其他模式下的状态,比如当为32位模式时,则用高低两个单位表示,从软件编程方面优化了筛选器组的配置。
以上可以理解为CAN_F0R1分成CAN_FilterldHigh和CAN_FilterldLow两个单位,而对于CAN_FilterMaskldHigh和CAN_FilterMaskldLow则是由CAN_F0R2而来,它们在掩码模式下时存放掩码,在列表模式时存放ID。
比如对于32位列表模式的配置:
已知这种情况下可筛选2个ID,第一个ID有32位,分别由CAN_FilterldHigh和CAN_FilterldLow存放,第二个ID也是32位,分别由CAN_FilterMaskldHigh和CAN_FilterMaskldLow存放,所以说此时CAN_FilterMaskldHigh和CAN_FilterldHigh功能相同。
比如对于16位掩码模式的配置:
已知这种情况下可筛选2个ID,对于ID1,由CAN_FilterldHigh存放,则由CAN_FilterMaskldHigh存放ID1对应的掩码;对于ID2,由CAN_FilterldLow,则由CAN_FilterMaskldLow存放,即:CAN_FilterldHigh和CAN_FilterMaskldHigh组成一组筛选器。
CAN_FilterFIFOAssignment
:用于设置当报文通过筛选器的匹配后,该报文会被存储到哪一个接收FIFO,它的可选值为FIFO0或FIFO1(宏CAN_Filter_FIFO0/1)。
5.2 硬件部分准备
使用回环模式,实现机制为CAN_TX发送的数据并不经过TJA1050,而是直接发送到CAN_RX引脚;发送前和正常模式一样需要打包数据成报文格式,再进行发送,接收时也是按照正常模式下一样接收,筛选、解析数据、读取报文的过程一个也没少。
5.3 代码实现回环模式
使用回环模式,节点输出端的所有内容都直接传输到自己的输入端,输出端的内容同时也会被传输到总线上,即:也可使用总线监测它的发送内容。输入端只接收自己发送端的内容,不接收来自总线上的内容。即:bxCAN 在内部把 Tx 输出回馈到 Rx 输入上,而完全忽略 CAN_RX 引脚的实际状态。
CAN_TX发送的数据经过TJA1050到了总线,可以在CAN_H和CAN_L检测到信号。发送前和正常模式下一样需要对数据进行打包成报文格式,再进行发送,接收时和正常模式下也一样,需要经过筛选、解析、读取数据。
对CAN的配置初始化
使能 CAN 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//使能CAN1时钟
设置 PA11 为上拉输入(CAN_RX 引脚)PA12 为复用输出(CAN_TX 引脚)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
设置 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);
//Fpclk1的时钟在初始化的时候设置为36M,如果分别设置:
//CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,分频为4
//则波特率为:36M/((8+9+1)*4)=500Kbps
CAN_InitTypeDef CAN_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=CAN_Mode_LoopBack;//模式设置: mode:0,普通模式;1,回环模式;
//设置波特率
CAN_InitStructure.CAN_SJW=tsjw; //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1tq CAN_SJW_2tq CAN_SJW_3tq CAN_SJW_4tq
CAN_InitStructure.CAN_BS1=tbs1; //Tbs1=tbs1+1个时间单位CAN_BS1_1tq ~CAN_BS1_16tq
CAN_InitStructure.CAN_BS2=tbs2; //Tbs2=tbs2+1个时间单位CAN_BS2_1tq ~ CAN_BS2_8tq
CAN_InitStructure.CAN_Prescaler=brp; //分频系数(Fdiv)为brp+1
CAN_Init(CAN1, &CAN_InitStructure); //初始化CAN1
//在CAN_Init函数t里,比较重要的是:
/*在初始化之前,会设置 CAN_MCR 寄存器的 INRQ 为 1 让其进入初始化模式,然后
初始化 CAN_MCR 寄存器和 CRN_BTR 寄存器之后,会设置 CAN_MCR 寄存器的 INRQ 为 0
让其退出初始化模式。*/
设置滤波器
//使用筛选器组0,32位掩码模式,即可过滤一个ID
//ID号存放在CAN_F0R1中,即CAN_FilterIdHigh和CAN_FilterIdLow分别存高低16位
//掩码存放在CAN_F0R2中,即CAN_FilterMaskIdHigh和CAN_FilterMaskIdLow分别存高低16位
//掩码全都为0时,则表明不过滤任何ID,接收总线上的所有报文
CAN_FilterInitTypeDef CAN_FilterInitStructure;
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); //滤波器初始化
//在CAN_FilterInit函数体中:
/*在初始化之前,会设置 CAN_FMR 寄存器的 INRQ 为 INIT 让其进入初始化模式,
然后初始化 CAN 滤波器相关的寄存器之后,会设置 CAN_FMR 寄存器的 FINIT 为 0 让其退出
初始化模式。*/
发送消息的实现
//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; //返回数据长度
}
主函数的实现逻辑
#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按下,发送一次数据
{
printf("发送数据: ");
for(i=0;i<8;i++)
{
canbuf[i]=cnt+i;//填充发送缓冲区
printf("%d\t",canbuf[i]);
}printf("\r\n\r\n");
res=Can_Send_Msg(canbuf,8);//发送8个字节
if(res) printf("发送失败!\r\n\r\n"); //提示发送失败
else printf("发送成功!\r\n\r\n"); //提示发送成功
}
key = Can_Receive_Msg(canbuf); //不断读取FIFO中是否有新数据
if(key) //接收到有数据
{
printf("接收到数据: ");
for(i=0;i<key;i++)
{
printf("%d\t",canbuf[i]);
}printf("\r\n\r\n");
}
t++;
delay_ms(10);
if(t==20)
{
LED0=!LED0;//提示系统正在运行
t=0;
cnt++;
}
}
}
运行结果
5.4 实验总结
从上面代码来看,可见在筛选器部分的配置很简单,确定好筛选模式后就能自动过滤报文,实验里过滤的 ID 为0000 0000,但掩码也全都是0,则说明不管映像ID中是啥都可以,也就是接收总线上所有的报文;当需要设置过滤ID为0x12时,则只需要将掩码改成0X11,表明只对高8位感兴趣,进来参与筛选的前8位必须和ID的前8位0x12一致才接收。
当使用两块及以上的STM32开发板相连通信时,总线只要保留一组匹配电阻即可,其他单元的匹配电阻需要断开,并联到总线上即可。(因为STM32开发板上都配备了匹配电阻)
感谢阅读,本文是我(纯小白)从头开始学习CAN通信时做的笔记记录,一边学习理解一边做笔记,记录了我整个的学习过程,感谢互联网,感谢各路大神在网上分享的知识,让我稍微知道了CAN通信是个什么玩意儿。
本文只记录了CAN的基础知识,我个人的理解也未必全都是正确的,所以希望对那些看到本篇文章的朋友能有参考性的帮助(对小白很友好,从头开始看,就能渐渐理解后面晦涩难懂的专业词汇),如有理解错误之处,欢迎指出!
【参考】:
野火《零死角玩转stm32》
正点原子《STM32F1开发指南-库函数版本_V3.1 》
《CAN入门教程》《STM32中文参考手册_V10》
https://blog.csdn.net/weixin_40528417/article/details/79476186
https://blog.csdn.net/weixin_40528417/article/details/79534483
https://www.cnblogs.com/luoxiao23/p/11210592.html
https://blog.csdn.net/u014156403/article/details/103725924
(吐槽一下正点原子关于这一节的视频教程好敷衍 https://www.bilibili.com/video/BV1Lx411Z7Qa?p=55)
感谢!🙂
原文地址:https://blog.csdn.net/fengge2018/article/details/107592487