1. 引言
在UDS刷写流程中,例程控制(0x31 Service) 是用途最广泛、功能最强大的服务之一。如果说0x34-36-37负责传输数据,0x2E负责写入配置信息,那么0x31则负责执行ECU内部预定义的操作序列。
在固件升级场景中,0x31服务承担着多项关键任务:
-
擦除编程分区:在写入新固件前,擦除目标Flash区域
-
编程条件检查:验证ECU是否满足升级条件(车速、电压等)
-
完整性校验:下载完成后校验固件数据的正确性
-
写入指纹信息(替代方案):部分项目使用0x31写入指纹
TOOMOSS_SID31_RoutineControl.vi封装了UDS 0x31服务的完整流程,通过调用TOOMOSS_SendAndWaitResp.vi通信基座,实现对ECU内部例程的启动、停止和结果查询。
2. 子VI功能概述
2.1 设计目标
-
根据用户指定的子功能(启动/停止/查询结果)、例程ID和可选参数,构造0x31服务请求报文
-
调用通信基座发送请求并等待ECU响应
-
解析响应报文,判断例程执行是否成功
-
返回响应数据和执行状态
2.2 UDS 0x31服务协议说明
服务名称:RoutineControl(例程控制)
功能:客户端(测试设备)能够启动、停止例程,并在例程执行后查看其生成的结果。RoutineControl服务用于执行定义的一系列步骤并获取相关结果。
2.2.1 子功能参数(routineControlType)
0x31服务使用子功能参数来区分例程的控制类型:
| 子功能值 | 名称 | 说明 |
|---|---|---|
| 0x01 | startRoutine(启动例程) | 请求ECU启动指定的例程 |
| 0x02 | stopRoutine(停止例程) | 请求ECU停止正在运行的例程 |
| 0x03 | requestRoutineResults(请求例程结果) | 请求获取例程执行的结果数据 |
2.2.2 请求报文格式
0x31服务的请求报文格式如下:
| 字节序号 | 参数 | 说明 |
|---|---|---|
| Byte 0 | SID | 固定值 0x31 |
| Byte 1 | sub-function | 子功能(0x01启动/0x02停止/0x03查询结果) |
| Byte 2 | routineIdentifier(高字节) | 例程标识符的高8位 |
| Byte 3 | routineIdentifier(低字节) | 例程标识符的低8位 |
| Byte 4 ~ N | routineControlOptionRecord | 可选的例程控制参数 |
2.2.3 肯定响应格式
肯定响应格式如下:
| 字节序号 | 参数 | 说明 |
|---|---|---|
| Byte 0 | SID | 0x71(0x31 + 0x40) |
| Byte 1 | sub-function | 回声,与请求中的子功能一致 |
| Byte 2 | routineIdentifier(高字节) | 例程标识符的高8位 |
| Byte 3 | routineIdentifier(低字节) | 例程标识符的低8位 |
| Byte 4 ~ N | routineStatusRecord | 例程执行结果/状态数据(可选) |
2.2.4 否定响应格式
Byte 0: SID = 0x7F
Byte 1: 0x31
Byte 2: NRC(负响应码)
2.3 常见例程ID(参考)
ISO 14229-1定义了一些标准的例程标识符:
| 例程ID | 名称 | 说明 |
|---|---|---|
| 0xFF00 | eraseMemory | 擦除内存 |
| 0xFF01 | checkProgrammingDependencies | 检查编程依赖性 |
| 0xFF02 | eraseMirrorMemoryDTCs | 擦除镜像内存DTC |
| 0x0200~0xDFFF | vehicleManufacturerSpecific | 车厂自定义 |
| 0xF000~0xFEFF | systemSupplierSpecific | 供应商自定义 |
注意:实际项目中,例程ID通常由车厂定义,请在诊断调查表中查阅具体定义。
2.4 输入参数
| 控件 | 类型 | 说明 |
|---|---|---|
| 设备句柄 | U32 | 由TOOMOSS_OpenDev(CAN).vi返回的设备句柄 |
| 通道号 | 枚举(CAN1/CAN2) | 选择使用CAN1还是CAN2通道 |
| 物理地址 | U32 | 请求报文ID(发送给ECU的CAN ID) |
| 响应地址 | U32 | 期望的响应报文ID(ECU回复的CAN ID) |
| 子功能 | 枚举(启动例程/停止例程/请求结果) | 例程控制类型 |
| 例程ID | U16 | 要控制的例程标识符 |
| 其他数据 | U8数组 | 可选的例程控制参数(由具体例程定义) |
| 数据长度 | I32 | 从0x31开始计算的请求数据总长度 |
2.5 输出参数
| 控件 | 类型 | 说明 |
|---|---|---|
| 响应数据 | U8数组 | ECU返回的完整响应数据(肯定响应或否定响应) |
| 返回值 | I32 | 0表示成功,-1表示失败 |
| 错误信息 | 字符串 | 失败时的错误描述 |
3. 前面板设计

