Files
AR-Autopilot/firmware/ar_autopilot_v1/src/hal/nvs_config.cpp
T
alro65 5f9b445572 sprint-7: Commissioning wizard + relay ZN auto-tuner + NVS config + knob encoder
Python:
- autotuner.py: RelayAutoTuner (Astrom-Hagglund relay method) → TunerResult
  with Ku/Tu; TunerResult.to_pid_gains() applies ZN formulas
- commissioning_wizard.py: 4-phase state machine (RUDDER_LIMITS → SENSOR_CAL
  → AUTO_TUNE → DONE); transport-injected for testability; abort on invalid cal
- test_autotuner.py: 17 tests covering relay convergence, ZN formulas,
  wizard full-run, abort, ADC swap, identical-ADC guard

Firmware:
- nvs_config.h/cpp: NVS-backed CalibrationData store (adc limits, rudder angles,
  outer Kp/Ki/Kd, commissioned flag); float stored as uint32 via memcpy
- knob_encoder.h/cpp: quadrature rotary encoder on GPIO 16/17 with ISR Gray-code
  decode; knob_arm coil arms for 5 s window; updates heading setpoint ±1 deg/detent
- modbus_slave.cpp: COIL_CMD_KNOB_ARM now calls knob_encoder_set_armed()
- main.cpp: nvs_config_init/load at boot; apply commissioned calibration to
  rudder sensor and outer loop gains; start knob encoder task

Tests: 326 passed | Flash: 28.5%

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 02:55:26 -04:00

122 lines
3.6 KiB
C++

// =============================================================================
// hal/nvs_config.cpp -- NVS-backed calibration store (Sprint 7)
// =============================================================================
#include "nvs_config.h"
#include <nvs.h>
#include <nvs_flash.h>
#include <Arduino.h>
#include "../system/ar_log.h"
namespace arautopilot::hal {
namespace {
constexpr const char* TAG = "AR/NVS";
constexpr const char* NS = "ar_cal";
} // namespace
bool nvs_config_init() {
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES ||
err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
AR_LOGW(TAG, "NVS partition needs erase (err=0x%x) -- factory-resetting", err);
nvs_flash_erase();
err = nvs_flash_init();
}
if (err != ESP_OK) {
AR_LOGE(TAG, "nvs_flash_init failed: 0x%x", err);
return false;
}
AR_LOGI(TAG, "NVS initialised (namespace: %s)", NS);
return true;
}
CalibrationData nvs_config_load() {
CalibrationData cal; // defaults in struct definition
nvs_handle_t h;
if (nvs_open(NS, NVS_READONLY, &h) != ESP_OK) {
AR_LOGI(TAG, "No calibration in NVS -- using factory defaults");
return cal;
}
// Helper lambdas: read or keep default on missing key.
auto get_i16 = [&](const char* key, int16_t& out) {
int16_t v;
if (nvs_get_i16(h, key, &v) == ESP_OK) out = v;
};
auto get_float = [&](const char* key, float& out) {
// NVS has no native float; store as uint32 via memcpy.
uint32_t raw;
if (nvs_get_u32(h, key, &raw) == ESP_OK) {
memcpy(&out, &raw, sizeof(float));
}
};
auto get_u8 = [&](const char* key, uint8_t& out) {
uint8_t v;
if (nvs_get_u8(h, key, &v) == ESP_OK) out = v;
};
get_i16("adc_port", cal.adc_port);
get_i16("adc_stbd", cal.adc_stbd);
get_float("rud_port", cal.rudder_port_deg);
get_float("rud_stbd", cal.rudder_stbd_deg);
get_float("ou_kp", cal.outer_kp);
get_float("ou_ki", cal.outer_ki);
get_float("ou_kd", cal.outer_kd);
uint8_t comm = 0;
get_u8("commissioned", comm);
cal.commissioned = (comm == 1);
nvs_close(h);
AR_LOGI(TAG, "Calibration loaded (commissioned=%d adc=%d..%d)",
(int)cal.commissioned, cal.adc_port, cal.adc_stbd);
return cal;
}
bool nvs_config_save(const CalibrationData& cal) {
nvs_handle_t h;
if (nvs_open(NS, NVS_READWRITE, &h) != ESP_OK) {
AR_LOGE(TAG, "nvs_open for write failed");
return false;
}
auto put_float = [&](const char* key, float v) {
uint32_t raw;
memcpy(&raw, &v, sizeof(float));
nvs_set_u32(h, key, raw);
};
nvs_set_i16(h, "adc_port", cal.adc_port);
nvs_set_i16(h, "adc_stbd", cal.adc_stbd);
put_float("rud_port", cal.rudder_port_deg);
put_float("rud_stbd", cal.rudder_stbd_deg);
put_float("ou_kp", cal.outer_kp);
put_float("ou_ki", cal.outer_ki);
put_float("ou_kd", cal.outer_kd);
nvs_set_u8(h, "commissioned", cal.commissioned ? 1u : 0u);
esp_err_t err = nvs_commit(h);
nvs_close(h);
if (err != ESP_OK) {
AR_LOGE(TAG, "nvs_commit failed: 0x%x", err);
return false;
}
AR_LOGI(TAG, "Calibration saved to NVS");
return true;
}
bool nvs_config_erase() {
nvs_handle_t h;
if (nvs_open(NS, NVS_READWRITE, &h) != ESP_OK) return false;
esp_err_t err = nvs_erase_all(h);
nvs_commit(h);
nvs_close(h);
AR_LOGW(TAG, "NVS namespace '%s' erased (factory reset)", NS);
return err == ESP_OK;
}
} // namespace arautopilot::hal