Crc校验在CAN报文中的实际应用介绍:
Crc在报文传输过程中的实际应用如下(在汽车中,Crc一般是对8个字节进行校验,目前我接触到的是这样):
ECU-A和ECU-B之间进行CAN报文的传输,双方规定ECU-A发出的0x123报文为64个字节,该报文中的Byte1-Byte7要进行Crc校验,校验结果放到Byte0中,Alivecounter为Byte7的低四位(范围为0-14)。其余的Byte8-Byte64不进行校验。校验范围如下图所示
因此,ECU-A往CAN总线发出这帧报文之前,要对每帧报文的Alivecounter按照0-14进行不断自增、循环。然后进行Crc校验并把校验结果存放到这帧报文的Byte0中,最后通过CAN总线发出这帧报文数据。
ECU-B接收到这帧报文后,由于Byte8-Byte64不在校验范围内,因此接收到这帧报文后这个范围内的数据可以直接使用。但Byte0-Byte7为校验范围,因此ECU-B按照双方规定好的Crc算法对Byte1-Byte7进行校验,校验结果跟接收到的Byte0进行对比,若对比结果一致,则使用这帧报文的Byte1-Byte7的数据,否则丢弃这些数据。
这就是Crc算法在CAN报文中的具体使用。
E2E和Crc的区别
接下来就是Autosar的E2E(End-to-End,可以理解为数据从一个ECU传到另一个ECU)。
刚开始接触的时候,我总没搞明白所谓的E2E和CRC到底有啥区别,因为在开发过程中,只关心两件事:Checksum计算是否正确、Counter的值是否按照要求进行递增并循环。
后来在不断的学习和深入开发的才知道:
Crc仅仅是一个校验算法
E2E是Autosar官方定义的标准,它包含了Crc算法、要求了Counter的值如何进行递增、并在Counter出错时会记录对应的错误状态等等。
讲完了上面这些东西,你应该对E2E有一个最简单的了解了。概况来说,其实就是校验。但Autosar标准定义的E2E并没有那么简单。下面,我用开发一帧接收报文的E2E功能慢慢来讲(发送的没啥讲的,就是发送前填充一下Checksum和Counter的值)。
需求:对接收到的长度为8个Byte的0x1AA报文开发E2E功能。
告诉你这个之后,还必须告诉你一些E2E的具体需求,没有这些你是无法开发的。(因为汽车上多个节点进行通讯,如果使用E2E功能的话,那么必须各个节点E2E需求保持一致,否则各个节点用的算法都不一样,这还校个啥。)
好了,E2E的具体需求释放给你了,如下:
算法:使用Autosar标准下的Profile01
Counter Offset(Bit):56
Checksum Offset(Bit):0
Data ID Mode:DataID Both
Data ID:0x01AB
Max Delta Counter Init: 2
Sync Counter Init: 1
Max No New Or Repeatd Data: 14
我们一个一个来解释这些都是些什么东西,有什么用。
Counter Offset(Bit):56,Checksum Offset(Bit):0。
对于这两个就很容易懂了,如下图,就是Checksum和Counter的位置在报文的什么地方
Data ID Mode
DataID的模式,共有四种模式可选:
E2E_P01_DATAID_BOTH
E2E_P01_DATAID_ALT
E2E_P01_DATAID_LOW
E2E_P01_DATAID_NIBBLE
那么,DataID是什么东西呢?
以E2E_P01_DATAID_BOTH为栗子。
首先,我们要知道,DATAID_BOTH,就是DataID为两个字节,且高字节叫做DataIDHigh,低字节为DataIDLow。
根据我们目前这个需求,DataID为0x01AB(即DataIDHigh = 0x01,DataIDLow=0xAB),E2E校验的范围为Byte1-Byte7(Byte0用来存计算结果Checksum的值)。
由于Byte1-Byte7共7个Byte,在校验的过程的中会把报文的Byte1-Byte7提取出来,放到一个新的数组里,新的数组长度为2+7=9(DataID长度+数据长度)。如数组的名字为Data[9]。则,Data[0] = DataIDLow(即0xAB),Data[1] = DataIDHigh(即0x01),Data[2]-Data[8]为报文的Byte1-Byte7。
最后把整理好的Data数据进行算法计算。
Max Delta Counter Init:
关于这个参数,官方解释如下:
简单来说就是说最大容忍Counter的间隔(,因为Counter是按1递增的,因此这参数即最大容忍丢失帧数)。正如它的例子说:如果MaxDeltaCounterInit配置为1,那么当在接收到上一帧的Counter为1的时候(下一帧若counter为2就不说了,因为Counter为2即没有丢帧),下一帧可以接收counter为3,但不能counter为4。因为1与3直接差了一帧(也就是丢失一帧了)。1与4直接已经差了2帧(也就是丢失两帧了),所以Counter为4不在容忍范围内了。
转换成直观的例子:
①如连续接收到的报文Counter分别为:1、3、4、5、6、7。那么Counter值为3时,虽然中间缺失了一帧(Counter值为2的帧),但在容忍范围内。
②如连续接收到的报文Counter分别为:1、8、9、10、11、12。那么Counter值为8时,中间缺失了不止一帧,因此不在容忍范围内。
Sync Counter Init:
官方解释如下:
关于这个参数,我们需要在后面讲E2E返回状态为E2E_P01STATUS_SYNC的时候才能反过来讲明白。
对于E2E参数,官方图形解释如下:
使用Vector工具配置报文E2E的各个栗子如下:
好了,其实讲到目前为止,你对这些参数应该还是明白的云里雾里。但要彻底明白这些参数是什么东西,有什么具体作用,需要知道接收到报文后进行E2E校验产出的东西------E2E状态
E2E状态:
接收到含E2E报文后,对E2E报文的检查结果就会有对应的E2E状态供应用层读取,应用层根据E2E状态决定接收到的数据是否使用,并根据E2E状态记录相关的故障,如Checksum校验错误故障等。
1
2
3
4
5
6
7
8
9
10typedef enum { E2E_P01STATUS_OK = 0x00, E2E_P01STATUS_NONEWDATA = 0x1, E2E_P01STATUS_WRONGCRC = 0x2, E2E_P01STATUS_SYNC = 0x03, E2E_P01STATUS_INITIAL = 0x4, E2E_P01STATUS_REPEATED = 0x8, E2E_P01STATUS_OKSOMELOST = 0x20, E2E_P01STATUS_WRONGSEQUENCE = 0x40 } E2E_P01CheckStatusType;
E2E_P01STATUS_WRONGCRC:
接收到的E2E报文Checksum验证失败。比如E2E算法、或DataID的值、或E2E校验的范围配置得跟需求不符合,各个节点计算没统一起来,都会导致Checksum验证失败。
E2E_P01STATUS_SYNC:
出现这个状态共有两种情况(假设MaxDeltaCounterInit配置为1,SyncCounterInit配置为2):
①初始化阶段。
初始化,即上电后从没有接收到该报文。假设现在上电后接收到如下连续报文Counter分别为:2、3、4、5、6。那么Counter为3、Counter为4的时候,就是同步阶段。E2E状态为:E2E_P01STATUS_SYNC。
②出现Counter值状态为E2E_P01STATUS_WRONGSEQUENCE之后。
即上一帧接收到Counter的值超过了MaxDeltaCounterInit。然后当前接收到Counter的值为正常。如接收到的连续报文的Counter值分别为:0、4、5、6、7、8。因此当Counter值为4时,E2E状态为E2E_P01STATUS_WRONGSEQUENCE。由于SyncCounterInit配置为2,E2E状态在Counter值为5、Counter值为6时,都为同步状态。E2E_P01STATUS_SYNC。
E2E_P01STATUS_INITIAL:
初始化阶段。即上电后就没接收到E2E报文。E2E就会持续保持初始化状态。
E2E_P01STATUS_REPEATED:
接收到的E2E报文Counter值重复。如接收到的连续报文的Counter值分别为:2、2、3、4、4、5。则,在接收到Counter值为第二个2时,E2E状态为E2E_P01STATUS_REPEATED。
E2E_P01STATUS_OKSOMELOST:
接收到的E2E报文连续两帧之间的Counter差值大于1(Counter差值为1时,即Counter正常,如:0、1、2、3、4),但小于MaxDeltaCounterInit。(简单来说,就是前后连续两帧报文的Counter值不是连续的,但在容忍范围内)。如MaxDeltaCounterInit配置为1,接收到的连续报文的Counter值分别为:0、2、4、5、6、7。则,在接收到Counter值位2时,E2E状态为E2E_P01STATUS_OKSOMELOST。
E2E_P01STATUS_WRONGSEQUENCE :
接收到的E2E报文连续两帧之间的Counter差值大于MaxDeltaCounterInit。如MaxDeltaCounterInit配置为1,接收到的连续报文的Counter值分别为:0、3、4、5、6、7。则,在接收到Counter值位3时,E2E状态为E2E_P01STATUS_WRONGSEQUENCE 。
E2E_P01STATUS_NONEWDATA:
一个报文周期内未接收到该E2E报文。
E2E_P01STATUS_OK:
接收到的E2E报文Checksum验证通过,且Counter验证正确。
另外还有下面的东西,应该不止。我下次再展开说(今晚太晚了,下次再修改):
DataIDNibbleOffset
官方定义如下
这个参数只有在DataID模式选择为“E2E_P01_DATAID_NIBBLE ”是会用到。目前我没用到,所以我也不知道这玩意干啥用的(看半天也没看懂-_-''')....
MaxNoNewOrRepeatedData
当接收到重复数据次数小于该配置参数,则接收端不需要执行数据同步处理
Autosar官方标准文档有如下所示报文接收检查流程图(太复杂了,大家随便看看就好了):
以下代码运行即可得到以Autosar官方标准的E2E_Profil01计算的结果(发送)。其中:
Checksum位置为Byte0
Counter位置为Byte7的低四位(即Bit56)
DataID模式为DATAID_BOTH且DataID=0x00AD。
注:以下是报文发送的例子。因为是发送,所以是没有E2E的状态处理的。
代码如下:
main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36#include <stdio.h> #include "typedef_datatype.h" #include "E2E_Config.h" uint16 DataID = 0x00AD;//DataID_Low = 0xAD DataID_High = 0x00 uint8 Test_Data_Array[8] = { 0x00, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x73, 0x20 }; //Byte0-Byte7 Test Data E2E_P01ConfigType E2E_Profile01_Test_Config = { 56, //CounterOffset 0, //CRCOffset 177, //DataID 0, //DataIDNibbleOffset E2E_P01_DATAID_BOTH, //DataIDMode 64, //DataLength 0, //MaxDeltaCounterInit 0, //MaxNoNewOrRepeatedData 0 //SyncCounterInit }; void main(void) { for (uint8 counter = 0; counter < 15; counter++) { Test_Data_Array[7] = (Test_Data_Array[7] & 0xF0) | counter; Test_Data_Array[0] = E2E_P01ComputeCRC(Test_Data_Array, &E2E_Profile01_Test_Config, 0xFF); for (uint8 i = 0; i < 8; i++) { printf("%x ", Test_Data_Array[i]); } printf("n"); } }
E2E_Config.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50#ifndef E2E_Config_H_ #define E2E_Config_H_ #include <stdio.h> #include "typedef_datatype.h" /* For CRC 8*/ #define CRC8_START_VALUE 0xFFU #define CRC8_XOR 0xFFU /* CRC 8 Configuration * Possible values and the mode decides what method to be used */ #define CRC_8_HARDWARE (0x01) /* Not supported */ #define CRC_8_RUNTIME (0x02) #define CRC_8_TABLE (0x04) /* Default value */ #define CRC_8_MODE CRC_8_RUNTIME /* E2E Profile01 */ #define CRC8_POLYNOMIAL 0x1DU typedef enum { E2E_P01_DATAID_BOTH = 0x0, E2E_P01_DATAID_ALT = 0x1, E2E_P01_DATAID_LOW = 0x2, E2E_P01_DATAID_NIBBLE = 0x3 } E2E_P01DataIDMode; typedef struct { uint16 CounterOffset; uint16 CRCOffset; uint16 DataID; uint16 DataIDNibbleOffset; E2E_P01DataIDMode DataIDMode; uint16 DataLength; uint8 MaxDeltaCounterInit; uint8 MaxNoNewOrRepeatedData; uint8 SyncCounterInit; } E2E_P01ConfigType; uint8 Crc_CalculateCRC8(const uint8* Crc_DataPtr, uint32 Crc_Length, uint8 Crc_StartValue8, boolean Crc_IsFirstCall); uint8 E2E_P01ComputeCRC(const uint8* Crc_DataPtr, const E2E_P01ConfigType* ConfigPtr, uint8 Counter); #endif
E2E.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138#include "E2E_Config.h" static uint8 CalculateCRC8(const uint8* message, uint32 nBytes, uint8 start, uint8 poly) { uint8 remainder = start; uint8 bit; uint8 topbit = 0x80; /* Perform modulo-2 division, a byte at a time. */ for (uint32 byte = 0; byte < nBytes; byte++) { /* Bring the next byte into the remainder. */ remainder ^= *message; message++; /* Perform modulo-2 division, a bit at a time. */ for (bit = 8; bit > 0; bit--) { /* Try to divide the current data bit. */ if ((remainder & topbit) != 0u) { remainder = (uint8)(remainder << 1) ^ poly; } else { remainder = (remainder << 1u); /*lint !e734 Lint exception: Intentional */ } } } return remainder; } uint8 Crc_CalculateCRC8(const uint8* Crc_DataPtr, uint32 Crc_Length, uint8 Crc_StartValue8, boolean Crc_IsFirstCall) { uint8 crc = 0; /* Default return value if NULL pointer */ if (Crc_DataPtr != NULL) { crc = (TRUE == Crc_IsFirstCall) ? CRC8_START_VALUE : (Crc_StartValue8 ^ CRC8_XOR); #if CRC_8_MODE == CRC_8_RUNTIME crc = CalculateCRC8(Crc_DataPtr, Crc_Length, crc, CRC8_POLYNOMIAL); #elif CRC_8_MODE == CRC_8_TABLE for (uint32 byte = 0; byte < Crc_Length; byte++) { crc = crc8_tab[crc ^ *Crc_DataPtr]; Crc_DataPtr++; } #endif /* Only XOR value if any calculation was done */ crc = crc ^ CRC8_XOR; } return crc; } uint8 E2E_P01ComputeCRC(const uint8* DataPtr, const E2E_P01ConfigType* ConfigPtr, uint8 Counter) { uint8 crc; uint8 CrcLength; uint8 LocalDataID[2]; uint16 CalculatedOffset; uint16 CalculatedLength; switch (ConfigPtr->DataIDMode) { case E2E_P01_DATAID_BOTH: { LocalDataID[0] = (uint8)(ConfigPtr->DataID); LocalDataID[1] = (uint8)(ConfigPtr->DataID >> 8u); CrcLength = 2; break; } case E2E_P01_DATAID_LOW: { LocalDataID[0] = (uint8)ConfigPtr->DataID; CrcLength = 1; break; } case E2E_P01_DATAID_NIBBLE: { LocalDataID[0] = (uint8)ConfigPtr->DataID; LocalDataID[1] = 0; CrcLength = 2; break; } case E2E_P01_DATAID_ALT: { CrcLength = 1; if ((Counter & 0x01u) == 0u) { LocalDataID[0] = (uint8)ConfigPtr->DataID; } else { LocalDataID[0] = (uint8)(ConfigPtr->DataID >> 8u); } break; } default: LocalDataID[0] = 0u; CrcLength = 0u; break; } crc = Crc_CalculateCRC8(LocalDataID, CrcLength, 0xFF, FALSE); CalculatedOffset = ConfigPtr->CRCOffset >> 3u; if ((CalculatedOffset) > 0u) { crc = Crc_CalculateCRC8(DataPtr , CalculatedOffset , crc , FALSE ); } CalculatedLength = ConfigPtr->DataLength >> 3u; if (CalculatedOffset < (CalculatedLength - 1u)) { crc = Crc_CalculateCRC8(&DataPtr[CalculatedOffset + 1u] , (uint32)CalculatedLength - CalculatedOffset - 1u , crc , FALSE ); } return crc ^ 0xFFu; }
参考:AUTOSAR E2E 简介_Archieeeeee的博客-CSDN博客_autosar e2e
最后
以上就是漂亮飞机最近收集整理的关于Autosar BSW层CAN通讯开发------08(Autosar的E2E开发-----以E2E Profile01为例)的全部内容,更多相关Autosar内容请搜索靠谱客的其他文章。
发表评论 取消回复