S32K14X CAN
目录
概要:
CAN 是 Controller Area Network 的缩写(以下称为 CAN),是 ISO⁽¹⁾国际标准化的串行通信协议。
在当前的汽车产业中,出于对安全性、舒适性、方便性、低公害、低成本的要求,各种各样的电子控制系统被开发了出来。由于这些系统之间通信所用的数据类型及对可靠性的要求不尽相同,由多条总线构成的情况很多,线束的数量也随之增加。为适应 “减少线束的数量”、“通过多个 LAN,进行大量数据的高速通信” 的需要,1986 年德国电气商博世公司开发出面向汽车的 CAN 通信协议。此后,CAN 通过 ISO11898 及 ISO11519 进行了标准化,现在在欧洲已是汽车网络的标准协议。
现在,CAN 的高性能和可靠性已被认同,并被广泛地应用于工业自动化、船舶、医疗设备、工业设备等方面。
图 1 是车载网络的构想示意图。CAN 等通信协议的开发,使多种 LAN 通过网关进行数据交换得以实现。

一、CAN原理:

CAN拓扑图如下:

1.CAN收发器:


CAN 收发器 Transceiver(PHY 物理层芯片,俗称 CAN 收发芯片,TJA1040/TJA1043)
独立外部芯片,不在单片机内部!必须外接 MCU 出来的 CAN_TX/CAN_RX 数字逻辑,不能直接接 CAN‑H CAN‑L 总线,必须经过收发器。
功能:
- 把 MCU 的
CAN_TX数字逻辑电平 → 转换成差分总线电平 CAN‑H / CAN‑L- 显性:CAN‑H 高,CAN‑L 低
- 隐性:CAN‑H、CAN‑L 近似等电位
- 把总线上差分 CAN‑H/CAN‑L,转换成数字信号送给 MCU
CAN_RX;这里就是给 CAN 控制器做 “回读采样” 的来源,即:自己发送的数据,自己可以接收。 - 总线 ESD 保护、待机模式、短路保护。


2.CAN控制器:
属于MCU 单片机内部外设,片上集成,属于数字逻辑,在单片机芯片内部。
功能:
- 组装报文:ID、DLC、数据、CRC 计算、填充位 stuff bit
- 解析接收报文、过滤 ID、接收 FIFO、发送缓冲区管理
- 协议错误检测:位错误 BitError、CRC 错误、格式错误、应答错误、仲裁逻辑,边发边回读总线比特的逻辑就在这里
- 对外接口:和外部收发器对接,输出
CAN_TX、输入CAN_RX,是数字信号:0/1 逻辑电平(3.3V 或者 5V)
3.高速CAN:

加120Ω电阻原因?
这就是硬件电路的设计了。看官们,由于我对模拟数字电路、电路分析、高频电路学习是一塌糊涂,对于这个原因也是一知半解,尤其是电路上MOS管操作。宝子们,这些课程可要学好啊,才能在嵌入式领域游刃有余。
CAN总线终端电阻,一般来说都是120欧姆,实际上在设计的时候,也是两个60欧姆的电阻串起来,而总线上一般有两个120Ω的节点,基本上稍微知道点CAN总线的人都知道这个道理。

但是作为学渣的我,知道这个是在各种标准以及各种数据手册和应用笔记里面常用的电阻值,但是这两个终端电阻的具体作用是什么呢?之前就知道阻抗匹配,但是究竟匹配的是什么呢?
然后我就上知乎遨游了一下,半抄半写的总结了下面的这些知识点。知道终端电阻的作用,对于日常工作中波形不稳定等问题,也能更快的找到问题的原因。
终端电阻的作用
CAN总线终端电阻的作用有3个:
1、提高抗干扰能力,让高频低能量的信号迅速走掉
2、确保总线快速进入隐性状态,让寄生电容的能量更快走掉;
3、提高信号质量,放置在总线的两端,让反射能量降低。
一、提高抗干扰能力
CAN总线有“显性”和“隐性”两种状态,“显性”代表“0”,“隐性”代表“1”,由CAN收发器决定。下图是一个CAN收发器的典型内部结构图,CANH、CANL连接总线。

总线显性时,收发器内部Q1、Q2导通,CANH、CANL之间产生压差;隐性时,Q1、Q2截止,CANH、CANL处于无源状态,压差为0。
总线若无负载,隐性时差分电阻阻值很大,内部的MOS管属于高阻态,外部的干扰只需要极小的能量即可令总线进入显性(一般的收发器显性门限最小电压仅500mV)。这个时候如果有差模干扰过来,总线上就会有明显的波动,而这些波动没有地方能够吸收掉他们,就会在总线上创造一个显性位出来。所以为提升总线隐性时的抗干扰能力,可以增加一个差分负载电阻,且阻值尽可能小,以杜绝大部分噪声能量的影响。然而,为了避免需要过大的电流总线才能进入显性,阻值也不能过小。
二、确保快速进入隐性状态
在显性状态期间,总线的寄生电容会被充电,而在恢复到隐性状态时,这些电容需要放电。如果CANH、CANL之间没有放置任何阻性负载,电容只能通过收发器内部的差分电阻放电,这个阻抗是比较大的,按照RC滤波电路的特性,放电时间就会明显比较长。我们在收发器的CANH、CANL之间加入一个220PF的电容进行模拟试验,位速率为500kbit/s,波形如图,这个波形的下降沿就是比较长的状态。

为了让总线寄生电容快速放电,确保总线快速进入隐性状态,需要在CANH、CANL之间放置一个负载电阻。增加一个60Ω的电阻后,波形如图,从图中看出,显性恢复到隐性的时间缩减到128nS,与显性建立时间相当。

