尧图精选

【AUTOSAR】 CP PDU Router(PduR)--从入门到放弃

🕒 发布时间:2026/9/26 18:01:46 📁 来源:尧图网络
【AUTOSAR】 CP PDU RouterPduR–从入门到放弃基于 AUTOSAR Classic Platform R23-11 的 PduR 规范AUTOSAR_CP_SWS_PDURouter.pdf配合EXP_LayeredSoftwareArchitecture、SWS_CommunicationStackTypes、SWS_CANTransportLayer、TPS_ECUConfiguration几份一起看。目录PduR 是干什么的两种通信模式IF API 和 TP API网关与几种路由形态运行时开关路由BswM 与路由路径组配置模型与 Handle ID 映射用纯 C 模拟一下查表转发几个常见故障一页小结1. PduR 是干什么的通信栈往上数是懂信号的模块COM 知道 PDU 里第几个字节是车速往下数是懂帧和总线的模块CanIf 只知道这 8 个字节要发到 CAN ID 0x123 上。这两拨人中间需要一个翻译就是 PduR。PduR 只做一件事按静态配置的路由表把收到的 I-PDU 转发到该去的地方。它不拆信号、不校验数据内容、不改载荷也不认识任何具体总线。1.1 没有它会怎样以前的做法是上层直接调底层驱动接口比如 COM 自己想发就调CanIf_Transmit。这样写会碰到三个问题换个总线就要大改。某个信号从 CAN 挪到 FlexRay 或以太网上层的调用代码得跟着重写。网关逻辑到处散。CAN 转 FlexRay 这种事最后要么塞在应用层要么塞在驱动层谁看谁头疼。ID 各管各的。每个模块自己定报文编号互相之间没有统一映射改动一处不知道会牵连到哪。PduR 夹在中间之后这三个问题都收了回去上层只认识 PduR 一个对象网关逻辑集中在路由表里ID 之间的对应关系也统一由 PduR 维护。1.2 设计上坚持的三件事位置透明。上层把 PDU 交给 PduR 就完事底层是 CAN、LIN、FlexRay 还是 Ethernet它不需要知道。对载荷透明。PduR 不关心 PDU 里装的是什么字节只按 ID 转发。IF 和 TP 两种接口它都统一处理但内容一个字节都不碰。IF 路径上不开销外的事。单帧直通路由就是查一次表、调一次函数没有内存拷贝也没有额外队列。有一点提前说清楚PduR 出问题很安静。路由表里少配一条那个 PDU 就在系统里凭空消失不会报错、不会回调、也没有日志——除非开了 DET 钩子。所以配置阶段的疏忽往往要到实车联调才被发现。2. 两种通信模式IF API 和 TP APIPduR 的两套接口是分开的按关联 PDU 的类型走其中一条不会混。2.1 IF API单帧定长报文IF API 处理的是单帧、长度固定的短报文比如标准 CAN、CAN FD、LIN、FlexRay 上的单帧 I-PDU。三条路径分别是发送、接收、触发发送。发送路径COM 调PduR_ComTransmit(PduId, PduInfoPtr)。PduR 拿PduId查静态路由表找到目标模块和它在目标模块里的句柄比如CanIfTxPduId。PduR 调CanIf_Transmit(CanIfTxPduId, PduInfoPtr)。底层发完之后成功或失败都算异步回调PduR_CanIfTxConfirmationPduR 再把这个结果转成Com_TxConfirmation通知 COM。接收路径CanIf 收到总线报文后在中断或 MainFunction 上调用PduR_CanIfRxIndication(RxPduId, PduInfoPtr)。PduR 查表把RxPduId换成 COM 那边的ComRxPduId。PduR 直接调Com_RxIndication(ComRxPduId, PduInfoPtr)。这条路上没有缓冲也没有队列——CanIf 调上来PduR 换个 ID 就调下去整件事在一次调用里做完。触发发送路径FlexRay、LIN 这类时间触发或主从调度的总线数据不是上层主动推下去的而是总线接口到点了来要FrIf 或 LinIf 调PduR_FrIfTriggerTransmit(PduId, PduInfoPtr)。PduR 换完句柄后调Com_TriggerTransmit(ComPduId, PduInfoPtr)COM 现场把最新的信号打包填进缓冲区。注意这个方向是反的由下层发起向上要数据。2.2 TP API长报文分片TP API 处理超过单帧容量的长报文比如诊断报文、SOME/IP 的大报文、LdCom 的数据。因为一次装不下所以走的是分阶段握手一层一层往上转。第一步StartOfReceptionCanTp 收到首帧FF或单帧SF时调用PduR_CanTpStartOfReception(PduId, PduInfoPtr, TpSduLength, bufferSizePtr)。PduR 把请求转给上层比如 Dcm上层按总长度TpSduLength准备好缓冲区并通过bufferSizePtr回填自己最多能提供多少容量。如果上层装不下返回BUFREQ_E_OVFLCanTp 据此回一帧FC(OVFLW)结束本次接收。这也是 TP 接收唯一一次还没开始就结束的情况。第二步CopyRxData之后每来一个连续帧CFCanTp 调一次PduR_CanTpCopyRxData(PduId, PduInfoPtr, bufferSizePtr)。PduR 转给上层的Dcm_CopyRxData把这片的字节写进上层的 RAM 缓冲区同时更新剩余可用空间。上层如果一时拿不出空间可以返回BUFREQ_E_BUSY。接收端 TP 收到这个返回值会向对端回一帧FC(WAIT)等上层腾出空间再继续收。第三步RxIndication最后一个分片复制完CanTp 调PduR_CanTpRxIndication(PduId, result)通知上层整包齐了可以开始解析。发送方向只有一个回调需要记CanTp 要发下一片时调PduR_CanTpCopyTxData上层把数据填进去。如果上层暂时没准备好返回BUFREQ_E_BUSYCanTp 会在计时窗口内重试。3. 网关与几种路由形态前面讲的是 ECU 内部的垂直路由上下层之间收发。PduR 还能干水平的事把一条总线上收到的 PDU 直接转到另一条总线中间不经过应用层解包。3.1 跨总线网关按源和目标的类型分成两种两者不能混着配。IF 网关源和目标都是接口模块。CanIf 调PduR_CanIfRxIndication上来PduR 查表发现这条路径标了网关就不往上送而是直接调到目标接口去比如CanIf_Transmit或FrIf_Transmit。IF 网关支持 1:N一个源可以同时转到多个目标。TP 网关源和目标都是传输协议模块。比如 CanTp 收完一包长报文PduR 转到SomeIpTp_Transmit发到以太网上。TP 网关只支持 1:1一个源对一种目标 TP没有一对多。两条硬约束IF 和 TP 不能互换。收到的是 IF PDU只能从 IF PDU 转出去收到的是 TP PDU只能从 TP PDU 转出去。配错了工具链直接报错。网关路径上的发送确认 PduR 基本不管。目的模块发上来的PduR_TxConfirmation对网关 PDU 没有意义PduR 收到后不做处理。只有配了 FIFO 的直接数据提供型 PDU 是例外。PduR 不重试。如果目的模块返回E_NOT_OKPduR 不会换条路再试一次直接把这个 PDU 丢掉。3.2 TP On-The-Fly边收边转TP 网关有两种做法差别在延迟和内存。整包转发要等所有 N-PDU 都收完才开始向目标总线重新分片发送。一块 4095 字节的诊断报文就得按整包长度开缓冲区而且在收完之前目标总线一直是空着的。On-The-Fly是收到一定字节数之后就开始转一边从源总线收、一边往目标总线写。内存上只需要一个很小的 FIFO暂存几个分片就够了延迟也从整包时间缩短到一个分片左右。切换开关是系统模板里的PduRTpThreshold——它决定先收够多少字节才起转。配成 0 就是收到首帧立刻开始配大一点相当于多缓几帧。代价是两侧速率不匹配时快的一侧会把 FIFO 填满所以 FIFO 深度要按最坏情况下的速率差来算。3.3 Fan-out一发多收一个源 PDU 要同时分发给多个目标时用。配置上的表现是一个PduRSrcPdu对应多个PduRDestPdu。几个要留意的地方确认只有一份而且是最后一个目的模块确认到达时才上报。只要有一个目标没发出去上层看到的就是整次发送失败。带 Update Bit 的 PDU 不要配 Fan-out。COM 的更新位本来表示这一帧的值变了一个源对多个目标之后没法再用一个位表达这个意思。FlexRay 有个例外。如果 Fan-out 的多个目标落在同一个 FlexRay Cluster 的不同 Channel 上PduR 只向 FlexRayIf 提交一次发送请求——两个 Channel 由同一份数据驱动重复提交反而会造成调度冲突。3.4 Fan-in多收一反过来多个源往同一个目标送。分两种情况纯网关的 Fan-in多条路径可以同时处于激活状态互不干扰。转到本地模块的 Fan-in任意时刻最多只能有一条路径激活需要由 BswM 用PduR_EnableRouting保证。两条路径同时开会往同一块接收缓冲区里写。另外COM 的 Update Bit 和序列计数在 Fan-in 接收上也不生效——多个源共用一个目标这两个字段本身就失去了唯一性。3.5 两侧长度不一致怎么办跨总线网关经常碰到这个问题源总线是 CAN FD 的 64 字节目标总线是标准 CAN只能装 8 字节。PduR 的原则是取最小值具体分两种情况不缓冲直通转发转发长度 min(收到的长度, 目标 I-PDU 配置的长度)。目标 I-PDU 的长度本来就在配置里写好了不用额外配参数。超出的字节直接丢弃。带缓冲转发复制长度 min(收到的长度,PduRPduMaxLength)。这个参数在PduRBuffer容器里配要配得不大于目标 I-PDU 的长度——配大了Trigger Transmit 时目标缓冲区装不下PduR 会返回E_NOT_OK并且不处理这次调用。要理解的是截断不是错误处理是设计上的保护两条总线的长度本来就不可能一样PduR 只保证不越界不保证数据完整。如果长度的变化有业务含义比如过了网关信号布局要重新排那就不能走网关得让 COM 接收后重新打包再发到目标总线。4. 运行时开关路由BswM 与路由路径组路由路径不是配完就定死的。BswM 会根据车辆当前的工作模式点火、诊断激活、休眠准备、OTA 刷写在运行时把某些路径关掉或打开。控制的最小单位是路由路径组PduRRoutingPathGroup不是单个 PDU。PduR_EnableRouting(GroupId)激活指定的路由路径组组内所有 PDU 恢复转发。PduR_DisableRouting(GroupId)禁用指定的路由路径组组内所有 PDU 被丢弃。每个组在配置里有PduRIsEnabledAtInit决定初始化之后它是开还是关。没被任何路由路径组关联的PduRDestPdu初始化之后一直是使能状态运行时改不了。想动态开关就得把它挂进某个组。两个典型用法诊断刷写保护进入 UDS 编程会话或 OTA 刷写前关掉应用报文组把总线带宽让给诊断数据流。总线休眠准备网络管理准备休眠时先关掉应用组免得应用不合时宜地把总线唤醒。被禁用的路径直接丢弃不报错也不回调。这点和配置错误的表现一样排障时容易被绕进去——所以查问题时要先确认组的使能状态再怀疑 ID 配错。5. 配置模型与 Handle ID 映射这部分是 PduR 配置里最需要先搞懂的东西。5.1 Global PDU一个 PDU 只有一份定义在 ECU 配置里每个 PDU 对应一个全局的System_Pdu容器。Com、PduR、CanIf、CanTp 各自那份本地 PDU配置最后都指向同一个全局对象。这么做解决的是配置依赖配 PduR 的时候只需要引用全局 PDU不用等 CanIf 或 COM 的内部容器都建好。工具链可以并行推进各个模块的配置。代价也要知道改这个 Pdu 的长度会同时影响 COM 那边的信号布局和 CanIf 那边的 DLC。工具链不会告诉你你改的这个长度影响了哪些模块得自己盯住。5.2 一条 PduRRoutingPath 的组成路由表就是一堆PduRRoutingPath条目每条定义一条从源到目标的单向转发规则。没有路由表PduR 就是个空壳所有 PDU 进来都找不到出口。PduRSrcPdu源。里面是SrcPduRef引用哪个全局 Pdu和源侧的 Handle ID。PduRDestPdu目标可以有一个或多个。里面是DestPduRef同样引用那个全局 Pdu、目标侧的 Handle ID、PduRDestPduDataProvisionDirect 还是 TriggerTransmit以及网关或触发发送场景才需要的PduRQueueDepth。上下行要成对配。只配了Com → CanIf没配CanIf → Com总线上能看到报文但应用层读不到新值。这是 PduR 配置里出现频率最高的一种错误。5.3 Handle ID 是怎么换的每条调用都涉及两套编号调用方那边有一套被调方那边有一套。中间靠 PduR 的路由表对接。以 CAN 总线上收到一条引擎转速报文为例CanIf 收到帧在自己那张表里找到RxPduId 17调PduR_CanIfRxIndication(17, info)。PduR 用 17 当数组下标查静态路由表PduR_RxRoutingTable[17]。从表项里取出目标的 Handle IDComRxPduId 3。取出表项里的目标函数指针并调用Com_RxIndication(3, info)。COM 解析完RTE 和 SWC 那边就能读到新值。顺带一步也在查表时完成检查这条路径所属的路由路径组是不是使能状态。两个编号之间没有算术关系17 和 3 之间不存在加减乘除的换算全靠路由表映射。所以改完配置必须重新生成两边的代码只改一边会出现句柄对不上、数据送到别的模块去了这种很难查的问题。6. 用纯 C 模拟一下查表转发把上面的机制落成代码下面这段可以直接编译运行方便对着看 PduR 内部到底做了什么。#includestdio.h#includestdint.h#includestdbool.h/* 1. 通信栈基本类型实际工程里由 ComStack_Types.h ComStack_Cfg.h 提供 */typedefuint16_tPduIdType;typedefuint16_tPduLengthType;typedefstruct{uint8_t*SduDataPtr;uint8_t*MetaDataPtr;PduLengthType SduLength;}PduInfoType;/* 2. 上层接收回调的函数指针类型 */typedefvoid(*PduR_RxIndicationFctPtr)(PduIdType RxPduId,constPduInfoType*PduInfoPtr);/* 3. 一条路由表项 */typedefstruct{PduIdType TargetPduId;/* 换出来的目标 Handle ID */PduR_RxIndicationFctPtr RxIndicationFunc;/* 目标模块的回调 */uint16_tRoutingPathGroupId;/* 所属路由路径组 */bool IsEnabled;/* 运行时使能状态BswM 控制 */}PduR_RxRoutingTableType;/* 模拟上层的两个接收回调 */voidCom_RxIndication(PduIdType ComRxPduId,constPduInfoType*PduInfoPtr){printf([COM] 收到 PDU (HandleID: %d), 长度: %d, Data[0]: 0x%02X\n,ComRxPduId,PduInfoPtr-SduLength,PduInfoPtr-SduDataPtr[0]);}voidDcm_RxIndication(PduIdType DcmRxPduId,constPduInfoType*PduInfoPtr){printf([DCM] 收到诊断 PDU (HandleID: %d), 长度: %d\n,DcmRxPduId,PduInfoPtr-SduLength);}/* 4. 静态路由表下标就是 CanIf 那边的句柄编号 */#defineROUTING_GROUP_APP1#defineROUTING_GROUP_DIAG2#defineRX_TABLE_SIZE(sizeof(PduR_CanIfRxRoutingTable)/sizeof(PduR_RxRoutingTableType))PduR_RxRoutingTableType PduR_CanIfRxRoutingTable[]{/* [0] CanIfRxPduId 0 - ComRxPduId 10应用组初始使能 */{.TargetPduId10,.RxIndicationFuncCom_RxIndication,.RoutingPathGroupIdROUTING_GROUP_APP,.IsEnabledtrue},/* [1] CanIfRxPduId 1 - DcmRxPduId 100诊断组初始禁用 */{.TargetPduId100,.RxIndicationFuncDcm_RxIndication,.RoutingPathGroupIdROUTING_GROUP_DIAG,.IsEnabledfalse},};/* 5. PduR 接收入口由 CanIf 调用 */voidPduR_CanIfRxIndication(PduIdType RxPduId,constPduInfoType*PduInfoPtr){PduR_RxRoutingTableType*route;/* 下标越界检查实际工程里靠 DET 报错 */if(RxPduIdRX_TABLE_SIZE){printf([PduR] 无效的 RxPduId: %d丢弃\n,RxPduId);return;}routePduR_CanIfRxRoutingTable[RxPduId];/* 路由路径组的使能检查 */if(!route-IsEnabled){printf([PduR] RxPduId %d 所属路径组被禁用丢弃\n,RxPduId);return;}/* 查表转发 */if(route-RxIndicationFunc!NULL){route-RxIndicationFunc(route-TargetPduId,PduInfoPtr);}}/* 6. BswM 用来开关路由路径组 */voidPduR_SetRouting(uint16_tGroupId,bool enable){size_ti;for(i0;iRX_TABLE_SIZE;i){if(PduR_CanIfRxRoutingTable[i].RoutingPathGroupIdGroupId){PduR_CanIfRxRoutingTable[i].IsEnabledenable;printf([PduR] 路由路径组 %d 已%s\n,GroupId,enable?使能:禁用);}}}/* 7. 跑一下 */intmain(void){uint8_tpayload[8]{0xAB,0x12,0x34,0x56,0x78,0x90,0xEF,0x00};PduInfoType pdu{.SduDataPtrpayload,.SduLength8,.MetaDataPtrNULL};printf(--- 场景 1常规应用报文CanIfRxPduId 0 ---\n);PduR_CanIfRxIndication(0,pdu);printf(\n--- 场景 2诊断报文CanIfRxPduId 1初始被禁用 ---\n);PduR_CanIfRxIndication(1,pdu);printf(\n--- 场景 3BswM 打开诊断组再发一次 ---\n);PduR_SetRouting(ROUTING_GROUP_DIAG,true);PduR_CanIfRxIndication(1,pdu);return0;}这段代码把三个要点露了出来下标越界的检查、路由路径组的使能检查、以及查表拿函数指针再调用这个动作本身。真实的 PduR 生成代码比这复杂得多发送侧还要处理网关缓冲、Fan-out 多目标、TP 的复制回调但主干就是这个样子。7. 几个常见故障现象可能的原因怎么查总线上有报文上层 COM 或 DCM 收不到1. BswM 没调PduR_EnableRouting路由路径组还关着。2.PduRSrcPdu的 Handle ID 和CanIfCanRxPduId对不上。在PduR_CanIfRxIndication下断点看传进来的RxPduId在不在路由表里再检查这条路径所属组的使能状态。长报文收到一半中断返回BUFREQ_E_NOT_OK上层在StartOfReception阶段就没能申请到接收缓冲区可能是缓冲区配小了也可能是上一次诊断请求没释放。看StartOfReception的返回值和TpSduLength对不对得上再核对 DCM 的DcmDslBufferSize。跨总线网关转发的报文目标侧末尾数据不对源总线 PDU 比目标总线长超出的部分被截断了。对比源侧和目标的报文长度检查目标 I-PDU 配置的长度以及带缓冲时的PduRPduMaxLength。初始化阶段就复位定位到空指针PduR 的配置结构体没传进来或者PduR_Init调的时机太晚。检查EcuM_Init/BswM_Init里的调用顺序确保PduR_Init在任何接收中断被使能之前就执行完。8. 一页小结如果只记五条就记这五条PduR 只做 PDU 级别的转发不拆信号、不改载荷。路由配漏一条PDU 静默消失。IF API 处理单帧定长直通调用不缓冲TP API 处理长报文靠StartOfReception/CopyData逐片握手。路由分三种垂直收发、跨总线网关、Fan-out / Fan-in。网关的 IF 和 TP 不能混配。网关侧的长度差异靠取最小值处理直通时按目标 I-PDU 配置长度截断带缓冲时按PduRPduMaxLength截断。运行时开关的最小单位是路由路径组由 BswM 调PduR_EnableRouting/PduR_DisableRouting控制。想再往下深入建议按这个顺序读规范先看SWS_PDURouter第 9 章的序列图收 / 发 / 网关各一条再回来对着第 10 章的配置容器看一遍PduRRoutingPath、PduRSrcPdu、PduRDestPdu这三个容器的参数。把一条真实的路由路径从配置到运行时代码串一遍比看十遍文字管用。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →