Files
inav/docs/development/parameter_groups/README.md
T
admin dac37fd077
Make sure docs are updated / settings_md (push) Canceled after 0s
Build firmware / test (push) Canceled after 0s
Build firmware / build-SITL-Windows (push) Canceled after 0s
Build firmware / build-SITL-Mac (push) Canceled after 0s
Build firmware / build-SITL-Linux (push) Canceled after 0s
Build firmware / build-SITL-Linux-arm64 (push) Canceled after 0s
Build firmware / upload-artifacts (push) Canceled after 0s
Build firmware / build-single-target (push) Canceled after 0s
Build firmware / build (9) (push) Canceled after 0s
Build firmware / build (8) (push) Canceled after 0s
Build firmware / build (7) (push) Canceled after 0s
Build firmware / build (6) (push) Canceled after 0s
Build firmware / build (5) (push) Canceled after 0s
Build firmware / build (4) (push) Canceled after 0s
Build firmware / build (3) (push) Canceled after 0s
Build firmware / build (2) (push) Canceled after 0s
Build firmware / build (14) (push) Canceled after 0s
Build firmware / build (13) (push) Canceled after 0s
Build firmware / build (12) (push) Canceled after 0s
Build firmware / build (11) (push) Canceled after 0s
Build firmware / build (10) (push) Canceled after 0s
Build firmware / build (1) (push) Canceled after 0s
Build firmware / build (0) (push) Canceled after 0s
Build firmware / detect (push) Canceled after 0s
Build pre-release / build (push) Canceled after 0s
Build pre-release / Release (push) Canceled after 0s
Sync inav to Gitea
2026-08-03 16:37:40 +08:00

258 lines
6.7 KiB
Markdown

