4. UDS协议栈集成

任务目标

在Bootloader中集成UDS(ISO 14229)协议栈,实现基础诊断服务:诊断会话控制(0x10)和安全访问(0x27)。Bootloader运行于CAN总线,接收并响应UDS请求,为后续编程会话和刷写功能建立通信基础。

实现方案

采用轻量级UDS协议栈设计,不依赖第三方库。核心模块包含:CAN帧收发适配层、UDS消息解析与组帧、服务分发表、NRC错误响应。当前案例聚焦会话控制与安全访问两个服务,其他服务(如0x34/0x36)在后续案例中扩展。

关键代码

// uds_stack.h
#ifndef UDS_STACK_H
#define UDS_STACK_H

#include <stdint.h>
#include <stdbool.h>

#define UDS_CAN_ID_REQUEST  0x7E0
#define UDS_CAN_ID_RESPONSE 0x7E8
#define UDS_MAX_PAYLOAD     8   // CAN标准帧数据长度

// 会话类型
#define SESSION_DEFAULT     0x01
#define SESSION_PROGRAMMING 0x02

// 安全访问状态
#define SEC_LOCKED          0x00
#define SEC_UNLOCKED        0x01

// 服务ID
#define SID_DIAG_SESSION    0x10
#define SID_SECURITY_ACCESS 0x27

// 返回值
#define UDS_OK              0x00
#define UDS_ERR_NRC         0x01

// NRC错误码
#define NRC_SERVICE_NOT_SUPPORTED   0x11
#define NRC_SUB_FUNCTION_NOT_SUPPORTED 0x12
#define NRC_INCORRECT_KEY           0x35
#define NRC_EXCEEDED_ATTEMPTS       0x36
#define NRC_REQUIRED_TIME_DELAY     0x37

typedef struct {
    uint8_t session;          // 当前会话
    uint8_t sec_access;       // 安全状态
    uint8_t sec_attempts;     // 连续失败次数
    uint16_t sec_key;         // 当前有效密钥
} UDS_Context;

// 初始化协议栈
void UDS_Init(UDS_Context *ctx);

// 处理接收到的CAN帧(由CAN接收中断调用)
void UDS_ProcessFrame(UDS_Context *ctx, const uint8_t *data, uint8_t len);

// 发送响应帧(由底层CAN驱动实现)
extern void CAN_SendFrame(uint32_t can_id, const uint8_t *data, uint8_t len);

#endif // UDS_STACK_H
// uds_stack.c
#include "uds_stack.h"
#include <string.h>

static void send_response(UDS_Context *ctx, uint8_t sid, const uint8_t *data, uint8_t len) {
    uint8_t frame[UDS_MAX_PAYLOAD];
    uint8_t idx = 0;
    frame[idx++] = 0x06; // 单帧 + 后续长度(实际长度+1)
    frame[idx++] = sid | 0x40; // 响应SID
    if (data && len > 0) {
        memcpy(&frame[idx], data, len);
        idx += len;
    }
    CAN_SendFrame(UDS_CAN_ID_RESPONSE, frame, idx);
}

static void send_nrc(UDS_Context *ctx, uint8_t sid, uint8_t nrc) {
    uint8_t data[2] = { sid | 0x40, nrc };
    send_response(ctx, 0x7F, data, 2);
}

static uint16_t seed_key(uint16_t seed) {
    // 简单密钥算法:种子异或0x5A5A后循环左移3位
    uint16_t key = seed ^ 0x5A5A;
    return (key << 3) | (key >> 13);
}

static void handle_session_control(UDS_Context *ctx, const uint8_t *data, uint8_t len) {
    if (len < 2) {
        send_nrc(ctx, SID_DIAG_SESSION, NRC_SUB_FUNCTION_NOT_SUPPORTED);
        return;
    }
    uint8_t sub = data[1];
    if (sub == SESSION_DEFAULT || sub == SESSION_PROGRAMMING) {
        ctx->session = sub;
        if (sub == SESSION_DEFAULT) {
            ctx->sec_access = SEC_LOCKED; // 退出编程会话时重新锁定
            ctx->sec_attempts = 0;
        }
        uint8_t resp[2] = { sub, 0x00 }; // 附加P2=0
        send_response(ctx, SID_DIAG_SESSION, resp, 2);
    } else {
        send_nrc(ctx, SID_DIAG_SESSION, NRC_SUB_FUNCTION_NOT_SUPPORTED);
    }
}