三、提高信号质量
信号在较高的转换速率情况下,信号边沿能量遇到阻抗不匹配时,会产生信号反射;传输线缆横截面的几何结构发生变化,线缆的特征阻抗会随之变化,也会造成反射。
能量发生反射时,导致反射的波形与原来的波形进行叠加,就会产生振铃。
在总线线缆的末端,阻抗急剧变化导致信号边沿能量反射,总线信号上会产生振铃,若振铃幅度过大,就会影响通信质量。在线缆末端增加一个与线缆特征阻抗一致的终端电阻,可以将这部分能量吸收,避免振铃的产生。
别人进行了一个模拟试验(图片都是我抄过来的),位速率为1Mbit/s,收发器CANH、CANL接一根10m左右的双绞线,收发器端接120Ω电阻保证隐性转换时间,末端不加负载。末端信号波形如图所示,信号上升沿出现了振铃。

若双绞线末端增加一个120Ω的电阻,末端信号波形明显改善,振铃消失。

一般在直线型拓扑中,线缆两端即是发送端,也是接收端,故线缆两端需各加一个终端电阻。
而在实际应用过程中,CAN总线一般都不是完美的总线式的设计,很多时候是总线型和星型的混合结构,这个时候一般都将CAN终端电阻布置在线束最远的两端,来尽量的模拟CAN总线的标准结构。
为什么选120Ω?
什么是阻抗?在电学中,常把对电路中电流所起的阻碍作用叫做阻抗。阻抗单位为欧姆,常用Z表示,是一个复数Z= R+i( ωL–1/(ωC))。具体说来阻抗可分为两个部分,电阻(实部)和电抗(虚部)。其中电抗又包括容抗和感抗,由电容引起的电流阻碍称为容抗,由电感引起的电流阻碍称为感抗。这里的阻抗是指Z的模。
任何一根线缆的特征阻抗都可以通过实验的方式得出。线缆的一端接方波发生器,另一端接一个可调电阻,并通过示波器观察电阻上的波形。调整电阻阻值的大小,直到电阻上的信号是一个良好的无振铃的方波,此时的电阻值可以认为与线缆的特征阻抗一致。
采用两根汽车使用的典型线缆,将它们扭制成双绞线,就可根据上述方法得到特征阻抗大约为120Ω,这也是CAN标准推荐的终端电阻阻值,所以这个120Ω是测出来的,不是算出来的,都是根据实际的线束特性进行计算得到的。当然在ISO 11898-2这个标准里面也是有定义的。

为什么功率还要选0.25W?
这个就要结合一些故障状态也计算,汽车ECU的所有接口都需要考虑短路到电源和短路到地的情况,所以我们也需要考虑CAN总线的节点短路到电源的情况,根据标准需要考虑短路到18V的情况,假设CANH短路到18V,电流会通过终端电阻流到CANL上,而CANL内部由于限流的原因,最大注入电流为50mA(TJA1145的规格书上标注),这时候120Ω电阻的功率就是50mA*50mA*120Ω=0.3W。考虑到高温情况下的降额,终端电阻的功率就是0.5W。
转至:CAN总线终端电阻的作用?为什么是120Ω?为什么是0.25W?
4.低速CAN:

5.CAN显隐性:

CAN电平标准
CAN总线采用差分信号,即两线电压差(VCAN_H-VCAN_L)传输数据位
高速CAN规定:
电压差为0V时表示逻辑1(隐性电平)
电压差为2V时表示逻辑0(显性电平)
低速CAN规定:
电压差为-1.5V时表示逻辑1(隐性电平)
电压差为3V时表示逻辑0(显性电平)
6.CAN特点:
(1)多主控制
CAN 本身没有地址概念,依靠 ID 区分报文,不是区分节点;是 “报文过滤”,不是 “节点寻址”。一节点发送消息,总线上其余节点都能接收该消息。在总线空闲时,所有的单元都可开始发送消息(多主控制)。 最先访问总线的单元可获得发送权(CSMA/CA 方式⁽¹⁾)。 多个单元同时开始发送时,发送高优先级 ID (越小)消息的单元可获得发送权。
(2)消息的发送
在 CAN 协议中,所有的消息都以固定的格式发送。总线空闲时,所有与总线相连的单元都可以开始发送新消息。两个以上的单元同时开始发送消息时,根据标识符(Identifier 以下称为 ID)决定优先级。ID 并不是表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时开始发送消息时,对各消息 ID 的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。
(3)系统的柔软性
与总线相连的单元没有类似于 “地址” 的信息。因此在总线上增加单元时,连接在总线上的其它单元的软硬件及应用层都不需要改变。
(4)通信速度
根据整个网络的规模,可设定适合的通信速度。 在同一网络中,所有单元必须设定成统一的通信速度。即使有一个单元的通信速度与其它的不一样,此单元也会输出错误信号,妨碍整个网络的通信。不同网络间则可以有不同的通信速度。
(5)远程数据请求
可通过发送 “遥控帧” 请求其他单元发送数据。
(6)错误检测功能・错误通知功能・错误恢复功能
所有的单元都可以检测错误(错误检测功能)。 检测出错误的单元会立即同时通知其他所有单元(错误通知功能)。 正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止(错误恢复功能)。
(7)故障封闭 CAN
可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。
(8)连接 CAN
总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。
7.错误:
关于错误,了解即可,因为是由硬件,即:控制器来操作的。
7.1错误状态:

(1) 主动错误状态
主动错误状态是可以正常参加总线通信的状态。 处于主动错误状态的单元检测出错误时,输出主动错误标志。
(2) 被动错误状态
被动错误状态是易引起错误的状态。 处于被动错误状态的单元虽能参加总线通信,但为不妨碍其它单元通信,接收时不能积极地发送错误通知。 处于被动错误状态的单元即使检测出错误,而其它处于主动错误状态的单元如果没发现错误,整个总线也被认为是没有错误的。 处于被动错误状态的单元检测出错误时,输出被动错误标志。 另外,处于被动错误状态的单元在发送结束后不能马上再次开始发送。在开始下次发送前,在间隔帧期间内必须插入 “延迟传送”(8 个位的隐性位)。
(3) 总线关闭态 busoff
总线关闭态是不能参加总线上通信的状态。 信息的接收和发送均被禁止。 这些状态依靠发送错误计数和接收错误计数来管理,根据计数值决定进入何种状态。
7.2错误计数:


