Files
DigiRadio/Software/components/drivers/si4684/include/si4684/Si4684Driver.hpp
T
micheleandCursor 176cdd9319 Release fw 0.8.4: doc sync, System UI, BT/FM polish.
Align README, manual, and backlog to 0.8.4; add Web UI System tab for OTA/DSP uploads, serial in health header, FM seek down, and BT1035 paired list plus auto-reconnect API.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 09:56:44 +02:00

413 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file Si4684Driver.hpp
* @brief Si4684 DAB+/FM tuner — boot, tuning, status, and service control.
*
* 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/FrequencyKHz.hpp"
#include "core/IFirmwareBlobReader.hpp"
#include "core/SeekDirection.hpp"
#include "si4684/Si4684Band.hpp"
#include "si4684/Si4684Error.hpp"
#include "si4684/Si4684Types.hpp"
#include <cstdint>
#include <expected>
#include <optional>
#include <span>
#include <vector>
namespace si4684 {
/**
* @brief Si4684Pins — board GPIO/SPI identifiers for the tuner.
*
* @dname Si4684Pins
* @return n/a (type)
* @pubstate Immutable wiring snapshot from board_pins.hpp.
*
* @author Michele Bigi
* @date 2026-07-06
*/
struct Si4684Pins {
int spiHost; ///< ESP32 SPI host peripheral index.
int csGpio; ///< Chip-select GPIO.
int misoGpio; ///< MISO GPIO.
int mosiGpio; ///< MOSI GPIO.
int sclkGpio; ///< SCLK GPIO.
int rstbGpio; ///< RESET# GPIO (active low).
int intbGpio; ///< INTB GPIO (optional status interrupt).
};
/**
* @brief Si4684Driver — full Si4684 control over SPI (AN649 / PE5PVB).
*
* @dname Si4684Driver
* @return n/a (type)
* @pubstate Owns the SPI link and boot state. Register-level command bytes
* stay private; callers use intent-level methods only.
*
* Implements the documented boot sequence plus band-specific tuning, property
* access, RSQ/DIGRAD reads, and DAB service selection.
*
* @author Michele Bigi
* @date 2026-07-06
*/
class Si4684Driver {
public:
/**
* @brief Si4684Driver — construct with board pins and firmware blobs.
*
* @dname Si4684Driver
* @param pins Board SPI and GPIO wiring.
* @param patch ROM patch blob reader for HOST_LOAD.
* @param dabImage DAB application image reader.
* @param fmImage FM application image reader.
* @pubstate stores pin map and blob references; not booted until boot().
*
* @author Michele Bigi
* @date 2026-07-06
*/
Si4684Driver(Si4684Pins pins,
const core::IFirmwareBlobReader& patch,
const core::IFirmwareBlobReader& dabImage,
const core::IFirmwareBlobReader& fmImage);
/**
* @brief ~Si4684Driver — release SPI resources.
*
* @dname ~Si4684Driver
* @pubstate removes SPI device and bus when active.
*
* @author Michele Bigi
* @date 2026-07-06
*/
~Si4684Driver();
Si4684Driver(const Si4684Driver&) = delete;
Si4684Driver& operator=(const Si4684Driver&) = delete;
/**
* @brief boot — cold-start: pulse RSTB#, load patch/image, configure I/O.
*
* @details Pulses RSTB# (active-low reset): gpio_config enables the ESP32
* internal pull-down while switching GPIO38 to output so the net
* cannot glitch high before gpio_set_level(0). That guards the
* Si4684 datasheet rule that RSTB must stay low until supplies are
* stable; the board's external pull-down already holds RSTB during
* rail ramp before app_main runs.
*
* @dname boot
* @param band DAB or FM application to load.
* @return Ok on success, or Si4684Error.
* @pubstate writes booted_ and loadedBand_ on success.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<void, Si4684Error> boot(Si4684Band band);
/**
* @brief isBooted — query whether boot completed successfully.
*
* @dname isBooted
* @return true after a successful boot().
* @pubstate reads booted_.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] bool isBooted() const noexcept;
/**
* @brief loadedBand — read the active application band.
*
* @dname loadedBand
* @return Si4684Band loaded by the last successful boot().
* @pubstate reads loadedBand_.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] Si4684Band loadedBand() const noexcept;
/**
* @brief getPartInfo — read chip identity and firmware revision.
*
* @dname getPartInfo
* @return Si4684PartInfo on success, or Si4684Error.
* @pubstate reads chip via GET_PART_INFO (AN649).
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<Si4684PartInfo, Si4684Error> getPartInfo();
/**
* @brief getSysState — read the running application state.
*
* @dname getSysState
* @return Si4684SysState on success, or Si4684Error.
* @pubstate reads chip via GET_SYS_STATE.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<Si4684SysState, Si4684Error> getSysState();
/**
* @brief setProperty — write a Skyworks property (SET_PROPERTY 0x13).
*
* @dname setProperty
* @param propertyId 16-bit property address.
* @param value 16-bit property value.
* @return Ok on success, or Si4684Error.
* @pubstate writes property via SPI command.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<void, Si4684Error> setProperty(
std::uint16_t propertyId, std::uint16_t value);
/**
* @brief setVolume — set audio attenuator 063 (property 0x0300).
*
* @dname setVolume
* @param level Attenuator level 063.
* @return Ok on success, or Si4684Error.
* @pubstate writes AUDIO_VOLUME property.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<void, Si4684Error> setVolume(std::uint8_t level);
/**
* @brief tuneFm — tune FM to a validated centre frequency.
*
* @dname tuneFm
* @param frequency FM centre frequency in kHz.
* @return Ok on success, or Si4684Error::WrongBand / TuneFailed.
* @pubstate sends FM_TUNE_FREQ and waits for STC (AN649).
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<void, Si4684Error> tuneFm(
core::FrequencyKHz frequency);
/**
* @brief seekFm — seek FM in the given direction.
*
* @dname seekFm
* @param direction Up or Down scan direction.
* @param wrap Band wrap behaviour for FM_SEEK_START.
* @return New centre frequency on success, or Si4684Error.
* @pubstate sends FM_SEEK_START and waits for STC.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<core::FrequencyKHz, Si4684Error> seekFm(
core::SeekDirection direction, SeekBandWrap wrap);
/**
* @brief readFmRsq — read FM signal quality metrics.
*
* @dname readFmRsq
* @return Si4684FmRsq on success, or Si4684Error.
* @pubstate reads FM_RSQ_STATUS response bytes.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<Si4684FmRsq, Si4684Error> readFmRsq();
/**
* @brief readFmRds — read a raw RDS group snapshot.
*
* @dname readFmRds
* @return Si4684FmRdsStatus on success, or Si4684Error.
* @pubstate reads FM_RDS_STATUS response bytes.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<Si4684FmRdsStatus, Si4684Error> readFmRds();
/**
* @brief readDabServiceData — read one queued DAB data-service block.
*
* @dname readDabServiceData
* @param statusOnly Poll queue depth without consuming payload.
* @param ack Acknowledge DSRVINT when reading payload.
* @return Data block on success, nullopt when queue empty, or Si4684Error.
* @pubstate reads GET_DIGITAL_SERVICE_DATA response bytes.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<std::optional<Si4684DabServiceData>, Si4684Error>
readDabServiceData(bool statusOnly, bool ack);
/**
* @brief installDefaultDabFrequencyPlan — load Band III frequency list.
*
* @dname installDefaultDabFrequencyPlan
* @return Ok on success, or Si4684Error.
* @pubstate sends DAB_SET_FREQ_LIST with kDefaultDabFrequencyKhz.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<void, Si4684Error> installDefaultDabFrequencyPlan();
/**
* @brief tuneDab — tune to a Band III ensemble index.
*
* @dname tuneDab
* @param freqIndex Ensemble index 037.
* @return Ok on success, or Si4684Error.
* @pubstate sends DAB_TUNE_FREQ and waits for STC.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<void, Si4684Error> tuneDab(std::uint8_t freqIndex);
/**
* @brief readDabDigRadStatus — read ensemble lock metrics.
*
* @dname readDabDigRadStatus
* @return Si4684DabDigRadStatus on success, or Si4684Error.
* @pubstate reads DAB_DIGRAD_STATUS response bytes.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<Si4684DabDigRadStatus, Si4684Error>
readDabDigRadStatus();
/**
* @brief readDabEventStatus — read DAB event flags.
*
* @dname readDabEventStatus
* @return Si4684DabEventStatus on success, or Si4684Error.
* @pubstate reads DAB_GET_EVENT_STATUS response bytes.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<Si4684DabEventStatus, Si4684Error>
readDabEventStatus();
/**
* @brief fetchDabServiceList — retrieve programmes for the ensemble.
*
* @dname fetchDabServiceList
* @return Service rows on success, or Si4684Error.
* @pubstate reads GET_DIGITAL_SERVICE_LIST chunks from the chip.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<std::vector<Si4684DabService>, Si4684Error>
fetchDabServiceList();
/**
* @brief startDabService — start DAB audio for a programme.
*
* @dname startDabService
* @param serviceId Selected service identifier.
* @param componentId Audio component within the service.
* @param type Digital service type (audio by default).
* @return Ok on success, or Si4684Error.
* @pubstate sends START_DIGITAL_SERVICE.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<void, Si4684Error> startDabService(
std::uint32_t serviceId,
std::uint32_t componentId,
Si4684DigitalServiceType type = Si4684DigitalServiceType::Audio);
/**
* @brief stopDabService — stop active DAB audio (STOP_DIGITAL_SERVICE).
*
* @dname stopDabService
* @param serviceId Selected service identifier.
* @param componentId Audio component within the service.
* @param type Digital service type (audio by default).
* @return Ok on success, or Si4684Error.
* @pubstate sends STOP_DIGITAL_SERVICE (AN649 opcode 0x82).
*
* @author Michele Bigi
* @date 2026-07-07
*/
[[nodiscard]] std::expected<void, Si4684Error> stopDabService(
std::uint32_t serviceId,
std::uint32_t componentId,
Si4684DigitalServiceType type = Si4684DigitalServiceType::Audio);
private:
enum class Command : std::uint8_t {
PowerUp = 0x01,
HostLoad = 0x04,
LoadInit = 0x06,
BootCmd = 0x07,
GetPartInfo = 0x08,
GetSysState = 0x09,
GetFuncInfo = 0x12,
SetProperty = 0x13,
FmTuneFreq = 0x30,
FmSeekStart = 0x31,
FmRsqStatus = 0x32,
FmRdsStatus = 0x34,
GetDigitalServiceList = 0x80,
StartDigitalService = 0x81,
StopDigitalService = 0x82,
GetDigitalServiceData = 0x84,
DabTuneFreq = 0xB0,
DabDigRadStatus = 0xB2,
DabGetEventStatus = 0xB3,
DabSetFreqList = 0xB8,
};
[[nodiscard]] std::expected<void, Si4684Error> ensureBooted() const;
[[nodiscard]] std::expected<void, Si4684Error> ensureBand(
Si4684Band band) const;
[[nodiscard]] std::expected<void, Si4684Error> waitCts();
[[nodiscard]] std::expected<void, Si4684Error> waitStc();
[[nodiscard]] std::expected<void, Si4684Error> sendCommand(
std::span<const std::uint8_t> bytes);
[[nodiscard]] std::expected<void, Si4684Error> readRaw(
std::span<std::uint8_t> buffer);
[[nodiscard]] std::expected<void, Si4684Error> writeCommand(
Command cmd, const std::uint8_t* payload, std::size_t length);
[[nodiscard]] std::expected<void, Si4684Error> hostLoadBlob(
const core::IFirmwareBlobReader& blob, std::size_t chunkPayload);
[[nodiscard]] std::expected<void, Si4684Error> configureAfterBoot(
Si4684Band band);
Si4684Pins pins_;
const core::IFirmwareBlobReader& patch_;
const core::IFirmwareBlobReader& dabImage_;
const core::IFirmwareBlobReader& fmImage_;
bool booted_;
Si4684Band loadedBand_;
bool spiBusActive_;
void* spiDevice_;
};
} // namespace si4684