Files
DigiRadio/Software/docs/manual/ch-si4684.tex
T
micheleandCursor d49b2011dd Add BT1035 driver with manual chapter and expand Si4684 tuning docs.
Implement Bt1035Driver AT init (AT+AUXCFG=1), core AT parser with host tests,
wire boot into HardwareBootstrap, and document DAB/FM tuning workflows in ch-si4684.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 17:07:37 +02:00

345 lines
13 KiB
TeX

% ============================================================
% 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}
\label{sec:si4684-dab-session}
\begin{enumerate}
\item \texttt{boot(Dab)} at power-up (already done in
\texttt{HardwareBootstrap}).
\item \texttt{tuneDab(index)} for the desired ensemble (Band III index
0--37, Table~\ref{tab:si4684-dab-plan}).
\item Poll \texttt{readDabDigRadStatus()} until FIC quality $> 0$.
\item \texttt{fetchDabServiceList()} when
\texttt{readDabEventStatus().serviceListReady}.
\item \texttt{startDabService(serviceId, componentId)} for the chosen
programme; PCM appears on I\textsuperscript{2}S to the ADAU1701.
\end{enumerate}
\begin{drnote}[DAB, not DVB]
DigiRadio receives \textbf{DAB/DAB+} (Digital \emph{Audio} Broadcasting),
not DVB-T/T2 video. Tuning is by ensemble frequency index and digital
service/component IDs --- there is no transport-stream PAT/PMT scan like
DVB-T.
\end{drnote}
\subsection{Typical FM session}
\label{sec:si4684-fm-session}
FM requires the FM application image loaded at boot
(\texttt{boot(Si4684Band::Fm)}). DigiRadio defaults to DAB at power-up;
switching to FM reloads patch + \texttt{fm\_firmware.bin} (full HOST\_LOAD).
\begin{enumerate}
\item \texttt{boot(Fm)} --- resets the chip and loads the FM image.
\item \texttt{tuneFm(frequencyKhz)} --- e.g.\ 101500 for 101.5\,MHz;
waits for seek/tune complete (STC).
\item \texttt{readFmRsq()} --- RSSI, SNR, stereo flag, validity.
\item Optional: \texttt{seekFm(up, wrap)} --- scan to next station.
\item Optional: \texttt{readFmRds()} --- last RDS group (PI, PS, RT).
\end{enumerate}
FM audio is emitted on the same I\textsuperscript{2}S pins as DAB once the
FM image is running.
\subsection{Choosing DAB or FM}
\label{sec:si4684-band-choice}
Only one application image runs at a time. \texttt{Si4684Driver::boot(band)}
selects the blob:
\begin{table}[htbp]
\centering
\begin{tabular}{@{}lll@{}}
\drhead Band & Image loaded & Primary API \\
\midrule
\texttt{Si4684Band::Dab} & \texttt{dab\_firmware.bin} &
\texttt{tuneDab}, service list, \texttt{startDabService} \\
\texttt{Si4684Band::Fm} & \texttt{fm\_firmware.bin} &
\texttt{tuneFm}, \texttt{seekFm}, RSQ, RDS \\
\bottomrule
\end{tabular}
\caption{Band-specific firmware and driver entry points.}
\label{tab:si4684-band-api}
\end{table}
Calling an FM method while the DAB image is loaded (or vice versa) returns
\texttt{Si4684Error::WrongBand}. Switching band performs reset and a complete
HOST\_LOAD of patch + the other image ($\sim$1\,s).
\subsection{HTTP API mapping (web UI)}
\label{sec:si4684-http}
The setup UI and REST clients use \texttt{tuner::TunerService}, which wraps
\texttt{Si4684Tuner} (Chapter~\ref{ch:api}). Summary:
\begin{table}[htbp]
\centering
\small
\begin{tabular}{@{}llp{5.5cm}@{}}
\drhead Endpoint & Band & Action \\
\midrule
\texttt{POST /api/tuner/tune} &
DAB & \texttt{\{"band":"dab","freq\_index":0..37\}} \\
\texttt{POST /api/tuner/tune} &
FM & \texttt{\{"band":"fm","frequency\_khz":64000..108000\}} \\
\texttt{GET /api/tuner/services} & DAB only & Programme list for ensemble \\
\texttt{POST /api/tuner/play} & DAB only & Start \texttt{service\_id}/\texttt{component\_id} \\
\texttt{POST /api/tuner/seek} & FM only & Seek up, return new kHz \\
\texttt{GET /api/tuner/status} & both & Locked state, RSQ or DIGRAD \\
\bottomrule
\end{tabular}
\caption{Tuner HTTP routes vs band (full schemas in Chapter~\ref{ch:api}).}
\label{tab:si4684-http}
\end{table}
\begin{drnote}[FM tune via HTTP today]
\texttt{POST /api/tuner/tune} with \texttt{band:"fm"} calls
\texttt{TunerService::tuneFm}, which may trigger a band reload if the device
booted in DAB. Plan station presets and automatic band selection in a later
slice.
\end{drnote}
\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}