参数存储与加载机制:.params 文件、SD卡与内部Flash

飞控参数这东西,说白了就是飞控的“记忆”。你调完PID、校准完传感器,这些数值得有个地方存着,断电不能丢。PX4怎么存、怎么读、启动时怎么恢复?这一章我带你从头捋一遍。

参数文件格式:.params 到底长什么样?

你在QGroundControl里导出参数,得到的就是一个 .params 文件。用记事本打开,你会发现它其实就是一个纯文本的键值对列表。

# 这是注释行,以 # 开头
# 参数名 = 值
# 注意:参数名全部大写,值可以是整数、浮点数或枚举

MC_ROLLRATE_P = 0.15
MC_ROLLRATE_I = 0.05
MC_ROLLRATE_D = 0.003
SYS_AUTOSTART = 4001
CAL_GYRO0_XOFF = 0.0012

格式规则很简单:

  • 参数名:全大写,下划线分隔,最长不超过16个字符
  • 值:支持整数、浮点数、布尔值(存为0/1)、枚举值(存为整数)
  • 注释:# 开头,整行忽略
  • 空行:忽略,随便加
我的习惯:每次调参后,我都会导出一份 .params 文件,文件名带上日期和飞机编号。比如 params_quad_20250315.params。这样万一刷固件后参数丢了,直接导入就行,不用重新调。

SD卡存储:参数的实际归宿

在大多数PX4飞控上(比如Pixhawk系列),参数文件存在SD卡里。具体路径是:

/fs/microsd/params/

这里面会有几个文件:

文件名 作用
current.params 当前正在使用的参数集,启动时自动加载
backup.params 上一次保存的备份,防止写入失败
factory.params 出厂默认参数,恢复出厂设置时使用

为什么用SD卡?因为参数数量不少——PX4有上千个参数,每个参数4字节,加上元数据,一次读写就是几十KB。SD卡容量大、读写方便,而且可以热插拔。你想想看,如果参数存在内部Flash里,每次改参数都要擦写Flash,寿命和速度都是问题。

我曾经踩过的坑:有一次在野外调试,SD卡松动了,结果飞控启动后参数全丢了,飞机直接失控。从那以后,我每次起飞前都会检查SD卡是否插紧,并且在飞控内部Flash里也存一份关键参数的备份。

内部Flash存储:最后的保险

SD卡虽然方便,但不可靠。震动、接触不良、文件系统损坏,都可能导致参数丢失。所以PX4在内部Flash里也保留了一份参数副本。

Flash存储的位置因芯片而异:

  • STM32F4/F7系列:通常使用最后几个扇区,比如扇区11或12
  • STM32H7系列:使用备份SRAM或专用参数扇区
  • 其他MCU:看具体移植情况

Flash存储的特点是:

  • 掉电不丢失:这是基本要求
  • 写入次数有限:一般10万次擦写寿命,所以不能频繁改
  • 读取快,写入慢:写入前需要擦除整个扇区

所以PX4的策略是:平时读写用SD卡,只有SD卡失效时才从Flash恢复。Flash里存的是最后一次成功保存的参数快照。

启动时参数加载流程:一步一步来

飞控上电后,参数是怎么恢复的?我画了一张流程图,你看完就明白了。

PX4 启动参数加载流程 系统上电启动 检查SD卡是否存在 无SD卡 有SD卡 从内部Flash加载参数 从SD卡加载参数 检查CRC校验 检查CRC校验 校验失败 → 使用默认值 校验通过 → 加载到内存 参数系统就绪 说明:SD卡优先,Flash作为备份;CRC校验失败时自动回退到默认值

流程其实不复杂,我拆解一下:

  1. 上电初始化:系统启动,文件系统挂载
  2. 检查SD卡:如果SD卡存在且文件系统正常,走绿色分支;否则走红色分支
  3. 加载参数文件:从SD卡读取 current.params,或者从Flash读取参数扇区
  4. CRC校验:每个参数文件末尾有CRC32校验值,确保文件没有被损坏
  5. 校验通过:参数加载到内存中的参数表,供各个模块使用
  6. 校验失败:使用编译时写入的默认参数,同时打印警告日志

