4. UDS协议栈集成
任务目标
在Bootloader中集成UDS(ISO 14229)协议栈,实现两种基础诊断能力:会话控制(0x10服务)和安全访问(0x27服务)。Bootloader通过CAN总线接收UDS请求,完成会话模式切换及安全解锁验证,为后续刷写流程提供前置条件。
实现方案
采用轻量级状态机方式实现UDS协议核心,不依赖第三方协议栈,便于在Bootloader环境中直接编译运行。CAN驱动层通过回调函数接收报文,UDS层解析并响应。安全访问算法使用简单的种子-密钥机制(实际项目中可替换为AES或自定义算法)。
关键代码
/* uds_stack.h */
#ifndef UDS_STACK_H
#define UDS_STACK_H
#include <stdint.h>
/* 诊断会话类型 */
#define SESSION_DEFAULT 0x01
#define SESSION_PROGRAM 0x02
/* 安全访问状态 */
#define SEC_LOCKED 0x00
#define SEC_UNLOCKED 0x01
/* UDS服务ID */
#define SID_SESSION 0x10
#define SID_SECURITY 0x27
/* 响应码 */
#define RESP_OK 0x00
#define RESP_SFNS 0x12 /* 服务不支持 */
#define RESP_ISNS 0x13 /* 无效长度 */
#define RESP_COND_NOT_OK 0x22
#define RESP_SEC_ACCESS 0x33 /* 安全访问被拒绝 */
typedef struct {
uint8_t session; /* 当前会话 */
uint8_t sec_status; /* 安全状态 */
uint8_t sec_seed[4]; /* 种子值 */
} UdsContext;
/* CAN报文回调接口(由底层驱动调用) */
void Uds_OnCanMessage(uint32_t id, uint8_t* data, uint8_t len);
/* 发送CAN响应(由底层驱动实现) */
extern void Can_SendMessage(uint32_t id, uint8_t* data, uint8_t len);
#endif
/* uds_stack.c */
#include "uds_stack.h"
#include <string.h>
static UdsContext ctx;
/* 安全密钥生成算法(示例:种子取反+固定偏移) */
static void GenerateKey(const uint8_t* seed, uint8_t* key) {
for (int i = 0; i < 4; i++) {
key[i] = (uint8_t)(~seed[i] + 0x5A);
}
}
static void SendResponse(uint8_t sid, uint8_t* data, uint8_t len) {
uint8_t resp[8];
resp[0] = (uint8_t)(sid + 0x40); /* 肯定响应SID */
memcpy(&resp[1], data, len);
Can_SendMessage(0x7E8, resp, (uint8_t)(len + 1));
}
static void SendNrc(uint8_t sid, uint8_t nrc) {
uint8_t resp[3];
resp[0] = 0x7F;
resp[1] = sid;
resp[2] = nrc;
Can_SendMessage(0x7E8, resp, 3);
}
/* 会话控制服务 (0x10) */
static void HandleSession(uint8_t* data, uint8_t len) {
if (len != 2) {
SendNrc(SID_SESSION, RESP_ISNS);
return;
}
uint8_t new_session = data[1];
if (new_session != SESSION_DEFAULT && new_session != SESSION_PROGRAM) {
SendNrc(SID_SESSION, RESP_SFNS);
return;
}
ctx.session = new_session;
/* 切换会话时锁定安全状态 */
ctx.sec_status = SEC_LOCKED;
uint8_t resp_data[1] = { new_session };
SendResponse(SID_SESSION, resp_data, 1);
}
/* 安全访问服务 (0x27) */
static void HandleSecurity(uint8_t* data, uint8_t len) {
if (ctx.session != SESSION_PROGRAM) {
SendNrc(SID_SECURITY, RESP_COND_NOT_OK);
return;
}
if (len < 2) {
SendNrc(SID_SECURITY, RESP_ISNS);
return;
}
uint8_t sub_func = data[1];
if (sub_func == 0x01) { /* 请求种子 */
if (ctx.sec_status == SEC_UNLOCKED) {
SendNrc(SID_SECURITY, RESP_COND_NOT_OK);
return;
}
/* 生成随机种子(示例用固定值,实际应使用随机数) */
ctx.sec_seed[0] = 0x12;
ctx.sec_seed[1] = 0x34;
ctx.sec_seed[2] = 0x56;
ctx.sec_seed[3] = 0x78;
uint8_t resp_data[5];
resp_data[0] = sub_func;
memcpy(&resp_data[1], ctx.sec_seed, 4);
SendResponse(SID_SECURITY, resp_data, 5);
}
else if (sub_func == 0x02) { /* 发送密钥 */
if (len != 6) {
SendNrc(SID_SECURITY, RESP_ISNS);
return;
}
uint8_t expected_key[4];
GenerateKey(ctx.sec_seed, expected_key);
if (memcmp(&data[2], expected_key, 4) == 0) {
ctx.sec_status = SEC_UNLOCKED;
uint8_t resp_data[1] = { sub_func };
SendResponse(SID_SECURITY, resp_data, 1);
} else {
ctx.sec_status = SEC_LOCKED;
SendNrc(SID_SECURITY, RESP_SEC_ACCESS);
}
}
else {
SendNrc(SID_SECURITY, RESP_SFNS);
}
}
/* CAN报文入口 */
void Uds_OnCanMessage(uint32_t id, uint8_t* data, uint8_t len) {
if (id != 0x7E0 || len < 2) return; /* 仅处理物理请求 */
uint8_t sid = data[0];
switch (sid) {
case SID_SESSION:
HandleSession(data, len);
break;
case SID_SECURITY:
HandleSecurity(data, len);
break;
default:
SendNrc(sid, RESP_SFNS);
break;
}
}
/* 初始化 */
void Uds_Init(void) {
ctx.session = SESSION_DEFAULT;
ctx.sec_status = SEC_LOCKED;
}
集成与运行
依赖说明:需要底层CAN驱动提供Can_SendMessage()发送函数,并在收到CAN报文时调用Uds_OnCanMessage()。初始化时调用Uds_Init()。
编译:将uds_stack.c加入工程,包含头文件路径,与CAN驱动一起编译。
测试序列:
- 发送
10 02→ 切换到编程会话,预期响应50 02 - 发送
27 01→ 请求种子,预期响应67 01 12 34 56 78 - 计算密钥:对
12 34 56 78取反加0x5A →ED A9 A3 21 - 发送
27 02 ED A9 A3 21→ 预期响应67 02 - 发送错误密钥
27 02 00 00 00 00→ 预期响应7F 27 33
验证方法:使用CAN工具(如PCAN、周立功CANTest)连接ECU,按上述序列发送报文,观察CAN总线上的响应帧ID为0x7E8。若响应与预期一致,且错误密钥被拒绝,则判定通过。
常见问题
- 响应超时:确认CAN波特率匹配、ID过滤配置正确(接收0x7E0,发送0x7E8)。
- 安全访问被拒:确认已先切换到编程会话(0x10 02),且种子与密钥计算逻辑一致。
- 多帧传输:本实现仅支持单帧(≤8字节),UDS超过8字节的请求需扩展ISO-TP层,当前案例不涉及。