edu16_sbc/sbc_fw/eeprom.cpp
2026-05-10 15:33:08 +08:00

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;
}