1. 建立XCP从站基础通信

建立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)。

若输出与上述一致,说明从站已正确完成连接建立、状态查询、地址设置和短上传功能,且错误处理路径有效。

通信时序与状态流转

XCP 主站 XCP 从站 RESET CONNECTED CONNECT成功 CONNECT (CMD=0xFF) RES: 状态=CONNECTED GET_STATUS (CMD=0xFD) RES: 状态+资源+MTA地址 SET_MTA (CMD=0xF6, 地址=0x10) RES: 成功 SHORT_UPLOAD (CMD=0xF4, len=4) RES: 04 10 11 12 13 未知命令 → ERR=0x11 命令帧 响应帧 错误帧

关键设计说明

从站内存中预置了0x00~0xFF的递增数据用于验证上传功能。校验和字段占用命令帧data[6],实际XCP协议中校验和位置由配置决定,此处为教学简化。代码中的send_response函数使用printf模拟CAN发送,实际集成时替换为对应CAN驱动接口即可。从站仅实现了4个核心命令,覆盖了连接建立和会话状态管理的最小闭环,其他XCP命令(如DOWNLOAD、PROGRAM等)可在同一框架下扩展。