# Parameter Groups System
## Overview
Parameter Groups (PG) is INAV's system for managing persistent configuration settings. Each configuration struct is registered with a unique ID and version number, stored in EEPROM/flash, and automatically validated on boot.
## Quick Reference
**When modifying a parameter group struct:**
-**Increment version** - Safest option, always works
- ⚠️ **Don't increment** - Only safe for comments or non-structural changes
**Key files:**
- `src/main/config/parameter_group.h` - Macros and registry structure
- `src/main/config/parameter_group.c` - Runtime load/store functions
- `src/main/config/parameter_group_ids.h` - PGN definitions
- `src/main/fc/settings.yaml` - Maps CLI settings to PG fields
## How It Works
### Registration
Parameter groups use linker sections to build a compile-time registry:
```c
// In yourfeature.h
typedef struct yourFeatureConfig_s {
uint8_t enabled;
uint16_t rate;
} yourFeatureConfig_t;
PG_DECLARE(yourFeatureConfig_t, yourFeatureConfig);
// In yourfeature.c
PG_REGISTER_WITH_RESET_TEMPLATE(
yourFeatureConfig_t,
yourFeatureConfig,
PG_YOUR_FEATURE_CONFIG, // Unique ID from parameter_group_ids.h
0 // Version number
);
PG_RESET_TEMPLATE(yourFeatureConfig_t, yourFeatureConfig,
.enabled = 1,
.rate = 100
);
```
### Version Checking
On boot, `pgLoad()` validates versions before loading settings:
```c
void pgLoad(const pgRegistry_t* reg, int profileIndex,
const void *from, int size, int version)
{
pgReset(reg, profileIndex); // Always reset to defaults first
if (version == pgVersion(reg)) {
// Only copy if versions match
const int take = MIN(size, pgSize(reg));
memcpy(pgOffset(reg, profileIndex), from, take);
}
// If version mismatch: defaults remain, no corruption
}
```
**Key behavior:** Version mismatch causes settings to remain at defaults. This prevents corruption when struct layout changes.
## Version Management
### When to Increment Version
Version numbers are stored in the top 4 bits of the PGN field (valid range: 0-15).
**The safe rule:** When modifying a parameter group struct, increment the version.
**Changes requiring version increment:**
- Removing a field
- Changing field type or size
- Reordering fields
- Adding packing attributes
**Example - Removing a field:**
```c
// Before: version 4
typedef struct blackboxConfig_s {
uint16_t rate_num;
uint16_t rate_denom;
uint8_t device;
uint8_t invertedCardDetection; // Removing this
uint32_t includeFlags;
int8_t arm_control;
} blackboxConfig_t;
PG_REGISTER_WITH_RESET_TEMPLATE(blackboxConfig_t, blackboxConfig, PG_BLACKBOX_CONFIG, 4);
// After: version 5 (MUST increment!)
typedef struct blackboxConfig_s {
uint16_t rate_num;
uint16_t rate_denom;
uint8_t device;
// invertedCardDetection removed - field offsets changed!
uint32_t includeFlags; // Now at different offset
int8_t arm_control; // Now at different offset
} blackboxConfig_t;
PG_REGISTER_WITH_RESET_TEMPLATE(blackboxConfig_t, blackboxConfig, PG_BLACKBOX_CONFIG, 5);
```
**Why:** Without version increment, `pgLoad()` sees matching versions (4==4) and copies old data, but field offsets have changed. Result: `includeFlags` reads wrong bytes from EEPROM, causing corruption.
With version increment (4→5), `pgLoad()` detects mismatch and keeps defaults instead.
### Changes NOT Requiring Version Increment
- Comments only
- Default values in `PG_RESET_TEMPLATE` (doesn't affect EEPROM layout)
- settings.yaml entries (CLI mapping, not struct layout)
### Technical Note on Appending
The `pgLoad()` function uses `MIN(size, pgSize(reg))` when copying data. This means:
- If new firmware has larger struct, it copies available bytes and keeps defaults for new fields
- If new firmware has smaller struct, it copies only what fits
This behavior theoretically allows appending fields at the end without version increment. However, **the recommended practice is to always increment** unless you have specific reason not to and understand the implications.
## Complete Example
### yourfeature.h
```c
#pragma once
#include <stdint.h>
#include "config/parameter_group.h"
typedef struct yourFeatureConfig_s {
uint8_t enabled;
uint16_t updateRate;
uint32_t flags;
} yourFeatureConfig_t;
PG_DECLARE(yourFeatureConfig_t, yourFeatureConfig);
```
### yourfeature.c
```c
#include "yourfeature.h"
#include "config/parameter_group_ids.h"
PG_REGISTER_WITH_RESET_TEMPLATE(
yourFeatureConfig_t,
yourFeatureConfig,
PG_YOUR_FEATURE_CONFIG,
0
);
PG_RESET_TEMPLATE(yourFeatureConfig_t, yourFeatureConfig,
.enabled = 1,
.updateRate = 100,
.flags = 0
);
void yourFeatureInit(void)
{
if (!yourFeatureConfig()->enabled) {
return;
}
// Use configuration...
}
```
### parameter_group_ids.h
```c
typedef enum {
// ... existing entries ...
PG_YOUR_FEATURE_CONFIG = 150, // Choose unused ID
// ...
} pgn_e;
```
### settings.yaml
```yaml
- name: your_feature_enable
description: "Enable yourFeature"
field: yourFeatureConfig.enabled
type: uint8_t
min: 0
max: 1
default_value: 1
- name: your_feature_rate
description: "Update rate in Hz"
field: yourFeatureConfig.updateRate
type: uint16_t
min: 1
max: 1000
default_value: 100
```
## Accessing Configuration
### Read-only (preferred)
```c
if (yourFeatureConfig()->enabled) {
processAtRate(yourFeatureConfig()->updateRate);
}
```
### Mutable (use sparingly)
```c
yourFeatureConfigMutable()->updateRate = newRate;
```
## Registration Macros
| Macro | Use |
|-------|-----|
| `PG_DECLARE(type, name)` | Declare system PG in header |
| `PG_DECLARE_PROFILE(type, name)` | Declare profile PG in header |
| `PG_REGISTER_WITH_RESET_TEMPLATE(...)` | Register with template defaults |
| `PG_REGISTER_WITH_RESET_FN(...)` | Register with custom reset function |
| `PG_RESET_TEMPLATE(type, name, ...)` | Define default values |
## Common Patterns
### Boolean Fields
```c
uint8_t enabled; // Use uint8_t, not bool (EEPROM needs fixed sizes)
```
### Enums
```c
typedef enum { MODE_A = 0, MODE_B = 1 } mode_e;
uint8_t mode; // Store enum as uint8_t
```
### Bitfields
```c
#define FLAG_A (1 << 0)
#define FLAG_B (1 << 1)
uint32_t flags;
```
## Testing Version Changes
When incrementing a version:
1. Flash old firmware, configure non-default settings, save
2. Flash new firmware with incremented version
3. Verify settings reset to defaults (not corrupted)
4. Check for errors in CLI
## See Also
- `src/main/config/parameter_group.h` - Full macro definitions
- `src/main/config/parameter_group.c` - Implementation
- Betaflight PG documentation: https://betaflight.com/docs/development/ParameterGroups