4. 程序框图设计
4.1 整体流程
子功能枚举 → 获取subFunction值
例程RID(U16)→ 拆分为高字节和低字节
其他数据(U8数组)→ 作为routineControlOptionRecord
↓
构造请求数据[0x31, subFunction, RID_High, RID_Low, 其他数据...]
↓
调用 TOOMOSS_SendAndWaitResp.vi
↓
判断响应类型
┌───────────────┼───────────────┐
↓ ↓ ↓
返回<0 返回=0 返回>0
(调用失败) (超时) (收到响应)
↓ ↓ ↓
返回-1 返回-1 判断响应首字节
┌───────────────┼───────────────┐
↓ ↓
0x71 0x7F
(肯定响应) (否定响应)
↓ ↓
返回0 返回-1
解析NRC返回错误信息
4.2 第一步:拆分例程ID
例程ID是一个2字节(U16)的标识符,需要拆分为高字节和低字节:
-
RID高字节 =
(RID >> 8) & 0xFF -
RID低字节 =
RID & 0xFF
4.3 第二步:构造请求数据
使用创建数组函数按顺序组合:
[0x31, subFunction, RID_High, RID_Low, 其他数据...]
其中其他数据是routineControlOptionRecord,是可选的例程控制参数。当子功能为startRoutine或stopRoutine时,此参数可选。
4.4 第三步:调用通信基座
将上一步构造的请求数据作为输入,调用TOOMOSS_SendAndWaitResp.vi:
| 输入参数 | 来源/值 |
|---|---|
| 设备句柄 | 子VI输入 |
| 通道号 | 子VI输入 |
| 物理地址 | 子VI输入 |
| 响应地址 | 子VI输入 |
| 请求数据 | 构造的[0x31, subFunction, RID_High, RID_Low, 其他数据...] |
| 数据长度 | 数据长度输入(从0x31开始计算的总长度) |
| 超时时间 | 根据不同例程设置(擦除类建议5000ms,查询类1000ms) |
4.5 第四步:判断响应类型
从通信基座获取返回值,分三种情况处理:
情况一:返回值 < 0(通信失败)
-
通信基座内部调用DLL失败
-
直接透传错误信息,子VI返回 -1
情况二:返回值 = 0(无响应/超时)
-
ECU未响应
-
设置错误信息为"ECU未响应,请检查物理地址和通信连接"
-
子VI返回 -1
情况三:返回值 > 0(收到响应)
-
响应数据有效,继续解析
-
判断响应数据的首字节(Byte 0)
肯定响应判断:
text
if 响应数据[0] == 0x71 → 例程控制成功,返回0
响应数据0 = 0x71 = 0x31 + 0x40,符合UDS肯定响应规则。
否定响应判断:
text
if 响应数据[0] == 0x7F → 例程控制失败
NRC = 响应数据[2]
根据NRC值生成对应的错误信息
返回-1
4.6 常见NRC错误信息映射
| NRC | 含义 | 错误信息 |
|---|---|---|
| 0x12 | 子功能不支持 | "该ECU不支持请求的子功能或例程不存在" |
| 0x13 | 报文长度错误 | "请求报文长度或格式不正确" |
| 0x22 | 条件不满足 | "先决条件不满足,请检查当前会话状态" |
| 0x31 | 参数无效 | "传入的参数超出例程允许范围" |
| 0x33 | 安全访问被拒绝 | "安全访问未解锁,请先完成0x27服务" |
| 0x72 | 故障处于活动状态 | "执行例程时检测到其他故障" |
| 0x7E | 当前会话下子功能不支持 | "当前会话下不支持该子功能" |
| 0x7F | 当前会话下服务不支持 | "当前会话下不支持31服务" |
5. 程序框图关键节点示意
5.1 例程ID拆分与请求数据构造

5.2 通信基座调用

5.3 响应解析

