3. 实现标准命令响应
任务说明
本案例要求实现XCP从设备(Slave)对标准命令CONNECT、DISCONNECT、GET_STATUS的正确响应。这些命令是XCP协议的基础会话管理命令,主设备(Master)通过CAN总线发送命令报文,从设备解析后返回对应的响应报文。完成本案例后,从设备应能正确建立/断开连接,并能返回当前会话状态。
协议报文格式
XCP over CAN的报文格式如下:
- 命令报文(Master→Slave):CAN ID = 0x100,DLC = 8字节。第0字节为命令代码(CMD),后续字节为命令参数。
- 响应报文(Slave→Master):CAN ID = 0x101,DLC = 8字节。第0字节为响应代码(RES),第1字节为错误代码(ERR),后续为响应数据。
本案例涉及的三个命令定义如下:
CONNECT (0xFF):建立连接。参数:MODE(第1字节,0x00=标准模式,0x01=扩展模式)。成功响应:RES=0xFF,ERR=0x00,第2字节返回RESOURCE(可用资源,固定0x01表示仅支持标准命令)。DISCONNECT (0xFE):断开连接。无参数。成功响应:RES=0xFE,ERR=0x00。GET_STATUS (0xFD):获取状态。无参数。成功响应:RES=0xFD,ERR=0x00,第2字节为SESSION_STATUS(bit0=连接状态,bit1=资源已保留,bit2=DAQ正在运行)。
错误响应统一为:RES=0xFE(ERROR),ERR为具体错误码。本案例涉及的错误码:ERR_CMD_BUSY=0x10(命令忙)、ERR_CMD_UNKNOWN=0x11(未知命令)、ERR_MODE_NOT_VALID=0x22(模式无效)。
状态机设计
从设备维护一个会话状态机,包含两个状态:DISCONNECTED(未连接)和CONNECTED(已连接)。状态转换规则如下:
- 在
DISCONNECTED状态收到CONNECT且MODE有效 → 进入CONNECTED状态,返回成功。 - 在
CONNECTED状态收到CONNECT→ 返回ERR_CMD_BUSY(已连接,不能重复连接)。 - 在
CONNECTED状态收到DISCONNECT→ 进入DISCONNECTED状态,返回成功。 - 在
DISCONNECTED状态收到DISCONNECT→ 返回ERR_CMD_BUSY(未连接,无需断开)。 GET_STATUS在任何状态均返回成功,但SESSION_STATUS的bit0反映当前连接状态。- 未知命令码 → 返回
ERR_CMD_UNKNOWN。
代码实现
以下代码使用纯C语言实现,不依赖特定硬件平台,通过标准输入模拟接收CAN报文,标准输出打印响应报文。核心逻辑封装在xcp_handle_command函数中,可直接移植到嵌入式平台。
// xcp_standard_cmds.c
// 编译: gcc -o xcp_standard_cmds xcp_standard_cmds.c
// 运行: ./xcp_standard_cmds
#include <stdio.h>
#include <stdint.h>
#include <string.h>
// ---- 命令/响应代码定义 ----
#define CMD_CONNECT 0xFF
#define CMD_DISCONNECT 0xFE
#define CMD_GET_STATUS 0xFD
#define RES_ERROR 0xFE
#define ERR_CMD_BUSY 0x10
#define ERR_CMD_UNKNOWN 0x11
#define ERR_MODE_NOT_VALID 0x22
// ---- 会话状态 ----
static uint8_t session_connected = 0; // 0=DISCONNECTED, 1=CONNECTED
// ---- 命令报文结构 ----
typedef struct {
uint8_t cmd; // 命令代码
uint8_t mode; // 参数(仅CONNECT使用)
} xcp_cmd_t;
// ---- 响应报文结构 ----
typedef struct {
uint8_t res; // 响应代码
uint8_t err; // 错误码
uint8_t data[6]; // 响应数据
} xcp_resp_t;
// ---- 核心处理函数 ----
// 输入: 解析后的命令
// 输出: 响应报文(通过指针返回)
void xcp_handle_command(const xcp_cmd_t *cmd, xcp_resp_t *resp) {
// 清空响应
memset(resp, 0, sizeof(xcp_resp_t));
switch (cmd->cmd) {
case CMD_CONNECT:
if (session_connected) {
// 已连接,重复CONNECT
resp->res = RES_ERROR;
resp->err = ERR_CMD_BUSY;
} else if (cmd->mode > 0x01) {
// 模式无效(仅支持0x00标准/0x01扩展)
resp->res = RES_ERROR;
resp->err = ERR_MODE_NOT_VALID;
} else {
// 连接成功
session_connected = 1;
resp->res = CMD_CONNECT;
resp->err = 0x00;
resp->data[0] = 0x01; // RESOURCE: 支持标准命令
}
break;
case CMD_DISCONNECT:
if (!session_connected) {
// 未连接,无需断开
resp->res = RES_ERROR;
resp->err = ERR_CMD_BUSY;
} else {
// 断开成功
session_connected = 0;
resp->res = CMD_DISCONNECT;
resp->err = 0x00;
}
break;
case CMD_GET_STATUS:
// 任何状态都返回成功
resp->res = CMD_GET_STATUS;
resp->err = 0x00;
resp->data[0] = session_connected ? 0x01 : 0x00; // SESSION_STATUS bit0
break;
default:
// 未知命令
resp->res = RES_ERROR;
resp->err = ERR_CMD_UNKNOWN;
break;
}
}
// ---- 报文打印辅助 ----
void print_resp(const xcp_resp_t *resp) {
printf("响应: RES=0x%02X ERR=0x%02X", resp->res, resp->err);
if (resp->res == CMD_CONNECT) {
printf(" RESOURCE=0x%02X", resp->data[0]);
} else if (resp->res == CMD_GET_STATUS) {
printf(" SESSION_STATUS=0x%02X", resp->data[0]);
}
printf("\n");
}
// ---- 主函数:模拟测试 ----
int main(void) {
// 测试用例表
struct {
const char *desc;
uint8_t cmd;
uint8_t mode;
} tests[] = {
{"1. 未连接时CONNECT(标准模式)", CMD_CONNECT, 0x00},
{"2. 已连接时重复CONNECT", CMD_CONNECT, 0x00},
{"3. CONNECT(无效模式0x02)", CMD_CONNECT, 0x02},
{"4. 已连接时GET_STATUS", CMD_GET_STATUS, 0x00},
{"5. 已连接时DISCONNECT", CMD_DISCONNECT, 0x00},
{"6. 未连接时DISCONNECT", CMD_DISCONNECT, 0x00},
{"7. 未连接时GET_STATUS", CMD_GET_STATUS, 0x00},
{"8. 未知命令0xAA", 0xAA, 0x00},
};
int num_tests = sizeof(tests) / sizeof(tests[0]);
printf("=== XCP标准命令响应测试 ===\n\n");
for (int i = 0; i < num_tests; i++) {
xcp_cmd_t cmd = {tests[i].cmd, tests[i].mode};
xcp_resp_t resp;
xcp_handle_command(&cmd, &resp);
printf("测试%s\n", tests[i].desc);
print_resp(&resp);
printf("\n");
}
return 0;
}
编译与运行
gcc -o xcp_standard_cmds xcp_standard_cmds.c
./xcp_standard_cmds
验证方法与判定标准
程序运行后,观察输出是否符合以下预期(这些预期由代码逻辑直接推出,非实测结果):
- 测试1:
RES=0xFF ERR=0x00 RESOURCE=0x01,连接成功。 - 测试2:
RES=0xFE ERR=0x10,重复连接返回忙。 - 测试3:
RES=0xFE ERR=0x22,无效模式。 - 测试4:
RES=0xFD ERR=0x00 SESSION_STATUS=0x01,已连接状态。 - 测试5:
RES=0xFE ERR=0x00,断开成功。 - 测试6:
RES=0xFE ERR=0x10,未连接时断开返回忙。 - 测试7:
RES=0xFD ERR=0x00 SESSION_STATUS=0x00,未连接状态。 - 测试8:
RES=0xFE ERR=0x11,未知命令。
若所有输出与上述预期一致,则判定实现正确。如需验证CAN总线实际通信,可将xcp_handle_command函数移植到目标平台,在CAN接收中断中解析报文并调用该函数,将响应报文通过CAN发送即可。