TEC是发送错误计数器,设备每发送一个错误这个TEC就会增加一次(注意不是加1),当然每进行一次正常的发送后TEC也会减小一次。REC是接收错误计数器,设备在接收时每发现一个错误这个REC就会增加一次,同样进行正常接收后REC也会减小一次。所以TEC和REC的值反映了当前设备检测错误的相对频率,如果正常传输没几次错误检测到了一堆,那TEC或REC肯定会迅速增加,如果正常传输的次数很多只是偶尔报告了一些错误,那么TEC或REC增加后肯定会迅速减回来。
初始状态是主动错误状态,主动错误状态是比较可靠的状态,如果TEC>127或者REC>127,只要发送或接收有一个不正常就是错误报告相对与正常传输太频繁了,那就把设备转入到被动错误状态,被动错误状态表示设备不太可靠,当然如果在被动错误状态设备又变好了进行了很多正常数据传输,那么TEC或REC就会减小,减小到TEC<=127并且REC<=127后,设备重新回到主动错误状态。但是如果设备在被动错误状态仍然不断报告错误,那么就说明这个设备可能出问题了,所以在TEC>255时,会直接把设备转为总线关闭状态,跟REC没关系。如果设备处于关闭状态,就不能进行通信,但是考虑到这个设备对系统可能有很重要的作用,所以处于总线关闭状态的设备还有一条生路,就是在执行上检测到128次连续11个位的隐形位,总线关闭状态的设备就会直接回到主动错误状态满血复活,TEC和REC都会清零。
7.3错误种类:

概 括:
主动错误状态、被动错误状态,是 CAN 控制器的内部状态,由 TEC(发送错误计数器)、REC(接收错误计数器)数值决定,不是由发生了什么错误种类决定。
错误种类(位错误、CRC 错误、填充错误、格式错误、应答错误)只是让 TEC/REC 计数增减;错误种类本身不切换节点状态。
只要 CAN 控制器检测到总线错误,立刻中止当前正在接收 / 发送的帧,马上发送错误帧;这个错误帧的数据是控制器根据状态自动生成发送的,和软件无关。
对于错误帧的采集,一般低端CAN盒子采集不了,高端CanOE、Vspy工具需要打开错误帧展示选项才能采集。
8.优先级定义:
若多个设备的发送需求同时到来或因等待而同时到来,则CAN总线协议会根据ID号(仲裁段)进行非破坏性仲裁,ID号小的(优先级高)取到总线控制权,ID号大的(优先级低)仲裁失利后将转入接收状态,等待下一次总线空闲时再尝试发送
实现非破坏性仲裁需要两个要求:
1.线与特性:总线上任何一个设备发送显性电平0时,总线就会呈现显性电平0状态,只有当所有设备都发送隐性电平1时,总线才呈现隐性电平1状态,即:0 & X & X = 0,1 & 1 & 1 = 1
2.回读机制:每个设备发出一个数据位后,都会读回总线当前的电平状态,以确认自己发出的电平是否被真实地发送出去了,根据线与特性,发出0读回必然是0,发出1读回不一定是1
回读机制目的是确认自己发出的电平是否真实地发送出去了。因为根据线与特性,我发送1,如果要别的设备也在发送数据,别的设备发送了0,那么我读回的数据就不再是1了,因为我的数据1被别的设备的数据0损坏了,这个现象对仲裁就很重要,我发出1但是读回的是0,这说明总线上有别的设备,我感知到了别的设备和我的冲突,所以这时候我就退出不再跟别的设备抢总线资源。

9.位填充:
防止突发错误而设定的功能。如:
- 增加波形的定时信息,利于接收方执行“再同步”,防止波形长时间无变化,导致接收方不能精确掌握数据采样时机
- 保持CAN总线在发送正常数据流时的活跃状态,防止被误认为总线空闲
- 时钟再同步:强制制造电平跳变沿,接收节点利用跳变沿修正采样点,抵消晶振偏差。
- 错误检测(填充错误 Stuff Error) 在填充生效区域,如果收到连续 6 个相同 bit → 判定位填充错误,节点发出错误帧,终止当前报文传输CSDN博...。
主动错误标志就是 6 个连续显性 0,正好利用这条规则被所有节点识别。
规则:
(1) 发送单元的工作 在发送数据帧和遥控帧时,SOF~CRC 段间的数据,相同电平如果持续 5 位,在下一个位(第 6 个位)则要插入 1 位与前 5 位反型的电平。
(2) 接收单元的工作 在接收数据帧和遥控帧时,SOF~CRC 段间的数据,相同电平如果持续 5 位,需要删除下一个位(第 6 个位)再接收。如果这个第 6 个位的电平与前 5 位相同,将被视为错误并发送错误帧。
✅ 需要位填充:SOF 帧起始、仲裁段、控制段、数据段、CRC 序列
❌ 不做位填充:CRC 界定符、ACK 段、EOF 帧结束、错误帧、过载帧
EOF 固定 7 个连续隐性 1,这个区域禁止位填充,用来标志帧结束。
- 位填充是CAN 控制器硬件自动完成,软件完全感知不到;总线抓包看到的报文长度,会比理论长度更长(多出若干填充位)。
- 最坏情况:每 5bit 插入 1 填充位,带宽最大增加 20% 负载;大数据报文,位填充会拉高总线实际占用时间,负载率计算要考虑。
-
CAN‑FD:SOF~ 数据段和经典 CAN 规则一样;CRC 域使用固定位置填充,不是 5bit 动态填充
10.位时序:
10.1采样

