1. 建立XCP从站基础通信
任务说明
本案例实现一个最小可用的XCP从站协议栈,支持主站通过CAN传输层完成连接建立(CONNECT)与会话状态管理(GET_STATUS、SET_MTA、SHORT_UPLOAD),并正确响应错误码。目标不是实现完整XCP规范,而是建立从站通信的骨架:能接收命令帧、解析命令码、维护会话状态、返回带校验的响应帧。
协议帧格式定义
为聚焦通信逻辑,本案例采用简化的XCP帧封装(与XCP over CAN标准一致):
- 每个CAN数据帧8字节,首字节为包标识(PID),后7字节为命令/响应数据;
- 命令帧PID=0x01,响应帧PID=0x02,错误响应帧PID=0x03;
- 多字节参数采用大端序(Motorola格式);
- 校验和采用简单的8位累加和(忽略进位),覆盖PID之后的所有字节。
从站状态定义
RESET(0) → CONNECTED(1) → 可执行上传命令
从站上电处于RESET状态,仅接受CONNECT命令;收到合法CONNECT后进入CONNECTED状态,此时允许GET_STATUS、SET_MTA、SHORT_UPLOAD命令。任何状态下收到未知命令码均返回错误响应ERR_CMD_BUSY(0x10)。
代码实现
#include <stdint.h>
#include <string.h>
#include <stdio.h>
/* XCP命令码 */
#define CMD_CONNECT 0xFF
#define CMD_GET_STATUS 0xFD
#define CMD_SET_MTA 0xF6
#define CMD_SHORT_UPLOAD 0xF4
/* 响应包标识 */
#define PID_CMD 0x01
#define PID_RES 0x02
#define PID_ERR 0x03
/* 错误码 */
#define ERR_CMD_BUSY 0x10
#define ERR_CMD_UNKNOWN 0x11
#define ERR_ACCESS_LOCKED 0x20
/* 会话状态 */
#define STATE_RESET 0
#define STATE_CONNECTED 1
/* 从站资源(模拟内存) */
static uint8_t slave_memory[256];
static uint32_t mta_address = 0;
static uint8_t session_state = STATE_RESET;
/* 简化XCP帧 */
typedef struct {
uint8_t pid;
uint8_t data[7];
} XcpFrame;
/* 计算校验和:对data[0..6]累加 */
static uint8_t calc_checksum(const XcpFrame *frame) {
uint8_t sum = 0;
for (int i = 0; i < 7; i++) {
sum += frame->data[i];
}
return sum;
}
/* 发送响应帧(实际工程中此处调用CAN发送接口) */
static void send_response(uint8_t pid, const uint8_t *payload, uint8_t len) {
XcpFrame resp;
memset(&resp, 0, sizeof(resp));
resp.pid = pid;
if (len > 7) len = 7;
memcpy(resp.data, payload, len);
uint8_t cs = calc_checksum(&resp);
printf("TX: PID=0x%02X Data=", resp.pid);
for (int i = 0; i < 7; i++) printf("%02X ", resp.data[i]);
printf("CS=0x%02X\n", cs);
}
/* 发送错误响应 */
static void send_error(uint8_t error_code) {
uint8_t payload[7] = {error_code, 0, 0, 0, 0, 0, 0};
send_response(PID_ERR, payload, 7);
}
/* 处理CONNECT命令 */
static void handle_connect(void) {
if (session_state == STATE_RESET) {
session_state = STATE_CONNECTED;
uint8_t payload[7] = {0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; /* 资源=0,状态=CONNECTED */
send_response(PID_RES, payload, 7);
} else {
send_error(ERR_CMD_BUSY);
}
}
/* 处理GET_STATUS命令 */
static void handle_get_status(void) {
if (session_state != STATE_CONNECTED) {
send_error(ERR_CMD_BUSY);
return;
}
uint8_t payload[7];
payload[0] = 0x00; /* 会话状态 */
payload[1] = 0x00; /* 资源保护状态 */
payload[2] = (uint8_t)(mta_address >> 24); /* MTA地址高字节 */
payload[3] = (uint8_t)(mta_address >> 16);
payload[4] = (uint8_t)(mta_address >> 8);
payload[5] = (uint8_t)(mta_address);
payload[6] = 0x00; /* 保留 */
send_response(PID_RES, payload, 7);
}
/* 处理SET_MTA命令:data[0..3]为地址,data[4]为扩展 */
static void handle_set_mta(const uint8_t *cmd_data) {
if (session_state != STATE_CONNECTED) {
send_error(ERR_CMD_BUSY);
return;
}
mta_address = ((uint32_t)cmd_data[0] << 24) |
((uint32_t)cmd_data[1] << 16) |
((uint32_t)cmd_data[2] << 8) |
((uint32_t)cmd_data[3]);
uint8_t payload[7] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
send_response(PID_RES, payload, 7);
}
/* 处理SHORT_UPLOAD:data[0]为长度,data[1..4]为地址 */
static void handle_short_upload(const uint8_t *cmd_data) {
if (session_state != STATE_CONNECTED) {
send_error(ERR_CMD_BUSY);
return;
}
uint8_t len = cmd_data[0];
if (len == 0 || len > 5) {
send_error(ERR_CMD_UNKNOWN);
return;
}
uint32_t addr = ((uint32_t)cmd_data[1] << 24) |
((uint32_t)cmd_data[2] << 16) |
((uint32_t)cmd_data[3] << 8) |
((uint32_t)cmd_data[4]);
if (addr + len > sizeof(slave_memory)) {
send_error(ERR_ACCESS_LOCKED);
return;
}
uint8_t payload[7];
payload[0] = len;
memcpy(&payload[1], &slave_memory[addr], len);
send_response(PID_RES, payload, 7);
}
/* 主处理入口:接收CAN帧并分发 */
void xcp_slave_process_frame(const XcpFrame *rx_frame) {
/* 校验和验证 */
if (calc_checksum(rx_frame) != rx_frame->data[6]) {
send_error(ERR_CMD_UNKNOWN);
return;
}
uint8_t cmd = rx_frame->data[0];
switch (cmd) {
case CMD_CONNECT: handle_connect(); break;
case CMD_GET_STATUS: handle_get_status(); break;
case CMD_SET_MTA: handle_set_mta(&rx_frame->data[1]); break;
case CMD_SHORT_UPLOAD: handle_short_upload(&rx_frame->data[1]); break;
default: send_error(ERR_CMD_UNKNOWN); break;
}
}
/* 测试主函数 */
int main(void) {
/* 初始化从站内存 */
for (int i = 0; i < 256; i++) slave_memory[i] = (uint8_t)i;
/* 构造CONNECT命令帧 */
XcpFrame cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.pid = PID_CMD;
cmd.data[0] = CMD_CONNECT;
cmd.data[6] = calc_checksum(&cmd); /* 填充校验和 */
printf("=== 测试1: CONNECT ===\n");
xcp_slave_process_frame(&cmd);
/* 构造GET_STATUS命令 */
memset(&cmd, 0, sizeof(cmd));
cmd.pid = PID_CMD;
cmd.data[0] = CMD_GET_STATUS;
cmd.data[6] = calc_checksum(&cmd);
printf("=== 测试2: GET_STATUS ===\n");
xcp_slave_process_frame(&cmd);
/* 构造SET_MTA命令:设置地址0x00000010 */
memset(&cmd, 0, sizeof(cmd));
cmd.pid = PID_CMD;
cmd.data[0] = CMD_SET_MTA;
cmd.data[1] = 0x00;
cmd.data[2] = 0x00;
cmd.data[3] = 0x00;
cmd.data[4] = 0x10;
cmd.data[6] = calc_checksum(&cmd);
printf("=== 测试3: SET_MTA(0x10) ===\n");
xcp_slave_process_frame(&cmd);
/* 构造SHORT_UPLOAD命令:从0x10读4字节 */
memset(&cmd, 0, sizeof(cmd));
cmd.pid = PID_CMD;
cmd.data[0] = CMD_SHORT_UPLOAD;
cmd.data[1] = 4; /* 长度 */
cmd.data[2] = 0x00; /* 地址高字节 */
cmd.data[3] = 0x00;
cmd.data[4] = 0x00;
cmd.data[5] = 0x10;
cmd.data[6] = calc_checksum(&cmd);
printf("=== 测试4: SHORT_UPLOAD(0x10, len=4) ===\n");
xcp_slave_process_frame(&cmd);
/* 测试未知命令 */
memset(&cmd, 0, sizeof(cmd));
cmd.pid = PID_CMD;
cmd.data[0] = 0x01; /* 未定义命令 */
cmd.data[6] = calc_checksum(&cmd);
printf("=== 测试5: 未知命令 ===\n");
xcp_slave_process_frame(&cmd);
return 0;
}
编译与运行
gcc -o xcp_slave xcp_slave.c
./xcp_slave
验证方法与判定标准
程序输出应依次显示5组响应帧。判定通过的标准:
- 测试1返回PID=0x02且状态字节为0x01(CONNECTED);
- 测试2返回PID=0x02且MTA地址为0x00000000;
- 测试3返回PID=0x02;
- 测试4返回PID=0x02,数据载荷为
04 10 11 12 13(从地址0x10读取的4字节); - 测试5返回PID=0x03且错误码为0x11(ERR_CMD_UNKNOWN)。
若输出与上述一致,说明从站已正确完成连接建立、状态查询、地址设置和短上传功能,且错误处理路径有效。
通信时序与状态流转
关键设计说明
从站内存中预置了0x00~0xFF的递增数据用于验证上传功能。校验和字段占用命令帧data[6],实际XCP协议中校验和位置由配置决定,此处为教学简化。代码中的send_response函数使用printf模拟CAN发送,实际集成时替换为对应CAN驱动接口即可。从站仅实现了4个核心命令,覆盖了连接建立和会话状态管理的最小闭环,其他XCP命令(如DOWNLOAD、PROGRAM等)可在同一框架下扩展。