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分离,所有通信通过单一报文通道完成。
代码实现
// 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重新计算验证正确,即框架的命令解析、分发、执行与响应构造链路完整可用。