导言
前两篇已经把 CanNm 的目录结构、NM 报文收发和 CanNm_MainFunction 状态机主线串起来了。这一篇继续往配置和上层交互看,重点回答三个问题:
1. PBcfg里面几个配置结构体的参数分别是什么意思?2. NM UserData 是如何被设置、缓存、组包和读取的? 3. Nm 层各个 callback 的含义是什么?
注意:本文只分析字段含义和源码逻辑,不展示当前工程 CanNm_PBcfg.c 里的实际配置值。涉及具体 PDU ID、Node ID、报文长度、定时参数、通道数量等内容,统一按“项目配置值”处理。
1. 为什么要先看 PB 配置
AUTOSAR BSW 模块通常是配置驱动的。CanNm 的状态机代码不会把通道数量、PDU 长度、Node ID 位置、CBV 位置、发送周期、超时时间等参数写死在逻辑里,而是通过配置表读取。
当前源码中,和配置关系最密切的文件是:
CanNm_Cfg.h
CanNm_Cfg_Internal.h
CanNm_PBcfg.h
CanNm_PBcfg.c
CanNm_Prv.h其中:
• CanNm_Cfg_Internal.h:主要放编译期功能开关和一些全局宏。• CanNm_Cfg.h:定义配置结构体类型。• CanNm_PBcfg.h:声明 Post-Build 配置对象和部分数量宏。• CanNm_PBcfg.c:真正填充配置结构体实例。• CanNm_Prv.h:提供内部访问宏,例如获取 channel 配置、获取通道数量等。
本文只讲结构体字段含义,不列实际初始化数组。
2. CanNm_ChannelConfigType:单个 NM Channel 的核心配置
CanNm_ChannelConfigType 是最重要的配置结构体。可以把它理解成“一个 CanNm channel 如何运行”的参数集合。
源码定义在 CanNm_Cfg.h 中,大致可以分成几类。
2.1 周期发送和超时相关参数
CanNm_TimerType MsgCycleTime;
CanNm_TimerType MsgCycleOffset;
CanNm_TimerType MsgTimeoutTime;
CanNm_TimerType MsgReducedTime;
CanNm_TimerType ImmediateNmCycleTime;
CanNm_TimerType NMTimeoutTime;
CanNm_TimerType RepeatMessageTime;
CanNm_TimerType RemoteSleepIndTime;
CanNm_TimerType WaitBusSleepTime;这些字段都和定时器有关。
MsgCycleTime 表示常规周期 NM 报文发送周期。CanNm_MainFunctionTx() 发送成功后,通常会把下一轮 MsgCyclePeriod 设置回这个周期。
MsgCycleOffset 表示启动发送后的首次发送偏移。进入 Network Mode 或 Repeat Message 后,CanNm_StartTransmission() 会使用它来决定第一帧 NM PDU 是否延迟。
MsgTimeoutTime 用于发送确认超时监控。CanNm 调用 CanIf_Transmit() 后,如果长时间等不到 CanNm_TxConfirmation(),就会触发发送超时处理。
MsgReducedTime 用于 Bus Load Reduction。如果开启总线负载降低,并且收到其他节点 NM 报文,源码可能会临时调整本节点发送周期,减少总线负载。
ImmediateNmCycleTime 表示 Immediate NM 报文的快速发送周期。当配置了立即发送次数时,CanNm 会先用这个周期快速发几帧,再回到普通周期。
NMTimeoutTime 是 NM Timeout 计时阈值。收到 NM 报文或收到发送确认时,ctNMTimeoutTimer 会被刷新;如果超时,状态机可能进入睡眠相关流程。
RepeatMessageTime 是 Repeat Message 状态保持时间。进入 NM_STATE_REPEAT_MESSAGE 后,定时器到期再根据网络请求状态转到 Normal Operation 或 Ready Sleep。
RemoteSleepIndTime 用于 Remote Sleep 检测。若开启该功能,长时间没有收到其他节点 NM 报文时,可以向 Nm 上报 Remote Sleep。
WaitBusSleepTime 是 Prepare Bus Sleep 到 Bus Sleep 的等待时间。进入 Prepare Bus Sleep 后,CanNm 会等待该时间,确认总线安静后再进入 Bus Sleep。
2.2 PDU 映射和通道映射参数
PduIdType TxPduId;
boolean TriggerTransmit;
#if (CANNM_COM_USER_DATA_SUPPORT != STD_OFF)
PduIdType PduRId;
#endif
NetworkHandleType NetworkHandle;TxPduId 是 CanNm 调用 CanIf_Transmit() 时使用的下层发送 PDU ID。它必须能映射到 CanIf 中对应的 CAN NM Tx PDU。
TriggerTransmit 表示 CanIf 是否使用 TriggerTransmit 方式取数。如果启用,CanIf 需要数据时会调用 CanNm_TriggerTransmit(),CanNm 在该回调中把当前 TxBuffer 提供给 CanIf。
PduRId 用于 PduR 交互,只有开启 COM UserData 支持时才存在。CanNm 通过它调用 PduR_CanNmTriggerTransmit() 或 PduR_CanNmTxConfirmation()。
NetworkHandle 是上层 Nm/ComM 视角下的网络通道句柄。CanNm 调用 Nm callback 时通常传这个 handle,而不是下层 CanIf PDU ID。
2.3 NM PDU 格式相关参数
uint8 PduLength_u8;
uint8 NodeIdPos_u8;
uint8 ControlBitVectorPos_u8;
boolean NodeDetectionEnabled_b;
boolean NodeIdEnabled_b;
boolean RepeatMsgIndEnabled_b;
uint8 NodeId_u8;
uint8 UserDataLength_u8;
uint8 ImmediateNmTransmissions_u8;PduLength_u8 表示 NM PDU 的总长度。CanNm 初始化时会把 Pdu_s.SduLength 设置成该长度,发送和接收拷贝也会按它处理。
NodeIdPos_u8 表示 Node ID 在 NM PDU 中的位置。如果配置为关闭值,说明该 PDU 中不使用 Node ID 字段。
ControlBitVectorPos_u8 表示 CBV 在 NM PDU 中的位置。CBV 里承载 Repeat Message bit、PNI bit、Active Wakeup bit、Coordinator Sleep Ready bit 等控制信息,具体取决于功能开关。
NodeDetectionEnabled_b 表示是否开启节点检测。开启后,收到对端 Repeat Message bit 或本地请求 Repeat Message 时,状态机会进入 Repeat Message。
NodeIdEnabled_b 表示是否启用 Node ID。它会影响 GetNodeIdentifier、GetPduData 等 API 是否能返回对应信息。
RepeatMsgIndEnabled_b 表示收到 Repeat Message bit 时,是否通过 Nm_RepeatMessageIndication() 通知 Nm 层。
NodeId_u8 是本节点发送 NM 报文时填入的 Node ID。本文不展示实际值。
UserDataLength_u8 表示 UserData 区域长度。CanNm 设置、读取、发送 UserData 时都会按这个长度拷贝。
ImmediateNmTransmissions_u8 表示进入网络后要快速发送的 Immediate NM PDU 数量。如果不使用 Immediate 发送,则周期发送直接按普通周期走。
2.4 ERA/EIRA 相关参数
#if (CANNM_ERACALC_ENABLED != STD_OFF)
uint8 ERA_ComPduId_u8;
uint8 ChannelOffset_u8;
boolean EraCalcStatus_b;
uint8 Era_PduLength_u8;
#endif这部分只在 ERA 计算开启时有效。
ERA_ComPduId_u8 是 CanNm 向 PduR/COM 上报 ERA 状态时使用的 PDU ID。
ChannelOffset_u8 用于在 ERA 全局数组中定位当前 channel 的起始位置。
EraCalcStatus_b 表示当前 channel 是否参与 ERA 计算。
Era_PduLength_u8 表示 ERA PDU 的长度。
这些字段服务于 Partial Networking 场景。当前文章只需要理解:ERA/EIRA 是把收到或本地的 PN 请求状态汇总后上报给上层的机制。
2.5 Channel 开关和功能开关
boolean stChannelActive_b;
boolean stBusLoadReductionActive_b;stChannelActive_b 决定该 channel 是否在 CanNm_Init() 中初始化。未激活的 channel 不参与正常运行。
stBusLoadReductionActive_b 表示是否启用 Bus Load Reduction。若开启,收到其他节点 NM 报文时,本节点可能降低自己的发送频率。
2.6 PN 相关参数
#if (CANNM_GLOBAL_PN_SUPPORT != STD_OFF)
boolean PnEnabled_b;
boolean AllNmMessagesKeepAwake_b;
boolean PnHandleMultipleNetworkRequests_b;
#if(CANNM_SYNCHRONIZED_PNC_SHUTDOWN_ENABLED == TRUE)
boolean PNC_is_GatewayActive_b;
boolean SyncPncShutdownEnabled_b;
#endif
#endif这部分只在 Partial Networking 支持开启时存在。
PnEnabled_b 表示当前 channel 是否启用 PN。
AllNmMessagesKeepAwake_b 表示是否所有 NM 报文都保持网络唤醒。如果关闭,CanNm 会根据 PN filter 判断收到的 NM PDU 是否和本 ECU 相关。
PnHandleMultipleNetworkRequests_b 表示是否处理多个网络请求相关的 PN 特性。
PNC_is_GatewayActive_b 用于区分 PNC gateway 行为。
SyncPncShutdownEnabled_b 表示是否启用同步 PNC shutdown。
2.7 Car Wakeup 和 Active Wakeup
#if (CANNM_CAR_WAKEUP_RX_ENABLED != STD_OFF)
boolean stCanNmCarWakeUpRxEnabled_b;
#if (CANNM_CAR_WAKEUP_FILTER_ENABLED != STD_OFF)
boolean stCarWakeUpFilterEnabled_b;
uint8 stCarWakeUpFilterNodeId_u8;
#endif
uint8 stCarWakeUpBytePosition_u8;
uint8 stCarWakeUpBitPosition_u8;
#endif
boolean ActiveWakeupBitEnabled_b;Car Wakeup 用于从收到的 NM PDU 中识别整车唤醒相关 bit。若启用 filter,还可以只接受指定 Node ID 的唤醒请求。
ActiveWakeupBitEnabled_b 表示主动唤醒 bit 是否启用。本节点主动请求网络时,源码可以在 CBV 中设置 Active Wakeup bit。
3. CanNm_PNConfigType:单个 PN 的位置描述
typedefstruct
{
uint8 ByteNum;
uint8 BitMask;
} CanNm_PNConfigType;这个结构体用于描述一个 PN 在 PN info 区域中的位置。
ByteNum 表示该 PN 属于 PN info 的哪个字节。
BitMask 表示该 PN 在这个字节中的 bit 掩码。
CanNm 计算 ERA/EIRA 或过滤 PN 报文时,会根据 ByteNum 和 BitMask 判断某个 PN 是否被请求。
4. CanNm_GlobalConfigType:跨 Channel 的全局配置
CanNm_GlobalConfigType 是全局配置结构体。它不是单个 channel 的参数,而是跨 channel 或跨 PN 的公共表。
typedefstruct
{
#if (CANNM_CONFIGURATION_VARIANT != CANNM_VARIANT_PRE_COMPILE)
const CanNm_ChannelConfigType * ChannelConfig_pcs;
#endif
#if (CANNM_GLOBAL_PN_SUPPORT != STD_OFF)
const uint8 * PnFilterMask_pcu8;
#endif
#if (CANNM_EIRACALC_ENABLED != STD_OFF) || (CANNM_ERACALC_ENABLED != STD_OFF)
const CanNm_PNConfigType * PnInfo_pcs;
CanNm_TimerType PnResetTime;
#if (CANNM_CONFIGURATION_VARIANT != CANNM_VARIANT_PRE_COMPILE)
uint8 NumOfPnsConfigured_u8;
#endif
#endif
#if (CANNM_CONFIGURATION_VARIANT == CANNM_VARIANT_POSTBUILD_SELECTABLE)
uint8 NumOfChannelsConfigured_u8;
const PduIdType *Channel_Mapping_Table;
#endif
} CanNm_GlobalConfigType;ChannelConfig_pcs 指向 channel 配置数组。非 Pre-Compile 变体下,CanNm 通过它找到当前配置集中的 channel 参数。
PnFilterMask_pcu8 是 PN 过滤掩码。收到 NM 报文时,源码会把 PN info 和这个 mask 相与,判断是否有本 ECU 关心的 PN 请求。
PnInfo_pcs 指向 PN 配置表,也就是一组 CanNm_PNConfigType。
PnResetTime 用于 ERA/EIRA 状态复位。收到某 PN 请求后,相关状态会保持一段时间,超时后复位。
NumOfPnsConfigured_u8 表示配置了多少个 PN。
NumOfChannelsConfigured_u8 表示当前 Post-Build 配置集中启用了多少 channel。
Channel_Mapping_Table 用于 Post-Build Selectable 变体,把外部 PDU ID 或 channel ID 映射到当前配置集里的 CanNm channel 下标。
5. CanNm_ConfigType:初始化入口使用的配置壳
typedefstruct
{
const void * CanNm_ConfigData;
const Std_VersionInfoType * versionInfo;
} CanNm_ConfigType;CanNm_ConfigType 是 CanNm_Init() 的入参类型。
CanNm_ConfigData 是一个 void *,实际会指向 CanNm_GlobalConfigType。源码在初始化时会把它转成全局配置指针。
versionInfo 用于 Post-Build 配置和模块版本校验。是否使用取决于配置变体和版本信息 API 开关。
6. UserData 是如何被操控的
CanNm 的 UserData 操作可以分成四条路径:
1. 初始化默认值。 2. 上层主动设置待发送 UserData。 3. 发送前把 UserData 组进 TxBuffer。 4. 从最近收到的 NM PDU 中读取 UserData。

