4. App固件头部信息设计与校验

任务目标

实现固件版本号的存储与读取,支持版本比较,为OTA升级提供依据。版本号采用三段式格式 major.minor.patch(如 2.1.0),存储在Flash固定区域,支持写入、读取和比较操作。

版本存储格式

版本号以紧凑二进制格式存储于Flash,占用4字节:

  • Byte 0:major(0~255)
  • Byte 1:minor(0~255)
  • Byte 2:patch(0~255)
  • Byte 3:校验字节(前3字节异或值)

校验字节用于检测Flash数据损坏,读取时若校验失败则返回默认版本 0.0.0。

完整实现

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

// Flash模拟存储(实际项目中替换为Flash驱动接口)
#define VERSION_FLASH_ADDR  0x0800FC00  // 示例地址
static uint8_t flash_buf[4];            // 模拟Flash内容

typedef struct {
    uint8_t major;
    uint8_t minor;
    uint8_t patch;
} fw_version_t;

// 版本比较结果
typedef enum {
    VER_LOWER = -1,
    VER_EQUAL = 0,
    VER_HIGHER = 1
} ver_cmp_t;

// 计算校验字节
static uint8_t calc_checksum(const fw_version_t *ver) {
    return ver->major ^ ver->minor ^ ver->patch;
}

// 写入版本到Flash
bool version_write(const fw_version_t *ver) {
    if (!ver) return false;
    
    flash_buf[0] = ver->major;
    flash_buf[1] = ver->minor;
    flash_buf[2] = ver->patch;
    flash_buf[3] = calc_checksum(ver);
    
    // 实际工程中在此调用Flash擦写驱动
    // flash_erase(VERSION_FLASH_ADDR);
    // flash_write(VERSION_FLASH_ADDR, flash_buf, 4);
    return true;
}

// 从Flash读取版本
fw_version_t version_read(void) {
    fw_version_t ver = {0, 0, 0};
    
    // 实际工程中在此调用Flash读取驱动
    // flash_read(VERSION_FLASH_ADDR, flash_buf, 4);
    
    // 校验数据有效性
    uint8_t checksum = flash_buf[0] ^ flash_buf[1] ^ flash_buf[2];
    if (checksum != flash_buf[3]) {
        return ver;  // 校验失败,返回0.0.0
    }
    
    ver.major = flash_buf[0];
    ver.minor = flash_buf[1];
    ver.patch = flash_buf[2];
    return ver;
}

// 比较两个版本号
ver_cmp_t version_compare(const fw_version_t *v1, const fw_version_t *v2) {
    if (!v1 || !v2) return VER_EQUAL;
    
    if (v1->major != v2->major)
        return (v1->major > v2->major) ? VER_HIGHER : VER_LOWER;
    if (v1->minor != v2->minor)
        return (v1->minor > v2->minor) ? VER_HIGHER : VER_LOWER;
    if (v1->patch != v2->patch)
        return (v1->patch > v2->patch) ? VER_HIGHER : VER_LOWER;
    return VER_EQUAL;
}

// 字符串解析辅助(可选,用于从固件头解析版本)
bool version_from_string(const char *str, fw_version_t *out) {
    if (!str || !out) return false;
    unsigned mj, mn, pt;
    if (sscanf(str, "%u.%u.%u", &mj, &mn, &pt) != 3) return false;
    if (mj > 255 || mn > 255 || pt > 255) return false;
    out->major = (uint8_t)mj;
    out->minor = (uint8_t)mn;
    out->patch = (uint8_t)pt;
    return true;
}

// 测试入口
#include <stdio.h>
int main(void) {
    // 模拟Flash初始为空(全0xFF)
    memset(flash_buf, 0xFF, sizeof(flash_buf));
    
    // 测试1:写入并读取
    fw_version_t v1 = {2, 1, 0};
    version_write(&v1);
    fw_version_t v2 = version_read();
    printf("写入: %u.%u.%u, 读取: %u.%u.%u\n",
           v1.major, v1.minor, v1.patch,
           v2.major, v2.minor, v2.patch);
    
    // 测试2:版本比较
    fw_version_t old_ver = {2, 0, 9};
    fw_version_t new_ver = {2, 1, 0};
    ver_cmp_t cmp = version_compare(&old_ver, &new_ver);
    printf("比较结果: %d (期望1,表示new_ver更高)\n", cmp);
    
    // 测试3:校验失败场景
    flash_buf[3] = 0x00;  // 破坏校验字节
    fw_version_t invalid = version_read();
    printf("损坏数据读取: %u.%u.%u (期望0.0.0)\n",
           invalid.major, invalid.minor, invalid.patch);
    
    return 0;
}

编译与运行

gcc -o version_test version_test.c
./version_test

验证方法

程序输出应满足以下条件即判定通过:

  • 写入版本 2.1.0 后读取结果一致,校验字节正确
  • version_compare 对 2.0.9 与 2.1.0 返回 1(新版本更高)
  • 人为破坏校验字节后,读取返回 0.0.0,不会返回错误版本

OTA集成说明

在实际OTA流程中,固件升级前调用 version_read() 获取当前运行版本,从升级包头部解析出新版本号,通过 version_compare() 判断新版本是否高于当前版本。只有新版本更高时才允许执行升级,升级完成后调用 version_write() 更新存储的版本号。Flash驱动接口(擦除、写入、读取)需根据目标MCU替换为实际实现。