2. XCP通信模式配置

任务说明

本案例实现一个XCP从设备端的命令处理框架,能够接收来自主设备的XCP命令报文,完成命令解析、校验、分发与响应构造。框架支持标准XCP命令集中的核心命令:CONNECT、DISCONNECT、GET_STATUS、GET_ID、SET_MTA、UPLOAD、DOWNLOAD、SHORT_UPLOAD,并为后续扩展保留命令分发表机制。

协议报文格式

XCP命令报文采用标准格式:首字节为命令标识符(CMD),后续字节为命令参数。响应报文首字节为数据包标识符(PID),0xFF表示成功响应,0xFE表示错误响应(ERR),错误码位于响应第二字节。本案例使用简化寻址模式,不涉及CTO/DTO分离,所有通信通过单一报文通道完成。

命令报文 (Command Packet) CMD (1B) 参数区 (Param[0..n]) CRC (1B) 响应报文 (Response Packet) PID (1B) 数据区 (Data[0..n]) CRC (1B) 命令分发流程 接收报文 CRC校验 查命令表 执行处理函数 构造响应 CRC错误

代码实现


// xcp_cmd_framework.h
#ifndef XCP_CMD_FRAMEWORK_H
#define XCP_CMD_FRAMEWORK_H

#include <stdint.h>
#include <stddef.h>

#define XCP_MAX_CMD_LEN    64
#define XCP_MAX_RES_LEN    64
#define XCP_CRC_POLY       0x1D

// 命令标识符
#define CMD_CONNECT        0xFF
#define CMD_DISCONNECT     0xFE
#define CMD_GET_STATUS     0xFD
#define CMD_GET_ID         0xFA
#define CMD_SET_MTA        0xF6
#define CMD_UPLOAD         0xF5
#define CMD_DOWNLOAD       0xF0
#define CMD_SHORT_UPLOAD   0xF4

// 响应标识符
#define PID_RESPONSE       0xFF
#define PID_ERROR          0xFE

// 错误码
#define ERR_CMD_BUSY       0x10
#define ERR_CMD_UNKNOWN    0x11
#define ERR_CRC_MISMATCH   0x12
#define ERR_OUT_OF_RANGE   0x20
#define ERR_ACCESS_DENIED  0x21

// 命令处理函数原型
typedef uint8_t (*xcp_cmd_handler_t)(const uint8_t *param, uint8_t param_len,
                                     uint8_t *resp_data, uint8_t *resp_len);

// 命令表项
typedef struct {
    uint8_t cmd_id;
    xcp_cmd_handler_t handler;
} xcp_cmd_entry_t;

// 框架API
void xcp_init(void);
uint8_t xcp_process_packet(const uint8_t *rx_buf, uint8_t rx_len,
                           uint8_t *tx_buf, uint8_t *tx_len);
uint8_t xcp_calc_crc(const uint8_t *data, uint8_t len);

#endif // XCP_CMD_FRAMEWORK_H

// xcp_cmd_framework.c
#include "xcp_cmd_framework.h"
#include <string.h>

// 从设备状态
static uint8_t session_active = 0;
static uint32_t mta_address = 0;

// 模拟内存区域(用于UPLOAD/DOWNLOAD演示)
static uint8_t memory_pool[256];
static const uint8_t *resource_ptr = (const uint8_t *)"XCP_DEMO_RESOURCE_001";

// 命令处理函数前置声明
static uint8_t handle_connect(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl);
static uint8_t handle_disconnect(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl);
static uint8_t handle_get_status(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl);
static uint8_t handle_get_id(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl);
static uint8_t handle_set_mta(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl);
static uint8_t handle_upload(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl);
static uint8_t handle_download(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl);
static uint8_t handle_short_upload(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl);

// 命令分发表
static const xcp_cmd_entry_t cmd_table[] = {
    {CMD_CONNECT,       handle_connect},
    {CMD_DISCONNECT,    handle_disconnect},
    {CMD_GET_STATUS,    handle_get_status},
    {CMD_GET_ID,        handle_get_id},
    {CMD_SET_MTA,       handle_set_mta},
    {CMD_UPLOAD,        handle_upload},
    {CMD_DOWNLOAD,      handle_download},
    {CMD_SHORT_UPLOAD,  handle_short_upload}
};
#define CMD_TABLE_SIZE (sizeof(cmd_table) / sizeof(cmd_table[0]))

// CRC8计算(多项式0x1D,初始值0xFF)
uint8_t xcp_calc_crc(const uint8_t *data, uint8_t len) {
    uint8_t crc = 0xFF;
    for (uint8_t i = 0; i < len; i++) {
        crc ^= data[i];
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x80)
                crc = (uint8_t)((crc << 1) ^ XCP_CRC_POLY);
            else
                crc = (uint8_t)(crc << 1);
        }
    }
    return crc;
}

void xcp_init(void) {
    session_active = 0;
    mta_address = 0;
    memset(memory_pool, 0xA5, sizeof(memory_pool));
}

