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)传输需在后续案例中扩展。