挂载在CAN总线上的所有设备初始都默认为接收方,当某一个设备想要广播自己的数据时,它就会主动出击变为发送方,拉开或释放总线产生一段波形。那这个发送方产生波形后其他所有接收方该如何准确地采样得到每一个数据位是1还是0呢?
1.CAN总线没有时钟线,总线上的所有设备通过约定波特率的方式确定每一个数据位的时长
2.发送方以约定的位时长每隔固定时间输出一个数据位
3.接收方以约定的位时长每隔固定时间采样总线的电平,输入一个数据位
4.理想状态下,接收方能依次采样到发送方发出的每个数据位,且采样点位于数据位中心附近
采样点位于PBS1结束处,随着时钟误差,采样点逐步偏离。
为了灵活调整每个采样点的位置,使采样点对齐数据位中心附近,CAN总线对每一个数据位的时长进行了更细的划分,分为同步段(SS)、传播时间段(PTS)、相位缓冲段1(PBS1)和相位缓冲段2(PBS2),每个段又由若干个最小时间单位(Tq)构成

Tq是程序中自己指定的,比如可以确定一个Tq=0.5us,之后一位包含的四个段,SS段固定为1Tq,PTS段可以由自己指定配置为1~8Tq,PBS1段也是自己指定配置为1~8Tq,PBS2段自己指定配置为2~8Tq,除了SS段固定为1Tq其他的可以由自己在一个范围内指定。指定好之后在一位中SS、PTS、PBS1、PBS2就会依次排列。SS段意为同步段,如果数据跳变沿正好出现在SS段那就说明当前设备与波形达成同步,如果数据跳变沿不在SS段那就要调整当前设备的位时序使跳变沿正好出现在同步段。PTS意为传播时间段,瑞萨手册里是这样说的:PTS用于吸收网络上的物理延迟,网络的物理延迟指发送单元的输出延迟、总线上信号的传播延迟、接收单元的输入延迟。PTS的时间为以上延迟时间的和的两倍。之后两个PBS1和PBS2意为相位缓冲段,作用就是确定采样点的位置,采样点会在PBS1和PBS2中间。

10.2硬件同步
硬同步也可以叫硬件同步,解决的问题是接收方第一个采样点与波形的第一位对齐。
1.每个设备都有一个位时序计时周期,当某个设备(发送方)率先发送报文,其他所有设备(接收方)收到SOF的下降沿时,接收方会将自己的位时序计时周期拨到SS段的位置,与发送方的位时序计时周期保持同步
2.硬同步只在帧的第一个下降沿(SOF下降沿)有效
3.经过硬同步后,若发送方和接收方的时钟没有误差,则后续所有数据位的采样点必然都会对齐数据位中心附近

如上图图,红线画的是总线上的波形空闲隐形电平,当红线由逻辑高电平跳变到逻辑低电平限行时,这个下降沿是发送方产生SOF的第一个下降沿,下降沿时刻肯定是发送方的SS段,那接收方一看这个下降沿并不在我的SS段里,所以接收方就直接把自己的SS段与波形下降沿对齐,也就是变成下面这个图,接收方SS段与波形下降沿重合,就能有效采样接收。
10.3再同步:
看上面的波形,这个位时序是接收方的,蓝色的线表示总线上发送方发出的波形,比如这个波形之前执行了硬同步,之后传了很多位,到这一位时可以看到当前数据跳变沿出现在了SS段之后,也就是接收方的秒表跑的比发送方快,接收方认为在SS部分就应该出现边沿但实际出现的晚了。所以接收方会执行再同步,在其PBS1段之后额外加上SJW指定的补偿宽度。比如下面那个波形,SJW配置为2(误差也是2Tq),所以PBS1延长2Tq,这样下一次接收方SS段就会往后挪,新的波形跳变沿就会往SS段的方向调整。
接下来是PBS2提前2Tq,不管是在哪个段,目的是将接收方采样和发送方同步,即对齐SS段。
10.4.硬件同步和再同步遵从如下规则:
(1) 1 个位中只进行一次同步调整。
(2) 只有当上次采样点的总线值和边沿后的总线值不同时,该边沿才能用于调整同步。
(3) 在总线空闲且存在隐性电平到显性电平的边沿时,则一定要进行硬件同步。
(4) 在总线非空闲时检测到的隐性电平到显性电平的边沿如果满足条件(1)和(2),将进行再同步。但还要满足下面条件。
(5) 发送单元观测到自身输出的显性电平有延迟时不进行再同步。
(6) 发送单元在帧起始到仲裁段有多个单元同时发送的情况下,对延迟边沿不进行再同步。
11.波特、采样率计算
一般芯片手册会提供Tq计算公式,可计算Tq时间,如S32K1xx芯片提供如下公式:
波特率 = 1 / 一个数据位的时长 = 1 / (TSS + TPTS + TPBS1 + TPBS2)
例如:
SS = 1Tq,PTS = 3Tq,PBS1 = 3Tq,PBS2 = 3Tq
Tq = 0.5us
波特率 = 1 / (0.5us + 1.5us + 1.5us + 1.5us) = 200kbps
由于采样点位于PBS1结束处,所以:
采样率= (TSS + TPTS + TPBS1) / (TSS + TPTS + TPBS1 + TPBS2)
二、CAN协议:
1.物理上7层协议:重点关注ISO11898数据链路层

数据链路层分为 MAC 子层和 LLC 子层,MAC 子层是 CAN 协议的核心部分。数据链路层的功能是将物理层收到的信号组织成有意义的消息,并提供传送错误控制等传输控制的流程。具体地说,就是消息的帧化、仲裁、应答、错误的检测或报告。数据链路层的功能通常在 CAN 控制器的硬件中执行。
在物理层定义了信号实际的发送方式、位时序、位的编码方式及同步的步骤。但具体地说,信号电平、通信速度、采样点、驱动器和总线的电气特性、连接器的形态等均未定义⁽¹⁾。这些必须由用户根据系统需求自行确定。


2.数据传输帧类型:
本文只针对数据帧、错误帧开展讲解。
1.数据帧:
数据帧由 7 个段构成。 数据帧的构成

