参数存储与加载机制:.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,寿命和速度都是问题。
内部Flash存储:最后的保险
SD卡虽然方便,但不可靠。震动、接触不良、文件系统损坏,都可能导致参数丢失。所以PX4在内部Flash里也保留了一份参数副本。
Flash存储的位置因芯片而异:
- STM32F4/F7系列:通常使用最后几个扇区,比如扇区11或12
- STM32H7系列:使用备份SRAM或专用参数扇区
- 其他MCU:看具体移植情况
Flash存储的特点是:
- 掉电不丢失:这是基本要求
- 写入次数有限:一般10万次擦写寿命,所以不能频繁改
- 读取快,写入慢:写入前需要擦除整个扇区
所以PX4的策略是:平时读写用SD卡,只有SD卡失效时才从Flash恢复。Flash里存的是最后一次成功保存的参数快照。
启动时参数加载流程:一步一步来
飞控上电后,参数是怎么恢复的?我画了一张流程图,你看完就明白了。
流程其实不复杂,我拆解一下:
- 上电初始化:系统启动,文件系统挂载
- 检查SD卡:如果SD卡存在且文件系统正常,走绿色分支;否则走红色分支
- 加载参数文件:从SD卡读取
current.params,或者从Flash读取参数扇区 - CRC校验:每个参数文件末尾有CRC32校验值,确保文件没有被损坏
- 校验通过:参数加载到内存中的参数表,供各个模块使用
- 校验失败:使用编译时写入的默认参数,同时打印警告日志
核心要点:参数加载是“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命令 - 自动保存:飞控关机时(检测到电压下降),自动触发一次保存
- 定时保存:某些版本有定时保存机制,但默认关闭
我个人建议:调参时养成手动保存的习惯。别指望自动保存,万一断电前没来得及写,你调了一下午的参数就白费了。
参数存储的可靠性设计
PX4在参数存储上做了不少可靠性设计,我总结一下:
| 设计 | 说明 |
|---|---|
| CRC32校验 | 每个参数文件末尾有4字节CRC,加载时重新计算对比 |
| 双备份 | SD卡和Flash各存一份,互为冗余 |
| 原子写入 | 保存时先写临时文件,写入成功后再重命名,防止写入中断导致文件损坏 |
| 默认值回退 | 所有参数都有编译时的默认值,即使存储完全失效也能飞 |
嗯,这里要注意:原子写入这个设计很巧妙。它先写一个 .tmp 文件,写完后用 rename() 替换原文件。如果写入过程中断电,原文件还在,不会丢失。这个技巧在嵌入式文件操作中很常用。
小结
参数存储与加载,说白了就是解决“怎么让飞控记住你的设置”这个问题。.params 文件是人类的可读格式,SD卡是主存储介质,Flash是备份,启动时按优先级加载,加上CRC校验和默认值回退,构成了一个可靠的参数系统。
你理解了这套机制,以后遇到参数丢失、加载失败的问题,就知道从哪排查了——先看SD卡有没有插好,再看Flash里的备份是否正常,最后检查默认值能不能用。一步一步来,问题总能找到根因。