您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 
 订阅
一文读懂autosar E2E
 
 
   次浏览      
 2024-4-11
 
编辑推荐:
本文主要介绍了autosar E2E相关内容。希望对你的学习有帮助。
本文来自于微信公众号车端软件开发,由火龙果软件Linda编辑,推荐。

本文不是解读规范。而是理解与场景相结合,噫在说E2E这个事情本身。可能没法直接套用,但是读完之后,你会在各种变化中都明白个大概。然后看看规范就知道怎么使用了。

1. overview

E2E 的保护概念是针对软件在运行时数据交换的保护。运行时数据发生问题的地方可能有很多。

数据链路,外围收发,单片机硬件,传输过程等等等等。外围电路的电池干扰等也都可能会对数据造成影响。这里End 2 End 就是为了检验出数据有没有发生问题。

从数据 发送端 到 接收端,可能是两个ECU 也可以是两个SWC, 甚至数据的上下环节都可以做E2E校验。下面以ECU之间的端到端检测例子。

Overview of E2E communication protection between a sender and a receiver

1.1 Pdu 简单介绍

所以这个数据到底是什么?我们有必要了解一下不同位置的数据的名字。因为在保护的过程是需要有针对性的。而且不同的层级保护路径覆盖是不一样的。也就意味着安全等级是可能不一样的。

传统互联网分层的报文与autosar定义的相对比。

应用层——消息

传输层——数据段/报文段(segment) (注:TCP叫TCP报文段,UDP叫UDP数据报,也有人叫UDP段)

网络层——分组、数据包(packet)

链路层——帧(frame)

物理层——P-PDU(bit)

互联网的定义

autosar的定义

从上面图可以看出来,在autosar体系中,只要从驱动出来到了中间件(BSW) 统一都叫做PDU. 只是前缀可能不一致。更为具体细致的pdu了解可以见下图,不过和本文关系不大。

现在我们知道了autosar体系中,每个层级的pdu 简单定义。下面我们来从几个角度说E2E 本身的事情。

1.2 保护路径

从过上面不同位置的PDU 的介绍,我们在数据保护的角度就需要找到可能发生数据出问题的路径,然后针对性的保护。

可以说有很多路径都会出现问题。所以我们就需要制定“比较好”的解决方案。

这里比较好可以从成本,安全性,便捷性等方面去考虑。所以这里引出了三种保护方案

1.3 Frame & pdu

通过前面的学习,我们知道,在autosar体系的内部,所有流动的数据,都可以叫做PDU. 我们以总线报文的收发来作为例子说一下。

1.3.1 pdu分类

说到总线报文和软件内部的PDU 进行保护,那么问题来了,frame 和 pdu的关系呢?注意了 E2E 保护的是什么。保护的是PDU. 可以理解为报文(frame)的一部分。我们来详细说一下 frame, pdu

在Arxml文件中定义的有如下类型:

说这个好像也没啥用,我的目的是为了说 frame = pdu*n + 额外信息 这里的n >= 1.

也就是说一帧frame 里面可以包含几个pdu的话,这帧报文就可能需要多次保护,因为保护的单位是pdu.

说到这个要说一下现在autosar 里面的定义了。

1.3.2 Autosar定义的PDU

对于普通CAN报文来说,一个message对应一个PDU,对于CANFD报文来说,引入Container PDU和contained I-PDU实现一个message对应多组PDU。

考虑这样做的优势:

不同的contained I-PDU可以映射到不同的Container PDU的不同位置。不仅提高了灵活性,而且也会降低总线负载率。

有了这些知识,我们来聊一下autosar提到的三种E2E 实现方式。

2. E2E 实现方式

从保护的角度来说,最终的算法无论是callout 还是 wrapper 还是transformer 都是一样的。假设用CRC8 来计算,那么最终给PDU 添加的计算算法都是CRC8, 下面三种方式不一样的指的是路径不一样,和集成方式不一样。我们详细说一说。

2.1 Callout

Callout 指的是com callout. 举个例子,我们有一个pdu从asw 发出来,经过RTE 到达了COM. COM中维护的PDU 配置 可以选择callout.

COM 中 PDU 配置界面

所以就是说 PDU 的tx 的内容 需要调用一个函数,然后继续往下走。这里面调用的函数就是callout. 我们在这里面去调用E2E 的相关接口。把PDU 内部的数据进行计算,进行更新。

然后 从COM 出去之后的数据就是经过了E2E 计算,赋值的数据包了。

从链路上来看,有一段处于空白,就是ASW 到COM 的阶段,这里面是没有保护的,所以这里是有可能发生数据错误的。我们从callout的角度来说,没办法去识别出来。

从下图也可以看出来调用callout的位置。

callout

