诊断服务实现(中):0x22、0x2E、0x2F 的配置与实现

好,我们接着聊诊断服务的实现。上一章我们把 0x10、0x11、0x19 这几个基础服务讲透了,今天要啃的这三块——0x22、0x2E、0x2F,说白了就是诊断世界里最常用的「读写控制」三板斧。

我个人习惯把这三个服务放在一起讲,因为它们在配置上有很多共通之处。你想想看,读取数据、写入数据、控制输入输出,本质上都是在跟 ECU 的内部数据打交道。只不过有的只读,有的可写,有的还要带点「副作用」。

0x22 读取数据标识符:最常用的诊断服务

0x22 服务,我敢说在量产项目中,它的调用频率能排进前三。不管是产线刷写后的版本校验,还是售后维修时的状态读取,都离不开它。

它的核心逻辑很简单:客户端发一个 DID(数据标识符),服务端把对应的数据值返回回去。但简单归简单,配置起来有几个坑我得提醒你。

DID 的配置要点

在 AUTOSAR 的配置工具里(比如 Vector 的 DaVinci Developer 或 EB 的 tresos),每个 DID 都需要绑定一个数据源。这个数据源可以是:

  • 信号(Signal):直接从 SWC 的 Port 上读取
  • 内存地址(Memory Address):直接读 RAM 或 Flash 的某个地址
  • 组合 DID(Composite DID):把多个信号拼成一个 DID 返回

我在项目中遇到过一种情况:客户要求用一个 DID 返回整车的 VIN 码、ECU 硬件版本和软件版本。这时候用组合 DID 就特别合适。你只需要在配置里把三个信号按顺序排好,DCM 模块会自动帮你打包成响应报文。

关键配置参数:

  • DcmDspDidIdentifier:DID 的 ID,比如 0xF190
  • DcmDspDidReadAccessAllowed:是否允许读取(有些 DID 只写不读)
  • DcmDspDidReadDataLength:返回数据的长度,单位是字节
  • DcmDspDidReadDataElementRef:绑定到具体的信号或内存地址

代码实现示例

嗯,这里要注意。虽然 AUTOSAR 的配置工具能生成大部分代码,但有些特殊 DID 还是得手写回调函数。比如读取一个需要实时计算的虚拟传感器值。

/* 0x22 读取 DID 的回调函数示例 */
Std_ReturnType Dcm_ReadDataByIdentifier(
    Dcm_OpStatusType OpStatus,
    Dcm_ServiceIdType ServiceId,
    Dcm_DidType Did,
    uint8 *DataBuffer,
    uint16 *DataLength
)
{
    (void)OpStatus;
    (void)ServiceId;

    switch (Did)
    {
        case 0xF190: /* 读取 ECU 软件版本 */
        {
            const uint8 swVersion[] = {0x01, 0x02, 0x03, 0x04};
            memcpy(DataBuffer, swVersion, sizeof(swVersion));
            *DataLength = sizeof(swVersion);
            return E_OK;
        }

        case 0xF191: /* 读取实时车速(需要计算) */
        {
            uint16 vehicleSpeed = GetVehicleSpeed_kmh(); /* 从 CAN 信号计算 */
            DataBuffer[0] = (uint8)(vehicleSpeed >> 8);
            DataBuffer[1] = (uint8)(vehicleSpeed & 0xFF);
            *DataLength = 2;
            return E_OK;
        }

        default:
            return E_NOT_OK; /* 不支持的 DID */
    }
}

我的经验:回调函数里尽量别做耗时操作。我曾经见过一个同事在 0x22 回调里调用了 EEPROM 写入函数,结果诊断仪超时了。记住,0x22 是读取服务,响应时间通常要求在 50ms 以内。

0x2E 写入数据标识符:写操作要谨慎

0x2E 服务,说白了就是 0x22 的反向操作。客户端发一个 DID 和一段数据,服务端把它存到指定的位置。

这个服务在产线标定和售后配置修改时用得比较多。比如修改 VIN 码、配置车辆参数等。但我要强调一点:写入操作一定要做安全校验

安全访问机制

大多数 OEM 都要求:在执行 0x2E 之前,必须先通过 0x27(安全访问)的解锁流程。否则,随便一个诊断仪就能改你的 ECU 参数,那还得了?

配置上,你需要把 DID 的写入权限和 Security Level 绑定起来:

DID 读取权限 写入权限 安全等级要求
0xF190(软件版本) 始终允许 不允许 N/A
0xF191(VIN 码) 始终允许 仅解锁后 Security Level 1
0xF192(标定参数) 始终允许 仅解锁后 Security Level 2

