Document the ADAU1701 schematic chain (sigma-chain.jpg), cross-link from hardware/firmware chapters, skip safeload on fixed high-pass band 0, and match EqProfile default peaking frequencies to SigmaStudio. Co-authored-by: Cursor <cursoragent@cursor.com>
241 lines
10 KiB
TeX
241 lines
10 KiB
TeX
% ============================================================
|
|
% DigiRadio — Manual section: Firmware Architecture
|
|
% Drop this into the manual as a \section (or \chapter).
|
|
%
|
|
% Required packages (add to the preamble if not present):
|
|
% \usepackage{tikz}
|
|
% \usetikzlibrary{positioning, arrows.meta, fit, backgrounds}
|
|
% \usepackage{booktabs} % for the interface table
|
|
%
|
|
% The environments used here are standard. Replace itemize / framed
|
|
% blocks with your own box taxonomy where you prefer.
|
|
% ============================================================
|
|
|
|
\chapter{Firmware Architecture}
|
|
\label{sec:firmware-architecture}
|
|
|
|
The DigiRadio firmware runs on the ESP32-S3 and orchestrates three
|
|
companion chips into a single high-fidelity audio path: the Si4684
|
|
DAB+/FM tuner, the ADAU1701 SigmaDSP, and the FSC-BT1035 Bluetooth
|
|
transmitter. This section describes how the firmware is organised, how
|
|
audio flows through the system, and how each chip is controlled. The
|
|
day-to-day coding conventions (style, documentation, review rules) are
|
|
intentionally kept out of this manual and live in the repository's
|
|
\texttt{CONTRIBUTING.md} and \texttt{AGENTS.md}.
|
|
|
|
\section{Design principles}
|
|
\label{sec:fw-principles}
|
|
|
|
Two ideas shape the whole codebase:
|
|
|
|
\begin{itemize}
|
|
\item \textbf{Strong typing.} Domain quantities (frequency, gain,
|
|
station identifier, band) are dedicated types rather than raw
|
|
integers or floats. Invalid states are made unrepresentable: input
|
|
from the network, UART, or flash is validated once at the boundary
|
|
into a domain type, and trusted thereafter.
|
|
\item \textbf{Functional core, imperative shell.} All hardware-free
|
|
logic --- coefficient math, boot-image framing, configuration
|
|
parsing, station-list operations --- lives in a pure \emph{core}
|
|
that compiles and is unit-tested on the host, with no dependency on
|
|
ESP-IDF. Every access to the hardware (I\textsuperscript{2}C, SPI,
|
|
UART, flash) lives in a thin \emph{shell} around that core. This
|
|
keeps the logic testable without a board attached.
|
|
\end{itemize}
|
|
|
|
\section{Layered structure}
|
|
\label{sec:fw-layers}
|
|
|
|
The firmware is organised in three layers, with dependencies pointing
|
|
inward only (Figure~\ref{fig:fw-layers}). The outer shell may depend on
|
|
the services and the core; the pure core depends on nothing outside
|
|
itself.
|
|
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\begin{tikzpicture}[
|
|
layer/.style={draw, rounded corners, minimum width=10cm,
|
|
minimum height=1.5cm, align=center, font=\small},
|
|
node distance=4mm]
|
|
\node[layer, fill=black!5] (shell) {%
|
|
\textbf{Imperative shell}\\[2pt]
|
|
chip drivers \textbullet\ web server \textbullet\ NVS \textbullet\
|
|
FreeRTOS tasks \textbullet\ ISRs};
|
|
\node[layer, fill=black!8, below=of shell] (services) {%
|
|
\textbf{Application services}\\[2pt]
|
|
TunerService \textbullet\ AudioService \textbullet\
|
|
ConfigService \textbullet\ NetworkService};
|
|
\node[layer, fill=black!12, below=of services] (core) {%
|
|
\textbf{Domain core (pure, host-tested)}\\[2pt]
|
|
Station \textbullet\ Frequency \textbullet\ EqProfile \textbullet\
|
|
MixerState \textbullet\ Credential \textbullet\ validation};
|
|
\draw[-{Latex}] (shell) -- (services);
|
|
\draw[-{Latex}] (services) -- (core);
|
|
\end{tikzpicture}
|
|
\caption{Firmware layering. Dependencies point inward; the pure core
|
|
has no hardware dependencies.}
|
|
\label{fig:fw-layers}
|
|
\end{figure}
|
|
|
|
Application services take driver \emph{interfaces}
|
|
(\texttt{ITuner}, \texttt{IDsp}, \texttt{IBtModule},
|
|
\texttt{ISecureStore}) injected at construction. Because the services
|
|
depend on abstractions rather than concrete hardware, the same logic can
|
|
be exercised against fakes in host tests and against real silicon on the
|
|
device.
|
|
|
|
\section{Audio signal path}
|
|
\label{sec:fw-audio}
|
|
|
|
The audio chain is built around sound quality. The Si4684 produces the
|
|
DAB+/FM audio stream; the ADAU1701 applies equalisation and mixes it
|
|
with the ESP32 audio source; the FSC-BT1035 streams the result over
|
|
Bluetooth with aptX Adaptive (Figure~\ref{fig:fw-audio}).
|
|
|
|
\begin{figure}[htbp]
|
|
\centering
|
|
\begin{tikzpicture}[
|
|
block/.style={draw, rounded corners, minimum height=1cm,
|
|
minimum width=2.4cm, align=center, font=\small},
|
|
>={Latex}, node distance=8mm]
|
|
\node[block] (si) {Si4684\\\scriptsize DAB+/FM};
|
|
\node[block, right=16mm of si] (dsp) {ADAU1701\\\scriptsize EQ + mixer};
|
|
\node[block, right=16mm of dsp] (bt) {FSC-BT1035\\\scriptsize aptX Adaptive};
|
|
\node[block, below=12mm of dsp] (esp) {ESP32-S3\\\scriptsize audio src};
|
|
\draw[->] (si) -- (dsp) node[midway, above, font=\scriptsize]{audio};
|
|
\draw[->] (dsp) -- (bt) node[midway, above, font=\scriptsize]{out};
|
|
\draw[->] (esp) -- (dsp) node[midway, right, font=\scriptsize]{mix in};
|
|
\end{tikzpicture}
|
|
\caption{Audio signal path. The ADAU1701 input mixer selects/blends
|
|
the Si4684 and ESP32 sources; equalisation is applied before the
|
|
Bluetooth output stage.}
|
|
\label{fig:fw-audio}
|
|
\end{figure}
|
|
|
|
Equalisation and input mixing are exposed to the rest of the firmware as
|
|
typed operations --- setting an EQ band from a gain, centre frequency,
|
|
and Q, or setting the mix level of a given source --- never as raw DSP
|
|
cell addresses. The biquad coefficient math runs in the pure core and is
|
|
verified against reference values in host tests.
|
|
|
|
\section{Chip control and boot}
|
|
\label{sec:fw-chips}
|
|
|
|
\paragraph{Si4684 (tuner).}
|
|
At power-up the driver follows the documented boot sequence: power on,
|
|
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
|
|
chunks from flash rather than buffered whole in RAM. See
|
|
Chapter~\ref{ch:si4684} for blob sources, uGreen extraction, and the full
|
|
\texttt{Si4684Driver} API (tuning, RSQ, DAB service list).
|
|
|
|
\paragraph{ADAU1701 (DSP).}
|
|
The board carries no self-boot EEPROM. Instead, the ESP32 writes the
|
|
SigmaStudio-exported program into the DSP's RAM at \emph{every} boot.
|
|
Runtime changes to equalisation and mixing use the ADAU1701 safeload
|
|
mechanism, so parameter updates are click-free while audio is playing.
|
|
Which blocks are runtime-controllable (source faders, EQ bands 2--6,
|
|
master volume) versus fixed at export (high-pass, limiters) is listed in
|
|
Chapter~\ref{ch:sigmastudio}, Section~\ref{sec:ss-runtime}.
|
|
|
|
\begin{drcaution}[Safeload]
|
|
Writing DSP parameter cells directly while audio is running produces
|
|
audible clicks. All runtime EQ and mixer changes must go through the
|
|
safeload registers.
|
|
\end{drcaution}
|
|
|
|
\paragraph{FSC-BT1035 (Bluetooth).}
|
|
The module is controlled by AT commands over UART. The initialisation
|
|
sequence includes enabling Line-In mode (\texttt{AT+AUXCFG=1}), which is
|
|
required for the wired audio path from the DSP; command responses are
|
|
parsed explicitly, with timeouts treated as errors.
|
|
|
|
\section{Configuration, storage, and user interface}
|
|
\label{sec:fw-config}
|
|
|
|
Network provisioning is implemented as an explicit state machine
|
|
(\texttt{net::NetState}): on first boot (or when STA join fails) the
|
|
device opens a setup SoftAP (\texttt{DigiRadio-setup}) and serves a
|
|
minimal gzipped web UI from flash. After the user submits credentials via
|
|
\texttt{POST /api/wifi}, values are validated in the pure core, persisted
|
|
through \texttt{core::ISecureStore}, and the device reboots into STA mode
|
|
on the next boot. The HTTP endpoints and JSON schemas are documented in
|
|
Chapter~\ref{ch:api}.
|
|
|
|
\paragraph{Implemented (Slices~1--2).}
|
|
\begin{itemize}
|
|
\item \texttt{GET /api/health} --- health DTO
|
|
(\texttt{core::HealthStatus}, serialised in the pure core).
|
|
\item \texttt{POST /api/wifi} --- Wi-Fi provisioning
|
|
(\texttt{core::WifiCredentials} via \texttt{parseWifiProvisionJson}).
|
|
\item \texttt{secure\_store::NvsSecureStore} --- NVS persistence for
|
|
SSID and PSK (\texttt{core::Secret}); station list and user
|
|
credentials arrive in later slices on the same
|
|
\texttt{ISecureStore} interface.
|
|
\end{itemize}
|
|
|
|
Sensitive data --- Wi-Fi credentials today; user credentials and the
|
|
station/frequency list in later slices --- uses \texttt{core::Secret} so
|
|
values cannot be logged or implicitly converted to a string; buffers are
|
|
cleared on destruction. Production builds should enable NVS encryption
|
|
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.
|
|
\item \textbf{Audio profile}: \texttt{audio::AudioService::loadAndApply()}
|
|
restores the saved \texttt{core::AudioProfile} from NVS (or factory
|
|
defaults) via ADAU1701 safeload before network bring-up.
|
|
\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}
|
|
\label{sec:fw-errors}
|
|
|
|
Every operation that can fail returns a typed result
|
|
(\texttt{std::expected<T, Error>}); nothing fails silently and every
|
|
timeout is an explicit error value. Errors propagate to the layer that
|
|
can act on them --- a driver reports, a service decides (retry, degrade,
|
|
or surface to the UI), and the top level logs. C++ exceptions are
|
|
disabled.
|
|
|
|
\section{Toolchain}
|
|
\label{sec:fw-toolchain}
|
|
|
|
\begin{table}[htbp]
|
|
\centering
|
|
\begin{tabular}{@{}ll@{}}
|
|
\toprule
|
|
\textbf{Item} & \textbf{Choice} \\
|
|
\midrule
|
|
Framework & ESP-IDF v5.5.x (native) \\
|
|
Language & C++23 (\texttt{-std=gnu++23}) \\
|
|
Error model & \texttt{std::expected}; exceptions off \\
|
|
Hardware licence & CERN-OHL-S v2 \\
|
|
Firmware licence & Apache-2.0 \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\caption{Firmware toolchain and licensing summary.}
|
|
\label{tab:fw-toolchain}
|
|
\end{table}
|
|
|
|
The firmware source, build instructions, and development conventions are
|
|
maintained in the \texttt{Software/} directory of the project
|
|
repository.
|