所以callout 的保护只是发送端的com 到 对等接收端的com. 如果com之上发生了错误, 这种E2E方法是无法检测到的。所以为了可靠性,我们需要更进一步的去覆盖保护范围

2.2 Wrapper

说不出来词形容wrapper. 用一个 不昏不素来形容吧。wrapper的实现有两种。目的是什么呢。目的就是 保护 pdu从swc发出的那一刻 一直到对端swc 接收到pdu的时候。

确实挺好的设计,但是为什么说不昏不素呢。autosar 给了两种方式。

2.2.1 Wrapper behind SWC

大家都知道SWC 发送 接收 pdu 走的是 RTE_Read,,,,,, 和 RTE_Write,,,,,, 两种实现方式一种是 可以说是修改了RTE 的接口,在调用RTE 的接口的时候,先走了一遍 E2ELib. 也就是说在SWC 的下方多了个Wrapper的适配层。如下图所示

保护的内容确实挺好,但是是不是每一个SWC 都需要这样的一个适配层。而且SWC 没办法调到原生的RTE 接口了。

2.2.2 Wrapper within SWC - Transmission Manager

然后呢,又来了个不昏不素的wrapper 实现。

既然想要原生的RTE 接口,那就把这个适配层放在RTE 接口和 SWC 之间。但是问题还是一样,每个SWC 都需要有这么个适配层。如下图,

所以可能就是前面提到的两个缺点,引入了一种新的方式,保护的方式,内容,路径和wrapper 是一致的。只是从系统的角度去实现。

2.3 Transformer

为什么说是系统的角度去实现,因为transformer 在做系统描述的时候,就已经去设计好了这一点。不需要在SWC 的设计阶段去做保护。

可以看得到,在系统描述文件的角度,已经把pdu 和 相对应的E2E 都连接好了。最终的实现是在RTE 内部进行实现。

那么问题来了,系统描述怎么知道pdu呢。毕竟在系统层面还是前面提到的frame啊。这里我CSDN 找了个写的比较好的。抄一段来说明一个message 包含多个pdu的概念。-- 来自 CSDN Autosar开发笔记

2.3.1 CANFD ContainerPDU定义

ContainerPDU并不是frame,但可以设置ContainerPDU包含frame所有的数据位。ContainerPDU是包含在frame中的。对于一个CANFD-frame,定义如下:

注:若CANFD报文实际只有8字节,那么就和普通报文一样,定义一个I-Signal-PDU就够了,不需要引入容器PDU。

Container-I-PDU定义如下:

Header Type有三种选择:

1.ShortHeader 2.LongHeader 3.NoHeader

Autosar中定义如下:

IpduM支持两种不同的动态Container Pdu的头大小(参见ECUC_IpduM_00183: IpduMContainerHeaderSize):

IPDUM_HEADERTYPE_SHORT, 24位ID, 8位长度

IPDUM_HEADERTYPE_LONG, 32位ID, 32位长度

如果是选择的ShortHeader,那么实际数据位中会有三个byte为ID,一个byte为DLC,8个byte为数据位

在Container-PDU定义页可以选择包含的PDU及设置PDU对应的ID。

2.3.2 CANFD Signal-I-PDU定义

这个Signal-I-PDU就类似普通的CAN报文,里面定义了具体的信号信息,及layout信息。

2.3.3 CANFD实际数据解析

CANFD带Container的报文,实际数据长度为24,包含两个Signal-I-PDU(每个12个byte)

对应的Signal-I-PDU还可以继续展开解析后的信号具体信息

回归主题,有了上面的介绍,Transformer 带来的好处就是 无需手动代码。

SWC 自己的接口实现,无需关注自己的read 和 write 是否需要 E2E, 因为他所调用的RTE 接口,已经在系统描述的阶段做好了E2E.

2.3.4 总结

好了这里三种方式说完了。说了半天也没说到具体的计算算法,其实保护算法本身没有什么含量。关键的点在于知道什么时候该用这三种方式,针对性的对我们的系统进行设计。

3. E2E 保护机制

前面系统的层面去说了E2E 的为什么,和怎么去选择,去设计。这一章我们说一下具体E2E 内部是怎么实现的。为什么不在前面说,因为实现的本身是一样的。

思路是下图这样的。

那么我们从比较书面的方式去解释。

就拿E2E_Profile1A 来举例子

3.1 E2E_Profile1A 概述

上面我手画的保护信息值得就是下图

注意规定好了保护方式后,保护的报文的某些字节需要放什么数据,是已经确定好了的。

最终形成的PDU 如下。

这里问个问题,上图是存在于哪里?

答:上图是被保护的单元,可能共同属于一个frame, 也可能就是一个frame 甚至属于多个frame的集合。

好了pdu,frame 的概念后面不提了。

那么我们怎么去实现呢。

3.2 E2E_Profile1A 实现

我们以发送pdu, 来保护这个pdu 为例子说明。