// 主处理入口:接收完整报文(含CRC),返回响应报文(含CRC)
uint8_t xcp_process_packet(const uint8_t *rx_buf, uint8_t rx_len,
                           uint8_t *tx_buf, uint8_t *tx_len) {
    if (rx_len < 3) return 0;  // 至少CMD+1参数+CRC
    
    uint8_t cmd = rx_buf[0];
    uint8_t param_len = rx_len - 2;  // 去掉CMD和CRC
    const uint8_t *param = &rx_buf[1];
    
    // CRC校验
    uint8_t calc_crc = xcp_calc_crc(rx_buf, rx_len - 1);
    if (calc_crc != rx_buf[rx_len - 1]) {
        tx_buf[0] = PID_ERROR;
        tx_buf[1] = ERR_CRC_MISMATCH;
        tx_buf[2] = xcp_calc_crc(tx_buf, 2);
        *tx_len = 3;
        return 1;
    }
    
    // 查找命令处理函数
    for (uint8_t i = 0; i < CMD_TABLE_SIZE; i++) {
        if (cmd_table[i].cmd_id == cmd) {
            uint8_t resp_len = 0;
            uint8_t err = cmd_table[i].handler(param, param_len, &tx_buf[1], &resp_len);
            if (err == 0) {
                tx_buf[0] = PID_RESPONSE;
            } else {
                tx_buf[0] = PID_ERROR;
                tx_buf[1] = err;
                resp_len = 1;
            }
            tx_buf[1 + resp_len] = xcp_calc_crc(tx_buf, 1 + resp_len);
            *tx_len = 2 + resp_len;
            return 1;
        }
    }
    
    // 未知命令
    tx_buf[0] = PID_ERROR;
    tx_buf[1] = ERR_CMD_UNKNOWN;
    tx_buf[2] = xcp_calc_crc(tx_buf, 2);
    *tx_len = 3;
    return 1;
}

// ========== 命令处理函数实现 ==========

static uint8_t handle_connect(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl) {
    (void)p; (void)plen;
    if (session_active) return ERR_CMD_BUSY;
    session_active = 1;
    rd[0] = 0x01;  // 资源类型:从设备
    rd[1] = 0x00;  // 保留
    *rl = 2;
    return 0;
}

static uint8_t handle_disconnect(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl) {
    (void)p; (void)plen;
    if (!session_active) return ERR_ACCESS_DENIED;
    session_active = 0;
    *rl = 0;
    return 0;
}

static uint8_t handle_get_status(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl) {
    (void)p; (void)plen;
    rd[0] = session_active ? 0x01 : 0x00;  // 会话状态
    rd[1] = 0x00;  // 保留
    rd[2] = 0x00;  // 保留
    *rl = 3;
    return 0;
}

static uint8_t handle_get_id(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl) {
    (void)p; (void)plen;
    if (!session_active) return ERR_ACCESS_DENIED;
    uint8_t id_len = (uint8_t)strlen((const char *)resource_ptr);
    rd[0] = 0x00;  // 类型:ASCII文本
    rd[1] = id_len;
    memcpy(&rd[2], resource_ptr, id_len);
    *rl = (uint8_t)(2 + id_len);
    return 0;
}

static uint8_t handle_set_mta(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl) {
    if (!session_active) return ERR_ACCESS_DENIED;
    if (plen != 4) return ERR_OUT_OF_RANGE;
    mta_address = ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
                  ((uint32_t)p[2] << 8) | (uint32_t)p[3];
    if (mta_address >= sizeof(memory_pool)) return ERR_OUT_OF_RANGE;
    *rl = 0;
    return 0;
}

static uint8_t handle_upload(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl) {
    if (!session_active) return ERR_ACCESS_DENIED;
    if (plen != 1) return ERR_OUT_OF_RANGE;
    uint8_t count = p[0];
    if (count == 0 || mta_address + count > sizeof(memory_pool))
        return ERR_OUT_OF_RANGE;
    memcpy(rd, &memory_pool[mta_address], count);
    mta_address += count;
    *rl = count;
    return 0;
}

static uint8_t handle_download(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl) {
    if (!session_active) return ERR_ACCESS_DENIED;
    if (plen < 1) return ERR_OUT_OF_RANGE;
    uint8_t count = p[0];
    if (count == 0 || plen != (1 + count)) return ERR_OUT_OF_RANGE;
    if (mta_address + count > sizeof(memory_pool)) return ERR_OUT_OF_RANGE;
    memcpy(&memory_pool[mta_address], &p[1], count);
    mta_address += count;
    *rl = 0;
    return 0;
}

static uint8_t handle_short_upload(const uint8_t *p, uint8_t plen, uint8_t *rd, uint8_t *rl) {
    if (!session_active) return ERR_ACCESS_DENIED;
    if (plen != 5) return ERR_OUT_OF_RANGE;
    uint32_t addr = ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
                    ((uint32_t)p[2] << 8) | (uint32_t)p[3];
    uint8_t count = p[4];
    if (count == 0 || addr + count > sizeof(memory_pool))
        return ERR_OUT_OF_RANGE;
    memcpy(rd, &memory_pool[addr], count);
    *rl = count;
    return 0;
}

