4. 实现CAL_PAGE操作

任务说明

本案例实现XCP协议中的CAL_PAGE命令,支持校准页面的选择、切换和状态查询。校准页面用于管理多套标定数据,在标定过程中可动态切换当前生效的标定集。本案例聚焦于命令解析与状态管理逻辑,不涉及具体ECU内存映射。

协议定义

CAL_PAGE命令格式如下:

  • 选择页面:CMD=0xDE,参数为页面编号(0x00或0x01)
  • 切换页面:CMD=0xDF,参数为页面编号(0x00或0x01)
  • 查询状态:CMD=0xE0,无参数

响应码:0x00表示成功,0x31表示参数无效,0x32表示页面不可用。

代码实现


#include <stdint.h>
#include <string.h>

#define CAL_PAGE_SELECT  0xDE
#define CAL_PAGE_SWITCH  0xDF
#define CAL_PAGE_STATUS  0xE0

#define RESP_OK          0x00
#define ERR_PARAM        0x31
#define ERR_UNAVAILABLE  0x32

#define PAGE_COUNT       2
#define PAGE_ACTIVE      0x01
#define PAGE_INACTIVE    0x00

typedef struct {
    uint8_t page_id;
    uint8_t state;      // PAGE_ACTIVE 或 PAGE_INACTIVE
    uint8_t available;  // 1=可用, 0=不可用
} cal_page_t;

static cal_page_t pages[PAGE_COUNT] = {
    {0x00, PAGE_ACTIVE,  1},
    {0x01, PAGE_INACTIVE, 1}
};

static uint8_t current_page = 0x00;

/* 查找页面索引,返回-1表示不存在 */
static int8_t find_page(uint8_t page_id) {
    for (int8_t i = 0; i < PAGE_COUNT; i++) {
        if (pages[i].page_id == page_id) {
            return i;
        }
    }
    return -1;
}

/* 处理CAL_PAGE命令,返回响应码 */
uint8_t cal_page_handler(uint8_t cmd, uint8_t param, uint8_t *status_out) {
    int8_t idx;
    
    switch (cmd) {
        case CAL_PAGE_SELECT:
            idx = find_page(param);
            if (idx < 0) {
                return ERR_PARAM;
            }
            if (!pages[idx].available) {
                return ERR_UNAVAILABLE;
            }
            /* 选择页面:标记为待激活,但不立即切换 */
            pages[idx].state = PAGE_ACTIVE;
            for (int8_t i = 0; i < PAGE_COUNT; i++) {
                if (i != idx) {
                    pages[i].state = PAGE_INACTIVE;
                }
            }
            return RESP_OK;
            
        case CAL_PAGE_SWITCH:
            idx = find_page(param);
            if (idx < 0) {
                return ERR_PARAM;
            }
            if (!pages[idx].available) {
                return ERR_UNAVAILABLE;
            }
            /* 切换页面:立即生效 */
            current_page = pages[idx].page_id;
            pages[idx].state = PAGE_ACTIVE;
            for (int8_t i = 0; i < PAGE_COUNT; i++) {
                if (i != idx) {
                    pages[i].state = PAGE_INACTIVE;
                }
            }
            return RESP_OK;
            
        case CAL_PAGE_STATUS:
            if (status_out != NULL) {
                *status_out = current_page;
            }
            return RESP_OK;
            
        default:
            return ERR_PARAM;
    }
}

/* 测试用例 */
#include <stdio.h>

void print_status(const char *label, uint8_t ret, uint8_t status) {
    printf("%s: ret=0x%02X, current_page=0x%02X\n", 
           label, ret, status);
}

int main(void) {
    uint8_t ret, status = 0xFF;
    
    /* 测试1:查询初始状态 */
    ret = cal_page_handler(CAL_PAGE_STATUS, 0, &status);
    print_status("初始状态查询", ret, status);
    
    /* 测试2:选择页面1 */
    ret = cal_page_handler(CAL_PAGE_SELECT, 0x01, NULL);
    printf("选择页面1: ret=0x%02X\n", ret);
    
    /* 测试3:查询状态(应仍为页面0,因为只是选择未切换) */
    ret = cal_page_handler(CAL_PAGE_STATUS, 0, &status);
    print_status("选择后查询", ret, status);
    
    /* 测试4:切换页面1 */
    ret = cal_page_handler(CAL_PAGE_SWITCH, 0x01, NULL);
    printf("切换页面1: ret=0x%02X\n", ret);
    
    /* 测试5:查询状态(应为页面1) */
    ret = cal_page_handler(CAL_PAGE_STATUS, 0, &status);
    print_status("切换后查询", ret, status);
    
    /* 测试6:无效页面 */
    ret = cal_page_handler(CAL_PAGE_SELECT, 0x05, NULL);
    printf("选择无效页面: ret=0x%02X\n", ret);
    
    return 0;
}

代码说明

代码维护一个包含两个校准页面的静态表,每个页面记录其ID、激活状态和可用性。cal_page_handler函数根据命令字分发处理:SELECT仅改变页面的激活标记但不改变当前生效页面;SWITCH立即更新当前页面指针并同步激活状态;STATUS返回当前生效页面编号。页面可用性检查在SELECT和SWITCH中均执行,无效页面编号返回ERR_PARAM。

验证方法

编译并运行测试程序:


gcc -o cal_page_test cal_page_test.c
./cal_page_test

预期输出如下(与代码逻辑严格对应):


初始状态查询: ret=0x00, current_page=0x00
选择页面1: ret=0x00
选择后查询: ret=0x00, current_page=0x00
切换页面1: ret=0x00
切换后查询: ret=0x00, current_page=0x01
选择无效页面: ret=0x31

判定标准:所有返回码与预期一致,且状态查询结果正确反映SELECT不切换、SWITCH立即切换的语义。

状态转换示意

页面0 状态: 激活 当前生效 页面1 状态: 未激活 非当前 SELECT 0x01 仅标记激活 SWITCH 0x01: 立即切换当前页面 STATUS 命令 → 返回当前生效页面编号 (0x00 或 0x01) SELECT路径 SWITCH路径

图中展示了两个校准页面的状态以及SELECT与SWITCH命令的不同作用路径:SELECT仅改变激活标记,SWITCH同时更新当前生效页面指针。STATUS命令用于查询当前生效页面。