核心要点:参数加载是“SD卡优先,Flash兜底”。如果两者都失效,还有最后一层保险——固件里硬编码的默认值。这就是PX4参数系统的“三保险”机制。

参数加载的代码实现:关键函数

如果你感兴趣,可以看看源码里的实现。核心文件在 src/modules/param/ 目录下。我挑几个关键函数说说:

// 参数初始化入口
int param_init()
{
    // 1. 尝试从SD卡加载
    if (sd_card_present() && file_exists(PARAM_FILE_PATH)) {
        return load_params_from_sd();
    }
    // 2. SD卡失败,尝试从Flash加载
    if (flash_param_valid()) {
        return load_params_from_flash();
    }
    // 3. 都失败,使用默认值
    return load_default_params();
}

// 从SD卡加载参数
static int load_params_from_sd()
{
    int fd = open(PARAM_FILE_PATH, O_RDONLY);
    if (fd < 0) return -1;

    // 逐行解析,格式:NAME=VALUE
    char line[128];
    while (read_line(fd, line, sizeof(line)) > 0) {
        if (line[0] == '#' || line[0] == '\n') continue;
        char *name = strtok(line, "=");
        char *value = strtok(NULL, "=");
        param_set(name, atof(value));
    }
    close(fd);

    // 校验CRC
    if (!verify_crc(PARAM_FILE_PATH)) {
        PX4_ERR("参数文件CRC校验失败,使用默认值");
        return -1;
    }
    return 0;
}

代码逻辑很直白。你注意看,它先读文件,再校验CRC。如果校验失败,整个加载过程会被标记为失败,然后回退到默认值。这个设计很关键——宁可飞控用默认参数飞,也不能用损坏的参数飞。

参数保存:什么时候写回存储?

参数不是随时都在写的。频繁写Flash会缩短寿命,写SD卡也会增加文件系统碎片。PX4的策略是:

  • 手动保存:通过QGC点击“保存参数”按钮,或者发送 param save 命令
  • 自动保存:飞控关机时(检测到电压下降),自动触发一次保存
  • 定时保存:某些版本有定时保存机制,但默认关闭

我个人建议:调参时养成手动保存的习惯。别指望自动保存,万一断电前没来得及写,你调了一下午的参数就白费了。

我曾经遇到的故障:有一次在调试过程中频繁改参数,但没有手动保存。结果飞控意外断电,所有参数回到上次保存的状态。那次我调了整整两天的PID参数全丢了。从那以后,我每次改完关键参数都会立刻保存,并且导出一份备份到电脑上。

参数存储的可靠性设计

PX4在参数存储上做了不少可靠性设计,我总结一下:

设计 说明
CRC32校验 每个参数文件末尾有4字节CRC,加载时重新计算对比
双备份 SD卡和Flash各存一份,互为冗余
原子写入 保存时先写临时文件,写入成功后再重命名,防止写入中断导致文件损坏
默认值回退 所有参数都有编译时的默认值,即使存储完全失效也能飞

嗯,这里要注意:原子写入这个设计很巧妙。它先写一个 .tmp 文件,写完后用 rename() 替换原文件。如果写入过程中断电,原文件还在,不会丢失。这个技巧在嵌入式文件操作中很常用。

小结

参数存储与加载,说白了就是解决“怎么让飞控记住你的设置”这个问题。.params 文件是人类的可读格式,SD卡是主存储介质,Flash是备份,启动时按优先级加载,加上CRC校验和默认值回退,构成了一个可靠的参数系统。

你理解了这套机制,以后遇到参数丢失、加载失败的问题,就知道从哪排查了——先看SD卡有没有插好,再看Flash里的备份是否正常,最后检查默认值能不能用。一步一步来,问题总能找到根因。