测试代码


// test_xcp_framework.c
#include <stdio.h>
#include <string.h>
#include "xcp_cmd_framework.h"

static void print_packet(const char *tag, const uint8_t *buf, uint8_t len) {
    printf("%s: ", tag);
    for (uint8_t i = 0; i < len; i++)
        printf("%02X ", buf[i]);
    printf("\n");
}

int main(void) {
    uint8_t tx_buf[XCP_MAX_RES_LEN];
    uint8_t tx_len;
    
    xcp_init();
    
    // 测试1: CONNECT
    uint8_t cmd1[] = {CMD_CONNECT, 0x00, 0x00};
    cmd1[2] = xcp_calc_crc(cmd1, 2);
    xcp_process_packet(cmd1, 3, tx_buf, &tx_len);
    print_packet("CONNECT响应", tx_buf, tx_len);
    
    // 测试2: GET_ID
    uint8_t cmd2[] = {CMD_GET_ID, 0x00, 0x00};
    cmd2[2] = xcp_calc_crc(cmd2, 2);
    xcp_process_packet(cmd2, 3, tx_buf, &tx_len);
    print_packet("GET_ID响应", tx_buf, tx_len);
    
    // 测试3: SET_MTA (地址=0x10)
    uint8_t cmd3[] = {CMD_SET_MTA, 0x00, 0x00, 0x00, 0x10, 0x00};
    cmd3[5] = xcp_calc_crc(cmd3, 5);
    xcp_process_packet(cmd3, 6, tx_buf, &tx_len);
    print_packet("SET_MTA响应", tx_buf, tx_len);
    
    // 测试4: DOWNLOAD (写入4字节: 0x11 0x22 0x33 0x44)
    uint8_t cmd4[] = {CMD_DOWNLOAD, 0x04, 0x11, 0x22, 0x33, 0x44, 0x00};
    cmd4[6] = xcp_calc_crc(cmd4, 6);
    xcp_process_packet(cmd4, 7, tx_buf, &tx_len);
    print_packet("DOWNLOAD响应", tx_buf, tx_len);
    
    // 测试5: SHORT_UPLOAD (从地址0x10读4字节)
    uint8_t cmd5[] = {CMD_SHORT_UPLOAD, 0x00, 0x00, 0x00, 0x10, 0x04, 0x00};
    cmd5[6] = xcp_calc_crc(cmd5, 6);
    xcp_process_packet(cmd5, 7, tx_buf, &tx_len);
    print_packet("SHORT_UPLOAD响应", tx_buf, tx_len);
    
    // 测试6: 错误CRC
    uint8_t cmd6[] = {CMD_GET_STATUS, 0x00, 0x00};
    cmd6[2] = 0x00;  // 故意错误CRC
    xcp_process_packet(cmd6, 3, tx_buf, &tx_len);
    print_packet("CRC错误响应", tx_buf, tx_len);
    
    // 测试7: 未知命令
    uint8_t cmd7[] = {0x01, 0x00, 0x00};
    cmd7[2] = xcp_calc_crc(cmd7, 2);
    xcp_process_packet(cmd7, 3, tx_buf, &tx_len);
    print_packet("未知命令响应", tx_buf, tx_len);
    
    // 测试8: DISCONNECT
    uint8_t cmd8[] = {CMD_DISCONNECT, 0x00, 0x00};
    cmd8[2] = xcp_calc_crc(cmd8, 2);
    xcp_process_packet(cmd8, 3, tx_buf, &tx_len);
    print_packet("DISCONNECT响应", tx_buf, tx_len);
    
    return 0;
}

编译与运行


gcc -Wall -o test_xcp xcp_cmd_framework.c test_xcp_framework.c
./test_xcp

验证方法与判定标准

运行测试程序后,观察各命令响应是否符合以下预期:

  • CONNECT响应:FF 01 00 [CRC],PID=0xFF表示成功,数据区为资源类型0x01和保留字节0x00。
  • GET_ID响应:FF 00 13 58 43 50 5F 44 45 4D 4F 5F 52 45 53 4F 55 52 43 45 5F 30 30 31 [CRC],其中0x13为字符串长度19,后续为ASCII字符"XCP_DEMO_RESOURCE_001"。
  • SET_MTA响应:FF [CRC],无数据区。
  • DOWNLOAD响应:FF [CRC],无数据区。
  • SHORT_UPLOAD响应:FF 11 22 33 44 [CRC],数据区为之前DOWNLOAD写入的4字节内容,验证内存读写正确。
  • CRC错误响应:FE 12 [CRC],PID=0xFE表示错误,错误码0x12对应ERR_CRC_MISMATCH。
  • 未知命令响应:FE 11 [CRC],错误码0x11对应ERR_CMD_UNKNOWN。
  • DISCONNECT响应:FF [CRC],无数据区。

判定标准:所有响应首字节与数据区内容与上述预期一致,且每帧末尾CRC字节通过xcp_calc_crc重新计算验证正确,即框架的命令解析、分发、执行与响应构造链路完整可用。