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。
DISCONNECTED 未连接 CONNECTED 已连接 CONNECT(MODE有效) DISCONNECT CONNECT→ERR_CMD_BUSY DISCONNECT→ERR_CMD_BUSY

代码实现

以下代码使用纯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发送即可。