Document Si4684 driver, TunerService, and tuner HTTP API (0.4.0).

Adds ch-si4684 and updates API/class reference chapters for slice 4.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-06 16:10:52 +02:00
co-authored by Cursor
parent 0b087a262a
commit 0e25c4cc1a
5 changed files with 431 additions and 24 deletions
+82 -3
View File
@@ -6,7 +6,7 @@ implemented in \texttt{SetupWebServer}. Request bodies are parsed into
domain types in the pure core (\texttt{components/core}) before any domain types in the pure core (\texttt{components/core}) before any
persistence or driver call. Exact C++ signatures live in the generated persistence or driver call. Exact C++ signatures live in the generated
Doxygen output under \texttt{docs/api/}; this chapter documents the Doxygen output under \texttt{docs/api/}; this chapter documents the
wire protocol and behaviour as shipped in firmware~0.2.0 (Slices~1--2). wire protocol and behaviour as shipped in firmware~0.4.0 (Slices~1--4).
\section{Transport and reachability} \section{Transport and reachability}
@@ -36,7 +36,7 @@ Returns a health-check DTO serialised by
\begin{drnote}[Response schema] \begin{drnote}[Response schema]
\begin{drcode}[JSON] \begin{drcode}[JSON]
{"status":"ok","fw":"0.2.0"} {"status":"ok","fw":"0.4.0"}
\end{drcode} \end{drcode}
\begin{itemize} \begin{itemize}
\item \texttt{status} --- coarse indicator; \texttt{ok} when the \item \texttt{status} --- coarse indicator; \texttt{ok} when the
@@ -93,10 +93,89 @@ HTTP status: \textbf{200 OK} on save success; \textbf{400 Bad Request} for
parse/validation failures; \textbf{500 Internal Server Error} when NVS parse/validation failures; \textbf{500 Internal Server Error} when NVS
persistence fails. persistence fails.
\subsection{\texttt{GET /api/tuner/status}}
\label{sec:api-tuner-status}
Returns a tuner snapshot serialised by
\texttt{core::serializeTunerStatusJson()} from \texttt{core::TunerStatus}.
The handler calls \texttt{tuner::TunerService::refreshStatus()}.
\begin{drnote}[Response schema (DAB example)]
\begin{drcode}[JSON]
{"booted":true,"band":"dab","locked":true,"volume":63,
"dab":{"freq_index":12,"fic_quality":80,"cnr_db":25},"fm":null}
\end{drcode}
For FM, \texttt{fm} carries \texttt{frequency\_khz}, \texttt{rssi\_dbuv},
\texttt{snr\_db}, and \texttt{stereo}; \texttt{dab} is \texttt{null}.
\end{drnote}
HTTP status: \textbf{200 OK}; \textbf{500} with
\texttt{\{"status":"error","reason":...\}} on driver failure;
\textbf{503} when the tuner service is unavailable.
\subsection{\texttt{GET /api/tuner/services}}
\label{sec:api-tuner-services}
Lists DAB programmes for the current ensemble via
\texttt{TunerService::listDabServices()}.
\begin{drnote}[Response schema]
\begin{drcode}[JSON]
{"services":[{"service_id":12345,"component_id":1,"label":"BBC R1"}]}
\end{drcode}
\end{drnote}
HTTP status: \textbf{200 OK}; \textbf{409 Conflict} when the list cannot be
fetched (wrong band, empty ensemble, hardware error).
\subsection{\texttt{POST /api/tuner/tune}}
\label{sec:api-tuner-tune}
Tunes to a DAB ensemble or FM frequency. Body parsed by
\texttt{core::parseTunerTuneJson()}.
\begin{drnote}[Request schema]
\begin{drcode}[JSON]
{"band":"dab","freq_index":12}
\end{drcode}
or
\begin{drcode}[JSON]
{"band":"fm","frequency_khz":101500}
\end{drcode}
DAB \texttt{freq\_index} is 0--37; FM \texttt{frequency\_khz} is
64\,000--108\,000.
\end{drnote}
On success returns the updated status JSON (same shape as
\texttt{GET /api/tuner/status}). HTTP status: \textbf{200 OK};
\textbf{400} for invalid JSON; \textbf{409} for tune failures.
\subsection{\texttt{POST /api/tuner/play}}
\label{sec:api-tuner-play}
Starts a DAB audio service. Body parsed by
\texttt{core::parseTunerPlayJson()}.
\begin{drnote}[Request schema]
\begin{drcode}[JSON]
{"service_id":12345,"component_id":1}
\end{drcode}
\end{drnote}
Success response: \texttt{\{"status":"playing"\}}. HTTP status:
\textbf{200 OK}; \textbf{409} when playback cannot start.
\subsection{\texttt{POST /api/tuner/seek}}
\label{sec:api-tuner-seek}
Seeks FM upward (no request body). Returns
\texttt{\{"frequency\_khz":...\}} on success. HTTP status: \textbf{200 OK};
\textbf{409} on seek failure.
\section{Boot and network state machine} \section{Boot and network state machine}
\label{sec:api-boot-flow} \label{sec:api-boot-flow}
At boot, \texttt{net::NetBootstrap::start()} consults At boot, \texttt{net::NetBootstrap::start(store, tuner)} consults
\texttt{ISecureStore::hasWifiCredentials()}: \texttt{ISecureStore::hasWifiCredentials()}:
\begin{enumerate} \begin{enumerate}
+65 -11
View File
@@ -50,15 +50,17 @@ RAII wrapper around the ESP-IDF Wi-Fi stack that starts and stops the
configured SoftAP. Imperative shell; no business logic. configured SoftAP. Imperative shell; no business logic.
\section{SetupWebServer}\label{cls:SetupWebServer} \section{SetupWebServer}\label{cls:SetupWebServer}
Minimal HTTP server: gzipped setup UI, \texttt{GET /api/health}, and Minimal HTTP server: gzipped setup UI, \texttt{GET /api/health},
\texttt{POST /api/wifi} for provisioning. JSON parsing and serialisation \texttt{POST /api/wifi}, and tuner routes (\texttt{/api/tuner/*}).
delegate to the pure core; credentials persist via \texttt{ISecureStore}. JSON parsing and serialisation delegate to the pure core; credentials
persist via \texttt{ISecureStore}; tuner operations via
\texttt{tuner::TunerService}.
\section{NetBootstrap}\label{cls:NetBootstrap} \section{NetBootstrap}\label{cls:NetBootstrap}
Owns network resources for setup or STA mode. \texttt{start(store)} Owns network resources for setup or STA mode.
initialises the platform, joins stored Wi-Fi when credentials exist, or \texttt{start(store, tuner)} initialises the platform, joins stored Wi-Fi
falls back to the \texttt{DigiRadio-setup} SoftAP. Must outlive when credentials exist, or falls back to the \texttt{DigiRadio-setup}
\texttt{app\_main} for the process lifetime. SoftAP. Must outlive \texttt{app\_main} for the process lifetime.
% ------------------------------------------------------------------ % ------------------------------------------------------------------
% Domain core + secure store (Slice 2) % Domain core + secure store (Slice 2)
@@ -92,8 +94,60 @@ are stored as NVS strings and never logged. Production should enable NVS
encryption using the reserved \texttt{nvs\_keys} partition. encryption using the reserved \texttt{nvs\_keys} partition.
% ------------------------------------------------------------------ % ------------------------------------------------------------------
% Future slices (drivers, services) % Hardware drivers (Slice 3)
% ------------------------------------------------------------------ % ------------------------------------------------------------------
% \section{Si4684Driver}\label{cls:Si4684Driver}
% \section{TunerService}\label{cls:TunerService} \section{IFirmwareBlobReader}\label{cls:IFirmwareBlobReader}
% ... Abstract streaming reader for firmware blobs embedded in flash. The Si4684
driver uses it to HOST\_LOAD patch and application images in bounded SPI
chunks without allocating the full image on the heap. Host tests use the
same interface over in-memory buffers.
\section{EmbeddedBlobReader}\label{cls:EmbeddedBlobReader}
Non-owning \texttt{IFirmwareBlobReader} over a contiguous byte range (linker
symbols from \texttt{EMBED\_FILES} or test data). Implements offset-based
\texttt{read()} with truncation at end-of-blob.
\section{Si4684EmbeddedImages}\label{cls:Si4684EmbeddedImages}
Owns \texttt{EmbeddedBlobReader} views over \texttt{rom\_patch\_016.bin},
\texttt{dab\_firmware.bin}, and \texttt{fm\_firmware.bin}. Constructed once;
passed to \texttt{Si4684Driver}. \texttt{applicationImage(Si4684Band)} selects
DAB or FM.
\section{Si4684Driver}\label{cls:Si4684Driver}
Full SPI driver for the Si4684 DAB+/FM tuner (Chapter~\ref{ch:si4684}).
\texttt{boot(Si4684Band)} runs AN649 HOST\_LOAD, configures I\textsuperscript{2}S
and band-specific properties, and records \texttt{loadedBand()}. After boot the
driver exposes FM tuning/seek/RSQ/RDS, DAB ensemble tuning, digital service list
fetch, and \texttt{startDabService}. Property access (\texttt{setProperty},
\texttt{setVolume}) and diagnostics (\texttt{getPartInfo}, \texttt{getSysState})
are included. Wrong-band calls return \texttt{Si4684Error::WrongBand}.
Register-level opcodes remain private; see \texttt{Si4684Types.hpp} for status
DTOs.
\section{Adau1701Driver}\label{cls:Adau1701Driver}
RAII I2C driver for the ADAU1701 SigmaDSP. \texttt{boot()} asserts RESET\#,
initialises the shared I2C bus, and replays the SigmaStudio export from
\texttt{Firmware/ADAU1701-Firmware/} on every power-up (no EEPROM self-boot
on DigiRadio).
% ------------------------------------------------------------------
% Application services (Slice 4)
% ------------------------------------------------------------------
\section{ITuner}\label{cls:ITuner}
Abstract tuner boundary in the pure core. Defines boot, band selection,
status readout, FM/DAB tuning, service list, playback, and volume without
ESP-IDF types. \texttt{si4684::Si4684Tuner} implements it on device; host
tests can use fakes.
\section{TunerService}\label{cls:TunerService}
Application service exposing intent-level tuner operations to HTTP and
future UI. Holds a reference to \texttt{core::ITuner}, tracks last tune
target and volume, and maps driver failures to \texttt{core::TunerError}.
\section{Si4684Tuner}\label{cls:Si4684Tuner}
\texttt{ITuner} adapter over \texttt{Si4684Driver}. Translates domain calls
into SPI commands and maps \texttt{Si4684Error} to \texttt{TunerError}.
Constructed once in \texttt{HardwareBootstrap} alongside the driver.
+25 -10
View File
@@ -125,16 +125,9 @@ verified against reference values in host tests.
At power-up the driver follows the documented boot sequence: power on, At power-up the driver follows the documented boot sequence: power on,
load the patch/bootloader, load the firmware image (FM or DAB), then load the patch/bootloader, load the firmware image (FM or DAB), then
boot. Firmware images are large and are streamed to the chip in bounded boot. Firmware images are large and are streamed to the chip in bounded
chunks from flash rather than buffered whole in RAM. chunks from flash rather than buffered whole in RAM. See
Chapter~\ref{ch:si4684} for blob sources, uGreen extraction, and the full
\begin{drref}[Datasheet] \texttt{Si4684Driver} API (tuning, RSQ, DAB service list).
The exact power-up, patch, image-load, and boot command sequence follows
the Si468x programming guide (AN649). Each step in the driver cites the
relevant section; no command byte is issued without a documented source.
\end{drref} The public
interface is intent-level (\texttt{powerUp}, \texttt{loadImage},
\texttt{tuneTo}, \texttt{readRsq}); register-level detail is private to
the driver.
\paragraph{ADAU1701 (DSP).} \paragraph{ADAU1701 (DSP).}
The board carries no self-boot EEPROM. Instead, the ESP32 writes the The board carries no self-boot EEPROM. Instead, the ESP32 writes the
@@ -184,6 +177,28 @@ values cannot be logged or implicitly converted to a string; buffers are
cleared on destruction. Production builds should enable NVS encryption cleared on destruction. Production builds should enable NVS encryption
at rest (see Chapter~\ref{sec:api-storage}). at rest (see Chapter~\ref{sec:api-storage}).
\section{Companion-chip boot at power-up}
\label{sec:fw-chip-boot}
The ESP32-S3 loads both audio companion chips from assets under
\texttt{Software/Firmware/} during \texttt{app\_main}, before network
bring-up:
\begin{enumerate}
\item \textbf{Si4684} (SPI): ROM patch \texttt{rom\_patch\_016.bin}, then either
DAB image \texttt{dab\_firmware.bin} or FM image \texttt{fm\_firmware.bin},
following AN649 (Chapter~\ref{ch:si4684}). DAB blob default from PE5PVB; FM
from eval pack or uGreen \texttt{radio\_cli}. Blobs are local-only
(\texttt{.gitignore}); embedded via ESP-IDF \texttt{EMBED\_FILES}.
\item \textbf{ADAU1701} (I\textsuperscript{2}C): SigmaStudio export
(\texttt{DigiRadio\_IC\_1.h}) replayed through
\texttt{SIGMA\_WRITE\_REGISTER\_BLOCK} after hardware reset. The DSP
program lives in RAM only; download runs on every boot.
\end{enumerate}
If either driver returns an error, the firmware logs the failure and stops
before starting Wi-Fi (fail-closed bring-up).
\section{Error-handling model} \section{Error-handling model}
\label{sec:fw-errors} \label{sec:fw-errors}
+258
View File
@@ -0,0 +1,258 @@
% ============================================================
% DigiRadio — Manual chapter: Si4684 tuner
% ============================================================
\chapter{Si4684 DAB+/FM Tuner}
\label{ch:si4684}
The Skyworks Si4684 is the primary RF front-end of DigiRadio: it demodulates
DAB/DAB+ and FM, decodes RDS, and delivers PCM over I\textsuperscript{2}S to
the ADAU1701. This chapter documents how firmware images are obtained and kept
local, how the chip is booted on the ESP32-S3, and how \texttt{Si4684Driver}
exposes every day-to-day operation without leaking register-level details to
application code.
\section{Role in the audio chain}
\label{sec:si4684-role}
After boot the Si4684 runs as an I\textsuperscript{2}S master at
44.1\,kHz stereo. The ESP32 loads proprietary application firmware into chip
RAM at every cold start (there is no self-boot EEPROM on DigiRadio). Tuning,
service selection, and signal-quality reads all go through
\texttt{si4684::Si4684Driver}; higher layers (\texttt{TunerService}, web UI)
will depend on that class rather than issuing SPI transactions directly.
\begin{drref}[Programming guide]
Command opcodes, property IDs, and reply layouts follow Skyworks application
note \textbf{AN649} (Si468x programming guide). The DigiRadio driver aligns
with the PE5PVB/SI4684-DAB-Receiver reference for DAB and with community FM
images for the FM application blob.
\end{drref}
\section{Firmware images}
\label{sec:si4684-firmware}
Three binary blobs live under \texttt{Firmware/Si4684-Firmware/}. They are
\textbf{not} committed to the public Git repository (proprietary Skyworks /
uGreen licence); \texttt{.gitignore} keeps them on the developer machine only.
\begin{table}[htbp]
\centering
\begin{tabular}{@{}llp{6.2cm}@{}}
\toprule
\textbf{Local file} & \textbf{Typ.\ size} & \textbf{Source} \\
\midrule
\texttt{rom\_patch\_016.bin} & 5796 B &
ROM patch (\texttt{rom00\_patch.016.bin}); refreshed from
PE5PVB or uGreen \texttt{radio\_cli}. \\
\texttt{dab\_firmware.bin} & $\sim$517--497 KB &
DAB application image; default from PE5PVB header extract; optional
uGreen \texttt{dab\_radio\_6\_0\_6.bin}. \\
\texttt{fm\_firmware.bin} & $\sim$530 KB &
FM/HD application; eval CD, dabpi \texttt{si46xx\_firmware/}, or uGreen
\texttt{fmhd\_radio\_5\_1\_3.bin}. \\
\bottomrule
\end{tabular}
\caption{Si4684 firmware blobs (local only).}
\label{tab:si4684-blobs}
\end{table}
\subsection{Where images come from}
\label{sec:si4684-sources}
Community projects name the same Skyworks files differently; none of them
redistribute the binaries in git:
\begin{itemize}
\item \textbf{PE5PVB/SI4684-DAB-Receiver} --- DAB image and ROM patch as
C arrays (\texttt{firmware.h}, \texttt{Si468xROM.h}). Safe to automate.
\item \textbf{uGreen DABBoard} (\url{https://ugreen.eu/downloads/}) ---
\texttt{Files\_v16.zip} embeds FM, DAB, and patch inside
\texttt{radio\_cli}; use \texttt{tools/extract\_ugreen\_radio\_cli.py}.
\item \textbf{hitech95/si468x\_dab\_receiver} --- Linux driver only; device
tree references \texttt{si468x/fmhd\_radio\_5\_1\_0.bin} but does not
ship the files (closed source).
\item \textbf{PhilBladen/Radio} --- STM32 reference using \emph{external}
SPI flash (\texttt{FLASH\_LOAD}); different hardware path from DigiRadio's
\texttt{HOST\_LOAD} flow.
\end{itemize}
\begin{drcaution}[Licence]
Do not push \texttt{*.bin} firmware to GitHub. Obtain images from your own
eval board, uGreen customer download, or dabpi folder; keep them in
\texttt{Firmware/Si4684-Firmware/} locally.
\end{drcaution}
\subsection{Refreshing blobs}
\label{sec:si4684-refresh}
\begin{verbatim}
# DAB + patch from PE5PVB (always safe to re-run):
python3 tools/fetch_si4684_firmware.py --dab-only
# FM from uGreen radio_cli (Files_v16.zip):
python3 tools/fetch_si4684_firmware.py \
--from-ugreen-radio-cli ~/Downloads/Files_v16.zip
# All three blobs from uGreen (overrides PE5PVB DAB):
python3 tools/fetch_si4684_firmware.py \
--from-ugreen-radio-cli ~/Downloads/Files_v16.zip --ugreen-all
# FM from dabpi / Skyworks eval folder:
python3 tools/fetch_si4684_firmware.py --si46xx-dir /path/to/si46xx_firmware
\end{verbatim}
Low-level helpers: \texttt{tools/extract\_si4684\_blob.py} (C header or copy),
\texttt{tools/extract\_ugreen\_radio\_cli.py} (ELF symbol scrape).
\section{Boot sequence (HOST\_LOAD)}
\label{sec:si4684-boot}
DigiRadio follows AN649 ``cold start with HOST\_LOAD'' (same as PE5PVB), not
the \texttt{FLASH\_LOAD} path used by boards with a pre-programmed SST25V.
\begin{figure}[htbp]
\centering
\begin{tikzpicture}[
step/.style={draw, rounded corners, minimum width=9cm,
minimum height=0.9cm, align=center, font=\small},
>={Latex}, node distance=3mm]
\node[step, fill=black!6] (rst) {Hardware reset (RST\#)};
\node[step, fill=black!6, below=of rst] (pu)
{POWER\_UP (crystal / clock args)};
\node[step, fill=black!6, below=of pu] (li1) {LOAD\_INIT};
\node[step, fill=black!8, below=of li1] (patch)
{HOST\_LOAD \texttt{rom\_patch\_016.bin} (124 B chunks)};
\node[step, fill=black!6, below=of patch] (li2) {LOAD\_INIT};
\node[step, fill=black!8, below=of li2] (img)
{HOST\_LOAD \texttt{dab\_} or \texttt{fm\_firmware.bin}
(2044 B chunks)};
\node[step, fill=black!6, below=of img] (boot) {BOOT};
\node[step, fill=black!10, below=of boot] (cfg)
{Properties: I\textsuperscript{2}S 44.1 kHz, RDS/DAB FE, volume};
\foreach \a/\b in {rst/pu, pu/li1, li1/patch, patch/li2, li2/img,
img/boot, boot/cfg} {
\draw[->] (\a) -- (\b);
}
\end{tikzpicture}
\caption{Si4684 boot flow on DigiRadio (AN649 / PE5PVB).}
\label{fig:si4684-boot}
\end{figure}
Images are embedded in the ESP32 flash partition via ESP-IDF
\texttt{EMBED\_FILES} and streamed through \texttt{IFirmwareBlobReader} so the
full $\sim$1\,MB of patch + DAB + FM never has to fit in RAM at once. Switching
band (\texttt{Si4684Band::Fm} $\leftrightarrow$ \texttt{Dab}) performs reset and
a complete reload of patch + the other application image.
\section{Si4684Driver API}
\label{sec:si4684-driver}
\texttt{Si4684Driver} owns SPI, implements boot, and exposes intent-level
methods grouped below. All return \texttt{std::expected<T, Si4684Error>}; FM
calls fail with \texttt{WrongBand} if the FM image is not loaded, and vice
versa for DAB.
\subsection{Bring-up and diagnostics}
\begin{table}[htbp]
\centering
\begin{tabular}{@{}ll@{}}
\toprule
\textbf{Method} & \textbf{Purpose} \\
\midrule
\texttt{boot(Si4684Band)} & Cold start + default properties \\
\texttt{isBooted()}, \texttt{loadedBand()} & State query \\
\texttt{getPartInfo()} & Chip ID + firmware version \\
\texttt{getSysState()} & Running application type \\
\texttt{setProperty(id, value)} & Generic SET\_PROPERTY \\
\texttt{setVolume(0--63)} & Audio attenuator \\
\bottomrule
\end{tabular}
\caption{Si4684 bring-up and property access.}
\end{table}
\subsection{FM operations}
Requires \texttt{boot(Si4684Band::Fm)}.
\begin{itemize}
\item \texttt{tuneFm(frequencyKhz)} --- FM\_TUNE\_FREQ, waits for STC.
\item \texttt{seekFm(up, wrap)} --- FM\_SEEK\_START; returns tuned kHz.
\item \texttt{readFmRsq()} --- RSSI, SNR, stereo flag, validity.
\item \texttt{readFmRds()} --- last RDS group blocks A--D.
\end{itemize}
\subsection{DAB operations}
Requires \texttt{boot(Si4684Band::Dab)}. The default Band~III frequency plan
(38 channels, Table~\ref{tab:si4684-dab-plan}) is installed automatically
after boot.
\begin{itemize}
\item \texttt{installDefaultDabFrequencyPlan()} --- DAB\_SET\_FREQ\_LIST
(also called from boot).
\item \texttt{tuneDab(freqIndex)} --- tune ensemble by index 0--37.
\item \texttt{readDabDigRadStatus()} --- FIC quality, CNR, lock.
\item \texttt{readDabEventStatus()} --- service-list-ready flag.
\item \texttt{fetchDabServiceList()} --- parsed services + labels.
\item \texttt{startDabService(serviceId, componentId)} --- begin audio
decode (START\_DIGITAL\_SERVICE).
\end{itemize}
\begin{table}[htbp]
\centering
\small
\begin{tabular}{@{}cl@{}}
\toprule
\textbf{Index} & \textbf{Centre frequency (kHz)} \\
\midrule
0--3 & 174928 -- 180064 (5A--5D) \\
4--7 & 181936 -- 187072 (6A--6D) \\
8--11 & 188928 -- 194064 (7A--7D) \\
12--15 & 195936 -- 201072 (8A--8D) \\
16--19 & 202928 -- 208064 (9A--9D) \\
20--23 & 209936 -- 215072 (10A--10D) \\
24--27 & 216928 -- 222064 (11A--11D) \\
28--31 & 223936 -- 229072 (12A--12D) \\
32--35 & 230784 -- 235776 (13A--13D) \\
36--37 & 237488 -- 239200 (13E--13F) \\
\bottomrule
\end{tabular}
\caption{DAB Band III plan (\texttt{kDefaultDabFrequencyKhz}).}
\label{tab:si4684-dab-plan}
\end{table}
\subsection{Typical DAB session}
\begin{enumerate}
\item \texttt{boot(Dab)} at power-up (already done in
\texttt{HardwareBootstrap}).
\item \texttt{tuneDab(index)} for the desired ensemble.
\item Poll \texttt{readDabDigRadStatus()} until FIC quality $> 0$.
\item \texttt{fetchDabServiceList()} when
\texttt{readDabEventStatus().serviceListReady}.
\item \texttt{startDabService(serviceId, componentId)} for the chosen
programme; audio appears on I\textsuperscript{2}S.
\end{enumerate}
\section{Integration at power-up}
\label{sec:si4684-integration}
\texttt{main/hardware\_bootstrap.cpp} constructs a static
\texttt{Si4684EmbeddedImages}, \texttt{Si4684Driver}, and
\texttt{Si4684Tuner}, calls \texttt{boot(Si4684Band::Dab)} before Wi-Fi,
then boots the ADAU1701. Failure is fail-closed (logged, no network start).
\texttt{main/main.cpp} wraps the tuner adapter in
\texttt{tuner::TunerService} and passes it to \texttt{NetBootstrap::start()}
so HTTP routes under \texttt{/api/tuner/*} can tune and play at runtime.
\section{Further reading}
\label{sec:si4684-reading}
\begin{itemize}
\item Skyworks AN649 --- command and property reference.
\item \texttt{Firmware/Si4684-Firmware/README.md} --- blob refresh cheatsheet.
\item PE5PVB \texttt{si4684.cpp} --- DAB service list and MOT (not yet ported).
\item uGreen \texttt{radio\_cli\_RELEASE\_NOTES.md} --- embedded firmware versions.
\end{itemize}
+1
View File
@@ -45,6 +45,7 @@
\include{ch-intro} \include{ch-intro}
\include{ch-hardware} \include{ch-hardware}
\include{ch-firmware} \include{ch-firmware}
\include{ch-si4684}
\include{ch-api} \include{ch-api}
\include{ch-classes} \include{ch-classes}
\include{ch-build} \include{ch-build}