static void handle_security_access(UDS_Context *ctx, const uint8_t *data, uint8_t len) {
    if (len < 2) {
        send_nrc(ctx, SID_SECURITY_ACCESS, NRC_SUB_FUNCTION_NOT_SUPPORTED);
        return;
    }
    uint8_t sub = data[1];
    
    // 仅编程会话允许安全访问
    if (ctx->session != SESSION_PROGRAMMING) {
        send_nrc(ctx, SID_SECURITY_ACCESS, NRC_SERVICE_NOT_SUPPORTED);
        return;
    }
    
    if (sub == 0x01) { // 请求种子
        if (ctx->sec_attempts >= 3) {
            send_nrc(ctx, SID_SECURITY_ACCESS, NRC_EXCEEDED_ATTEMPTS);
            return;
        }
        uint16_t seed = 0x1234; // 实际应用中可由随机数或计数器生成
        ctx->sec_key = seed_key(seed);
        uint8_t resp[3] = { sub, (seed >> 8) & 0xFF, seed & 0xFF };
        send_response(ctx, SID_SECURITY_ACCESS, resp, 3);
    } else if (sub == 0x02) { // 发送密钥
        if (len < 4) {
            send_nrc(ctx, SID_SECURITY_ACCESS, NRC_SUB_FUNCTION_NOT_SUPPORTED);
            return;
        }
        uint16_t key = ((uint16_t)data[2] << 8) | data[3];
        if (key == ctx->sec_key) {
            ctx->sec_access = SEC_UNLOCKED;
            ctx->sec_attempts = 0;
            uint8_t resp[1] = { sub };
            send_response(ctx, SID_SECURITY_ACCESS, resp, 1);
        } else {
            ctx->sec_attempts++;
            if (ctx->sec_attempts >= 3) {
                send_nrc(ctx, SID_SECURITY_ACCESS, NRC_EXCEEDED_ATTEMPTS);
            } else {
                send_nrc(ctx, SID_SECURITY_ACCESS, NRC_INCORRECT_KEY);
            }
        }
    } else {
        send_nrc(ctx, SID_SECURITY_ACCESS, NRC_SUB_FUNCTION_NOT_SUPPORTED);
    }
}

void UDS_Init(UDS_Context *ctx) {
    ctx->session = SESSION_DEFAULT;
    ctx->sec_access = SEC_LOCKED;
    ctx->sec_attempts = 0;
    ctx->sec_key = 0;
}

void UDS_ProcessFrame(UDS_Context *ctx, const uint8_t *data, uint8_t len) {
    if (len < 2) return; // 无效帧
    
    uint8_t pci = data[0];
    if ((pci & 0xF0) != 0x00) return; // 仅支持单帧(SF)
    
    uint8_t sid = data[1];
    switch (sid) {
        case SID_DIAG_SESSION:
            handle_session_control(ctx, data, len);
            break;
        case SID_SECURITY_ACCESS:
            handle_security_access(ctx, data, len);
            break;
        default:
            send_nrc(ctx, sid, NRC_SERVICE_NOT_SUPPORTED);
            break;
    }
}
// main.c 示例调用
#include "uds_stack.h"
#include <stdio.h>

// 模拟CAN发送
void CAN_SendFrame(uint32_t can_id, const uint8_t *data, uint8_t len) {
    printf("TX [0x%03X]: ", can_id);
    for (int i = 0; i < len; i++) printf("%02X ", data[i]);
    printf("\n");
}

int main() {
    UDS_Context ctx;
    UDS_Init(&ctx);
    
    // 测试1:切换到编程会话
    uint8_t req1[] = { 0x02, 0x10, 0x02 };
    printf("-- 请求编程会话 --\n");
    UDS_ProcessFrame(&ctx, req1, sizeof(req1));
    
    // 测试2:请求种子
    uint8_t req2[] = { 0x02, 0x27, 0x01 };
    printf("-- 请求种子 --\n");
    UDS_ProcessFrame(&ctx, req2, sizeof(req2));
    
    // 测试3:发送正确密钥(根据seed_key算法计算)
    uint16_t seed = 0x1234;
    uint16_t key = ((seed ^ 0x5A5A) << 3) | ((seed ^ 0x5A5A) >> 13);
    uint8_t req3[] = { 0x04, 0x27, 0x02, (key >> 8) & 0xFF, key & 0xFF };
    printf("-- 发送正确密钥 --\n");
    UDS_ProcessFrame(&ctx, req3, sizeof(req3));
    
    // 测试4:错误密钥
    uint8_t req4[] = { 0x04, 0x27, 0x02, 0x00, 0x00 };
    printf("-- 发送错误密钥 --\n");
    UDS_ProcessFrame(&ctx, req4, sizeof(req4));
    
    return 0;
}

编译与运行

gcc -o uds_test uds_stack.c main.c
./uds_test

验证方法

程序输出应依次显示:

  • 请求编程会话后返回 50 02 00(成功响应)
  • 请求种子后返回 67 01 12 34(种子值0x1234)
  • 发送正确密钥后返回 67 02(解锁成功)
  • 发送错误密钥后返回 7F 27 35(NRC:密钥错误)

通过条件:四条响应与上述预期完全一致,且连续3次错误密钥后返回 7F 27 36(尝试次数超限)。

集成说明

在实际Bootloader工程中,将 UDS_ProcessFrame 挂接到CAN接收中断,将 CAN_SendFrame 指向底层CAN驱动发送函数。安全访问的种子生成建议使用定时器计数器或真随机数源,密钥算法可替换为AES等更安全方案。当前实现仅覆盖单帧(SF)传输,多帧(FF/CF)传输需在后续案例中扩展。