4. XCP内存写入
任务说明
本案例实现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立即切换的语义。
状态转换示意
图中展示了两个校准页面的状态以及SELECT与SWITCH命令的不同作用路径:SELECT仅改变激活标记,SWITCH同时更新当前生效页面指针。STATUS命令用于查询当前生效页面。