AUTOSAR Dcm服务列表配置:从原理到实战全解析 做 AUTOSAR 诊断栈最头疼的一步往往不是把 Dcm 模块拖进工程里而是面对配置工具里那几百行服务配置表一头雾水。DcmDiagnostic Communication Manager在 AUTOSAR 中负责所有 UDS 诊断请求的接收、校验、路由和响应而所谓“服务列表”就是告诉 Dcm“这个 ECU 支持哪些诊断服务、在什么会话下能用、要不要安全等级、走物理寻址还是功能寻址”的那张核心配置表。可以说诊断能不能跑通一半看协议栈一半看服务列表配得对不对。这篇文章我会基于实际项目里的 AUTOSAR 配置经验把 Dcm 服务列表从原理到工具配置完整拆一遍重点讲每个配置项的决策逻辑以及那些文档里不会写、只有踩过坑才懂的点。适合刚接触 AUTOSAR 诊断开发、正在配 Dcm 模块或者准备排查诊断异常问题的朋友。1. 先从诊断链路说起Dcm 到底站在哪一层1.1 一条诊断请求从诊断仪到 ECU 的完整旅程搞清楚服务列表之前得先知道一条诊断请求在 ECU 内部是怎么走的。诊断仪通过 CAN现在也有 DoIP、LIN发一帧或几帧诊断数据比如“10 03”表示请求切换到扩展会话。这组字节从物理层进来经过 CanIf被 CanTp 做单帧或多帧组包完整重组出一条 UDS 报文PduR 拿着这条报文根据配置找到 Dcm。Dcm 内部其实分了三个子模块DSLDiagnostic Session Layer、DSDDiagnostic Service Dispatcher、DSPDiagnostic Service Processing。DSL 管会话状态、P2/P2* 定时DSD 收到完整的请求后去查服务列表判断这个 SID 是否支持、当前会话对不对、安全等级够不够全部检查通过后转给 DSP 里对应的服务处理函数比如 0x22 就进 Dcm_Dsp_ReadDataByIdentifier0x2E 就进 WriteDataByIdentifier。理解这个链路之后就会发现服务列表其实是 DSD 层“查表审核”的依据。也就是说服务列表不只是给你自己维护用的文档它直接参与 ECU 运行时对请求的仲裁。配置错了轻则诊断失败重则会出安全漏洞。1.2 服务列表等于把“需求文档”翻译成“协议栈配置”很多项目一开始拿到的诊断规范里会有一张表格SID、服务名、支持会话、安全等级、功能寻址是否允许……这份文档是给测试、标定、产线看的。而 Dcm 服务列表配置本质上是把这张表翻译成 AUTOSAR 工具链能识别的结构化参数。这也解释了为什么服务列表在 AUTOSAR 里不是一个孤立的“列表文件”而是散布在 Dcm 模块的多个配置容器里DcmDspService 定义服务行为DcmDspSession 定义会话有效范围DcmDspSecurity 定义安全等级DcmDspSubFunction 定义子功能位与抑制响应位。用达芬奇配置的时候你看到的是树形节点导出的 ARXML 里它们才是完整的一张张服务表。我遇到过不少开发者习惯在代码里硬编码一个 switch-case 来处理诊断请求出了 AUTOSAR 项目进了 Classic 平台就转不过弯。AUTOSAR 的做法是把路由和策略全部外置成配置业务代码只关心“DID 怎么读、例程怎么跑”。服务列表配好了协议栈就帮你把大部分通用逻辑做完了。2. 服务列表核心结构Session、Security、SubFunction 三位一体2.1 一条记录里到底要填哪些关键项实际配置 DcmDspService 时每个服务都会有一组关联参数常被人忽略又容易踩坑的是下面这几个服务 IDServiceId0x00-0xFF就是 UDS 请求的第一个字节。会话组合SessionRef决定该服务在默认、编程、扩展哪个会话下可用。安全等级SecurityLevel0 表示不需要安全解锁非 0 要和 0x27 安全访问的等级对应。SuppressBitSupport也就是响应抑制位子功能字节 bit7是否启用。子功能镜像SubFunctionRefs每个支持的子功能要单独维护一个镜像里面又包含 SubFunctionBehavior支持还是不支持、是否含正响应。功能寻址支持DiagnosticMessageType区分物理寻址和功能寻址。在达芬奇里DcmDspService 下通常会列一个“AllowedSession”的数组比如 0x22 读写 DID 习惯上允许 default 和 extended 会话而 0x27 安全访问一般默认会话也要开0x34/0x36/0x37 这类刷写服务通常只在编程会话放行。安全等级不是越高越好故意把所有服务的 SecurityLevel 都配成 0 会在后期安全评审时被打回要按诊断规范严格来。2.2 子功能与抑制响应位容易被忽略的 bit7UDS 服务分两类一类有子功能0x10、0x27、0x28、0x85 等一类没有0x22、0x2E、0x31 例外0x31 有子功能。有子功能的服务请求的第二个字节 bit7 是抑制正响应位。如果置 1ECU 执行完功能后不回复正响应码只有错误时才发负响应。AUTOSAR 里这个开关叫 SubFunctionSuppressBit。配置成“支持”时协议栈会让位给请求方决定是否要正响应配置成“不支持”时即使诊断仪置了 bit7ECU 也照样返回正响应。这里有个容易出问题的地方如果规范里要求该服务不允许抑制正响应而你把 SuppressBitSupport 配成 enabled功能测试一板子打下来响应行为和规范不一致测试报告直接一片红。另外0x31 例程控制这类服务子功能例程控制类型和例程 ID 都在数据里Dcm 会先查子功能是否支持再丢给上层处理例程 ID。服务列表里的子功能镜像和上层回调里真正实现的例程集合必须保持一致不然会出现“配置上支持了但回调返回 NRC 0x31”的怪问题。2.3 物理寻址和功能寻址为什么不能一刀切诊断请求可以走物理寻址一对一跳过所有 ECU也可以走功能寻址同一网段下多个 ECU 同时执行。服务列表里每个服务都要单独指定支持哪种寻址方式。常见做法是0x22 读 DID 允许功能寻址0x2E 写 DID 出于安全考虑仅物理寻址0x19 读 DTC 允许功能寻址0x14 清 DTC 仅物理寻址0x10 会话切换功能寻址也能做但代价是网段内所有 ECU 一起切会话整车联调时容易乱。功能寻址还有一个坑Dcm 对功能寻址请求的处理不能等太久如果每个 ECU 都各自正向响应诊断仪收到的报文数量是成倍增长的。测试的时候用 CANoe 发功能寻址请求一旦有节点配置错了响应路由总线上直接一堆意外帧。而且功能寻址请求多个 ECU 都会触发所以涉及 NvM 写入、DTC 状态变化的服务尽量只开放物理寻址。3. 常用服务配置细节与实操要点3.1 0x10 会话切换几乎所有诊断流程的第一步0x10 服务的三个常用子功能0x01 default、0x02 programming、0x03 extended。配置 DcmDspSession 的时候除了把三个子节点建出来还要设置 P2Server P2 timing和 P2*enhanced timing。P2 默认配置成 50msP2* 根据 ECU 能力可能给到 5000ms这组值是后续每个服务的响应定时基准。实际操作中我发现默认会话切换到扩展会话的流程常常被忽略一个细节Dcm 切会话是会通知到上层应用和 BswM 的。比如切进编程会话BswM 需要关通信、停止周期报文避免刷写过程被干扰如果服务列表和 BswM 条件没打通就会出现“会话明明切了但报文一直发”的尴尬局面。配置 0x10 时SessionRef 的正确做法是把 default 会话配成允许 0x10 服务否则你一开始连默认会话切换请求都会被拒。很多项目第一次联调先发 10 02 想进扩展会话结果返回 0x7F 10 0x31requestOutOfRange查半天原来 default 会话下根本不允许 0x10。3.2 0x27 安全访问种子和密钥的安全性不能只靠协议栈0x27 服务配置相对固定子功能 0x01/0x02 表示请求种子、发送密钥0x03/0x04 表示级别 20x05/0x06 表示级别 3。每个安全等级在 Dcm 里对应一个 SecurityLevel 数值比如等级 A 是 0x01 对 0x02、等级 B 是 0x03 对 0x04。服务列表里配置 SecurityLevel 时要保证和 0x27 子功能的映射一致。种子生成和密钥校验的算法不在 Dcm 里而是通过 Dcm_SecurityAccess.Authentication 回调到上层或者走 CSMCrypto Service Manager/ SecOC 方案。这里我要重点说一句种子算法千万不要在回调里裸奔。曾经见过一个项目用固定常数当种子安全等级形同虚设。正确做法是用随机数配合计数器的机制防止重放攻击。另一个实际经验做产线刷写时如果安全访问失败会出现 ECU 锁定、连续失败次数过多后要求下电等待的情况。这个“尝试次数限制”最好放在上层策略里Dcm 只负责透传。如果把限制逻辑写在协议栈层后期想调整会很麻烦。3.3 0x22/0x2E 读写 DIDDID 和数据源要提前约定好0x22 读 DID 的配置比较简单通常是列一个 DID 列表每个 DID 对应一个数据 ID 处理实体DcmDspDataElement再绑定到底层数据源。数据源可以是 NvM 块、RAM 变量、Dem 的状态甚至是模拟量采集。配置时我建议把 DID 的读写权限也提前和需求核对清楚有的 DID 只读、有的只写、有的可读可写DcmDspDataElement 里虽然不直接区分读写但是如果你把只读 DID 配进 0x2E 的写列表实际敲不到数据还好敲到了却发现底层数据源没有写接口就会返回 NRC 0x31排查起来很费劲。0x2E 写 DID 也有讲究。大数据的 DID超过单个 CAN 帧容量会走 CanTp 多帧传输Dcm 收到完整数据之后才调用上层回调但有些项目为了“快速响应”会在收到第一帧就开始写 NvM结果多帧传输中断后数据半截入库。正确做法是 Dcm 判定数据接收完整并校验通过后再触发写入这些行为 Dcm 已经封装好了我们要做的就是别在配置里乱开优化选项。DID 的地址范围也不可乱来。0xF1 系列、0xF2 系列、0xF3 系列通常由 OEM 规范统一分配自定义 DID 一般放在 0x22 和 0x2E 的 OEM 自定义区。开发阶段如果随意超范围使用联网联调时可能和其他件冲突。3.4 0x14/0x19 诊断故障码管理和 Dem 是一条链路上的事0x19 服务配置时要确认支持哪些子服务0x01 读取 DTC 数量、0x02 按状态掩码读取 DTC、0x04 读取快照、0x0A 读取最近发生的 DTC。Dcm 不会自己存 DTC它只是把 DSMDiagnostic Status Mask参数透传给 Dem最终的数据来源是 Dem 模块。配置时 DcmDspEventStatus 和 Dem 的 DTC 状态位要对齐否则会出现 0x19 返回空列表但诊断仪上能看到故障码的诡异情况。0x14 清除诊断信息需要特别谨慎。清除动作会删掉 Dem 里的故障历史而且很多 ECU 在运行时清除会影响冻结帧和老化计数。常见的配置做法是0x14 仅允许物理寻址必须扩展会话或编程会话并且要走安全访问。功能寻址清除 DTC 是极端危险的一旦整车一帧功能寻址清 DTC 下去全部 ECU 的故障记录全没了。清除 DTC 之前还有个隐藏动作有些规范要求先进入扩展会话并安全解锁否则返回 0x7F 14 0x33 或 0x7F 14 0x7FserviceNotSupportedInActiveSession。排查这种问题时别急着去看 0x14 的配置先确认 0x27 安全等级和会话切换有没有生效很多负响应 0x22conditionsNotCorrect就是会话等级没满足。3.5 0x31 例程控制刷写和标定的隐形入口0x31 服务常用于启动例程、停止例程、请求例程结果刷写场景下典型例程有 0x0202 擦除、0xFF00 检查编程预条件等。配置 Dcm 时0x31 的子功能由第三个字节例程控制类型决定例程 ID 的数据则由上层函数判断。这里最容易犯的错误是在 DcmDspSubFunction 里漏配了例程控制类型的镜像导致例程命令发下去直接被 NRC 0x12 拦截。从协议栈角度看0x31 的处理函数 Dcm_DspRoutineControl 收到请求后会调用用户回调 Dcm_Application_RoutineControl这时例程 ID 和操作方式已经解析好了。回调函数的形参里有 OpStatusstart/stop/requestRoutineResults要根据它决定例程的执行状态机。很多新人在回调里不分状态start 例程和请求结果返回同一个数据导致诊断仪拿到的时间参数对不上。0x31 的性能有个工程经验如果例程执行时间超过 P2*必须让例程执行完之后再统一回复响应或者走 0x31 的“pendingResponse”机制。不要在回调里做长阻塞否则会影响 CanTp 的接收超时判断。3.6 0x85 控制 DTC 设置给特殊工况的“静音”开关0x85 服务用 0x02 子功能关闭 DTC 记录0x01 重新开启。配置不复杂但它牵扯到 Dem 的行为。ECU 在某些工况比如产线刷写、售后特殊测试下希望临时关闭故障码记录避免误报可一旦关了后续诊断这段过程发生的故障就都没了。Dcm 服务列表里对 0x85 的建议是只允许在默认会话或扩展会话、物理寻址下执行并且要注意开机时 DTC 记录状态的恢复策略。通常 ECU 上电后默认恢复为开启状态但如果 Dcm 配置了掉电保存存 NvM下次上电可能还是关闭状态这个问题在项目里出现过最后定位下来是上层应用在 Dcm_Init 之后把状态错误地初始化了。4. 在达芬奇DaVinci里配置服务列表的完整实操4.1 配置前准备把需求表整理成一张 Excel 服务清单无论用达芬奇还是其他 AUTOSAR 配置工具第一步都是先把诊断规范里的服务整理成一张可操作的清单。我建议至少包含这些列服务 ID服务名支持的子功能允许的会话安全等级寻址方式抑制正响应支持对应回调/数据源0x10Session01/02/03任意0物理功能支持BswM 联动0x22ReadDataByIdentifier无default/extended0物理功能无DataElement 列表0x27SecurityAccess01/02/...default/extended0物理支持上层鉴权回调0x2EWriteDataByIdentifier无扩展1物理无DataElement 列表0x14ClearDiagnosticInformation无扩展1物理无Dem0x19ReadDTCInformation01/02/04/0A任意0物理功能无Dem0x31RoutineControl01/02/03扩展/编程1物理支持上层例程回调0x85ControlDTCSetting01/02default/extended1物理支持Dem这张表填完后面的工具配置基本就是照单点鼠标不会漏项。4.2 在达芬奇配置器里建立服务记录的步骤打开 DaVinci Configurator 后先确认 Dcm 模块已经使能并且底层 PduR、CanTp 的路径已经打通。然后进 Dcm 的配置视图按容器一路展开到 DcmDspService新增服务记录。以配置 0x22 为例大致步骤如下。新建一个 DcmDspServiceServiceId 填 0x22服务名随意建议用“ReadDataByIdentifier”这样一眼能认出来的名字。配置 SessionRef把允许的会话节点加进去。默认会话一般至少保留否则发生意外请求时协议栈连读 DID 的机会都不给。配置 SecurityLevel如果需求要求 0x27 解锁后读就填对应的安全等级没有要求就填 0。新增 DcmDspDataElement一个 DID 对应一个把 DID 编号和数据长度填好。把 DataElement 关联到后端的回调函数或数据源。实际项目中读 DID 通常需要额外写一个转换函数把 NvM 里的原始字节打包成规范要求的格式再放回响应缓冲区。生成代码之后检查 RTERuntime Environment层是否把 Dcm 和 Dem、NvM、BswM 的接口连接起来。这一步最容易出问题配置了 Dcm 的数据元素但 RTE 映射没连上最后生成代码里回调变成空 stub。4.3 生成后的验证先从最简单的 10 03 开始代码生成完之后不要急着用完整诊断仪软件做测试。我习惯先用 CANoe 或者 PCAN 发最基础的请求按“会话切换—读 DID—安全访问—清 DTC”的顺序逐步验证。第一步发 10 03看返回是不是 50 03 00 32 01 F4 这类正响应。这里要注意看 Dcm 返回的时间戳是否在 P2 内如果 P2* 配置过短大 DID 读取会直接被诊断仪判定超时但 ECU 这边其实还在处理。第二步发 22 F1 90如果返回 0x7F 22 0x31就要去查 DataElement 的 DID 编号、长度、数据源是否都对得上。第三步再验证安全访问种子返回后再发密钥如果密钥算法正常之后再去读需要安全等级的服务。等这几步全部跑通服务列表的配置基本就稳了。5. 服务列表相关的典型问题与排查记录5.1 请求发下去了ECU 完全没有响应发 10 02 没有任何响应连负响应都没有。先别怀疑服务列表用 CANoe 的 Trace 窗口先看 CanTp 层报文有没有回如果连流控帧都没有说明问题出在 CanTp 或 PduR 路径上和 Dcm 无关。如果 CanTp 有流控帧但没有诊断响应再查 Dcm 的 DSD 层是否把请求路由到了服务处理函数常见原因是 Dcm 配置里没有使能对应的诊断协议或者 PduR 到 Dcm 的 Up 路径没连上。还有一次我发现 Dcm 一直无响应查到最后是“DcmDslProtocol”里的 DiagPdu 配置错误导致 Dcm 不认识这个 PDU 的 ID。协议 ID 不匹配在多个诊断 PDU 同时存在时尤其容易踩比如 CAN 诊断和 DoIP 诊断共用一个 Dcm 模块PDU 配置串了。5.2 0x22 读 DID 返回 0x31requestOutOfRange0x22 服务本身能进不会回 0x11但具体 DID 返回 0x31说明问题出在 DataElement 层面。排查顺序是查 DID 编号有没有写错查数据长度和实际缓存长度是否一致查会话和安全等级是否满足查 RTE 映射函数是不是被裁剪了最后查底层数据源读取函数是否返回了错误码。大部分“0x22 返回 0x31”的根因是 DataElement 配置里没有把 DID 和实际数据缓冲绑定生成了默认的 stub。5.3 会话切换成功了但业务服务还是拒不执行这个现象很有迷惑性10 03 返回 50 03然后发 2E F1 XX 却回 0x7F 2E 0x22conditionsNotCorrect。排查时要去确认两件事。第一0x2E 服务的 SessionRef 里是否真的加了 extended 会话别只看 DcmDspSession 里建了节点DcmDspService 的 SessionRef 数组没加进去。第二安全等级是否满足0x27 安全访问做过没有这时候发一个 27 01 看看种子返回是否正常。实际经验里90% 的这类问题都是会话和安全等级“概念上支持了、配置上没写全”。5.4 功能寻址请求行为不一致功能寻址下 0x22 能响应但功能寻址下 0x10 不响应这通常是正常的因为某些 OEM 规范默认禁止功能寻址切换会话。真正要查的是同一网段下两个 ECU同样的功能寻址请求一个响应一个不响应这时要检查两边 DcmDspService 里 DiagnosticMessageType 配的是不是一样一边配了 PHYSICAL另一边配了 PHYSICAL_AND_FUNCTIONAL行为自然不同。如果功能寻址回 0x7F 0x11大概率是服务记录里没勾功能寻址支持这是配置问题不是协议栈问题。5.5 服务列表改动后代码没生效这是开发期最容易犯的低级错误在达芬奇里改了 DcmDspService重新生成代码但烧录后发现行为没变。原因往往是 build 脚本没有重新生成 Dcm 的模块配置头文件。AUTOSAR 配置生成的代码和手写代码是分开的Dcm_Cfg.c、Dcm_Cfg.h 这类文件必须重新跑配置生成器再整体编译。可以在配置后搜一下 DCM_CFG_SUPPORT_xxx 这类宏看改动那几个服务相关宏是否已经更新。6. 个人经验与后续扩展6.1 服务列表要当成接口规范来维护做过的项目多了就发现服务列表的价值在于它能作为诊断需求和代码实现之间的“契约”。需求方改一个会话限制开发把 ARXML 重新导入再生成测试拿着更新后的服务清单去验三方都能对齐。我强烈建议在项目一开始就把服务列表的评审纳入变更流程别等联调出问题再回来看配置。同时维护一张“服务—会话—安全等级—寻址”矩阵表作为所有诊断联调的基础文档。6.2 刷写类服务务必配套安全机制如果你负责的项目涉及 0x34/0x36/0x37请求下载、传输数据、传输退出服务列表里这些服务应当限定在编程会话、物理寻址并且要安全访问之后才能执行。不要因为开发阶段图方便就把它们放开成 default 会话测试可能发现不了问题但后期安全检查一定过不了。程序刷写流程还要考虑和 Bootloader 的衔接App 的 Dcm 在刷写过程中不能干扰 Bootloader 的会话这部分需要确认 Dcm 模块和 BswM 之间有没有通信降级策略。6.3 这套思路可以顺延到 DoIP 和诊断刷新Dcm 服务列表的思路除了 CAN 诊断同样适用于 DoIP、LIN 诊断。换传输层之后Dcm 内部逻辑基本不变变的只是底层适配。如果你后面要扩展诊断刷写、远程诊断这些场景服务列表结构依然适用无非是多加几个会话、多配几个安全等级、多接几个数据源。配置的核心永远是对需求的理解而不是工具的操作熟练度。结合上面这些服务列表其实不难难的是每一步都知其所以然。等你自己把一段 10 03 的请求从总线一路追到服务分发再追到上层回调配置这些服务项目就会变得心里有底。