C                  
Std_ReturnType E2E_P01Protect(const E2E_P01ConfigType* ConfigPtr, E2E_P01ProtectStateType* StatePtr, uint8* DataPtr)

 

看一下这里面的形参

C                  
/** @brief Protects the array/buffer to be transmitted using the E2E profile 1.                  
*         This includes checksum calculation, handling of counter and Data ID.                  
*                  
*  @param ConfigPtr Pointer to static configuration.                  
*  @param StatePtr Pointer to port/data communication state.                  
*  @param DataPtr Pointer to Data to be transmitted.                  
*                  
*  @return Error code (E2E_E_INPUTERR_NULL || E2E_E_INPUTERR_WRONG || E2E_E_INTERR || E2E_E_OK).                  
*/
       

我们需要对pdu进行参数配置。配置的内容如下

这些的信息如果看清楚了前面我手画的信息,就很容易明白了,实际上就是各个参数的位置,长度,以及判断失败的标准。

第二个参数 是counter. E2E 是有CRC + Counter的。这个counter 也是需要这个算法本身去维护的。

第三个就是数据本身。

跟着源码一步一步解释

C                  
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 */ // ASR4.2.2                  
        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;                  
}
       

 

在保护数据的前面,需要对Counter进行赋值。前面介绍到了counter的位置是固定的,也是有配置信息,的所以进行了赋值。不过有个需要注意的点就是mode。

E2E_P01DataIDMode = E2E_P01_DATAID_NIBBLE

这个是什么意思呢。

这里的DataID 的一部分,需要放到counter的位置里面去。所以说 在更新counter的时候。用到了一部分DataId.

其他的点都很好理解。

这里counter已经更新完毕。我们开始计算CRC

C                  
uint8 calculateCrcP01(const E2E_P01ConfigType* Config, uint8 Counter, const uint8* Data)

在计算CRC, 需要用到什么。

DataId 需要计算

Counter 需要计算

Data 需要计算。

DataId 如何计算去觉得Config->DataIDMode 的配置。

唯一的数据 ID 用于验证每个传输的安全相关数据元素的身份。

二字节Data ID计算一字节CRC有以下四种包含方式:1. E2E_P01_DATAID_BOTH:CRC中包含两个字节(双ID配置),先低字节后高字节(请参见变体 1A - PRS_E2EProtocol_00227)或 2. E2E_P01_DATAID_ALT:取决于计数器的奇偶校验(交替 ID 配置),包括高字节和低字节(参见变体 1B - PRS_E2EProtocol_00228)。对于偶数计数器值,包括低字节,对于奇数计数器值,包括高字节。3. E2E_P01_DATAID_LOW:只包含低字节,从不使用高字节。这相当于数据 ID(在给定应用进程中)只有 8 位的情况。

这就是计算过程。

那么具体怎么算的呢。这个就是比较标准的做法。不多解释,直接给码