6.1 初始化:UserDataBuffer 和 TxBuffer 的默认值
在 CanNm_Init() 中,如果配置了 UserData 长度,源码会初始化两份数据:
RamPtr_ps->TxBuffer_au8[...user data area...] = CANNM_DEFAULT_USER_BYTE;
RamPtr_ps->UserDataBuffer_au8[Index_ui] = CANNM_DEFAULT_USER_BYTE;这里有两个 buffer:
• TxBuffer_au8:真正用于发送的整帧 NM PDU 缓冲区。• UserDataBuffer_au8:只保存 UserData 的内部缓存。
发送前,CanNm 会把 UserDataBuffer_au8 的内容复制到 TxBuffer_au8 的 UserData 区域。
6.2 CanNm_SetUserData:Nm 设置下一帧要发送的 UserData
CanNm_SetUserData() 是 AUTOSAR CanNm 提供给 Nm 层调用的 API。
它的核心逻辑是:
UserDataLen = ConfigPtr_pcs->UserDataLength_u8;
BufferPtr = &(CanNm_RamData_s[CanNm_NetworkHandle].UserDataBuffer_au8[0]);
CanNm_CopyBuffer(nmUserDataPtr, BufferPtr, UserDataLen);也就是说,CanNm_SetUserData() 并不直接发送 NM 报文。它只是把 Nm 传入的数据复制到 UserDataBuffer_au8,等待下一次发送路径使用。
这个 API 有几个前提:
• CANNM_USER_DATA_ENABLED必须打开。• Passive Mode 不能打开。 • CANNM_COM_USER_DATA_SUPPORT必须关闭。
这说明当前路径适合“Nm 直接写 CanNm UserData”的模式。如果使用 COM UserData 支持,则 UserData 由 COM/PduR 路径提供。
6.3 CanNm_UpdateTxPdu:发送前把 UserData 写进 TxBuffer
周期发送路径中,CanNm_MainFunctionTx() 会先调用:
CanNm_UpdateTxPdu(ConfigPtr_pcs, RamPtr_ps);CanNm_UpdateTxPdu() 会做两件和 UserData 相关的事情。
如果开启 COM UserData 支持,并且不是 TriggerTransmit 模式,它会先通过 PduR 拉取上层数据:
PduR_CanNmTriggerTransmit(ConfigPtr_pcs->PduRId, &PduInfo_s);
CanNm_CopyBuffer(TxDataBuffer_pu8,
RamPtr_ps->UserDataBuffer_au8,
ConfigPtr_pcs->UserDataLength_u8);然后把 UserDataBuffer_au8 复制到 TxBuffer_au8 的 UserData 区域:
CanNm_CopyBuffer(UserDataPtr,
TxBufferPtr,
ConfigPtr_pcs->UserDataLength_u8);所以发送前的 UserData 来源有两种:
• 非 COM UserData:由 CanNm_SetUserData()写入UserDataBuffer_au8。• COM UserData:由 PduR/COM 提供,CanNm 拉取后写入 UserDataBuffer_au8。
最终都会进入 TxBuffer_au8,再由 CanIf_Transmit() 发出去。
6.4 CanNm_TriggerTransmit:CanIf 触发取数时的 UserData 处理
如果 CanIf 配置为 TriggerTransmit 模式,下层不是直接使用 CanIf_Transmit() 入参中的数据,而是在需要发送数据时回调:
CanNm_TriggerTransmit(TxPduId, PduInfoPtr)这个函数会检查 CanIf 提供的 buffer 是否足够,然后更新 CBV 和 UserData,再把 PduInfoPtr->SduDataPtr 指向 TxBuffer_au8:
PduInfoPtr->SduDataPtr = &RamPtr_ps->TxBuffer_au8[0];
PduInfoPtr->SduLength = ConfigPtr_pcs->PduLength_u8;如果开启 COM UserData,它同样会先通过 PduR 拉取最新 UserData。
6.5 CanNm_GetUserData:读取最近收到的 UserData
CanNm_GetUserData() 用于从最近收到的 NM PDU 中取 UserData。
它不是读取 UserDataBuffer_au8,而是读取 RxBuffer_au8:
BufferPtr = &(RamPtr_ps->RxBuffer_au8[
ConfigPtr_pcs->PduLength_u8 - ConfigPtr_pcs->UserDataLength_u8]);
CanNm_CopyBuffer(BufferPtr,
nmUserDataPtr,
ConfigPtr_pcs->UserDataLength_u8);源码还会先判断:
if (RamPtr_ps->RxStatus_b != FALSE)也就是说,只有当前 NM sleep cycle 中确实收到过 NM PDU,GetUserData 才会返回有效数据。进入 Bus Sleep 时,RxStatus_b 会被清除。
6.6 GetPduData 与 GetUserData 的区别
CanNm_GetUserData() 只拷贝 UserData 区域。
CanNm_GetPduData() 拷贝整帧 NM PDU:
CanNm_CopyBuffer(BufferPtr, nmPduDataPtr, ConfigPtr_pcs->PduLength_u8);所以:
• 想拿 UserData,用 CanNm_GetUserData()。• 想拿 Node ID、CBV、UserData 全部内容,用 CanNm_GetPduData()。
7. 当前 Nm 层 callback 的含义
CanNm 不直接通知应用,而是通过 Nm callback 把网络管理事件交给 Nm 层。当前工程的 Nm callback 声明在 Nm.h,实现位于 Nm.c。

