/* * @file eeprom.cpp * @brief EEPROM management with dual-partition wear leveling. * * Created: 21.09.2025 * Author: ThePetrovich * * Copyright YKSA - Sakha Aerospace Systems, LLC. * See the LICENSE file for details. * * SPDX-License-Identifier: BSD-3-Clause */ #include "eeprom.h" #include "config.h" #include #include config_t config; /* * Each partition occupies sizeof(config_t)+1 bytes: * [0 .. sizeof(config_t)-1] config_t struct * [sizeof(config_t)] generation counter (uint8_t) */ #define PARTITION_SIZE ((uint16_t)(sizeof(config_t) + 1)) #define PARTITION_A_ADDR 0 #define PARTITION_B_ADDR PARTITION_SIZE #define GEN_OFFSET ((uint16_t)sizeof(config_t)) /** * @brief Track which partition was last written so saves alternate. * 0 = partition A is current, 1 = partition B is current. */ static uint8_t g_active_partition = 0; /** * @brief Check whether the partition at @p base contains a valid magic pair. */ static bool partition_valid(uint16_t base) { uint8_t m1 = EEPROM.read(base + offsetof(config_t, magic1)); uint8_t m2 = EEPROM.read(base + offsetof(config_t, magic2)); return (m1 == 0xA5 && m2 == 0x5A); } /** * @brief Read a config_t struct from the partition at @p base into @p dst. */ static void read_partition(uint16_t base, config_t *dst) { uint8_t *p = (uint8_t *)dst; for (uint16_t i = 0; i < sizeof(config_t); i++) { p[i] = EEPROM.read(base + i); } } /** * @brief Write @p src and a generation byte to the partition at @p base. * Uses EEPROM.update() so unchanged cells incur no wear. */ static void write_partition(uint16_t base, const config_t *src, uint8_t generation) { const uint8_t *p = (const uint8_t *)src; for (uint16_t i = 0; i < sizeof(config_t); i++) { EEPROM.update(base + i, p[i]); } EEPROM.update(base + GEN_OFFSET, generation); } /** * @brief Populate @p cfg with safe factory defaults. * Used on first boot or when both partitions are corrupt. */ static void set_defaults(config_t *cfg) { cfg->magic1 = 0xA5; cfg->magic2 = 0x5A; cfg->pots.pot_hv = DEFAULT_POT_VALUE; cfg->pots.pot_amp = DEFAULT_POT_VALUE; cfg->pots.pot_det = DEFAULT_POT_VALUE; cfg->calibration.adccal0 = 0xFFFF; cfg->calibration.adccal3 = 0xFFFF; cfg->calibration.tempcal = 0; cfg->flags.value = 0; /* pot_low == pot_high disables drift compensation per channel. */ cfg->temp_drift_compensation.temp_drift = 0; cfg->temp_drift_compensation.vbias_drift = 0; cfg->temp_drift_compensation.gain_drift = 0; cfg->temp_drift_compensation.threshold_drift = 0; cfg->temp_drift_compensation.drift_period_ms = 5000; cfg->temp_drift_compensation.pot_hv_low = DEFAULT_POT_VALUE; cfg->temp_drift_compensation.pot_hv_high = DEFAULT_POT_VALUE; cfg->temp_drift_compensation.pot_amp_low = DEFAULT_POT_VALUE; cfg->temp_drift_compensation.pot_amp_high = DEFAULT_POT_VALUE; cfg->temp_drift_compensation.pot_det_low = DEFAULT_POT_VALUE; cfg->temp_drift_compensation.pot_det_high = DEFAULT_POT_VALUE; } void eeprom_init(void) { bool a_valid = partition_valid(PARTITION_A_ADDR); bool b_valid = partition_valid(PARTITION_B_ADDR); if (a_valid && b_valid) { uint8_t gen_a = EEPROM.read(PARTITION_A_ADDR + GEN_OFFSET); uint8_t gen_b = EEPROM.read(PARTITION_B_ADDR + GEN_OFFSET); /* Signed delta handles wrap-around: B is newer if (gen_b - gen_a) > 0 mod 256. */ if ((int8_t)(gen_b - gen_a) > 0) { read_partition(PARTITION_B_ADDR, &config); g_active_partition = 1; } else { read_partition(PARTITION_A_ADDR, &config); g_active_partition = 0; } } else if (a_valid) { read_partition(PARTITION_A_ADDR, &config); g_active_partition = 0; } else if (b_valid) { read_partition(PARTITION_B_ADDR, &config); g_active_partition = 1; } else { set_defaults(&config); write_partition(PARTITION_A_ADDR, &config, 0); g_active_partition = 0; } } void eeprom_save_config(void) { /* Write to whichever partition is NOT currently active. */ uint8_t target_partition = (g_active_partition == 0) ? 1 : 0; uint16_t target_base = (target_partition == 0) ? PARTITION_A_ADDR : PARTITION_B_ADDR; uint16_t active_base = (g_active_partition == 0) ? PARTITION_A_ADDR : PARTITION_B_ADDR; uint8_t old_gen = EEPROM.read(active_base + GEN_OFFSET); uint8_t new_gen = old_gen + 1; config.magic1 = 0xA5; config.magic2 = 0x5A; write_partition(target_base, &config, new_gen); g_active_partition = target_partition; }