4.3 E2E库源码走读——六种Profile的共同骨架
场景:当一个 CAN 帧到达 ECU
ECU 的 CAN 控制器从总线上收到一帧数据:8 个字节,包含了转向角度传感器的当前值。这 8 个字节从传感器 MCU 的 RAM 出发,经过 SPI 总线、CAN 收发器、差分线缆、另一个 CAN 收发器、ECU 内部总线,最终到达你的应用层 SW-C。在这一路上,数据经历了电磁干扰、电压波动、收发器故障,任何一个 bit 翻转都可能导致方向盘“看“到错误的角度。
AUTOSAR 规范为此定义了一套端到端(End-to-End, E2E)保护协议。Arctic Core 的 E2E 库实现覆盖了 6 种 Profile(P01/P02/P04/P05/P06/P11),物理文件只有 4 个核心源文件。今天我们打开 P01 的完整实现。
一、E2E.h:共同契约
首先打开 E2E.h,它是所有 Profile 和 State Machine 的共同接口:
/** @file E2E.h
* The E2E library provides
* *The definition of profiles 1,2,4,5 and 6 including check
* and protect functions
* *A state machine describing the logical algorithm of E2E
* monitoring independent of the used profile
*/
/* The E2E library shall not call any functions from external
* modules apart from explicitly listed expected interfaces: Crc */
#include "Crc.h"
#include "Std_Types.h"
关键约束写在注释里:E2E 库不调用 RTE,不调用 BSW 模块(DEM/DET),不出错报告。它是纯函数库,输入数据和状态,输出校验值和状态更新。所有错误通过返回值传递。这确保了 E2E 库可以作为 Safety Element out of Context(SEooC)独立开发、独立认证。
公共错误码:
#define E2E_E_INPUTERR_NULL 0x13 // NULL指针
#define E2E_E_INPUTERR_WRONG 0x17 // 参数越界
#define E2E_E_INTERR 0x19 // 内部错误
#define E2E_E_OK 0x00 // 成功
#define E2E_E_WRONGSTATE 0x1A // 状态机未初始化
模块版本检查,配合 Crc 库:
#if (E2E_AR_RELEASE_MAJOR_VERSION != CRC_AR_RELEASE_MAJOR_VERSION) \
|| (E2E_AR_RELEASE_MINOR_VERSION != CRC_AR_RELEASE_MINOR_VERSION)
#error "SW Version mismatch between E2E.h and Crc.h"
#endif
如果在集成阶段把 AUTOSAR 4.2 的 E2E 头文件和 4.3 的 Crc 库混用,编译器直接报错。
二、E2E_P01.c:Profile 1 全景
Profile 1 是 A-SPICE 和 ISO 26262 中最常见的 E2E 配置。它的保护链只有三项:
- CRC-8-SAE J1850(多项式 0x1D, Start=0x00, XOR=0xFF)
- 4-bit Counter(0 到 14 循环,跳过 0xF)
- DataID(1 字节或 2 字节,4 种模式)
文件开头是一系列编译时约束:
#define MAX_P01_DATA_LENGTH_IN_BITS (240) // 最大30字节
#define MAX_P01_COUNTER_VALUE (14) // 计数器跳过0xF
#define Crc_8_Xor 0xFFU
版本双重检查,源文件 vs 头文件:
#if (E2E_P01_SW_MAJOR_VERSION != E2E_P01_SW_MAJOR_VERSION_INT)
|| (E2E_P01_SW_MINOR_VERSION != E2E_P01_SW_MINOR_VERSION_INT)
#error "SW Version mismatch between E2E_P01.c and E2E_P01.h"
#endif
#if (E2E_P01_AR_RELEASE_MAJOR_VERSION != E2E_P01_AR_RELEASE_MAJOR_VERSION_INT)
|| ...
#error "AR Version mismatch between E2E_P01.c and E2E_P01.h"
#endif
这是 AUTOSAR 强制要求的文件级版本一致性检查。你会在 Arctic Core 的每一个源文件中看到这个模式。
三、配置检查器:checkConfigP01
在 Protect 和 Check 执行之前,配置本身要先通过完整性检查:
static INLINE Std_ReturnType checkConfigP01(const E2E_P01ConfigType* Config)
{
Std_ReturnType status;
status = E2E_E_OK;
if (Config == NULL_PTR) {
status = E2E_E_INPUTERR_NULL;
}
else if ((Config->DataLength > MAX_P01_DATA_LENGTH_IN_BITS)
|| ((Config->DataLength % 8) != 0)
|| ((Config->CounterOffset % 4) != 0)
|| ((Config->CRCOffset % 8) != 0)) {
status = E2E_E_INPUTERR_WRONG;
}
else if (((Config->CRCOffset + 8) > Config->DataLength)
|| ((Config->CounterOffset + 4) > Config->DataLength)
|| ((Config->CRCOffset/8) == (Config->CounterOffset/8))) {
status = E2E_E_INPUTERR_WRONG;
}
else if ((Config->DataIDMode != E2E_P01_DATAID_NIBBLE)
&& (Config->DataIDNibbleOffset != 0)) {
status = E2E_E_INPUTERR_WRONG;
}
else if ((Config->DataIDMode == E2E_P01_DATAID_LOW)
&& ((Config->DataID>>8) != 0)) {
status = E2E_E_INPUTERR_WRONG;
}
else if ((Config->DataIDMode == E2E_P01_DATAID_NIBBLE)
&& ((Config->DataID>>12) != 0)) {
status = E2E_E_INPUTERR_WRONG;
}
// ... return E2E_E_OK
return status;
}
检查清单:
- CRC 和 Counter 不能重叠在同一个字节
- CRC 位置必须在数据范围内(
CRCOffset+8 <= DataLength) - Counter 必须是 4-bit 对齐(偏移量 %4 == 0)
- DataID 模式下高字节必须按规范限制为 0
如果配置数据静默损坏了(比如 Flash 中某个常量翻转了),checkConfigP01 会在第一次调用时拦截。
四、CRC 计算:跨越 DataID 和数据的数学签名
static uint8 calculateCrcP01(const E2E_P01ConfigType* Config,
uint8 Counter, const uint8* Data)
{
uint8 crc = 0x00u ^ Crc_8_Xor; /* Need to cancel first XOR in CRC */
uint8 lowerByteId = (uint8)Config->DataID;
uint8 upperByteId = (uint8)(Config->DataID>>8);
/* Calculate CRC on the Data ID */
if (Config->DataIDMode == E2E_P01_DATAID_BOTH)
{
crc = Crc_CalculateCRC8(&lowerByteId, 1, crc, FALSE);
crc = Crc_CalculateCRC8(&upperByteId, 1, crc, FALSE);
}
else if (Config->DataIDMode == E2E_P01_DATAID_LOW)
{
crc = Crc_CalculateCRC8(&lowerByteId, 1, crc, FALSE);
}
else if (Config->DataIDMode == E2E_P01_DATAID_NIBBLE)
{
crc = Crc_CalculateCRC8(&lowerByteId, 1, crc, FALSE);
upperByteId = 0;
crc = Crc_CalculateCRC8(&upperByteId, 1, crc, FALSE);
}
else if ((Counter % 2) == 0) // E2E_P01_DATAID_ALT: even
{
crc = Crc_CalculateCRC8(&lowerByteId, 1, crc, FALSE);
}
else // E2E_P01_DATAID_ALT: odd counter
{
crc = Crc_CalculateCRC8(&upperByteId, 1, crc, FALSE);
}
/* Calculate CRC on the data (skip the CRC byte itself) */
if (Config->CRCOffset >= 8) {
crc = Crc_CalculateCRC8(Data,
(Config->CRCOffset / 8), crc, FALSE);
}
if ((Config->CRCOffset / 8) < ((Config->DataLength / 8) - 1)) {
crc = Crc_CalculateCRC8(
&Data[(Config->CRCOffset/8) + 1],
(((Config->DataLength / 8) - (Config->CRCOffset / 8)) - 1),
crc, FALSE);
}
return crc ^ Crc_8_Xor; /* Need to cancel last XOR in CRC */
}
关键设计细节:
- CRC 库的默认 start value 是 0xFF,E2E P01 要求 0x00,所以
crc = 0x00 ^ 0xFF取消了第一个 XOR。 - CRC 字节本身不参与计算,数据被分成 CRC 之前和之后两段分别喂入
Crc_CalculateCRC8。 - DataID 的 4 种模式:
BOTH:低字节+高字节都参与 CRCLOW:仅低字节(高字节为 0x00)NIBBLE:低字节 + 高字节低 4-bit(高 4-bit 为 0x0)ALT:根据 Counter 的奇偶性交替使用低字节或高字节(变体 1B)
五、Protect:发送端的加保护术
Std_ReturnType E2E_P01Protect(const E2E_P01ConfigType* ConfigPtr,
E2E_P01ProtectStateType* StatePtr, uint8* DataPtr)
{
Std_ReturnType status;
status = E2E_E_OK;
Std_ReturnType returnValue = checkConfigP01(ConfigPtr);
if (E2E_E_OK != returnValue) {
status = returnValue;
}
else if ((StatePtr == NULL_PTR) || (DataPtr == NULL_PTR)) {
status = E2E_E_INPUTERR_NULL;
}
else {
/* Put counter in data */
if ((ConfigPtr->CounterOffset % 8) == 0) {
DataPtr[ConfigPtr->CounterOffset/8] =
(DataPtr[(ConfigPtr->CounterOffset/8)] & 0xF0u)
| (StatePtr->Counter & 0x0Fu);
}
else {
DataPtr[ConfigPtr->CounterOffset/8] =
(DataPtr[ConfigPtr->CounterOffset/8] & 0x0Fu)
| ((StatePtr->Counter<<4) & 0xF0u);
}
/* Put counter in data for E2E_P01_DATAID_NIBBLE */
if (ConfigPtr->DataIDMode == E2E_P01_DATAID_NIBBLE) {
if ((ConfigPtr->DataIDNibbleOffset % 8) == 0) {
DataPtr[ConfigPtr->DataIDNibbleOffset/8] =
(DataPtr[(ConfigPtr->DataIDNibbleOffset/8)] & 0xF0u)
| ((uint8)((ConfigPtr->DataID>>8) & 0x0Fu));
}
else {
DataPtr[ConfigPtr->DataIDNibbleOffset/8] =
(DataPtr[ConfigPtr->DataIDNibbleOffset/8] & 0x0Fu)
| ((uint8)((ConfigPtr->DataID>>4) & 0xF0u));
}
}
/* Calculate CRC */
DataPtr[(ConfigPtr->CRCOffset/8)] =
calculateCrcP01(ConfigPtr, StatePtr->Counter, DataPtr);
/* Update counter */
StatePtr->Counter = (StatePtr->Counter+1) % 15;
}
return status;
}
Protect 的三步操作:
- 嵌入 Counter:用
& 0xF0 | Counter或& 0x0F | Counter<<4将 4-bit 计数器嵌入指定偏移的半字节 - 计算并嵌入 CRC:调用
calculateCrcP01将整个 CRC8 写入指定字节 - 更新计数器:
(Counter+1) % 15,跳过 0xF(15),从 0xE 直接回到 0x0
特别注意 Counter 偏移的位操作:Counter 是 4-bit 对齐的,需要根据偏移量判断是低半字节还是高半字节。(ConfigPtr->CounterOffset % 8) == 0 判断是否是字节边界:字节边界用低半字节,否则用高半字节。这段代码没有任何“推测“,每个 bit 的位置都是编译时配置决定的。
六、Check:接收端的图算法
E2E_P01Check 的完整状态机逻辑(对应 AUTOSAR Fig 7-7):
Std_ReturnType E2E_P01Check(const E2E_P01ConfigType* ConfigPtr,
E2E_P01CheckStateType* StatePtr, const uint8* DataPtr)
{
Std_ReturnType status;
status = E2E_E_OK;
boolean ret;
ret = TRUE;
uint8 receivedCounter;
uint8 receivedCrc;
uint8 calculatedCrc;
uint8 receivedDataIDNibble;
Std_ReturnType returnValue = checkConfigP01(ConfigPtr);
if (E2E_E_OK != returnValue) { status = returnValue; }
else if ((StatePtr == NULL_PTR) || (DataPtr == NULL_PTR)) {
status = E2E_E_INPUTERR_NULL;
}
else {
/* MaxDeltaCounter increment before check */
if (StatePtr->MaxDeltaCounter < MAX_P01_COUNTER_VALUE) {
StatePtr->MaxDeltaCounter++;
}
if (StatePtr->NewDataAvailable == FALSE) {
if (StatePtr->NoNewOrRepeatedDataCounter
< MAX_P01_COUNTER_VALUE) {
StatePtr->NoNewOrRepeatedDataCounter++;
}
StatePtr->Status = E2E_P01STATUS_NONEWDATA;
status = E2E_E_OK;
ret = FALSE;
}
if (ret == TRUE) {
receivedCounter = readCounterP01(ConfigPtr, DataPtr);
if (receivedCounter > MAX_P01_COUNTER_VALUE) {
status = E2E_E_INPUTERR_WRONG;
} else {
receivedCrc = DataPtr[(ConfigPtr->CRCOffset/8)];
receivedDataIDNibble =
readDataIDNibbleOffsetP01(ConfigPtr, DataPtr);
calculatedCrc =
calculateCrcP01(ConfigPtr, receivedCounter, DataPtr);
if (isCheckOfReceivedDataNotOkP01(ConfigPtr, StatePtr,
receivedCounter, receivedCrc, calculatedCrc,
receivedDataIDNibble) == TRUE) {
status = E2E_E_OK; // Status already set inside
} else {
doChecksP01(ConfigPtr, StatePtr, receivedCounter);
}
}
}
}
return status;
}
先看 CRC 校验和首次数据检测:
static INLINE boolean isCheckOfReceivedDataNotOkP01(
const E2E_P01ConfigType* ConfigPtr, E2E_P01CheckStateType* StatePtr,
uint8 receivedCounter, uint8 receivedCrc, uint8 calculatedCrc,
uint8 receivedDataIDNibble)
{
boolean status = FALSE;
if ( (receivedCrc != calculatedCrc) ||
( (ConfigPtr->DataIDMode == E2E_P01_DATAID_NIBBLE) &&
(receivedDataIDNibble != ((ConfigPtr->DataID)>>8)) ) )
{
StatePtr->Status = E2E_P01STATUS_WRONGCRC;
status = TRUE;
}
else if (isFirstDataSinceInitialisationP01(ConfigPtr,
StatePtr, receivedCounter) == TRUE) {
status = TRUE; // Status set to E2E_P01STATUS_INITIAL inside
}
return status;
}
然后是核心的 delta counter 检查逻辑:
static INLINE void doChecksP01(const E2E_P01ConfigType* ConfigPtr,
E2E_P01CheckStateType* StatePtr, uint8 receivedCounter)
{
uint8 delta;
delta = calculateDeltaCounterP01(receivedCounter,
StatePtr->LastValidCounter);
if (delta == 0) {
// REPEATED: 收到重复消息
if (StatePtr->NoNewOrRepeatedDataCounter < MAX_P01_COUNTER_VALUE) {
StatePtr->NoNewOrRepeatedDataCounter++;
}
StatePtr->Status = E2E_P01STATUS_REPEATED;
}
else if (delta == 1) {
// OK: 计数器恰好连续
StatePtr->MaxDeltaCounter = ConfigPtr->MaxDeltaCounterInit;
StatePtr->LastValidCounter = receivedCounter;
StatePtr->LostData = 0;
if (StatePtr->NoNewOrRepeatedDataCounter
<= ConfigPtr->MaxNoNewOrRepeatedData) {
StatePtr->NoNewOrRepeatedDataCounter = 0;
if (StatePtr->SyncCounter > 0) {
StatePtr->SyncCounter--;
StatePtr->Status = E2E_P01STATUS_SYNC;
} else {
StatePtr->Status = E2E_P01STATUS_OK;
}
} else {
StatePtr->NoNewOrRepeatedDataCounter = 0;
StatePtr->SyncCounter = ConfigPtr->SyncCounterInit;
StatePtr->Status = E2E_P01STATUS_SYNC;
}
}
else if (delta <= StatePtr->MaxDeltaCounter) {
// OKSOMELOST: 有丢帧但仍在容忍范围内
StatePtr->MaxDeltaCounter = ConfigPtr->MaxDeltaCounterInit;
StatePtr->LastValidCounter = receivedCounter;
StatePtr->LostData = delta - 1;
// ... SyncCounter logic ...
StatePtr->Status = E2E_P01STATUS_OKSOMELOST;
}
else {
// WRONGSEQUENCE: 超过容忍范围
StatePtr->SyncCounter = ConfigPtr->SyncCounterInit;
StatePtr->Status = E2E_P01STATUS_WRONGSEQUENCE;
}
}
Delta counter 的循环计算(考虑 0xE→0x0 的跳变):
static INLINE uint8 calculateDeltaCounterP01(
uint8 receivedCounter, uint8 lastValidCounter)
{
uint8 res;
res = 0;
if (receivedCounter >= lastValidCounter) {
res = receivedCounter - lastValidCounter;
}
else {
res = MAX_P01_COUNTER_VALUE + 1 + receivedCounter
- lastValidCounter;
}
return res;
}
MAX_P01_COUNTER_VALUE = 14(即 0xE)。当 receivedCounter=0, lastValidCounter=14 时:res = 14+1+0-14 = 1,正确识别为连续。delta=0→重复,delta=1→正常连续,1<delta≤MaxDelta→丢帧,delta>MaxDelta→乱序。
七、E2E_SM.c:滑动窗口状态机
E2E State Machine 独立于 Profile,它只关心历史 Check 结果的累积统计:
static INLINE void E2E_SMAddStatus(E2E_PCheckStatusType ProfileStatus,
E2E_SMCheckStateType* StatePtr, uint8 windowSize)
{
StatePtr->ProfileStatusWindow[StatePtr->WindowTopIndex]
= (uint8)ProfileStatus;
count=0;
for (i=0; i<windowSize; i++) {
if (StatePtr->ProfileStatusWindow[i] == (uint8)E2E_P_OK) {
count++;
}
}
StatePtr->OkCount = count;
count=0;
for (i=0; i<windowSize; i++) {
if (StatePtr->ProfileStatusWindow[i] == (uint8)E2E_P_ERROR) {
count++;
}
}
StatePtr->ErrorCount = count;
if (StatePtr->WindowTopIndex >= (windowSize-1)) {
StatePtr->WindowTopIndex = 0;
} else {
StatePtr->WindowTopIndex++;
}
}
这是一个固定宽度的滑动窗口。每次 AddStatus 后重新统计窗口内 OK 和 ERROR 的数量,然后根据当前 SMState 做状态迁移:
static INLINE void E2E_SMCheck_VALID(const E2E_SMConfigType* ConfigPtr,
E2E_SMCheckStateType* StatePtr)
{
if ((StatePtr->ErrorCount <= ConfigPtr->MaxErrorStateValid)
&& (StatePtr->OkCount >= ConfigPtr->MinOkStateValid)) {
StatePtr->SMState = E2E_SM_VALID;
} else {
StatePtr->SMState = E2E_SM_INVALID;
}
}
四个状态迁移逻辑:
- NODATA:收到非 NONEWDATA/ERROR 状态→进入 INIT
- INIT:ErrorCount ≤ MaxErrorStateInit && OkCount ≥ MinOkStateInit → VALID;ErrorCount > MaxErrorStateInit → INVALID
- VALID:ErrorCount ≤ MaxErrorStateValid && OkCount ≥ MinOkStateValid → 保持 VALID;否则→INVALID
- INVALID:ErrorCount ≤ MaxErrorStateInvalid && OkCount ≥ MinOkStateInvalid → VALID
窗口大小是编译时配置的。例如 WindowSize=5, MinOkStateValid=3, MaxErrorStateValid=1,意味着 5 帧中至少 3 帧 OK,最多 1 帧 ERROR 才保持 VALID。
八、MapStatusToSM:Profile 状态→通用状态
E2E_P01MapStatusToSM 支持两种行为模式,ASR 4.2.2 前后规范变动:
E2E_PCheckStatusType E2E_P01MapStatusToSM(
Std_ReturnType CheckReturn, E2E_P01CheckStatusType Status,
boolean profileBehavior)
{
E2E_PCheckStatusType retValue = E2E_P_ERROR;
if (CheckReturn != E2E_E_OK) {
retValue = E2E_P_ERROR;
}
else if (profileBehavior == TRUE) {
// ASR4.2.2+ behavior
switch(Status) {
case E2E_P01STATUS_OK:
case E2E_P01STATUS_OKSOMELOST:
case E2E_P01STATUS_SYNC:
retValue = E2E_P_OK; break;
case E2E_P01STATUS_REPEATED:
retValue = E2E_P_REPEATED; break;
case E2E_P01STATUS_NONEWDATA:
retValue = E2E_P_NONEWDATA; break;
case E2E_P01STATUS_WRONGSEQUENCE:
case E2E_P01STATUS_INITIAL:
retValue = E2E_P_WRONGSEQUENCE; break;
}
}
else {
// Pre-ASR4.2.2 behavior: INITIAL maps to OK, SYNC to WRONGSEQUENCE
switch(Status) {
case E2E_P01STATUS_OK:
case E2E_P01STATUS_OKSOMELOST:
case E2E_P01STATUS_INITIAL:
retValue = E2E_P_OK; break;
case E2E_P01STATUS_REPEATED:
retValue = E2E_P_REPEATED; break;
case E2E_P01STATUS_NONEWDATA:
retValue = E2E_P_NONEWDATA; break;
case E2E_P01STATUS_WRONGSEQUENCE:
case E2E_P01STATUS_SYNC:
retValue = E2E_P_WRONGSEQUENCE; break;
}
}
return retValue;
}
新旧行为的关键差异:旧版本把 INITIAL 视为 OK(宽容启动)、SYNC 视为 WRONGSEQUENCE(严格同步要求);新版本把 INITIAL 视为 WRONGSEQUENCE(严格启动)、SYNC 视为 OK(宽容同步过渡)。一个 profileBehavior 布尔参数保留了向后兼容。这是 AUTOSAR 规范演进中常见的“双模式编译支持“。
E2E 库的精髓不在于单个 CRC 计算或计数器嵌入,而在于它的“无依赖纯函数“架构。E2E_P01Check 只负责“这一帧数据对不对“,E2E_SMCheck 负责“最近 N 帧的表现能不能信任“。两个层次独立可测、独立认证。没有动态内存分配,没有函数指针回调,没有递归,只有 const 配置 + 纯函数 + 有限状态机。
本篇小结
- E2E 库是纯函数库,不调用 RTE 或 BSW 模块,依赖仅限
Crc.h和Std_Types.h,可独立认证为 SEooC;所有错误通过返回值传递,无 DET/DEM 报告。 E2E_P01Protect和E2E_P01Check遵循统一的三段式模式:配置校验(checkConfigP01)→ CRC 计算(DataID + 数据,跳过 CRC 字节自身)→ 计数器嵌入/提取,CRC8 使用 SAE J1850 多项式 0x1D。- Check 端通过
calculateDeltaCounterP01处理 0xE→0x0 循环跳变,delta=0 判重复、delta=1 判连续、delta≤MaxDelta 判丢帧、delta>MaxDelta 判乱序,覆盖所有通信故障模式。 E2E_SM状态机独立于 Profile 类型,通过固定宽度滑动窗口统计最近 N 帧的 OK/ERROR 计数,实现 NODATA→INIT→VALID→INVALID 四态迁移,窗口大小和阈值均为编译时配置。E2E_P01MapStatusToSM通过profileBehavior参数支持 ASR 4.2.2 前后的行为差异(INITIAL/SYNC 的 OK/WRONGSEQUENCE 归属),实现规范的向后兼容。
【下集预告】: E2E 依赖 CRC 引擎做数学签名,但 CRC 引擎本身只是一个黑盒子——下一节打开
Crc_32.c的 232 行,看 IEEE-802.3 多项式如何在运行时逐位模式和查表模式之间切换,连 reflect 操作都精细到每个比特。然后进入Safety_Queue.c,一个让你脊背发凉的问题浮出水面:E2E 保护了传输中的数据,但如果数据已经到达 RAM 后被宇宙射线静默翻转了呢?双 CRC 的设计正是为此而生。