7.1 Nm_NetworkStartIndication
void Nm_NetworkStartIndication(NetworkHandleType nmChannelHandle)含义:CanNm 在 Bus Sleep 中收到 NM PDU,说明网络可能被其他节点唤醒,于是通知 Nm 层网络启动。
当前实现中,该回调会调用:
CanNm_PassiveStartUp(nmChannelHandle);这表示 Nm 层收到 network start 后,让 CanNm 以被动启动方式进入网络相关流程。
7.2 Nm_RepeatMessageIndication
void Nm_RepeatMessageIndication(NetworkHandleType nmChannelHandle)含义:CanNm 收到对端 NM 报文中的 Repeat Message bit,并且配置允许上报时,通过这个回调通知 Nm 层。
Repeat Message 的作用是让网络中的节点重新广播存在性,常见于节点检测、唤醒扩散或网络重新同步场景。
当前实现是空函数,说明该事件暂时没有额外上层处理。
7.3 Nm_NetworkMode
void Nm_NetworkMode(NetworkHandleType nmChannelHandle)含义:CanNm 进入 Network Mode 时通知 Nm 层。
源码中 CanNm_GotoNetworkMode() 会调用它。对上层来说,这意味着对应网络已经从睡眠相关模式回到网络管理活跃模式。
当前实现是空函数,可能只是保留接口。
7.4 Nm_StateChangeNotification
void Nm_StateChangeNotification(NetworkHandleType nmChannelHandle,
Nm_StateType PrevState,
Nm_StateType CurState)含义:CanNm 状态发生变化时通知 Nm 层。只要 CANNM_STATE_CHANGE_IND_ENABLED 打开,状态机中的多个切换点都会调用它。
当前实现中,这个回调用来操控 UserData 中的一个状态指示位:
Nm_SetRmsBit(nmChannelHandle, (CurState == NM_STATE_REPEAT_MESSAGE));这里的含义是:当 CanNm 当前状态为 NM_STATE_REPEAT_MESSAGE 时,把某个 UserData 指示位设置为 active;离开 Repeat Message 后设置为 inactive。
具体 bit 位置和实际业务含义属于项目约定,文章不展开实际配置值。关键是理解这条链路:
CanNm 状态变化
-> Nm_StateChangeNotification
-> Nm_SetRmsBit
-> CanNm_SetUserData
-> 下一帧 NM PDU 携带新的 UserData7.5 Nm_PrepareBusSleepMode
void Nm_PrepareBusSleepMode(NetworkHandleType nmChannelHandle)含义:CanNm 进入 Prepare Bus Sleep Mode 时通知 Nm 层。
这表示网络管理状态已经从 Network Mode 向 Bus Sleep 过渡,当前处于总线安静等待阶段。上层可以在这里做准备休眠相关动作。
当前实现是空函数。
7.6 Nm_BusSleepMode
void Nm_BusSleepMode(NetworkHandleType nmChannelHandle)含义:CanNm 已进入 Bus Sleep Mode,通知 Nm 层总线可以进入睡眠。
在完整系统中,Nm 层可能继续通知 ComM、EcuM 或其他模块,触发关闭通信、进入低功耗等流程。
当前实现是空函数。
7.7 Nm_TxTimeoutException
void Nm_TxTimeoutException(NetworkHandleType nmChannelHandle)含义:CanNm 发送 NM PDU 后,等待发送确认超时,向 Nm 层上报异常。
在 CanNm_MainFunctionTx() 中,如果 TxTimeoutMonitoringActive_b 打开,并且 MsgTimeoutTime 到期仍没有正常确认,就会调用这个回调。
当前实现是空函数。实际工程里可以在这里做诊断、日志、通信恢复或状态监控。
7.8 Nm_PduRxIndication
void Nm_PduRxIndication(NetworkHandleType nmChannelHandle)含义:CanNm 收到 NM PDU 后,如果 CANNM_PDU_RX_INDICATION_ENABLED 打开,会通过该回调通知 Nm 层“有 NM 报文接收”。
它和 Nm_NetworkStartIndication 的区别是:
• Nm_PduRxIndication更像普通接收通知。• Nm_NetworkStartIndication是 Bus Sleep 中收到 NM PDU 时的网络启动事件。
当前实现是空函数。
8. UserData 与 Nm 回调如何串起来
当前源码里最有代表性的链路是状态变化回调操控 UserData:
CanNm 状态机发生状态切换
|
v
Nm_StateChangeNotification(nmChannelHandle, PrevState, CurState)
|
v
根据 CurState 判断是否处于 Repeat Message
|
v
Nm_SetRmsBit(...)
|
v
CanNm_SetUserData(nmChannelHandle, data)
|
v
UserDataBuffer_au8 被更新
|
v
下一次 CanNm_UpdateTxPdu() 组包
|
v
NM PDU 发出这条链路说明,UserData 不只是“上层随便填几个字节”,它可以承载网络管理状态、应用约定标志、PN 信息或诊断相关标志。CanNm 本身只负责缓存、组包和收发,具体每个 bit 表达什么业务含义,由 Nm 层或项目约定决定。
9. 小结
这一篇可以抓住三个结论。
第一,CanNm_ChannelConfigType 是单 channel 的核心运行参数,决定定时器、PDU 映射、PDU 格式、Node ID、UserData、PN、唤醒等行为。
第二,UserData 的发送路径本质是“上层写入 UserDataBuffer_au8,发送前复制到 TxBuffer_au8,再通过 CanIf 发出”;接收路径则是“从 RxBuffer_au8 中按配置位置取出 UserData”。
第三,Nm callback 是 CanNm 和上层 Nm 的事件边界。当前实现里,Nm_StateChangeNotification 已经用来根据 CanNm 状态更新 UserData 指示位,其余 callback 多数还是预留接口,但它们分别对应网络启动、Repeat Message、进入 Network Mode、准备睡眠、总线睡眠、发送超时和 PDU 接收等事件。
理解这些之后,再看 CanNm_PBcfg.c 就不会只看到一串配置值,而能知道每个字段会影响 CanNm 的哪一段源码逻辑。
夜雨聆风