6. 在UDS刷写流程中的位置
在整个UDS刷写流程中,0x31服务在多个阶段被调用:
预编程阶段:
(2) 编程条件检查 [31 01 02 03] ← 调用本子VI(启动例程)
主编程阶段:
(5) 擦除编程分区 [31 01 FF 00] ← 调用本子VI(启动例程)
(7) 完整性校验 [31 01 02 02] ← 调用本子VI(启动例程或查询结果)
6.1 典型应用场景
| 场景 | 子功能 | 例程ID示例 | 参数说明 |
|---|---|---|---|
| 编程条件检查 | 启动例程(0x01) | 0x0203 | 检查车速、电压等条件 |
| 擦除编程分区 | 启动例程(0x01) | 0xFF00 | 擦除目标Flash区域 |
| 完整性校验 | 启动例程(0x01) | 0x0202 | 校验固件CRC |
6.2 前置条件
执行0x31服务前,通常需要满足以下条件:
-
会话状态 :已切换到编程会话(0x02) 或扩展会话(0x03)
-
安全访问:已通过0x27服务完成安全访问解锁(敏感例程)
-
例程ID有效性:例程ID在ECU中已定义
6.3 子功能使用说明
| 子功能 | 使用场景 |
|---|---|
| 启动例程(0x01) | 请求ECU开始执行例程,如擦除内存、条件检查等 |
| 停止例程(0x02) | 请求ECU停止正在执行的例程 |
| 请求结果(0x03) | 请求获取例程执行结果数据 |
7. 典型调用示例
7.1 启动擦除例程(0xFF00)
输入配置:
-
物理地址 = 0x700
-
响应地址 = 0x708
-
子功能 = 启动例程(0x01)
-
例程ID = 0xFF00
-
其他数据 = 擦除起始地址和长度(如
[0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00]) -
数据长度 = 11(0x31 + 子功能 + RID 2字节 + 8字节参数)
实际发送 :[0x31, 0x01, 0xFF, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00]
期望响应 :[0x71, 0x01, 0xFF, 0x00, 0x00](肯定响应,0x00表示擦除成功)
7.2 启动编程条件检查例程
输入配置:
-
物理地址 = 0x700
-
响应地址 = 0x708
-
子功能 = 启动例程(0x01)
-
例程ID = 0x0203(具体ID由车厂定义)
-
其他数据 = 空(无参数)
-
数据长度 = 4
实际发送 :[0x31, 0x01, 0x02, 0x03]
期望响应 :[0x71, 0x01, 0x02, 0x03, 0x00](0x00表示条件满足)
7.3 停止例程
输入配置:
-
子功能 = 停止例程(0x02)
-
例程ID = 0xFF00
-
其他数据 = 空
-
数据长度 = 4
实际发送 :[0x31, 0x02, 0xFF, 0x00]
期望响应 :[0x71, 0x02, 0xFF, 0x00]
7.4 请求例程结果
输入配置:
-
子功能 = 请求结果(0x03)
-
例程ID = 0xFF00
-
其他数据 = 空
-
数据长度 = 4
实际发送 :[0x31, 0x03, 0xFF, 0x00]
期望响应 :[0x71, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00](返回详细状态数据)
8. 错误处理
| 错误场景 | 返回值 | 错误信息 |
|---|---|---|
| 设备句柄无效 | -1 | "设备句柄无效,请检查设备是否已打开" |
| 通信基座返回<0 | -1 | 透传通信基座的错误信息 |
| 通信基座返回=0 | -1 | "ECU未响应,请检查物理地址和通信连接" |
| 响应首字节为0x7F | -1 | 根据NRC返回对应错误信息 |
| 响应首字节非0x71非0x7F | -1 | "收到未知响应,SID=0x%02X" |
9. 注意事项
| 要点 | 说明 |
|---|---|
| 会话要求 | 敏感例程(如擦除内存)通常需要编程会话(0x02),其他例程可能需要扩展会话 |
| 安全访问 | 擦除内存等敏感例程必须在完成0x27安全访问解锁后执行 |
| 例程ID定义 | 例程ID由车厂定义,请在诊断调查表中查阅 |
| 超时时间 | 不同例程执行时间差异大:擦除Flash建议5000ms以上,条件检查建议2000ms |
| 例程参数 | 部分例程需要携带参数(如擦除地址、长度),参数格式由具体例程定义 |
| 子功能与参数 | 当子功能为startRoutine或stopRoutine时,routineControlOptionRecord是可选的 |
| 权限检查 | UDS对0x31服务的权限检查是在例程ID级别进行的,而非子功能级别 |
10. 总结
TOOMOSS_SID31_RoutineControl.vi封装了UDS 0x31例程控制服务,是UDS刷写上位机中功能最强大、用途最广泛的服务之一。
0x31服务的核心价值在于:
-
执行复杂操作:擦除内存、运行自检、条件检查等无法通过简单读写完成的操作
-
灵活可扩展:车厂可根据需要为ECU定义各种内部操作
-
三种控制模式:启动、停止、查询结果,覆盖例程的完整生命周期
理解子功能(启动/停止/查询结果)和例程ID的含义,以及不同例程对会话状态和安全访问的要求,是正确使用该子VI的关键。
下一篇将介绍 Main**.vi --- 主VI**的完整实现,敬请期待。