C                  
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_8_Mode == CRC_8_TABLE                  
    static const uint8 Crc_8_Tab[256] = {0x0, 0x1d, 0x3a, 0x27, 0x74, 0x69, 0x4e, 0x53,                  
                                     0xe8, 0xf5, 0xd2, 0xcf, 0x9c, 0x81, 0xa6, 0xbb,                  
                                     0xcd, 0xd0, 0xf7, 0xea, 0xb9, 0xa4, 0x83, 0x9e,                  
                                     0x25, 0x38, 0x1f, 0x2, 0x51, 0x4c, 0x6b, 0x76,                  
                                     0x87, 0x9a, 0xbd, 0xa0, 0xf3, 0xee, 0xc9, 0xd4,                  
                                     0x6f, 0x72, 0x55, 0x48, 0x1b, 0x6, 0x21, 0x3c,                  
                                     0x4a, 0x57, 0x70, 0x6d, 0x3e, 0x23, 0x4, 0x19,                  
                                     0xa2, 0xbf, 0x98, 0x85, 0xd6, 0xcb, 0xec, 0xf1,                  
                                     0x13, 0xe, 0x29, 0x34, 0x67, 0x7a, 0x5d, 0x40,                  
                                     0xfb, 0xe6, 0xc1, 0xdc, 0x8f, 0x92, 0xb5, 0xa8,                  
                                     0xde, 0xc3, 0xe4, 0xf9, 0xaa, 0xb7, 0x90, 0x8d,                  
                                     0x36, 0x2b, 0xc, 0x11, 0x42, 0x5f, 0x78, 0x65,                  
                                     0x94, 0x89, 0xae, 0xb3, 0xe0, 0xfd, 0xda, 0xc7,                  
                                     0x7c, 0x61, 0x46, 0x5b, 0x8, 0x15, 0x32, 0x2f,                  
                                     0x59, 0x44, 0x63, 0x7e, 0x2d, 0x30, 0x17, 0xa,                  
                                     0xb1, 0xac, 0x8b, 0x96, 0xc5, 0xd8, 0xff, 0xe2,                  
                                     0x26, 0x3b, 0x1c, 0x1, 0x52, 0x4f, 0x68, 0x75,                  
                                     0xce, 0xd3, 0xf4, 0xe9, 0xba, 0xa7, 0x80, 0x9d,                  
                                     0xeb, 0xf6, 0xd1, 0xcc, 0x9f, 0x82, 0xa5, 0xb8,                  
                                     0x3, 0x1e, 0x39, 0x24, 0x77, 0x6a, 0x4d, 0x50,                  
                                     0xa1, 0xbc, 0x9b, 0x86, 0xd5, 0xc8, 0xef, 0xf2,                  
                                     0x49, 0x54, 0x73, 0x6e, 0x3d, 0x20, 0x7, 0x1a,                  
                                     0x6c, 0x71, 0x56, 0x4b, 0x18, 0x5, 0x22, 0x3f,                  
                                     0x84, 0x99, 0xbe, 0xa3, 0xf0, 0xed, 0xca, 0xd7,                  
                                     0x35, 0x28, 0xf, 0x12, 0x41, 0x5c, 0x7b, 0x66,                  
                                     0xdd, 0xc0, 0xe7, 0xfa, 0xa9, 0xb4, 0x93, 0x8e,                  
                                     0xf8, 0xe5, 0xc2, 0xdf, 0x8c, 0x91, 0xb6, 0xab,                  
                                     0x10, 0xd, 0x2a, 0x37, 0x64, 0x79, 0x5e, 0x43,                  
                                     0xb2, 0xaf, 0x88, 0x95, 0xc6, 0xdb, 0xfc, 0xe1,                  
                                     0x5a, 0x47, 0x60, 0x7d, 0x2e, 0x33, 0x14, 0x9,                  
                                     0x7f, 0x62, 0x45, 0x58, 0xb, 0x16, 0x31, 0x2c,                  
                                     0x97, 0x8a, 0xad, 0xb0, 0xe3, 0xfe, 0xd9, 0xc4};                  
#endif                  
                 
    /* @req SWS_BSW_00212 NULL pointer checking */                  
    if (Crc_DataPtr != NULL_PTR) {                  
                 
        crc = (TRUE == Crc_IsFirstCall) ? Crc_8_StartValue : (Crc_StartValue8 ^ Crc_8_Xor);                  
                 
#if Crc_8_Mode == CRC_8_RUNTIME                  
        crc = calculateCRC8(Crc_DataPtr, Crc_Length, crc, Crc_8_Polynomial);                  
#elif Crc_8_Mode == CRC_8_TABLE                  
        for (uint32 byte = 0; byte < Crc_Length; byte++) {                  
            crc = Crc_8_Tab[crc ^ *Crc_DataPtr];                  
            Crc_DataPtr++;                  
        }                  
#endif                  
                 
        /* Only XOR value if any calculation was done */                  
        crc = crc ^ Crc_8_Xor;                  
    }                  
                 
    return crc;                  
}
       

 

 
   
次浏览       
相关文章

中央计算的软件定义汽车架构设计
汽车电子控制系统中的软件开发过程
一文读懂汽车芯片-有线通信芯片
OTA在汽车上有哪些难点痛点?
相关文档

汽车设计-汽车的整体结构及动力系统
自动驾驶汽车软件计算框架
SysML在汽车领域的应用实践
电子电气架构-大陆汽车系统架构平台
相关课程

AutoSAR原理与实践
功能安全管理体系(基于ISO26262)
MBSE(基于模型的系统工程)
基于SOA的汽车电子架构设计与开发

最新活动计划
C++高级编程 12-25 [线上]
白盒测试技术与工具实践 12-24[线上]
LLM大模型应用与项目构建 12-26[特惠]
需求分析最佳实践与沙盘演练 1-6[线上]
SysML建模专家 1-16[北京]
UAF架构体系与实践 1-22[北京]
 
 
最新文章
中央计算的软件定义汽车架构设计方案解析
汽车电子控制系统中的软件开发过程
一文读懂汽车芯片-有线通信芯片
OTA在汽车上有哪些难点痛点?
智能汽车车用基础软件的内核和中间件
最新课程
Auto SAR原理与实践
MBSE(基于模型的系统工程)
基于SOA的汽车电子架构设计与开发(域控模式)
人工智能助力汽车行业升级
基于UML和EA进行系统分析设计
SysML和EA进行系统设计建模
更多...   
成功案例
奇瑞商用车 购买建模工具EA完全版
航空发动机研究院 购买建模工具EA完全版
联创汽车 购买建模工具EA完全版
江淮汽车 购买建模工具EA
更多...