Files
DigiRadio/Software/main/hardware_bootstrap.hpp
T
micheleandClaude Sonnet 5 a7a5311c2c Persist Si4684 crystal calibration (ibias/ctun/xtalFreqHz) to EEPROM
Extends the existing FM/DAB ANTCAP EEPROM persistence pattern
(Eeprom24aa::writeFmAntCap/writeDabAntCap) to the crystal trim found by
POST /api/tuner/xtal-calibrate, which previously only applied live and
was lost on every reboot.

- Eeprom24aa gains readXtalCalibration()/writeXtalCalibration() at word
  addresses 0x02 (ibias), 0x03 (ctun), 0x04-0x07 (xtalFreqHz,
  big-endian), right after the existing FM/DAB ANTCAP bytes.
- HardwareBootstrap::boot() now boots ADAU1701 before Si4684 (needed so
  the EEPROM read, which borrows ADAU1701's I2C bus, can happen before
  Si4684's boot() call, which takes the crystal trim as an argument),
  loads the saved trim if present, and falls back to the compiled-in
  defaults (ibias=72, ctun=0, xtalFreqHz=19199750) otherwise.
- POST /api/tuner/xtal-calibrate now persists every successful live
  recalibration automatically ("persisted":true/false in the response)
  via a new saveXtalCalibration()/net::AntennaCalibration::saveXtal
  bridge, mirroring the ANTCAP save pattern.

Verified live: boot log confirms "Xtal not calibrated" before the first
save, "Xtal calibration loaded: ibias=72 ctun=0 xtal_freq_hz=19199750"
after, surviving a reboot; DAB/FM tuning unaffected (DAB CNR 17-19dB,
locked).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 15:22:03 +02:00

223 lines
7.2 KiB
C++

/**
* @file hardware_bootstrap.hpp
* @brief Boot Si4684 and ADAU1701 companion chips at application start.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-07-06
*/
#pragma once
#include "core/CompanionChipStatus.hpp"
#include "core/DeviceIdentity.hpp"
#include "bt1035/Bt1035Driver.hpp"
#include <cstdint>
#include <expected>
#include <optional>
namespace audio {
class AudioService;
} // namespace audio
namespace si4684 {
class Si4684Tuner;
} // namespace si4684
namespace hardware {
/**
* @brief HardwareBootError — companion-chip boot failure causes.
*
* @dname HardwareBootError
* @return n/a (type)
* @pubstate n/a
*
* @author Michele Bigi
* @date 2026-07-06
*/
enum class HardwareBootError {
Si4684BootFailed,
Adau1701BootFailed,
Bt1035BootFailed,
};
/**
* @brief HardwareBootstrap — orchestrates Si4684 then ADAU1701 bring-up.
*
* @dname HardwareBootstrap
* @return n/a (type)
* @pubstate Owns static driver instances for the process lifetime. Must be
* called once from app_main before network start.
*
* @author Michele Bigi
* @date 2026-07-06
*/
class HardwareBootstrap {
public:
/**
* @brief boot — load Si4684 DAB image then replay ADAU1701 program.
*
* @dname boot
* @return Ok on success, or HardwareBootError.
* @pubstate constructs static Si4684, ADAU1701, and BT1035 drivers on first call.
*
* Fail-closed: callers must not start Wi-Fi when this returns an error.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] static std::expected<void, HardwareBootError> boot();
/**
* @brief si4684Tuner — borrow the Si4684 ITuner adapter after boot.
*
* @dname si4684Tuner
* @return Reference to the static Si4684Tuner instance.
* @pubstate reads static storage initialised by boot().
*
* Valid only after a successful boot() call in the same process.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] static si4684::Si4684Tuner& si4684Tuner();
/**
* @brief audioService — borrow the audio orchestration service after boot.
*
* @dname audioService
* @return Reference to the static AudioService instance.
* @pubstate reads static storage initialised by boot().
*
* Valid only after a successful boot() call in the same process.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] static audio::AudioService& audioService();
/**
* @brief companionChipStatus — snapshot of companion-chip boot flags.
*
* @dname companionChipStatus
* @return Ready flags for Si4684, ADAU1701, BT1035.
* @pubstate reads static driver isBooted() after boot().
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] static core::CompanionChipStatus companionChipStatus() noexcept;
/**
* @brief bt1035Driver — borrow the BT1035 driver after boot.
*
* @dname bt1035Driver
* @return Reference to the static Bt1035Driver instance.
* @pubstate reads static storage initialised by boot().
*
* Valid only after a successful boot() call in the same process.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] static bt1035::Bt1035Driver& bt1035Driver();
/**
* @brief deviceIdentity — per-board identity from the 24AA025E48 EUI-48.
*
* @dname deviceIdentity
* @return Reference to identity loaded during boot().
* @pubstate reads gDeviceIdentity after boot(); unknown() on EEPROM failure.
*
* @author Michele Bigi
* @date 2026-07-07
*/
[[nodiscard]] static const core::DeviceIdentity& deviceIdentity() noexcept;
/**
* @brief fmAntCapCalibration — saved FM antenna calibration, if any.
*
* @dname fmAntCapCalibration
* @return Calibrated ANTCAP (0-128) read from the 24AA025E48 during
* boot(), or nullopt if never calibrated / the read failed.
* @pubstate reads gFmAntCapCalibration; set once during boot().
*
* @author Michele Bigi
* @date 2026-08-19
*/
[[nodiscard]] static std::optional<std::uint8_t>
fmAntCapCalibration() noexcept;
/**
* @brief saveFmAntCapCalibration — persist a new FM ANTCAP to EEPROM.
*
* @dname saveFmAntCapCalibration
* @param antCap Value found via a calibration sweep (0-128).
* @return true on success, false on an I2C failure.
* @pubstate writes the 24AA025E48 user region and gFmAntCapCalibration.
* Does not itself change any live tuner state — callers must
* also call TunerService::setDefaultFmAntCap() to apply it.
*
* @author Michele Bigi
* @date 2026-08-19
*/
[[nodiscard]] static bool saveFmAntCapCalibration(std::uint8_t antCap);
/**
* @brief dabAntCapCalibration — saved DAB antenna calibration, if any.
*
* @dname dabAntCapCalibration
* @return Calibrated ANTCAP (0-128) read from the 24AA025E48 during
* boot(), or nullopt if never calibrated / the read failed.
* @pubstate reads gDabAntCapCalibration; set once during boot().
*
* @author Michele Bigi
* @date 2026-08-20
*/
[[nodiscard]] static std::optional<std::uint8_t>
dabAntCapCalibration() noexcept;
/**
* @brief saveDabAntCapCalibration — persist a new DAB ANTCAP to EEPROM.
*
* @dname saveDabAntCapCalibration
* @param antCap Value found via a calibration sweep (0-128).
* @return true on success, false on an I2C failure.
* @pubstate writes the 24AA025E48 user region and gDabAntCapCalibration.
* Does not itself change any live tuner state — callers must
* also call TunerService::setDefaultDabAntCap() to apply it.
*
* @author Michele Bigi
* @date 2026-08-20
*/
[[nodiscard]] static bool saveDabAntCapCalibration(std::uint8_t antCap);
/**
* @brief saveXtalCalibration — persist the Si4684 crystal trim.
*
* @dname saveXtalCalibration
* @param ibias Value found via POST /api/tuner/xtal-calibrate.
* @param ctun Value found via POST /api/tuner/xtal-calibrate.
* @param xtalFreqHz Value found via POST /api/tuner/xtal-calibrate.
* @return true on success, false on an I2C failure.
* @pubstate writes the 24AA025E48 user region. Does not itself reboot
* the Si4684 — callers apply the values live via
* Si4684Tuner::recalibrateXtal() first; boot() reads this back
* on the next power-up so the trim survives a reboot.
*
* @author Michele Bigi
* @date 2026-08-24
*/
[[nodiscard]] static bool saveXtalCalibration(std::uint8_t ibias,
std::uint8_t ctun,
std::uint32_t xtalFreqHz);
};
} // namespace hardware