(1) 帧起始 表示数据帧开始的段。
(2) 仲裁段 表示该帧优先级的段。
(3) 控制段 表示数据的字节数及保留位的段。
(4) 数据段 数据的内容,可发送 0~8 个字节的数据。
(5) CRC 段 检查帧的传输错误的段。
(6) ACK 段 表示确认正常接收的段。
(7) 帧结束 表示数据帧结束的段。
下面对帧的构成进行说明:
在发送数据帧之前总线必须处于空闲状态,空闲状态时总线是隐形电平(1),随后数据帧开始,数据帧第一位表示是显性电平0,它的意思是SOF,帧起始。与串口波形的起始位一样帧起始的作用是“打破宁静”,因为空闲是隐形1所有设备都不去碰总线,那你要开始发数据帧第一位就必须得张开总线发送显性0。
一位帧起始之后,首先发送的是报文ID,标准格式是11位,报文ID可以表示后面数据的功能,因为总线上各种报文信息都有,如果没有ID加以区分那肯定就搞混了。同时报文ID还用于区分优先级,当多个设备同时发送时,根据仲裁规则ID小的报文优先发送,ID大的报文等待下一次总线空闲再重新发送,不同功能的数据帧其ID都不同。
报文ID后紧跟着的是RTR,占据1位,在数据帧里必须为显性0,RTR的意思是远程请求标志位,用于区分数据帧还是遥控帧,数据帧必须为显性0,遥控帧必须为隐形1。报文ID+RTR位可以称为仲裁段,仲裁主要靠ID来实现,RTR也加入进来的目的是相同ID的数据帧和遥控帧,数据帧的优先级大于遥控帧。
之后进入控制段,控制段首先是IDE,意思是ID拓展标志位,用于区分标准格式还是拓展格式,标准格式固定为显性0,拓展格式固定为隐形1。
之后下一位是r0,必须为显性0,r0意思是保留位,目前还没有用到。
后面四位是DLC,意思是表示数据段的长度,CAN总线一帧数据可以有1~8个字节有效载荷,并且可以灵活指定,就靠这个DLC指定,如果想发一个字节DLC就给0001,如果想发8个字节,DLC就给1000,DLC要配合后面数据段使用。
数据段就是有效载荷的数据了,根据DLC的指定,数据段可以有多个字节,DLC指定几个字节数据段就发几个字节。数据段长度是0~64位,最大64位即8个字节,这里位数是8的倍数。
数据段之后是CRC段,有15位,CRC是高效的校验算法。从SOF到Data这些数据位计算得到的校验码附在这后面,接收方收到数据和校验码也会调用CRC算法进行计算。
CRC 顺序是根据多项式生成的 CRC 值,CRC 的计算范围包括帧起始、仲裁段、控制段、数据段。 接收方以同样的算法计算 CRC 值并进行比较,不一致时会通报错误。
下一个是CRC界定符,1位,必须是隐形电平.
**1 发送单元的 ACK 段 发送单元在 ACK 段发送 2 个位的隐性位。
**2 接收单元的 ACK 段 接收到正确消息的单元在 ACK 槽 (ACK Slot) 发送显性位,通知发送单元正常接收结束。这称作 “发送 ACK” 或者 “返回 ACK”。
之后就是ACK段,里面有ACK槽和ACK界定符,都是1位,ACK槽的作用就是应答,发送方发送一帧数据到底有没有设备接收到呢就靠ACK位来实现,这里的应答设计和I2C的应答异曲同工,它的基本思想就是当发送方发完一帧数据的主要内容后在应答这一位时发送方释放总线,总线回归默认状态隐形1,如果接收方收到数据了它就会在ACK槽这一位主动出击把总线再拉开时总线呈现显性0的状态,发送方释放总线后在ACK槽会读取总线状态,如果发送方读取为显性0那就是数据被接收了,如果发送方读取为隐形1就说明发送失败发送方可以配置自动重发。可以看到在ACK槽这一位操作总线的权力是有一个短暂的交接的,前面所有波形都只有发送方有权操作总线,在ACK槽这一位变为接收方操作总线,为了给权力交接流出时间,ACK槽前后就要留两个界定符,在CRC界定符时发送方必须发隐形1,除了做一个分隔另一个作用就是在ACK槽之前发送方必须释放总线,发送隐形1就是释放总线,之后在ACK槽的时间点接收方会拉开总线,ACK槽接收后接收方不能一直拉着不放所以在ACK界定符时接收方必须及时释放总线交出控制权。
这里有两个注意事项,
第一个是ACK槽时可以允许多个接收方共同拉开总线,因为一个报文消息可以被多个设备同时接收,所以多个设备可以在这里同时拉开,多个设备同时操作总线输出显性0是没问题的。
第二个是,我没要知道并不是发送方把一段波形完整发出去然后在接收应答的,而是发送方和接收方共同完成一整个波形,发送方每发出一位,接收方就立刻接收到这一位了,所以在这条时序的最后整个数据帧还没结束接收方其实已经收完了,也就是应答位夹在发送过程之中。
最后应答位结束发送方再发7个隐形1,作为EOF帧结束,与串口波形里的停止位是一个意思。
任何设备检测到连续11个隐性电平,即认为总线空闲。
SOF(Start of Frame):帧起始,表示后面一段波形为传输的数据位
ID(Identify):标识符,区分功能,同时决定优先级
RTR(Remote Transmission Request ):远程请求位,区分数据帧和遥控帧
IDE(Identifier Extension):扩展标志位,区分标准格式和扩展格式
SRR(Substitute Remote Request):替代RTR,协议升级时留下的无意义位
r0/r1(Reserve):保留位,为后续协议升级留下空间
DLC(Data Length Code):数据长度,指示数据段有几个字节
Data:数据段的1~8个字节有效数据
CRC(Cyclic Redundancy Check):循环冗余校验,校验数据是否正确
ACK(Acknowledgement):应答位,判断数据有没有被接收方接收
CRC/ACK界定符:为应答位前后发送方和接收方释放总线留下时间
EOF(End of Frame ):帧结束,表示数据位已经传输完毕
2.错误帧:
用于在接收和发送消息时检测出错误通知错误的帧。错误帧由错误标志和错误界定符构成。错误帧的构成如图 25 所示。