避坑指南:我曾经在某个项目里,因为配置工具里忘了勾选「写入需要安全访问」的选项,结果导致产线上一个普通工位就能修改 ECU 的标定参数。还好在 DV 阶段发现了,不然量产就出大事了。所以每次配置完 0x2E,一定要检查安全访问的绑定关系。

写入回调的实现

/* 0x2E 写入 DID 的回调函数示例 */
Std_ReturnType Dcm_WriteDataByIdentifier(
    Dcm_OpStatusType OpStatus,
    Dcm_ServiceIdType ServiceId,
    Dcm_DidType Did,
    const uint8 *DataBuffer,
    uint16 DataLength
)
{
    (void)OpStatus;
    (void)ServiceId;

    switch (Did)
    {
        case 0xF191: /* 写入 VIN 码 */
        {
            if (DataLength != 17) /* VIN 码固定 17 字节 */
            {
                return E_NOT_OK;
            }
            /* 写入 NVM(非易失性存储器) */
            if (Nvm_WriteBlock(VIN_BLOCK_ID, DataBuffer) != E_OK)
            {
                return E_NOT_OK;
            }
            return E_OK;
        }

        default:
            return E_NOT_OK;
    }
}

0x2F 输入输出控制:最灵活也最危险的服务

0x2F 服务,我个人觉得是这三个里面最有趣的。它允许诊断仪直接控制 ECU 的输入或输出信号。比如强制让某个执行器动作、模拟某个传感器的值。

这个服务在诊断测试和售后维修时特别有用。比如你要测试一个车窗电机,不需要真的去按开关,直接发一个 0x2F 请求就能让它升降。

控制模式

0x2F 支持三种控制模式:

  • 0x00:返回控制状态——查询当前这个信号是否被诊断控制
  • 0x01:复位为默认值——解除诊断控制,恢复 ECU 正常逻辑
  • 0x02-0x7F:短时控制——强制信号为指定值,持续一段时间

嗯,这里要注意。0x2F 的控制是有时效性的。你想想看,如果诊断仪断开了连接,但 ECU 还一直保持强制控制状态,那车还怎么开?所以 AUTOSAR 规定,0x2F 的控制状态在 ignition off 或者 ECU 复位后会自动清除。

配置与实现

配置 0x2F 时,你需要为每个可控制的信号指定:

  • 信号的数据类型(uint8、uint16、boolean 等)
  • 控制的有效范围(比如某个执行器只能控制 0-100%)
  • 超时时间(超过这个时间没有新的控制请求,自动复位)
/* 0x2F 输入输出控制回调示例 */
Std_ReturnType Dcm_InputOutputControlByIdentifier(
    Dcm_OpStatusType OpStatus,
    Dcm_ServiceIdType ServiceId,
    Dcm_DidType Did,
    const uint8 *DataBuffer,
    uint16 DataLength
)
{
    (void)OpStatus;
    (void)ServiceId;

    switch (Did)
    {
        case 0xF200: /* 控制左前车窗电机 */
        {
            uint8 controlMode = DataBuffer[0];
            uint8 targetValue = DataBuffer[1];

            if (controlMode == 0x01) /* 复位 */
            {
                WindowMotor_Reset(LEFT_FRONT);
            }
            else if (controlMode == 0x02) /* 短时控制 */
            {
                /* targetValue: 0=停止, 1=上升, 2=下降 */
                WindowMotor_SetTarget(LEFT_FRONT, targetValue);
            }
            else
            {
                return E_NOT_OK;
            }
            return E_OK;
        }

        default:
            return E_NOT_OK;
    }
}

安全第一:0x2F 服务一定要加安全访问保护。我曾经在测试时,不小心用 0x2F 强制让一个风扇全速运转,结果测试台架上的温度传感器直接报警了。所以,对于可能造成物理伤害的控制(比如电机、风扇、阀门),建议使用 Security Level 3 或更高的等级。

三个服务的对比总结

最后,我用一张表帮你理清这三个服务的区别:

服务 方向 典型用途 安全要求 时效性
0x22 读取 版本信息、状态值、故障码 通常不需要
0x2E 写入 VIN 码、标定参数、配置 需要安全访问 永久存储
0x2F 控制 执行器测试、传感器模拟 强烈建议 临时(复位后清除)

好了,这一章的内容就到这。0x22、0x2E、0x2F 这三个服务,配置起来不算复杂,但细节很多。尤其是安全访问和超时处理,稍不注意就会出问题。下一章我们聊 0x14(清除诊断信息)和 0x19 的进阶用法,到时候见。