1. 从一个实际场景说起:为什么网络管理报文值得单独拎出来学
如果你做过车身控制器、网关或者电池管理系统的开发,大概率遇到过这样的问题:整车下电之后,某些ECU的电流迟迟降不下来,静态电流超标,第二天早上车主打不着火。排查一圈硬件没毛病,最后发现是某个节点的网络管理报文没停,总线一直被唤醒。这类问题在分布式电子电气架构里非常典型,而解决它的核心机制,就是AUTOSAR网络管理,也就是大家常说的CanNm。
我接触AUTOSAR网络管理大概是在做第一个量产网关项目的时候。当时对NM的理解停留在“周期性发报文”这个层面,觉得无非就是定时器加发送,能有多复杂。结果第一次做网络休眠测试就翻车了:总线上所有节点都进入Prepare Bus-Sleep模式之后,有一个节点因为收到了一帧残留的应用报文,又跳回了Repeat Message状态,整个网络被重新唤醒。那次之后我才认真把CanNm的状态机、定时器参数、报文格式从头到尾捋了一遍。
这篇笔记就是把这些年踩过的坑、调过的参数、看过的Trace整理出来。内容会覆盖CanNm的报文结构、状态机运转逻辑、定时器参数怎么算、DaVinci Configurator里怎么配、CANoe里怎么抓和分析、以及实际项目中常见的休眠异常怎么排查。适合刚接触AUTOSAR网络管理的朋友,也适合已经上手但被休眠唤醒问题折磨过的同行。核心关键词就几个:AUTOSAR、网络管理、报文、NM、CanNm,全文围绕这几个词展开,不跑偏。
2. AUTOSAR网络管理的整体设计思路拆解
2.1 网络管理到底管的是什么
很多人第一次听到“网络管理”这个词,会以为是管理网络拓扑或者路由。其实在AUTOSAR语境下,网络管理管的核心只有一件事:协调总线上所有节点同步进入休眠,或者同步保持唤醒。它不负责路由,不负责诊断,也不负责应用数据的传输。它的存在意义是让整车的静态电流可控,同时保证需要通信的时候网络能快速被唤醒。
你可以把它想象成一个办公室的熄灯规则。下班时间到了,不能某个人说走就走直接关灯,因为可能还有人要加班。得有一套机制:想走的人先举手,大家都举手了,再统一熄灯。如果有人还在干活,灯就得继续亮着。CanNm就是这套“举手—确认—熄灯”的规则在CAN总线上的实现。
AUTOSAR网络管理有两种主要形态:直接网络管理和间接网络管理。直接网络管理就是每个节点都主动发送NM报文,通过NM报文来协调状态,CanNm、FrNm、UdpNm都属于这一类。间接网络管理则是通过监控应用报文的活跃度来判断网络是否需要保持唤醒,节点本身不额外发NM报文。乘用车里CanNm用得最多,因为CAN总线成本低、节点多,直接网络管理的协调性更好。
2.2 为什么选CanNm而不是OSEK NM
稍微老一点的平台可能用的是OSEK网络管理。OSEK NM和AUTOSAR CanNm解决的是同一个问题,但机制差别不小。OSEK NM的报文里带一个逻辑环,节点按顺序传递令牌,谁拿到令牌谁发言,机制相对复杂,而且节点增减对逻辑环影响大。AUTOSAR CanNm简化了这个模型,不再搞逻辑环,而是用基于标识符的NM报文加上状态机来协调。
具体来说,CanNm的NM报文使用固定的CAN ID,所有节点的NM报文都发到同一个ID上,通过报文里的源节点标识来区分是谁发的。这样做的好处是:节点增减不影响机制运转,新节点上线只要开始发NM报文就行,不需要重新编排逻辑环。对于现在动辄几十个节点的域控架构来说,这个简化非常关键。
另外,AUTOSAR CanNm和COM、PDU Router、CanIf这些模块的耦合方式也更清晰。NM报文本质上是一个PDU,走的是标准的AUTOSAR通信栈,配置起来比OSEK NM更规范。所以新项目基本都选CanNm,OSEK NM只在一些老平台或者特定商用车上还能见到。
2.3 CanNm在AUTOSAR架构里的位置
从分层角度看,CanNm位于服务层,它下面依赖CanIf和Can驱动,上面通过Nm模块和ComM、BswM、EcuM这些模块交互。具体链路是这样的:CanNm负责NM报文的收发和状态机运转,Nm模块做统一封装,ComM根据Nm的状态来请求通信模式,BswM做模式仲裁,EcuM最终决定ECU能不能休眠。
这里有个容易混淆的点:CanNm不直接控制ECU休眠。它只负责告诉Nm模块“我现在处于什么状态”,Nm再告诉ComM“网络能不能释放”,ComM再通过BswM和EcuM去走休眠流程。所以如果你发现ECU不休眠,不能只盯着CanNm看,得顺着这条链路往上查。
理解这个链路很重要,因为实际排查休眠问题时,很多时候CanNm状态是对的,但ComM没释放,或者BswM仲裁没通过,最后EcuM不执行休眠。只懂CanNm不够,得知道它在整个模式管理链路里的位置。
3. CanNm报文格式与核心字段逐字节解析
3.1 NM PDU的标准结构
CanNm的报文格式在AUTOSAR规范里有明确定义。一个标准的NM PDU包含以下部分:
| 字段 | 长度 | 说明 |
|---|---|---|
| Source Node Identifier | 1字节 | 源节点标识,标识是谁发的NM报文 |
| Control Bit Vector | 1字节 | 控制位向量,携带状态信息 |
| User Data | 0-6字节 | 用户数据,可选 |
| Padding | 可变 | 填充字节,补齐到8字节 |
Source Node Identifier是每个节点唯一的,配置在CanNm模块里。接收方通过这个字段知道是谁在发NM报文。Control Bit Vector里比较重要的位包括:Repeat Message Request位、Active Wakeup位、NM Coordinator Sleep Ready位等。这些位在状态机运转和休眠协调里起关键作用。
User Data是可选的,有些项目会用它传一些自定义信息,比如节点健康状态或者唤醒原因。但大多数项目不用User Data,直接填0或者Padding。Padding的作用是让报文长度固定为8字节,避免DLC不一致导致的问题。
3.2 Control Bit Vector里每个位的含义
Control Bit Vector是NM报文里信息密度最高的一个字节。我按位拆开说:
- Bit 0:Repeat Message Request。这个位置1表示发送方请求总线上的其他节点进入Repeat Message状态。通常在新节点上线或者网络需要重新同步时使用。
- Bit 1:NM Coordinator Sleep Ready。这个位和网络协调有关,主协调节点用它来通知从节点可以准备休眠了。
- Bit 2:Active Wakeup。这个位置1表示这个节点是主动唤醒网络的,不是被其他节点唤醒的。排查唤醒源的时候这个位很有用。
- Bit 3:NM Coordinator Sleep Ready的补充位,具体含义看项目配置。
- Bit 4-7:保留位,一般填0。
实际抓报文的时候,你可以通过Control Bit Vector快速判断当前网络的状态。比如看到某个节点的Repeat Message Request置1,就知道网络正在重新同步。看到Active Wakeup置1,就知道这个节点是唤醒源。
3.3 报文发送周期与DLC的注意事项
CanNm报文的发送周期由NmMsgCycleTime参数决定,典型值是100ms、200ms或者500ms。这个周期不是随便定的,它和NmMsgCycleOffset、NmTimeoutTime这些参数一起决定了网络的响应速度和总线负载。
DLC方面,标准NM报文是8字节。但有些项目为了省总线负载,会把DLC配成更小的值。这里有个坑:如果DLC配得不一致,接收方可能直接丢弃报文。CanIf层有DLC检查,DLC不匹配的报文会被当成无效报文丢掉。所以配置的时候一定要确认所有节点的NM报文DLC一致。
另外,NM报文的CAN ID通常是固定的,比如0x500或者0x600这种。这个ID在CanIf的RxPdu和TxPdu里配置,和CanNm的配置要对应上。ID配错了,报文发不出去或者收不到,状态机就转不起来。
4. CanNm状态机运转逻辑与定时器参数计算
4.1 三个核心状态:Bus-Sleep、Prepare Bus-Sleep、Network Mode
CanNm的状态机可以分成两大块:Bus-Sleep Mode和Network Mode。Network Mode里面又细分为三个子状态:Repeat Message State、Normal Operation State、Ready Sleep State。加上Prepare Bus-Sleep State,一共是五个状态。
Bus-Sleep Mode是最终状态,进入这个状态后NM报文停发,ECU可以走休眠流程。Prepare Bus-Sleep State是一个过渡状态,进入这个状态后NM报文还会发一段时间,等总线上的报文都发完了再进Bus-Sleep。这个过渡状态的存在是为了避免最后一帧NM报文还没发完就断电,导致总线异常。
Network Mode里的三个子状态各有分工。Repeat Message State是网络刚唤醒或者需要重新同步时进入的状态,这个状态下NM报文会以较快的周期发送,确保总线上所有节点都能收到。Normal Operation State是正常工作状态,NM报文按正常周期发送。Ready Sleep State是准备休眠状态,这个状态下NM报文停发,但节点还在监听总线,如果收到其他节点的NM报文,会跳回Normal Operation State。
4.2 状态迁移的触发条件
状态迁移的触发条件主要有几个:NM报文收发、定时器超时、上层请求。
从Bus-Sleep到Network Mode的迁移,通常由唤醒事件触发。唤醒事件可以是总线唤醒,也可以是本地唤醒。总线唤醒就是总线上有报文活动,CanIf检测到之后通知CanNm。本地唤醒就是ECU自己的唤醒源,比如KL15电、传感器触发等。
从Network Mode到Prepare Bus-Sleep的迁移,条件是所有节点都进入Ready Sleep State,并且NmTimeoutTime超时。这里的关键是“所有节点都Ready Sleep”。只要有一个节点还在发NM报文,其他节点就会保持在Network Mode。
从Prepare Bus-Sleep到Bus-Sleep的迁移,条件是NmWaitBusSleepTime超时。这个定时器保证总线上的报文都发完了,再进休眠。
4.3 定时器参数的计算过程
CanNm的定时器参数是配置的重点,也是容易出错的地方。主要参数有四个:
- NmMsgCycleTime:NM报文的发送周期,典型值100ms-500ms。
- NmTimeoutTime:NM报文接收超时时间,超过这个时间没收到NM报文,就认为网络可以休眠。
- NmWaitBusSleepTime:Prepare Bus-Sleep状态的等待时间。
- NmRepeatMessageTime:Repeat Message状态的持续时间。
这些参数的计算逻辑是这样的:NmTimeoutTime必须大于NmMsgCycleTime,通常取NmMsgCycleTime的2-3倍。比如NmMsgCycleTime是500ms,NmTimeoutTime可以取1500ms或者2000ms。这样做的原因是:如果某个节点偶尔丢了一帧NM报文,不至于立刻触发超时,避免误判。
NmWaitBusSleepTime通常取100ms-500ms,保证最后一帧NM报文发完。NmRepeatMessageTime通常取NmMsgCycleTime的几倍,确保所有节点都能收到Repeat Message Request。
这里有个实际项目中的经验:NmTimeoutTime不能设得太小。我见过一个项目把NmTimeoutTime设成NmMsgCycleTime的1.5倍,结果总线负载一高,NM报文偶尔延迟,就频繁触发超时,网络状态来回跳,Trace上看非常乱。后来改成3倍就稳定了。
5. DaVinci Configurator里CanNm的配置实操
5.1 模块依赖与配置顺序
在DaVinci Configurator里配CanNm,不能只配CanNm一个模块。它依赖CanIf、Can、Nm、ComM、BswM、EcuM这些模块。配置顺序建议是:先配Can和CanIf,再配CanNm和Nm,最后配ComM、BswM、EcuM。
CanIf里要配NM报文的RxPdu和TxPdu,指定CAN ID和DLC。CanNm里要配Source Node Identifier、定时器参数、Control Bit Vector的默认值。Nm里要配Nm通道和CanNm的绑定关系。ComM里要配通信模式,把Nm通道和ComM通道关联起来。
这个顺序不是随便定的。CanIf是CanNm的下层,下层没配好,上层配了也跑不起来。ComM和BswM是CanNm的上层,上层要根据下层的状态来仲裁,所以放在后面配。
5.2 关键参数配置与避坑点
在CanNm的配置界面里,有几个参数需要特别注意:
Source Node Identifier:每个节点的值必须唯一。我见过两个节点配了同一个值,结果Trace上分不清是谁发的NM报文,排查问题的时候非常痛苦。建议在项目初期就做好节点标识分配表,避免冲突。
NmMsgCycleTime:这个参数的单位是毫秒。配置的时候要注意和ComM的MainFunction周期匹配。如果ComM的周期是10ms,NmMsgCycleTime是100ms,那CanNm的主函数每10ms跑一次,累计10次发一帧NM报文。如果周期不匹配,发送时机可能会有偏差。
CanNmPnEnabled:这个参数控制是否启用Partial Networking。如果项目不用PN,一定要把这个参数关掉。开着PN但没配PN信息,NM报文里会多出PN相关的字节,接收方可能解析异常。
CanNmActiveWakeupBitEnabled:这个参数控制是否在Control Bit Vector里使用Active Wakeup位。如果项目需要区分主动唤醒和被动唤醒,就打开。不需要的话关掉,简化逻辑。
5.3 配置完成后的检查清单
配完CanNm之后,建议按这个清单检查一遍:
- Source Node Identifier是否唯一,是否和项目节点标识表一致。
- NmMsgCycleTime、NmTimeoutTime、NmWaitBusSleepTime、NmRepeatMessageTime是否满足计算关系。
- CanIf里的RxPdu和TxPdu的CAN ID、DLC是否和CanNm配置一致。
- ComM通道是否和Nm通道正确关联。
- BswM的仲裁规则是否覆盖了NM状态迁移的场景。
- EcuM的休眠流程是否在NM进入Bus-Sleep后能正常执行。
这个清单看着简单,但实际项目里经常有遗漏。尤其是第4和第5条,ComM和BswM的配置容易被忽略,导致NM状态对了但ECU不休眠。
6. CANoe抓取与分析NM报文的实操方法
6.1 抓包环境搭建与过滤设置
用CANoe抓NM报文,第一步是配好硬件通道和波特率。CAN通道的波特率和总线上其他节点一致,否则报文会报错。配好之后,在Trace窗口里加过滤条件,只显示NM报文的CAN ID。这样Trace会干净很多,不会被应用报文淹没。
如果项目用了CAN FD,要注意NM报文是CAN还是CAN FD。CanNm本身不限制是CAN还是CAN FD,但配置的时候要确认CanIf里的PDU类型。CAN FD的NM报文DLC可以更大,但标准NM报文还是8字节。
过滤条件可以用CANoe的Filter功能,按ID过滤。也可以写CAPL脚本,在on message事件里判断ID,只记录NM报文。CAPL脚本的好处是可以同时记录时间戳和Control Bit Vector的值,方便后续分析。
6.2 用Trace窗口观察状态迁移
Trace窗口是分析NM状态迁移最直观的工具。把NM报文的Source Node Identifier和Control Bit Vector加到列显示里,就能看到每个节点在什么时间发了什么NM报文,Control Bit Vector的值是多少。
观察状态迁移的时候,重点看几个时间点:网络唤醒的时间点、第一个NM报文出现的时间点、Repeat Message Request置1的时间点、NM报文停发的时间点、总线彻底安静的时间点。这几个时间点对应了状态机的关键迁移。
我一般会在Trace里加几个Marker,标记这些关键时间点。然后对照CanNm的状态机图,看实际迁移是否符合预期。如果某个迁移提前或者延迟了,就去查对应的定时器参数。
6.3 用CAPL脚本自动记录NM状态
手动看Trace效率低,尤其是网络节点多的时候。我习惯写一个CAPL脚本,自动记录每个节点的NM状态变化。脚本逻辑大概是这样的:
variables { msTimer tCheck; int nodeState[10]; } on message 0x500 { int srcId; srcId = this.byte(0); if (this.byte(1) & 0x01) { write("Node %d requests Repeat Message at %d", srcId, timeNow()); } nodeState[srcId] = 1; } on timer tCheck { int i; for (i = 0; i < 10; i++) { if (nodeState[i] == 1) { write("Node %d is active", i); } } setTimer(tCheck, 1000); }这个脚本只是示例,实际项目里可以根据需要扩展。比如记录每个节点的NM报文发送间隔,判断是否有节点发送异常。或者记录Control Bit Vector的变化,分析网络协调过程。
7. 常见休眠唤醒问题与排查技巧实录
7.1 网络无法休眠的典型原因
网络无法休眠是最常见的问题。表现是:所有应用报文都停了,但NM报文还在发,总线一直不安静。原因通常有几个:
某个节点一直在发NM报文。可能是这个节点的应用层还在请求通信,ComM没有释放。也可能是这个节点的NM状态机卡在Normal Operation State,没有进入Ready Sleep State。排查方法是看Trace,找出哪个节点的NM报文一直在发,然后查这个节点的ComM请求源。
NmTimeoutTime设得太大。如果NmTimeoutTime设得很大,即使所有节点都停了NM报文,也要等很久才能进Prepare Bus-Sleep。这个参数要根据项目需求平衡,不能太大也不能太小。
BswM仲裁没通过。NM状态对了,但BswM因为其他原因(比如诊断还在进行、应用还在请求)没有释放通信,EcuM就不休眠。这种情况要看BswM的仲裁日志。
7.2 网络频繁唤醒的排查思路
网络频繁唤醒比无法休眠更麻烦,因为它会导致静态电流反复波动。表现是:网络刚休眠不久,又被唤醒,然后再次休眠,如此反复。原因通常是:
残留报文触发唤醒。总线上有节点在休眠后还在发报文,或者有干扰导致总线活动,CanIf检测到之后触发唤醒。排查方法是看唤醒时间点附近的Trace,找出是哪帧报文触发了唤醒。
唤醒源配置错误。有些ECU的唤醒源配置了多个,比如KL15、CAN、LIN都配了唤醒。如果KL15有抖动,就会频繁触发唤醒。排查方法是查EcuM的唤醒源配置,确认每个唤醒源的触发条件。
NM报文发送时机不对。如果某个节点的NM报文在Prepare Bus-Sleep阶段还在发,就会把其他节点重新拉回Network Mode。这种情况要检查NmWaitBusSleepTime是否足够长,确保最后一帧NM报文发完再进Bus-Sleep。
7.3 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| 网络无法休眠 | 某节点NM报文持续发送 | 看Trace找发送节点,查ComM请求源 |
| 网络频繁唤醒 | 残留报文或唤醒源抖动 | 看唤醒时间点Trace,查EcuM唤醒源配置 |
| NM报文收不到 | CAN ID或DLC配置错误 | 查CanIf的RxPdu配置,对比发送方配置 |
| 状态机不迁移 | 定时器参数不满足关系 | 检查NmTimeoutTime和NmMsgCycleTime的关系 |
| ECU不休眠 | BswM仲裁未通过 | 查BswM仲裁日志,确认仲裁条件 |
| Repeat Message不生效 | Control Bit Vector配置错误 | 查CanNm的Control Bit Vector默认值配置 |
这个表是我在实际项目中总结的,覆盖了大部分常见问题。遇到新问题的时候,先对照这个表排查,能省不少时间。
7.4 几个容易被忽略的细节
NM报文和应用报文的发送顺序。网络唤醒后,通常是先发NM报文,再发应用报文。如果应用报文先发,接收方可能还没准备好,导致报文丢失。这个顺序由ComM和BswM控制,配置的时候要注意。
NM报文和诊断报文的优先级。诊断报文通常优先级更高,如果诊断在进行,NM报文可能会被延迟。这种情况要确认CanIf的发送队列配置,避免NM报文被诊断报文堵住。
多通道NM的协调。如果ECU同时挂在多个CAN通道上,每个通道都有自己的CanNm实例。多通道之间的协调由Nm模块处理,配置的时候要确认Nm通道的绑定关系,避免通道之间互相干扰。
8. 我个人在实际项目中的几点体会
CanNm这个东西,看规范觉得简单,实际调起来坑不少。我最大的体会是:不要只盯着CanNm看。休眠唤醒问题往往是整条链路的问题,CanNm只是其中一环。ComM、BswM、EcuM的配置同样重要,有时候问题出在上层,但表现却在CanNm上。
另一个体会是:Trace是最好的老师。规范再熟,不如实际抓一次报文看状态迁移。我习惯在项目初期就把CANoe的Trace配好,把NM报文的关键字段都显示出来。这样每次调网络管理,都能快速定位问题。
最后分享一个小技巧:在项目里建一个NM参数对照表。把每个节点的Source Node Identifier、NmMsgCycleTime、NmTimeoutTime这些参数都列出来,配置的时候对照检查。这个表看着简单,但能避免很多配置不一致的问题。尤其是节点多的项目,没有这个表,很容易配错。
这个内容后续还可以扩展的方向包括:CanNm和Partial Networking的结合使用、多通道NM的协调机制、NM和诊断的交互场景。这些在实际项目中都会遇到,有机会再单独整理。