(1) 错误标志 错误标志包括主动错误标志和被动错误标志两种。
— 主动错误标志:6 个位的显性位。
— 被动错误标志:6 个位的隐性位。
(2) 错误界定符 错误界定符由 8 个位的隐性位构成。
*1 主动错误标志 处于主动错误状态的单元检测出错误时输出的错误标志。
*2 被动错误标志 处于被动错误状态的单元检测出错误时输出的错误标志。
三、CAN通讯实现过程:
基于NXP S32K144芯片
关于CAN、CANFD异同解释:
CAN、CANFD都是使用在CAN总线上,只是物理协议有所差异,所配置寄存器,需要根据各自的协议配置,参考标准数据帧格式
CAN、CANFD总体配置几乎都一样,选择经典CAN细说配置。由于UDS诊断,采用14229协议,在处理多帧的时候,会有流控帧控制、帧间隔、单帧、首帧、连续帧等,CANFD一下就传送很多字节,反而不太实用。
1.初始化:
void can_init(void)
{
#define MSG_BUF_SIZE 4 /* Msg Buffer Size. (CAN 2.0AB: 2 hdr + 2 data= 4 words) */
uint32_t i=0;
PCC->PCCn[PCC_FlexCAN0_INDEX] |= PCC_PCCn_CGC_MASK; /* CGC=1: enable clock to FlexCAN0 */
CAN0->MCR |= CAN_MCR_MDIS_MASK; /* MDIS=1: Disable module before selecting clock */
CAN0->CTRL1 &= ~CAN_CTRL1_CLKSRC_MASK; /* CLKSRC=0: Clock Source = oscillator (8 MHz) */
CAN0->MCR &= ~CAN_MCR_MDIS_MASK; /* MDIS=0; Enable module config. (Sets FRZ, HALT)*/
while (!((CAN0->MCR & CAN_MCR_FRZACK_MASK) >> CAN_MCR_FRZACK_SHIFT)) {}
/* Good practice: wait for FRZACK=1 on freeze mode entry/exit */
CAN0->CTRL1 = 0x00DBC006; /* Configure for 500 KHz bit time */
/* Time quanta freq = 16 time quanta x 500 KHz bit time= 8MHz */
/* PRESDIV+1 = Fclksrc/Ftq = 8 MHz/8 MHz = 1 */
/* so PRESDIV = 0 */
/* PSEG2 = Phase_Seg2 - 1 = 4 - 1 = 3 */
/* PSEG1 = PSEG2 = 3 */
/* PROPSEG= Prop_Seg - 1 = 7 - 1 = 6 */
/* RJW: since Phase_Seg2 >=4, RJW+1=4 so RJW=3. */
/* SMP = 1: use 3 bits per CAN sample */
/* CLKSRC=0 (unchanged): Fcanclk= Fosc= 8 MHz */
/* BOFFMASK/ERRMASK enable*/
for(i=0; i<128; i++ ) { /* CAN0: clear 32 msg bufs x 4 words/msg buf = 128 words*/
CAN0->RAMn[i] = 0; /* Clear msg buf word */
}
for(i=0; i<16; i++ ) { /* In FRZ mode, init CAN0 16 msg buf filters */
CAN0->RXIMR[i] = 0xFFFFFFFF; /* Check all ID bits for incoming messages */
}
// CAN0->RXMGMASK = 0x1FFFFFFF; /* Global acceptance mask: check all ID bits */
// CAN0->RAMn[ 4*MSG_BUF_SIZE + 0] = 0x04000000; /* Msg Buf 4, word 0: Enable for reception */
// /* EDL,BRS,ESI=0: CANFD not used */
// /* CODE=4: MB set to RX inactive */
// /* IDE=0: Standard ID */
// /* SRR, RTR, TIME STAMP = 0: not applicable */
//
// CAN0->RAMn[ 4*MSG_BUF_SIZE + 1] = 0x15540000; // Msg Buf 4 to receive msg, word 1: STD ID 0x555 //ID=bit28~18,so 0x1554>>2=0x555
// /* PRIO = 0: CANFD not used */
CAN0->MCR = 0x0000001F; /* Negate FlexCAN 1 halt state for 32 MBs */
//
// CAN0->IMASK1 = 0x00000010; // MB4 interrupt enable
while ((CAN0->MCR && CAN_MCR_FRZACK_MASK) >> CAN_MCR_FRZACK_SHIFT) {}
/* Good practice: wait for FRZACK to clear (not in freeze mode) */
while ((CAN0->MCR && CAN_MCR_NOTRDY_MASK) >> CAN_MCR_NOTRDY_SHIFT) {}
/* Good practice: wait for NOTRDY to clear (module ready) */
// /* Enable FlexCAN interrupts.*/
S32_NVIC->ISER[(uint32_t)(80) >> 5U] = (uint32_t)(1UL << ((uint32_t)(80) & (uint32_t)0x1FU)); // wake_up
S32_NVIC->ISER[(uint32_t)(79) >> 5U] = (uint32_t)(1UL << ((uint32_t)(79) & (uint32_t)0x1FU)); // err
S32_NVIC->ISER[(uint32_t)(78) >> 5U] = (uint32_t)(1UL << ((uint32_t)(78) & (uint32_t)0x1FU)); // off irq
S32_NVIC->ISER[(uint32_t)(81) >> 5U] = (uint32_t)(1UL << ((uint32_t)(81) & (uint32_t)0x1FU)); // BM0~15 irq
S32_NVIC->ISER[(uint32_t)(82) >> 5U] = (uint32_t)(1UL << ((uint32_t)(82) & (uint32_t)0x1FU)); // MB16~31 irq
/* Save runtime structure pointers so irq handler can point to the correct state structure */
g_flexcanStatePtr[0] = &canCom0_State;
cfg_rx_mb(RX_MAILBOX_0);
canid_mask();
can_start_recv();
}
2.T/Rx Mb配置:
#define FLEXCAN_RX_NOT_USED 0xf
#define FLEXCAN_RX_INACTIVE 0
#define FLEXCAN_RX_EMPTY 0x4
void set_rx_mb_buf(uint32_t mb_id, uint32_t code)
{
/* Clean up the arbitration field area */
CAN0->RAMn[mb_id * 4] = 0;
CAN0->RAMn[mb_id * 4 + 1] = 0;
/* Make sure IDE and SRR are not set */
CAN0->RAMn[mb_id * 4] &= ~(0x200000u | 0x400000u);
/* ID[28-18] */
CAN0->RAMn[mb_id * 4 + 1] &= ~0x1FFC0000u;
CAN0->RAMn[mb_id * 4 + 1] |= (0x777 << 18) & 0x1FFC0000u;
/* Set MB CODE */
if (code != (uint32_t)0xf)
{
CAN0->RAMn[mb_id * 4] &= ~0xF000000u;
CAN0->RAMn[mb_id * 4] |= (code << 24) & 0xF000000u;
}
}
void cfg_rx_mb(uint32_t mb_id)
{
uint32_t code;
/* Initialize rx mb*/
code = FLEXCAN_RX_NOT_USED;
set_rx_mb_buf(mb_id, code);
/* Initialize receive MB*/
code = FLEXCAN_RX_INACTIVE;
set_rx_mb_buf(mb_id, code);
/* Set up FlexCAN message buffer fields for receiving data*/
code = FLEXCAN_RX_EMPTY;
set_rx_mb_buf(mb_id, code);
}
3..数据接受处理:
void can_start_recv(void)
{
g_flexcanStatePtr[0]->mbs[RX_MAILBOX_0].state = FLEXCAN_MB_RX_BUSY;
g_flexcanStatePtr[0]->mbs[RX_MAILBOX_0].mb_message = &recvMsg0;
/* Enable MB interrupt*/
/* Enable the corresponding message buffer Interrupt */
(CAN0->IMASK1) = ((CAN0 ->IMASK1) | (1UL << RX_MAILBOX_0));
}
can_data_queue can_datas; // can接受的数据
void en_quene(uint32_t can_id, uint8_t *can_data, uint32_t data_len)
{
int i;
// 判断满栈丢帧情况
// TODO:
if (8 == data_len)
{
can_datas.data_queue[can_datas.front].can_id = can_id;
for (i = 0; i < data_len; i++)
{
can_datas.data_queue[can_datas.front].frame_datas[i] = can_data[i];
}
can_datas.front = (can_datas.front + 1) % CAN_BUFF_SIZE;
if(can_datas.quene_size < CAN_BUFF_SIZE)
{
can_datas.quene_size ++;
}
}
}
void CAN0_ORed_0_15_MB_IRQHandler(void)
{
/* Get the interrupts that are enabled and ready */
// uint32_t mb_idx = 0; // mb of interrupting
uint32_t rx_irq_flag = 0; // flag of interrupting mb
uint32_t tx_irq_flag = 0; // flag of interrupting mb
uint8_t i;
uint32_t MsgID;
uint32_t DataLen,data[2];
// INT_SYS_DisableIRQ(CAN0_ORed_0_15_MB_IRQn);
uint32_t *flexcan_mb_data_32 = &CAN0->RAMn[RX_MAILBOX_0 * 4 + 2];
// uint32_t *data = (uint32_t *)g_flexcanStatePtr[0]->mbs[RX_MAILBOX_0].mb_message->data;
rx_irq_flag = (uint8_t)(((CAN0->IFLAG1 & (CAN0->IMASK1 & 0xFFFFFFFFu)) >> (RX_MAILBOX_0 % 32U)) & 1U);
tx_irq_flag = (uint8_t)(((CAN0->IFLAG1 & (CAN0->IMASK1 & 0xFFFFFFFFu)) >> (TX_MAILBOX_0 % 32U)) & 1U);
/* Check Tx/Rx interrupt flag and clear the interrupt */
// if(CAN0->IFLAG1 >> RX_MAILBOX_0)
if (rx_irq_flag)
{
/* Check mailbox completed reception */
if (g_flexcanStatePtr[0]->mbs[RX_MAILBOX_0].state == FLEXCAN_MB_RX_BUSY)
{
// /* Lock RX message buffer and RX FIFO and Get DataLen */
DataLen= (uint8_t)(((CAN0->RAMn[RX_MAILBOX_0 * 4]) & 0xF0000) >> 16);
/* Get RX MB field values*/
// CAN ID
MsgID = CAN0->RAMn[RX_MAILBOX_0 * 4 + 1] >> 18;
// /* Copy MB data field into user's buffer */
for (i = 0; i < 8; i += 4U)
{
FlexcanSwapBytesInWord(flexcan_mb_data_32[i >> 2U], data[i >> 2U]);
}
/* Unlock RX message buffer and RX FIFO*/
CAN0->TIMER;
en_quene(MsgID, (uint8_t *)data, DataLen);
// /* Complete receive data */
/* disable the corresponding message buffer Interrupt */
(CAN0->IMASK1) = ((CAN0 ->IMASK1) & ~(1UL << RX_MAILBOX_0));
g_flexcanStatePtr[0]->mbs[RX_MAILBOX_0].state = FLEXCAN_MB_IDLE;
/* Clear the corresponding message buffer interrupt flag*/
(CAN0->IFLAG1) = (uint32_t)1U << RX_MAILBOX_0;
/* open irq next frame */
can_start_recv();
}
}
// INT_SYS_EnableIRQ(CAN0_ORed_0_15_MB_IRQn);
}
uint32_t FLEXCAN0_receive_msg (uint8_t *data)
{
int i;
uint32_t canid = 0xFFFFFFFF;
if(can_datas.quene_size > 0)
{
canid = can_datas.data_queue[can_datas.rear].can_id;
for (i = 0; i < 8; i++)
{
data[i] = can_datas.data_queue[can_datas.rear].frame_datas[i];
}
can_datas.rear = (can_datas.rear + 1) % CAN_BUFF_SIZE;
can_datas.quene_size --;
}
return canid;
}
4.数据发送处理:
#define FlexcanSwapBytesInWord(a, b) (b = ((a & 0xFF000000U) >> 24U) | ((a & 0xFF0000U) >> 8U) | \
((a & 0xFF00U) << 8U) | ((a & 0xFFU) << 24U))
void can_send(const uint8_t MB_Num,uint8_t *indata, uint32_t canid)
{
unsigned char databyte;
uint32_t flexcan_mb_config = 0;
uint32_t *flexcan_mb_data_32 = &CAN0->RAMn[MB_Num * 4 + 2];
uint32_t *data = (uint32_t *)indata;
g_flexcanStatePtr[0]->mbs[MB_Num].state = FLEXCAN_MB_TX_BUSY;
/* Copy user's buffer into the message buffer data area */
for (databyte = 0; databyte < 8; databyte += 4U)
{
FlexcanSwapBytesInWord(data[databyte >> 2U], flexcan_mb_data_32[databyte >> 2U]);
}
/* Clean up the arbitration field area */
CAN0->RAMn[MB_Num * 4] = 0;
CAN0->RAMn[MB_Num * 4 + 1] = 0;
/* Set the ID according the format structure */
/* ID[28-18] */
CAN0->RAMn[MB_Num * 4 + 1] &= ~0x1FFC0000u;
CAN0->RAMn[MB_Num * 4 + 1] |= (canid << 18) & 0x1FFC0000u;
/* make sure IDE and SRR are not set */
flexcan_mb_config &= ~(0x200000u | 0x400000u);
/* Set the length of data in bytes */
flexcan_mb_config &= ~0xF0000u;
flexcan_mb_config |= (8 << 16) & 0xF0000u;
/* Set MB CODE */
/* Reset the code */
flexcan_mb_config &= ~0xF000000u;
/* Set the code */
flexcan_mb_config |= (0x0C << 24) & 0xF000000u;
CAN0->RAMn[MB_Num * 4] |= flexcan_mb_config;
/* Enable MB interrupt*/
/* Enable the corresponding message buffer Interrupt */
// (CAN0->IMASK1) = ((CAN0 ->IMASK1) | (1UL << TX_MAILBOX_0));
}
5.CAN错误、busoff异常:
一般设定异常把CAN高、低线短接即可。为何,高速纵向上,两端各接一个120Ω电阻,产生阻抗,电信号可以顺利进入其他节点;短路,阻抗≈0Ω
高低两线回路等于一根线,两线电压一致,造成显隐性难以区分,产生错误进入err中断,CAN分析仪,检测到ERR,会发送错误帧停止收发。
若想产生busoff,这时只需,代码不断向总线发送CAN帧即可,TEC计数快速累加超过256.
/* Implementation of CAN0 IRQ handler for OR'ed interrupts (Bus Off,
Transmit Warning, Receive Warning). */
void CAN0_ORed_IRQHandler(void)
{
CAN0->ESR1 |= (1 << 2);
}
/* Implementation of CAN0 IRQ handler for interrupts indicating that errors were
detected on the CAN bus. */
void CAN0_Error_IRQHandler(void)
{
CAN0->ESR1 |= (1 << 1);
}
6.接受CANID过滤:
注意:要独立设置掩码,需要先设定MCR下IRMQ,使能CANID过滤独立掩码。
硬件内部:把总线上收到的ID和MB存储的ID,按RXIMR掩码逐位做按位比较,条件成立,报文才存入该 MB,产生接收中断
`RXIMRx.bit = 1`:该位必须相等,参与比较
`RXIMRx.bit = 0`:该位忽略,不比较,不管总线上这一位是 0 还是 1 都算匹配
> 举例子:
- RXIMR = 0x7FF(STD11bit 全部置 1):总线 ID 必须完全等于 MB.ID,才匹配;白名单精准过滤。
- RXIMR = 0x7F0:bit3~bit0 为 0,低 4 位忽略;只要高 7 位和 MB.ID 高 7 位相等就匹配,实现 ID 范围过滤。
若MB中ID配置为是0x7F0,RXIMR对应MB ID配置0x7FF,总线只接受0x7FF;若此时RXIMR对应MB ID配置0x7F0,总线接收0x7F0~0x7FF.
void canid_mask(void)
{
CAN0->MCR = (CAN0->MCR & ~CAN_MCR_FRZ_MASK) | CAN_MCR_FRZ(1U);
CAN0->MCR = (CAN0->MCR & ~CAN_MCR_HALT_MASK) | CAN_MCR_HALT(1U);
/* Wait for entering the freeze mode */
while (((CAN0->MCR & CAN_MCR_FRZACK_MASK) >> CAN_MCR_FRZACK_SHIFT) == 0U) {}
/* Enable Individual Rx Masking and Queue */
CAN0->MCR = (CAN0->MCR & ~CAN_MCR_IRMQ_MASK) | CAN_MCR_IRMQ(1U);
(CAN0->RXIMR[RX_MAILBOX_0]) = (0x700 << 18) & 0x1FFC0000u;
CAN0->MCR = (CAN0->MCR & ~CAN_MCR_HALT_MASK) | CAN_MCR_HALT(0U);
CAN0->MCR = (CAN0->MCR & ~CAN_MCR_FRZ_MASK) | CAN_MCR_FRZ(0U);
/* Wait till exit freeze mode */
while (((CAN0->MCR & CAN_MCR_FRZACK_MASK) >> CAN_MCR_FRZACK_SHIFT) != 0U) {}
}
更多推荐

所有评论(0)