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>
345 lines
13 KiB
TeX
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}
|