144 lines
4.3 KiB
C++
144 lines
4.3 KiB
C++
/*
|
|
* @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 <Arduino.h>
|
|
#include <EEPROM.h>
|
|
|
|
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;
|
|
}
|