【AUTOSAR】 CP 通信栈类型(ComStack_Types)--从入门到放弃
【AUTOSAR】 CP 通信栈类型ComStack_Types–从入门到放弃目录这份规范管什么核心类型逐个看这些类型在工程里怎么用搭一份最小的 ComStack_Types.h三个真实踩坑现场小结1. 这份规范管什么AUTOSAR Classic Platform 的通信栈不是一两个模块而是一长串上面接着 RTE中间有 Standard COM、LdCom、SecOC、Dcm往下经过 PduR 路由再进 CanTp / SomeIpTp / FrTp / LinTp 做分片最后通过 CanIf / EthIf 这类接口层落到驱动上。这些模块由不同的人、甚至不同的公司开发。它们之间能拼在一起靠的是AUTOSAR_CP_SWS_CommunicationStackTypes.pdf这一份规范。规范里定义的类型全部集中在一个头文件里ComStack_Types.h。1.1 为什么要单独抽一份类型头文件把类型集中定义主要解决三件事模块之间不用互相认识。COM 要把数据交给 CanTp它不需要 include CanTp 的头文件也不需要知道 CanTp 内部长什么样两边都用ComStack_Types.h里的定义说话就够了。该省的地方能省下来。PduIdType和PduLengthType到底是 8 位、16 位还是 32 位不是写死的而是配置工具数完这颗 ECU 上有多少个 PDU、最长报文有多长之后决定的。PDU 只有几十个的项目没必要为它占两个字节。流控和状态反馈有统一的说法。TP 层向上层要缓冲区上层答话用的是BufReq_ReturnType重传时告诉上层这段要重来一遍用的是RetryInfoType。COM、LdCom、Dcm 都用同一套语义谁都不用自己发明一套。一句话概括这份规范是通信栈内部的公共词汇表篇幅不长但所有接口都建立在它上面。2. 核心类型逐个看ComStack_Types.h里的类型不少但日常真正打交道的就是下面这几个。2.1 PduIdType是什么PDU 的句柄几乎所有收发接口的第一个参数都是它。规范编号SWS_COMTYPE_00005。底层类型uint8或uint16取哪个由配置决定。怎么定下来的工具看EcucPduCollection里配置的 PDU 总量。只要 ECU 上任何一个模块处理的 PDU 数量超过 255就统一按uint16生成全部低于 256才可以用uint8。生成结果放在ComStack_Cfg.h里。一条容易踩的约束SWS_Comtype_00007模块内部的PduId必须从 0 开始连续递增。原因是很多模块拿 PduId 直接当下标去查表中间空一个号后面全错位。发送和接收各自维护一段连续编号最大值是N - 1。2.2 PduLengthType是什么PDU 的字节长度规范编号SWS_COMTYPE_00008。底层类型uint8、uint16或uint32配置项是PduLengthTypeEnumSWS_ECUC_00042取值适用场景uint8没开 TP 分片、最长帧不超过 255 字节的总线。经典 CAN 是 8 字节FlexRay 最大 254 字节。uint16走 TP 分片传长报文的总线比如 CanTp、LinTp、FrTp。ISO 15765-2 规定单包最大 4095 字节CAN FD 也落在这里。uint32以太网EthTp、SomeIpTp和大块数据传输LdCom。这个类型的原则是够用且不浪费。选了uint32去传 8 字节的 CAN 报文不会报错但每次传参、每个缓冲区长度变量都白占两个字节量大了就看得出来。2.3 PduInfoType通信栈里出现频率最高的结构体规范编号SWS_COMTYPE_00011。它把一次传输需要的信息打包成三个成员typedefstruct{uint8*SduDataPtr;/* 指向 PDU 实际载荷 (SDU) 的缓存 */uint8*MetaDataPtr;/* 指向元数据的缓存 */PduLengthType SduLength;/* 载荷的字节数 */}PduInfoType;SduDataPtr指向真正的数据。有个特殊约定值得记住当SduLength 0时这个指针允许是NULL_PTR。这种情况不是出错而是调用方在问你还能收多少不打算真的搬数据。MetaDataPtr指向元数据。元数据是 AUTOSAR 为了把动态协议头信息从报文里摘出来而设计的一条旁路CAN 报文的动态 ID、SoAd 的动态 Socket 连接 ID、诊断地址、LIN 的 NAD 都属于这类信息。它们该动态就让它动态不用为了传一个 ID 去改 SDU 的内容。没有配置元数据的 PDU这个指针就是NULL_PTR。SduLength是双向的。传进去的时候表示这次给你多少或者要你复制多少传出来的时候被调用方可能已经把它改成还剩多少。写代码时别假设它出去时还是原值。2.4 BufReq_ReturnType传输协议层在做分片传输时要不断向上层要缓冲区、把收到或要发的数据复制进上层提供的地址。上层能不能满足用这个枚举回答规范编号SWS_COMTYPE_00012。枚举值值含义TP 接下来怎么做BUFREQ_OK0x00缓冲区请求或数据复制成功继续下一片不需要额外处理。BUFREQ_E_NOT_OK0x01硬错误缓冲区不可用传输就地终止。网关会话中断、底层链路异常这类情况没有重试价值。BUFREQ_E_BUSY0x02暂时没空数据先留在原地TP 在计时窗口内重试。BUFREQ_E_OVFL0x03容量装不下只在接收路径出现上层说装不下CanTp 回一帧FC(OVFLW)并结束本次接收。补充一句BUFREQ_E_BUSY只出现在CopyTxData、CopyRxData这种逐片复制的调用上StartOfReception不会返回它。2.5 TpDataStateType 与 RetryInfoType重传和分片确认场景下TP 要告诉上层刚才复制的那段数据现在是什么情况。用的是这两个类型规范编号SWS_COMTYPE_00027和SWS_COMTYPE_00037。typedefenum{TP_DATACONF0x00,/* 之前复制的数据已确认可以从 TP 缓存释放 */TP_DATARETRY0x01,/* 传输出错要求回退到指定位置重新复制 */TP_CONFPENDING0x02/* 还没确认数据必须留在 TP 缓存里 */}TpDataStateType;typedefstruct{TpDataStateType TpDataState;/* TP 缓冲区的状态 */PduLengthType TxTpDataCnt;/* TP_DATARETRY 时要回退多少字节 */}RetryInfoType;为什么要留着已经复制过的数据因为两种事都可能发生发送侧可能得重发接收侧可能远端还没确认。真出问题时得能回到之前的位置把数据再取一遍。RetryInfoType里只有两个成员TpDataState说明状态TxTpDataCnt说明回退多少字节别的没有了。2.6 其余辅助类型这几个平时碰到得少用到时知道去哪找就行。NetworkHandleTypeSWS_COMTYPE_00038派生自uint8网络通道的句柄ComM、BswM 用它标识一条网络。PNCHandleTypeSWS_COMTYPE_00036派生自uint16标识局部网络簇Partial Network Cluster。TPParameterTypeSWS_COMTYPE_00031枚举用来动态修改 TP 参数TP_STMIN0x00最小间隔时间Separation Time。TP_BS0x01块大小Block Size。TP_BC0x02FlexRay 传输协议的带宽控制参数。CbkHandleIdTypeSWS_COMTYPE_91001规范里标为 Draft派生自uint16给 COM 和 LdCom 的用户回调当句柄用。ListElemStructTypeSWS_COMTYPE_91005同属 Draft单链表节点用于硬件加速或以太网、CAN XL 的多段数据链式传输typedefstructListElemStruct{uint16 DataLength;/* 本节点数据长度 */uint8*DataPtr;/* 本节点数据指针 */structListElemStruct*NextListElemPtr;/* 下一个节点末尾为 NULL_PTR */}ListElemStructType;3. 这些类型在工程里怎么用3.1 类型的位数是怎么定下来的这条链路值得走一遍因为很多人误以为ComStack_Types.h里的类型是硬编码的其实是三层搬运配置 → 生成 → 落地。EcucPduCollection是 EcuC 里的顶层容器跟类型相关的配置项有三个PduIdTypeEnumUINT8/UINT16→ 决定PduIdType。PduLengthTypeEnumUINT8/UINT16/UINT32→ 决定PduLengthType。MetaDataType和MetaDataItem→ 定义每个 PDU 挂哪几项元数据比如CAN_ID_32、SOCKET_CONNECTION_ID_16。代码生成器读完这些配置产出的就是ComStack_Cfg.h#defineCOMSTACK_PDUID_TYPEuint8/* 或 uint16 */#defineCOMSTACK_PDULENGTH_TYPEuint16/* 或 uint8 / uint32 */ComStack_Types.h里对应的就剩两行 typedef把宏接到标准类型上typedefCOMSTACK_PDUID_TYPE PduIdType;typedefCOMSTACK_PDULENGTH_TYPE PduLengthType;好处是明显的想改位数只改配置、重新生成不用去动头文件也不会出现某个模块按 uint8 编译、另一个按 uint16 编译这种对不上的情况。3.2 TP 分片收发的两条时序CanTp、SomeIpTp、FrTp 这几个模块的接口形状是一样的下面统一写成PduR_CanTpXxx换个模块把CanTp换掉即可。3.2.1 接收分片报文StartOfReceptionCanTp 收到首帧FF或单帧SF时调用一次。传入的info里带着首帧已经携带的那部分数据和元数据TpSduLength是整包总长度。上层负责分配缓冲区并通过bufferSizePtr告诉 CanTp 自己能提供多少容量然后返回BUFREQ_OK。如果装不下返回BUFREQ_E_OVFLCanTp 据此回一帧FC(OVFLW)并结束接收。CopyRxData每收到一个连续帧CF调用一次把这片的字节写进上层缓冲区同时通过bufferSizePtr回填剩余空间。CanTp 拿这个值判断还能不能继续收。RxIndication所有分片收完后调用一次通知上层整包齐了。3.2.2 发送分片报文CopyTxDataCanTp 要发下一片时调用。上层把数据复制到info-SduDataPtr指向的位置 —— 注意方向是上层往 TP 给出来填不是 TP 去上层拿。返回值是BUFREQ_OK或BUFREQ_E_BUSY返回BUSY说明数据暂时没准备好CanTp 会在等待窗口内重试。TxConfirmation整包发完或中途失败时调用一次。两个回调的参数里各有一个容易忽略的东西。发送侧多传了一个retryInfo它是 TP 告诉上层的不是上层告诉 TP 的 —— 方向上别搞反。它说明上一片是已经确认了、需要重传、还是还悬着如果是重传TxTpDataCnt指示要回退多少字节上层就得从那个位置重新复制。接收侧则是bufferSizePtr它是双向的参数进去时是本次要写入的数据量出来时被改成剩余容量。3.3 LdCom 的零拷贝传图像、点云这类大块数据时如果每经过一层就复制一次内存带宽和 CPU 都吃不消。所以AUTOSAR_CP_SWS_LargeDataCOM.pdf里的 LdCom 配了一套零拷贝机制靠的还是PduInfoType那几个指针只是约定更细。加锁上层调用Rte_Write触发LdCom_Transmit之后RTE 把这块缓冲区锁定标记为正在发送。直接取用TP 模块在中断或轮询上下文里经 PduR 回调 RTE 生成的那组回调函数工具里通常叫Rte_LdComCbkCopyTxData这类名字RTE 把锁定缓冲区的地址交给 TP中间不再额外拷一份。并发保护锁没释放之前如果应用层又调了一次Rte_WriteRTE 直接返回RTE_E_COM_BUSY正在发的数据一个字节都不会被覆盖。解锁收到Rte_LdComCbkTpTxConfirmation之后释放锁应用层可以写下一帧了。所以零拷贝并不是一次拷贝都没有而是只有一次数据从一开始就待在 RTE 的缓冲区里TP 直接从这里取走。代价是加锁期间这块缓冲区不能写第二份数据。4. 搭一份最小的 ComStack_Types.h把上面讲的类型串一遍下面这份可以直接编译用来对照实际工程里的版本/* ComStack_Types.h - 精简版用于对照理解 */#ifndefCOMSTACK_TYPES_H#defineCOMSTACK_TYPES_H#includeStd_Types.h#includeComStack_Cfg.h/* 里面定义 COMSTACK_PDUID_TYPE 和 COMSTACK_PDULENGTH_TYPE *//* 1. PDU 句柄位数由配置生成 */typedefCOMSTACK_PDUID_TYPE PduIdType;/* 2. PDU 长度位数由配置生成 */typedefCOMSTACK_PDULENGTH_TYPE PduLengthType;/* 3. 一次传输的全部信息 */typedefstruct{uint8*SduDataPtr;/* 载荷缓存 */uint8*MetaDataPtr;/* 元数据缓存 */PduLengthType SduLength;/* 载荷字节数 */}PduInfoType;/* 4. 缓冲区请求的结果 */typedefenum{BUFREQ_OK0x00,BUFREQ_E_NOT_OK0x01,BUFREQ_E_BUSY0x02,BUFREQ_E_OVFL0x03}BufReq_ReturnType;/* 5. TP 缓冲区状态 */typedefenum{TP_DATACONF0x00,TP_DATARETRY0x01,TP_CONFPENDING0x02}TpDataStateType;/* 6. 重传信息 */typedefstruct{TpDataStateType TpDataState;PduLengthType TxTpDataCnt;}RetryInfoType;/* 7. 网络通道句柄 */typedefuint8 NetworkHandleType;#endif/* COMSTACK_TYPES_H */要注意的是真实工程里这个文件不要自己写由 BSW 供应商或配置工具提供手写的版本跟工具生成的代码对不上。5. 三个真实踩坑现场5.1 PduIdType 位数不够路由到了错误的模块现象系统跑着跑着就崩或者某几个功能时好时坏。原因项目后期加了一批诊断和通信 PDU总数超过了 256但配置里的PduIdTypeEnum还是UINT8也没重新生成代码。PduId 到 255 之后再往上一截断就绕回去了 —— 256 变成 0。PduR 拿着这个 0 去查路由表就送去了完全不相干的上层模块后面要么空指针要么踩内存。处理检查EcucPduCollection里的PduIdTypeEnum是否设成了UINT16然后重新生成通信栈代码。加 PDU 的时候顺手看一眼总数比事后查崩溃省事得多。5.2 SduDataPtr 没初始化直接 HardFault现象调用PduR_CanTransmit(pduId, pduInfo)之后立刻跑飞。原因只写了pduInfo.SduLength 8SduDataPtr和MetaDataPtr没管。这两个是局部变量栈上的值是随机的底层驱动照着这个地址去复制数据解引用到非法地址总线错误或者 HardFault 就来了。处理声明之后把三个成员都赋上习惯成自然PduInfoType pduInfo;pduInfo.SduDataPtrtxBuffer;pduInfo.MetaDataPtrNULL_PTR;/* 没配元数据就显式给 NULL_PTR */pduInfo.SduLength8;5.3 上层一直 BUSY最后超时挂起现象CanTp 发长报文发到一半停了最后以上层E_NOT_OK上报失败。原因上层回调CopyTxData一直返回BUFREQ_E_BUSY。这种情况常见于上层在等某个硬件或服务准备好但一直没等到忙状态也没解除。CanTp 等超时之后只能放弃回一帧TxConfirmation(E_NOT_OK)。处理检查上层的数据准备逻辑把准备工作放到发起传输请求之前做完。CopyTxData里应该是复制一下就走不该在这里等外部条件。6. 小结这份规范看着短但它决定了通信栈里每个接口长什么样。真正需要记牢的是四件事PduIdType是句柄位数由配置决定模块内部必须从 0 开始连续编号。PduLengthType是长度选多宽取决于走不走 TP 分片、是不是以太网。PduInfoType打包一次传输三个成员各有约定SduLength是双向的SduDataPtr可以为NULL_PTR但要显式赋值。BufReq_ReturnType决定传输命运返回什么值下一步怎么走就定了。想再往下深入的话建议对着 PduR 和 CanTp 的时序图一起看同一个PduInfoType从 COM 传到 PduR再传到 CanTp每一层往里填了什么、取走了什么串一遍就清楚了。