AUTOSAR 中的 PDU Router(PduR,PDU 路由模块) 通过在系统中路由协议数据单元(PDU)来管理各软件模块(如 CanTp、Dcm、LinTp)之间的通信。本文介绍如何配置PduR 的路由规则(routines)、网络(networks)、内存池(memory)和网关缓冲区(buffers),以及 JSON 定义与自动代码生成机制。
PduR 是一个软件路由器:
Dcm 与 CanTp 之间);CanTp 与 CanTp 之间把 CAN PDU 路由到另一个 CAN 网络);Com 和 CanIf(PduR 本身不直接操作硬件)。CAN0、LIN0),用于从 DBC 文件自动生成路由规则;from / to 字段使用以下模块标识:
| 模块 | 类型 | 说明 |
|---|---|---|
CanIf |
低层模块 | CAN 接口层 |
CanTp |
传输协议(TP) | CAN 传输协议 |
LinTp |
传输协议(TP) | LIN 传输协议 |
DoIP |
传输协议(TP) | DoIP 传输协议 |
J1939Tp |
传输协议(TP) | J1939 传输协议 |
Dcm |
高层模块 | 诊断通信管理 |
Com |
低层模块 | 基于信号的通信 |
OsekNm |
网络管理 | OSEK 网络管理 |
CanNm |
网络管理 | CAN 网络管理 |
PduR |
路由器 | PDU Router(自引用) |
SecOC |
安全 | 安全车载通信 |
Mirror |
镜像 | 用于测试/观测的 Mirror 模块 |
TP 模块:
DoIP、CanTp、LinTp、J1939Tp- 当from和to都是 TP 模块时,判定为网关场景,生成网关专用代码(缓冲、拷贝函数等)。高层模块:
Dcm- 目标为 Dcm 的路由按高优先级诊断路径处理。低层模块:TP 模块加
CanIf- 除 Dcm 外的所有其他模块。
下图展示 PduR 位于 AUTOSAR 通信栈中心,在高层模块(Dcm、Com)、安全模块(SecOC)、传输协议(CanTp、LinTp、DoIP、J1939Tp)和总线接口(CanIf、LinIf)之间路由 PDU。Mirror 通过 PduR_MirrorTransmit 从 PduR 获得路由出的 PDU,但镜像帧直接发送到CanIf/LinIf。CanNm 和 OsekNm 完全绕过 PduR,直接与 CanIf 通信。
flowchart TB
subgraph Upper["高层(编号 > ISOTP)"]
DCM["Dcm"]
COM["Com"]
end
subgraph Security["安全层"]
SECOC["SecOC"]
end
subgraph RouterCenter["PDU 路由器"]
PR["PduR<br/>路由路径 / 网关缓冲 / API 表 / 内存池"]
end
subgraph Transport["传输协议(TP)层"]
CANTP["CanTp"]
LINTP["LinTp"]
DOIP["DoIP"]
J1939TP["J1939Tp"]
end
subgraph BusIF["总线接口层"]
CANIF["CanIf"]
LINIF["LinIf"]
end
subgraph NM["网络管理"]
CANNM["CanNm"]
OSEKNM["OsekNm"]
end
subgraph Other["其他"]
MIRROR["Mirror"]
end
DCM <--> PR
COM <--> PR
SECOC <--> PR
PR <---> CANTP
PR <---> LINTP
PR <---> DOIP
PR <---> J1939TP
PR <---> CANIF
PR <---> LINIF
MIRROR -.->|PduR_MirrorTransmit| PR
CANTP <---> CANIF
LINTP <---> LINIF
CANNM <--> CANIF
CANNM -.->|PduR_CanNmTriggerTransmit<br/>仅用户数据| COM
OSEKNM <--> CANIF
MIRROR <---> CANIF
MIRROR <---> LINIF
分层边界:ISOTP 边界(模块编号 > 8)把高层模块(Dcm、Com、SecOC、Mirror)与低层模块(CanIf、CanTp、LinTp、DoIP、J1939Tp、CanNm、OsekNm)分开。该边界决定网关转发行为 - 高层目标通过 TP 回调接收数据,低层目标则通过直接调用 Transmit 接收数据。
{
"class": "PduR",
"routines": [
{
"name": "P2P_RX",
"from": "CanTp",
"to": "Dcm"
},
{
"name": "P2P_TX",
"from": "Dcm",
"to": "CanTp"
}
],
"networks": [
{
"name": "CAN0",
"network": "CAN",
"me": "AS",
"use_dbc": true,
"dbc": "CAN0.dbc",
"ignore": ["IGNORED_MSG"]
}
],
"memory": [
{
"name": "MyPool",
"size": 256,
"number": 2
}
],
"buffers": [
{
"name": "GwBuf",
"size": 4096
}
]
}
Routines 定义 PDU 如何在模块之间路由。每条规则把一个 PDU 从源模块映射到一个或多个目标模块。生成器为每条规则分配唯一索引(宏 PDUR_<name>),并生成源/目标 PDU结构体。
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
name |
string | 是 | - | PDU 名称,引用自 EcuC.Pdus[].name,用于生成 PDUR_<name> 宏 |
from |
string | 是 | - | 源模块,取值:CanIf、CanTp、OsekNm、CanNm、PduR、Dcm、Com、LinTp、DoIP、J1939Tp、SecOC、Mirror |
to |
string | 是 | - | 目标模块,枚举同 from |
useDest |
bool | 否 | true |
启用/禁用 dest 字段;为 false 时忽略 dest,目标 PDU 名直接使用 name |
dest |
string | 否 | name |
替代的目标 PDU 名,仅在 useDest 为 true 时生效;设置后目标 PDU 标识使用该名称,同时生成 PDUR_<name> 和 PDUR_<dest> 两个宏 |
useDestBuffer |
bool | 否 | false |
启用/禁用网关缓冲配置;为 true 时 DestBufferType、DestBuffer、DestBufferSize 生效 |
DestBufferType |
string | 否 | private |
网关缓冲类型:"private" 分配大小为 DestBufferSize 的专用缓冲;"shared" 通过 DestBuffer 名引用 buffers[] 中预定义的缓冲。仅在 useDestBuffer 为 true 时生效 |
DestBuffer |
string | 否 | - | buffers[] 中预定义的共享网关缓冲名,仅在 useDestBuffer 为 true 且 DestBufferType 为 "shared" 时生效 |
DestBufferSize |
integer | 否 | 0 | 本规则专用私有网关缓冲的大小;当 > 0 且 useDestBuffer 为 true、DestBufferType 为 "private" 时,分配私有的 PduR_GwBuffer_<name> |
useFake |
bool | 否 | true |
启用/禁用 fake 字段;为 false 时忽略 fake,不生成 fake PDU 宏 |
fake |
string | 否 | - | 镜像路由使用的 fake PDU 名,仅在 useFake 为 true 时生效;生成指向同一索引的附加宏 PDUR_<fake> |
destinations |
array | 否 | - | 多播/镜像路由的附加目标,每个条目可把 PDU 复制到其他模块 |
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
name |
string | 是 | - | 该附加分支的目标 PDU 名 |
to |
string | 是 | - | 目标模块(枚举同规则的 to);from 继承父规则 |
useFake |
bool | 否 | false |
是否为该目标启用 fake PDU 名 |
fake |
string | 否 | - | 该目标分支镜像路由的 fake PDU 名,仅在父规则 useFake 为 true 时生效 |
当 from 和 to(或任一 destinations[].to)都是 TP 模块(DoIP、CanTp、LinTp、J1939Tp)时,生成器:
hasGW = true,生成 PDUR_USE_TP_GATEWAY 宏;PduR_BufferType;PduR_<Mod>GwStartOfReception、PduR_<Mod>GwCopyRxData、PduR_<Mod>GwRxIndication 等)。对需要数据缓冲的网关规则,先把 useDestBuffer 置为 true,再通过DestBufferType 选择缓冲类型:
DestBufferType = "private":为本规则分配专用uint8_t PduR_GwBuffer_<name>[<DestBufferSize>],大小由 DestBufferSize 指定;DestBufferType = "shared":通过 DestBuffer 按名引用 buffers[] 中预定义的缓冲。useDestBuffer 为 false 时不使用专用网关缓冲(NULL)。
对每条规则,生成器输出:
#define PDUR_<name> <index> - 路由路径的唯一从零开始索引;PduR_SrcPdu_<from>_<to>_<name> - 包含模块 ID、PDU ID 和 API 表指针;PduR_DstPdu_<from>_<to>_<name>[] - 目标 PDU 条目数组;PduR_RoutingPaths[] 表中的一个条目,把源与各目标(及可选网关缓冲)连接起来。下图展示路由路径数据结构以及一个源 PDU 如何扇出到多个目标:
flowchart LR
subgraph Src["源(PduR_RoutingPathType)"]
SP["SrcPduRef<br/>PduR_PduType"]
end
subgraph Dests["目标数组(DestPduRef[0..N])"]
D0["[0] to: Dcm"]
D1["[1] to: Mirror"]
D2["[2] to: CanTp"]
end
subgraph API["API 表(PduR_ApiType)"]
ADCM["DcmApi: StartOfReception / CopyRxData / TpRxIndication / CopyTxData / TpTxConfirmation"]
AMIR["MirrorApi: TxConfirmation"]
ACTP["CanTpApi: GwStartOfReception / GwCopyRxData / GwRxIndication / GwCopyTxData / GwTxConfirmation / CanTp_Transmit"]
end
subgraph GW["网关缓冲(可选)"]
GB["GwBuffer / DestTxBufferRef<br/>静态 uint8 数组或内存池"]
end
SP --> D0
SP --> D1
SP --> D2
D0 -.-> ADCM
D1 -.-> AMIR
D2 -.-> ACTP
D2 -.-> GW
路由模式:
| API | 扇出范围 | 目标 | 是否支持网关 |
|---|---|---|---|
PduR_Transmit / PduR_RxIndication |
所有目标 | DestPduRef[0..N] | 否 |
PduR_TpTransmit |
仅第一个 | DestPduRef[0] | 是(使用 GwBuffer) |
PduR_StartOfReception / CopyRxData / TpRxIndication |
仅第一个 | DestPduRef[0] | 否 |
PduR_CopyTxData / TxConfirmation |
仅源 | SrcPduRef | 是(使用 GwBuffer) |
PduR_Gw* |
网关专用 | 缓冲 -> DestPduRef[0..N] | 是 |
{
"name": "DiagRequest_RX",
"from": "CanTp",
"to": "Dcm"
}
把 PDU 从 CanTp(传输层)路由到 Dcm(诊断管理)。
{
"name": "CAN0_Diag",
"from": "CanTp",
"to": "DoIP",
"DestBufferSize": 2048
}
把 PDU 从 CanTp 路由到 DoIP,使用专用的 2048 字节网关缓冲。
{
"name": "CAN1_MSG",
"from": "CanIf",
"to": "Com",
"destinations": [
{
"name": "CAN1_MSG_MIRROR",
"to": "Mirror",
"from": "CanIf"
}
]
}
把 CAN 报文路由到 Com,同时镜像一份到 Mirror 模块用于监测。
{
"name": "J1939_To_CanTp",
"from": "J1939Tp",
"to": "CanTp",
"DestBuffer": "GwBuf"
}
把 PDU 从 J1939Tp 路由到 CanTp,使用名为 GwBuf(在 buffers[] 中定义)的共享缓冲。
每条规则在 PduR_Cfg.h 中生成以下宏:
#define PDUR_<name> <index>
#define PDUR_<dest> <index> // 仅当 dest != name
#define PDUR_<fake> <index> // 仅当设置了 fake
#define PDUR_<destinations[i].name> <index> // 每个附加目标
#define PDUR_<destinations[i].fake> <index> // 每个带 fake 的附加目标
Networks 定义逻辑通信通道(如 CAN0、LIN0),用于从 DBC 文件自动生成路由规则。与 CanIf(负责物理总线配置)不同,PduR 的 networks 关注模块间路由的自动发现。
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
name |
string | 是 | CAN? |
网络逻辑名(如 CAN0),用作生成 PDU 符号的前缀 |
network |
string | 是 | - | 物理网络类型,必须为 "CAN" 或 "LIN";决定低层模块:CAN -> CanIf,LIN -> LinIf |
me |
string | 是 | AS |
自身节点名。node == me 的报文视为发送(Com -> CanIf/LinIf),其余视为接收(CanIf/LinIf -> Com) |
use_dbc |
boolean | 否 | false |
启用基于 DBC 的路由自动生成;为 true 时 dbc 必须指向有效文件 |
dbc |
string | 条件需要 | "" |
Vector CAN DBC 文件路径,相对于配置目录解析;use_dbc 为 true 时必需 |
ignore |
array | 否 | - | 从 DBC 自动生成中排除的 PDU/报文名列表 |
use_dbc 为 true 时,生成器的 extract() 函数:
ignore 中的报文:
msg.node == me(自身发送):生成 TX 路由(from: "Com" -> to: "CanIf"/"LinIf"),PDU 名加后缀 _TX;msg.node != me(接收他人报文):生成 RX 路由(from: "CanIf"/"LinIf" -> to: "Com"),PDU 名加后缀 _RX;PduR.json。常用后缀:接收为
_RX,发送为_TX。生成器会检查后缀,缺失时自动补上。
{
"name": "CAN0",
"network": "CAN",
"me": "AS",
"use_dbc": true,
"dbc": "config/CAN0.dbc",
"ignore": ["DEBUG_MSG", "TEST_MSG"]
}
为 CAN0.dbc 中除 DEBUG_MSG 和 TEST_MSG 之外的所有报文生成路由规则。
memory 数组定义 PduR 内部数据缓冲使用的内存池。配置后生成PDUR_USE_MEMPOOL 宏,并输出 MemCluster 定义。
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
name |
string | 是 | - | 内存池名,用作 cluster 标识 |
size |
integer | 否 | 256 | 每个内存块的字节数,范围 0 - 4294967295 |
number |
integer | 否 | 2 | 池中内存块数量,范围 0 - 4294967295 |
"memory": [
{
"name": "PduR_MemPool",
"size": 512,
"number": 4
}
]
创建名为 PduR_MemPool 的内存池,含 4 个各 512 字节的内存块。
存在 memory 时,生成器:
#define PDUR_USE_MEMPOOL;MC.Gen_Macros() 生成 MemCluster 宏;MC.Gen_Defs() 生成内存池定义;PduR_Config 中引用 &MC_PduR。buffers 数组定义共享网关缓冲池,可被多条路由规则通过 DestBuffer 参数引用,用于 TP 到 TP 网关场景中的临时数据存储。
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|---|---|---|---|---|
name |
string | 是 | - | 缓冲名,被路由规则的 DestBuffer 引用 |
size |
integer | 否 | 4096 | 缓冲字节数,范围 0 - 4294967295 |
"buffers": [
{
"name": "GwBuf",
"size": 8192
}
]
分配静态缓冲 uint8_t PduR_GwBuffer_GwBuf[8192]。
每个缓冲条目生成:
static uint8_t PduR_GwBuffer_<name>[<size>];
以下构建期配置开关由宏控制:
| 宏 | 说明 | 默认值 |
|---|---|---|
PDUR_USE_PB_CONFIG |
启用 post-build 配置支持 | 启用 |
PDUR_USE_MEMPOOL |
启用内存池支持 | 配置了 memory 时启用 |
PDUR_USE_TP_GATEWAY |
启用 TP 到 TP 网关支持 | 存在 TP 到 TP 路由时启用 |
PDUR_USE_PB_CONFIG 宏由 PduR 配置级的 UsePostBuildConfig 选项控制(默认true);置为 false 时该宏被注释掉。
PduR 公共 API 定义在 PduR.h,遵循 AUTOSAR CP 4.4.0。
| 宏 | 值 | 说明 |
|---|---|---|
PDUR_E_PDU_ID_INVALID |
0x02 | PDU ID 超出有效范围 |
PDUR_E_ROUTING_PATH_GROUP_ID_INVALID |
0x08 | 路由路径组 ID 无效 |
PDUR_E_PARAM_POINTER |
0x09 | 向 API 传入空指针 |
| 函数 | 说明 |
|---|---|
PduR_Init(const PduR_ConfigType *ConfigPtr) |
初始化 PduR 模块;定义了 PDUR_USE_MEMPOOL 时调用 PduR_MemInit() 初始化内存池 |
PduR_EnableRouting(PduR_RoutingPathGroupIdType id) |
使能一个路由路径组 |
PduR_DisableRouting(PduR_RoutingPathGroupIdType id, boolean initialize) |
禁用一个路由路径组 |
PduR_GetVersionInfo(Std_VersionInfoType *versionInfo) |
返回 PduR 模块版本信息:vendor STD_VENDOR_ID_AS、module MODULE_ID_PDUR、版本 4.0.0 |
每个接入模块暴露一组适配函数作为 PduR 的回调入口,定义在各模块专用头文件中:
| 函数 | 方向 | 说明 |
|---|---|---|
PduR_CanIfRxIndication(RxPduId, PduInfoPtr) |
Rx | 把 CAN 接收指示转发给 PduR 路由,委托给 PduR_RxIndication() |
PduR_CanIfTxConfirmation(TxPduId, result) |
Tx | 转发 CAN 发送确认,委托给 PduR_TxConfirmation() |
| 函数 | 说明 |
|---|---|
PduR_CanTpStartOfReception(id, info, TpSduLength, bufferSizePtr) |
委托给 PduR_StartOfReception() |
PduR_CanTpCopyRxData(id, info, bufferSizePtr) |
委托给 PduR_CopyRxData() |
PduR_CanTpCopyTxData(id, info, retry, availableDataPtr) |
委托给 PduR_CopyTxData() |
PduR_CanTpRxIndication(id, result) |
委托给 PduR_TpRxIndication() |
PduR_CanTpTxConfirmation(id, result) |
委托给 PduR_TxConfirmation() |
PduR_CanTpGwStartOfReception(id, info, TpSduLength, bufferSizePtr) |
网关:委托给 PduR_GwStartOfReception() |
PduR_CanTpGwCopyRxData(id, info, bufferSizePtr) |
网关:委托给 PduR_GwCopyRxData() |
PduR_CanTpGwCopyTxData(id, info, retry, availableDataPtr) |
网关:委托给 PduR_GwCopyTxData() |
PduR_CanTpGwRxIndication(id, result) |
网关:先调用 LINTP_GW_USER_HOOK_RX_IND,再委托给 PduR_GwRxIndication() |
PduR_CanTpGwTxConfirmation(id, result) |
网关:委托给 PduR_GwTxConfirmation() |
| 函数 | 说明 |
|---|---|
PduR_ComTransmit(TxPduId, PduInfoPtr) |
委托给 PduR_Transmit() |
PduR_ComRxIndication(RxPduId, PduInfoPtr) |
委托给 PduR_RxIndication() |
PduR_ComTxConfirmation(TxPduId, result) |
委托给 PduR_TxConfirmation() |
PduR_ComTriggerTransmit(TxPduId, PduInfoPtr) |
触发发送(SWS_PduR_00369) |
| 函数 | 说明 |
|---|---|
PduR_DcmTransmit(TxPduId, PduInfoPtr) |
委托给 PduR_TpTransmit() |
PduR_DcmCancelTransmit(TxPduId) |
取消正在进行的 TP 发送 |
PduR_DcmCancelReceive(RxPduId) |
取消正在进行的 TP 接收 |
| 函数 | 说明 |
|---|---|
PduR_DoIPStartOfReception(id, info, TpSduLength, bufferSizePtr) |
委托给 PduR_StartOfReception() |
PduR_DoIPCopyRxData(id, info, bufferSizePtr) |
委托给 PduR_CopyRxData() |
PduR_DoIPCopyTxData(id, info, retry, availableDataPtr) |
委托给 PduR_CopyTxData() |
PduR_DoIPRxIndication(id, result) |
委托给 PduR_TpRxIndication() |
PduR_DoIPTxConfirmation(id, result) |
委托给 PduR_TxConfirmation() |
PduR_DoIPGwStartOfReception(id, info, TpSduLength, bufferSizePtr) |
网关:委托给 PduR_GwStartOfReception() |
PduR_DoIPGwCopyRxData(id, info, bufferSizePtr) |
网关:委托给 PduR_GwCopyRxData() |
PduR_DoIPGwCopyTxData(id, info, retry, availableDataPtr) |
网关:委托给 PduR_GwCopyTxData() |
PduR_DoIPGwRxIndication(id, result) |
网关:委托给 PduR_GwRxIndication() |
PduR_DoIPGwTxConfirmation(id, result) |
网关:委托给 PduR_GwTxConfirmation() |
| 函数 | 说明 |
|---|---|
PduR_J1939TpTransmit(TxPduId, PduInfoPtr) |
委托给 PduR_TpTransmit() |
PduR_J1939TpStartOfReception(id, info, TpSduLength, bufferSizePtr) |
委托给 PduR_StartOfReception() |
PduR_J1939TpCopyRxData(id, info, bufferSizePtr) |
委托给 PduR_CopyRxData() |
PduR_J1939TpCopyTxData(id, info, retry, availableDataPtr) |
委托给 PduR_CopyTxData() |
PduR_J1939TpRxIndication(id, result) |
委托给 PduR_TpRxIndication() |
PduR_J1939TpTxConfirmation(id, result) |
委托给 PduR_TxConfirmation() |
函数集与 CanTp 相同(StartOfReception、CopyRxData、CopyTxData、RxIndication、TxConfirmation 加网关变体)。
| 函数 | 说明 |
|---|---|
PduR_SecOCTransmit(TxPduId, PduInfoPtr) |
委托给 PduR_Transmit() |
PduR_SecOCRxIndication(RxPduId, PduInfoPtr) |
委托给 PduR_RxIndication() |
PduR_SecOCTxConfirmation(TxPduId, result) |
委托给 PduR_TxConfirmation() |
PduR_SecOCTriggerTransmit(TxPduId, PduInfoPtr) |
触发发送 |
| 函数 | 说明 |
|---|---|
PduR_MirrorTransmit(TxPduId, PduInfoPtr) |
委托给 PduR_TpTransmit() |
私有头文件 PduR_Priv.h 定义核心运行时数据结构。
PduR_ModuleType)typedef enum {
PDUR_MODULE_CANIF, // 0 - CAN 接口层
PDUR_MODULE_CANTP, // 1 - CAN 传输协议
PDUR_MODULE_J1939TP, // 2 - J1939 传输协议
PDUR_MODULE_LINIF, // 3 - LIN 接口层
PDUR_MODULE_LINTP, // 4 - LIN 传输协议
PDUR_MODULE_DOIP, // 5 - DoIP 传输协议
PDUR_MODULE_CANNM, // 6 - CAN 网络管理
PDUR_MODULE_OSEKNM, // 7 - OSEK 网络管理
/* ---- 边界 ---- */
PDUR_MODULE_ISOTP, // 8 - ISO-TP 边界标记
PDUR_MODULE_SECOC, // 9 - 安全车载通信
PDUR_MODULE_COM, // 10 - 通信(基于信号)
PDUR_MODULE_DCM, // 11 - 诊断通信管理
PDUR_MODULE_MIRROR, // 12 - Mirror 模块
} PduR_ModuleType;
架构意义:编号不大于
PDUR_MODULE_ISOTP(0-8)的模块视为”低层”模块,编号在其上(9-12)的为”高层”模块。这会影响网关路由行为(见网关数据流一节)。
PduR_ApiType)每个模块注册一张含 7 个函数指针的表:
typedef struct {
BufReq_ReturnType (*StartOfReception)(PduIdType, const PduInfoType*,
PduLengthType, PduLengthType*);
BufReq_ReturnType (*CopyRxData)(PduIdType, const PduInfoType*, PduLengthType*);
void (*TpRxIndication)(PduIdType, Std_ReturnType);
void (*RxIndication)(PduIdType, const PduInfoType*);
Std_ReturnType (*Transmit)(PduIdType, const PduInfoType*);
BufReq_ReturnType (*CopyTxData)(PduIdType, const PduInfoType*,
const RetryInfoType*, PduLengthType*);
void (*TxConfirmation)(PduIdType, Std_ReturnType);
} PduR_ApiType;
未使用的回调允许为 NULL 指针,每个模块只填写自己支持的回调。
PduR_PduType)typedef struct {
PduR_ModuleType Module; // 模块标识(枚举)
PduIdType PduHandleId; // 该模块侧的 PDU 标识
const PduR_ApiType *api; // 指向模块 API 表
} PduR_PduType;
PduR_BufferType)typedef struct {
uint8_t *data; // 缓冲数据指针
PduLengthType size; // 缓冲总大小
PduLengthType index; // 当前读写位置
} PduR_BufferType;
PduR_RoutingPathType)typedef struct {
const PduR_PduType *SrcPduRef; // 源 PDU 描述符
const PduR_PduType *DestPduRef; // 目标 PDU 数组(首条目)
PduR_BufferType *DestTxBufferRef; // 网关发送缓冲(可选)
uint8_t *GwBuffer; // 静态网关目标缓冲
PduLengthType GwBufferSize; // 静态缓冲大小
uint16_t numOfDestPdus; // 目标条目数量
} PduR_RoutingPathType;
struct PduR_Config_s {
#if defined(PDUR_USE_MEMPOOL)
const mem_cluster_t *mc; // 内存池 cluster 描述符
#endif
const PduR_RoutingPathType *RoutingPaths; // 所有路由路径数组
uint16_t numOfRoutingPaths; // 路由路径数量
};
每个 PduR API 函数根据路径配置遵循特定的路由模式。
PduR_Transmit)pathId 是否小于 numOfRoutingPaths(失败上报PDUR_E_PDU_ID_INVALID);PduInfoPtr 和 PduInfoPtr->SduDataPtr(失败上报PDUR_E_PARAM_POINTER);DestPduRef[0..numOfDestPdus-1]);Transmit 回调;E_OK,否则返回第一个失败码。PduR_TpTransmit)DestPduRef[0](仅第一个目标,不是全部);DestTxBufferRef != NULL),使用 pathId 作为 PDU 句柄;PduHandleId。PduR_StartOfReception -> PduR_CopyRxData ->PduR_TpRxIndication)
三个函数都只路由到 DestPduRef[0]:
PduR_RxIndication)PduR_Transmit 相同);RxIndication 回调。PduR_CopyTxData、PduR_TxConfirmation)
SrcPduRef(源模块,不是目标);pathId 作为 PDU 句柄;PduHandleId。全局使用一致的模式:当 DestTxBufferRef 不为 NULL(网关模式)时,pathId 本身作为 PDU 句柄 ID;否则使用模块配置的 PduHandleId:
if (RoutingPath->DestTxBufferRef != NULL)
PduHandleId = pathId; // 使用路由路径索引
else
PduHandleId = DestPduRef->PduHandleId; // 使用模块自身 PDU ID
网关路由处理 TP 到 TP 转发(如 CanTp 与 DoIP、CanTp 与 LinTp 之间)。网关机制使用内部缓冲先暂存完整 TP 报文,再转发。
下图展示一次 TP 到 TP 网关转发的完整生命周期:
sequenceDiagram
participant SRC as 源 TP 模块(如 CanTp)
participant PR as PduR 网关
participant BUF as 网关缓冲(GwBuffer / 内存池)
participant DST_H as 高层目标(如 Dcm,>ISOTP)
participant DST_L as 低层目标(如 CanIf,<=ISOTP)
Note over SRC,PR: 收到到来的 TP 帧
SRC->>PR: GwStartOfReception(id, info, length, &bufSize)
PR->>BUF: 分配缓冲(静态或内存池)
BUF-->>PR: 缓冲已分配
PR-->>SRC: BUFREQ_OK
loop 每个数据分段
SRC->>PR: GwCopyRxData(id, info, &bufSize)
PR->>BUF: 把数据分段拷入 buffer[index]
BUF-->>PR: 拷贝完成
PR-->>SRC: BUFREQ_OK
end
SRC->>PR: GwRxIndication(id, E_OK)
PR->>BUF: 缓冲索引回绕
par 转发到高层目标(>ISOTP)
PR->>DST_H: StartOfReception(id, info, length, &bufSize)
DST_H-->>PR: BUFREQ_OK
PR->>DST_H: CopyRxData(id, info, &bufSize)
DST_H-->>PR: BUFREQ_OK
PR->>DST_H: TpRxIndication(id, E_OK)
and 转发到低层目标(<=ISOTP)
PR->>DST_L: Transmit(id, &PduInfo)
DST_L-->>PR: E_OK/E_NOT_OK
end
Note over SRC,PR: 最终清理
SRC->>PR: GwTxConfirmation(id, E_OK)
PR->>BUF: 释放缓冲(内存池)或复位(静态)
PduR_GwStartOfReception - 缓冲分配GwBuffer != NULL 且GwBufferSize >= TpSduLength):
buffer->data = RoutingPath->GwBuffer;PDUR_USE_MEMPOOL):
PduR_MemAlloc(TpSduLength) 按实际所需大小分配。buffer->size = TpSduLength、buffer->index = 0,返回 BUFREQ_OK。PduR_GwCopyRxData - 数据拷贝info->SduDataPtr 拷入 buffer->data[buffer->index];buffer->index 前进 info->SduLength;BUFREQ_E_OVFL。PduR_GwRxIndication - 转发逻辑接收成功(result == E_OK)时遍历所有目标:
| 目标模块类型 | 行为 |
|---|---|
高层(模块编号 > PDUR_MODULE_ISOTP,如 SecOC、Com、Dcm、Mirror) |
依次调用 StartOfReception + CopyRxData + TpRxIndication,把完整缓冲数据作为一次 TP 接收投递 |
低层(模块编号 <= PDUR_MODULE_ISOTP,如 CanIf、CanTp、DoIP) |
置 buffer->index = 0,直接调用 Transmit 把完整缓冲数据作为单个 I-PDU 发出 |
接收失败(E_NOT_OK)时,若定义了 PDUR_USE_MEMPOOL 且缓冲为动态分配,则通过PduR_MemFree() 释放。
PduR_GwCopyTxData() -- 从缓冲拷到输出(用于重试)
|
v
PduR_GwTxConfirmation() -- 发送成功后释放缓冲
PduR_GwCopyTxData - 出站数据拷贝info->MetaDataPtr 读取 offset;buffer->data[offset] 拷贝 info->SduLength 字节到info->SduDataPtr;BUFREQ_OK。PduR_GwTxConfirmation - 缓冲清理buffer->data = NULL;PDUR_USE_MEMPOOL 且缓冲为动态分配(不是静态 GwBuffer),调用PduR_MemFree() 释放。PduR 支持零开销路由宏,可完全绕过 PduR 层实现模块间直连,在编译期消除路由开销。
| 宏 | 效果 |
|---|---|
PDUR_DCM_CANTP_ZERO_COST |
PduR_DcmTransmit -> 直接 CanTp_Transmit;PduR_CanTp* 回调 -> 直接 Dcm_* |
PDUR_DCM_LINTP_ZERO_COST |
PduR_DcmTransmit -> 直接 LinTp_Transmit;PduR_LinTp* 回调 -> 直接 Dcm_* |
PDUR_DCM_J1939TP_ZERO_COST |
PduR_J1939Tp* 回调 -> 直接 Dcm_* |
使能后,相应适配函数在头文件中被替换为 #define 宏,消除函数调用开销并减小代码体积。
PduR_Dcm.h 中的示例:
#ifdef PDUR_DCM_CANTP_ZERO_COST
#define PduR_DcmTransmit CanTp_Transmit
#endif
PduR_CanTp.h 中的示例:
#ifdef PDUR_DCM_CANTP_ZERO_COST
#define PduR_CanTpCopyTxData Dcm_CopyTxData
#define PduR_CanTpRxIndication Dcm_TpRxIndication
#define PduR_CanTpTxConfirmation Dcm_TpTxConfirmation
#define PduR_CanTpStartOfReception Dcm_StartOfReception
#define PduR_CanTpCopyRxData Dcm_CopyRxData
#endif
LinTp 网关实现提供用户自定义挂钩宏:
| 挂钩宏 | 位置 | 说明 |
|---|---|---|
LINTP_GW_USER_HOOK_RX_IND(id, result) |
PduR_LinTp.c | 在 PduR_GwRxIndication() 之前调用,默认为空操作 |
LINTP_GW_USER_HOOK_TX_CONFIRM(id, result) |
PduR_LinTp.c | 在 PduR_TxConfirmation() 之前调用,默认为空操作 |
通过这些挂钩可以在不修改 PduR 核心代码的情况下加入应用定制处理(如日志、统计、流量整形)。
PduR 与 Default Error Tracer(Det) 模块集成,进行运行时诊断错误上报。所有 API使用 DET_VALIDATE 宏校验参数:
| 校验 | 错误码 | 上报的 Service ID |
|---|---|---|
pathId >= numOfRoutingPaths |
PDUR_E_PDU_ID_INVALID |
各函数自有(0x40-0xF5) |
| Transmit 中空指针 | PDUR_E_PARAM_POINTER |
0x49 |
| GetVersionInfo 中空指针 | PDUR_E_PARAM_POINTER |
0xF1 |
DET_THIS_MODULE_ID 设置为 MODULE_ID_PDUR。
内存池子系统(PduR_Mem.c)为网关场景提供动态缓冲分配,仅在定义了 PDUR_USE_MEMPOOL 时编译。
| 函数 | 说明 |
|---|---|
PduR_MemInit() |
通过 mc_init(config->mc) 初始化内存池 cluster |
PduR_MemAlloc(size) |
从池中分配 size 字节的缓冲 |
PduR_MemGet(size) |
获取下一个可用缓冲但不分配 |
PduR_MemFree(buffer) |
把缓冲归还内存池 |
内存池通过 memory[] 配置(见第 5 节),由 MemPool 库管理(通过 SConscript链接)。
来自app/bootloader/config/PduR/PduR.json:
{
"class": "PduR",
"routines": [
{ "name": "P2P_RX", "from": "CanTp", "to": "Dcm" },
{ "name": "P2P_TX", "from": "Dcm", "to": "CanTp" },
{ "name": "P2A_RX", "from": "CanTp", "to": "Dcm" },
{ "name": "P2A_TX", "from": "Dcm", "to": "CanTp" }
]
}
简单的诊断请求/响应路由,不含网关和 DBC 支持。
来自 app/app/config/Com/PduR.json:
{
"class": "PduR",
"routines": [
{ "name": "P2P_RX", "from": "CanTp", "to": "Dcm" },
{ "name": "P2P_TX", "from": "Dcm", "to": "CanTp" },
{ "name": "P2A_RX", "from": "CanTp", "to": "Dcm",
"destinations": [{ "name": "P2A_FW_TX", "to": "CanTp", "fake": "P2A_FW_RX" }] },
{ "name": "P2A_TX", "from": "Dcm", "to": "CanTp" },
{ "name": "CAN0_SECOC_MSG0_TX", "from": "Com", "to": "SecOC" },
{ "name": "FW_CAN0_SECOC_MSG0_TX", "from": "SecOC", "to": "CanIf" },
{ "name": "FW_CAN0_SECOC_MSG1_RX", "from": "CanIf", "to": "SecOC" },
{ "name": "CAN0_SECOC_MSG1_RX", "from": "SecOC", "to": "Com" },
{ "name": "MIRROR_TX", "from": "Mirror", "to": "CanIf" }
],
"networks": [
{ "name": "CAN0", "network": "CAN", "me": "AS", "dbc": "CAN0.dbc",
"ignore": ["CanNmUserData"] },
{ "name": "CAN1", "network": "CAN", "me": "AS", "dbc": "CAN0.dbc",
"ignore": ["CanNmUserData"] }
],
"memory": [
{ "name": "middle", "size": 64, "number": 2 }
]
}
展示的关键特性:
Com -> SecOC -> CanIf,接收为CanIf -> SecOC -> Com;P2A_FW_TX,用 fake 镜像接收);Mirror -> CanIf 支持测试/监测注入;备用路由路径(注释为 backup-routines-secoc-test-over-cantp)演示了把 SecOC 保护的 PDU 经 CanTp 而非 CanIf 路由:
{
"name": "FW_CAN0_SECOC_MSG0_TX",
"from": "SecOC",
"to": "CanTp"
},
{
"name": "FW_CAN0_SECOC_MSG0_RX",
"from": "CanTp",
"to": "SecOC",
"dest": "CAN0_SECOC_MSG1_RX"
}
SConscript 构建脚本:
.c 文件;CanIf_Cfg、Com_Cfg、CanTp_Cfg、LinTp_Cfg、J1939Tp_Cfg、Dcm_Cfg、NvM_Cfg、SecOC_Cfg、Mirror_Cfg、DoIP_Cfg;CWS12(HC(S)12 的 CodeWarrior)时链接 MemPool 库。生成器在 <cfg_dir>/GEN/ 下产出两个文件:
| 文件 | 说明 |
|---|---|
PduR_Cfg.h |
头文件,含 PDUR_<name> 宏、配置开关和 MemCluster 宏 |
PduR_Cfg.c |
源文件,含路由表、API 表、缓冲定义和 PduR_Config 结构 |
对路由中引用的每个模块生成一张 PduR_<Module>Api 表,包含以下函数指针:
PduR_ApiType {
StartOfReception // TP 接收开始时调用
CopyRxData // 拷贝接收数据
TpRxIndication // TP 接收完成指示
RxIndication // 单帧接收指示(仅 Com)
Transmit // 发送函数
CopyTxData // 拷贝待发送数据
TxConfirmation // 发送确认
}
网关模块(hasGW 为 true 时)获得网关专用回调:
PduR_<Mod>GwStartOfReceptionPduR_<Mod>GwCopyRxDataPduR_<Mod>GwRxIndicationPduR_<Mod>GwCopyTxDataPduR_<Mod>GwTxConfirmationconst PduR_ConfigType PduR_Config = {
&MC_PduR, // MemCluster 指针(定义 PDUR_USE_MEMPOOL 时)
PduR_RoutingPaths, // 路由路径表
ARRAY_SIZE(PduR_RoutingPaths) // 路由路径数量
};
PduR.py 生成代码PduR.py 生成器脚本:
extract() 处理 DBC 文件并自动生成路由规则;Gen_PduR() 在 GEN/ 输出目录生成 PduR_Cfg.h 和PduR_Cfg.c。extract())routines、memory 和 buffers;dbc 文件的网络:
../);Com.get_messages() 解析报文;node == me 比较生成 TX/RX 路由;PduR.json。Gen_PduR())PduR_RoutingPaths[] 表;PduR_Config 结构。| 节 | 类型 | 必需 | 说明 |
|---|---|---|---|
routines |
array | 是 | PDU 路由规则(至少一条) |
networks |
array | 否 | 用于基于 DBC 自动生成的网络定义 |
memory |
array | 否 | 内存池配置 |
buffers |
array | 否 | 共享网关缓冲池 |
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name |
string | - | PDU 名(引用自 EcuC.Pdus) |
from |
enum | - | 源模块 |
to |
enum | - | 目标模块 |
useDest |
bool | true |
启用/禁用替代目标名 |
dest |
string | name |
替代目标 PDU 名 |
useDestBuffer |
bool | false |
启用/禁用网关缓冲 |
DestBufferType |
string | private |
"private" 或 "shared" |
DestBuffer |
string | - | 共享网关缓冲名 |
DestBufferSize |
int | 0 | 私有网关缓冲大小 |
useFake |
bool | true |
启用/禁用镜像 fake PDU 名 |
fake |
string | - | 镜像用 fake PDU 名 |
destinations |
array | - | 附加路由分支 |
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name |
string | CAN? |
网络逻辑名 |
network |
enum | - | "CAN" 或 "LIN" |
me |
string | AS |
自身节点名 |
use_dbc |
bool | false |
启用 DBC 解析 |
dbc |
string | "" |
DBC 文件路径 |
ignore |
array | - | 从 DBC 解析中排除的 PDU |
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name |
string | - | 内存池名 |
size |
int | 256 | 内存块字节数 |
number |
int | 2 | 内存块数量 |
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name |
string | - | 缓冲名 |
size |
int | 4096 | 缓冲字节数 |