Files
DigiRadio/Software/docs/manual/ch-adau1701.tex
T
micheleandCursor fcda4ef151 Fix manual typographic overflow in tables and API chapter.
Add wrapping table columns, breakable paths, and ragged callout boxes in digiradio-manual.sty, then reflow long endpoint headings, JSON examples, and hardware/driver tables to stay within page margins.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-08 15:02:30 +02:00

469 lines
18 KiB
TeX

% ============================================================
% DigiRadio — Manual chapter: ADAU1701 DSP
% ============================================================
\chapter{ADAU1701 Audio DSP}
\label{ch:adau1701}
The Analog Devices ADAU1701 is the central audio processor of DigiRadio: it
mixes the Si4684 radio stream with the ESP32 I\textsuperscript{2}S source,
applies parametric equalisation and master volume, and feeds the
FSC-BT1035 over I\textsuperscript{2}S. This chapter documents how the
SigmaStudio program is stored and replayed at boot, how
\texttt{adau1701::Adau1701Driver} controls the chip over I\textsuperscript{2}C,
and how application code (\texttt{audio::AudioService}, HTTP, web UI) uses the
driver without touching parameter RAM addresses directly.
\begin{drref}[Companion chapter]
The SigmaStudio \emph{design} --- hardware settings, signal chain, EQ bands,
and export procedure --- is documented in Chapter~\ref{ch:sigmastudio}. This
chapter covers the \emph{firmware side}: boot, safeload, driver API, domain
types, persistence, and HTTP integration.
\end{drref}
\section{Role in the audio chain}
\label{sec:adau1701-role}
After boot the ADAU1701 runs as the I\textsuperscript{2}S \textbf{master}
at 48\,kHz, 24-bit stereo. It receives PCM from the Si4684 on serial input~0
and from the ESP32 on serial input~1, mixes and equalises the result, and
outputs to the Bluetooth module on \texttt{SDATA\_OUT0}. The ESP32 never
processes audio samples in software; all tone shaping and mixing happen inside
the DSP program exported from SigmaStudio.
Every runtime adjustment --- input level, EQ band, master volume, stereo/bass
enhancement --- flows through typed domain objects in \texttt{components/core}
and reaches the hardware only via \texttt{Adau1701Driver} safeload writes.
Board wiring and I\textsuperscript{2}C address are in
Section~\ref{sec:hw-adau} (Chapter~\ref{ch:hardware}).
\section{Program image (SigmaStudio export)}
\label{sec:adau1701-firmware}
Unlike the Si4684 blobs (local-only, not in git), the ADAU1701 export is
\textbf{committed} under \texttt{Firmware/ADAU1701-Firmware/}. It is the
authoritative DSP program for firmware builds.
\begin{table}[htbp]
\centering
\begin{tabular}{@{}L{3.8cm}L{3.5cm}L{6.2cm}@{}}
\toprule
\textbf{File} & \textbf{Role} & \textbf{Used by firmware} \\
\midrule
\texttt{DigiRadio\_IC\_1.h} &
Program + parameter RAM init data &
\texttt{default\_download\_IC\_1()} replayed at boot \\
\texttt{DigiRadio\_IC\_1\_PARAM.h} &
Parameter cell indices &
\texttt{ADDR\_*} macros; safeload targets \\
\texttt{DigiRadio\_IC\_1\_REG.h} &
Control register map &
Safeload trigger registers \\
\texttt{DigiRadio\_NetList.xml} &
Schematic netlist &
Reference when re-exporting \\
\texttt{DigiRadio.params} &
Parameter metadata &
SigmaStudio round-trip \\
\texttt{SigmaStudioFW.h} &
Safeload / I2C glue header &
\texttt{sigma\_safeload\_*} API \\
\bottomrule
\end{tabular}
\caption{ADAU1701 SigmaStudio export files (in git).}
\label{tab:adau1701-files}
\end{table}
The driver component wraps the generated C in three layers:
\texttt{adau1701\_program.c} calls \texttt{default\_download\_IC\_1()};
\texttt{SigmaStudioFW.c} implements \texttt{SIGMA\_WRITE\_REGISTER\_BLOCK}
and safeload on ESP-IDF I\textsuperscript{2}C; \texttt{Adau1701Driver.cpp}
adds reset, boot state, and typed runtime control.
\subsection{Refreshing after a SigmaStudio change}
\label{sec:adau1701-refresh}
When the schematic changes in SigmaStudio (new block, different EQ layout,
renamed cells):
\begin{enumerate}
\item Edit the project offline (Chapter~\ref{ch:sigmastudio},
Section~\ref{sec:ss-export}).
\item \emph{Action $\rightarrow$ Export System Files} into
\texttt{Firmware/ADAU1701-Firmware/}, overwriting the generated headers.
\item Copy or reconcile any new \texttt{ADDR\_*} symbols into
\texttt{Adau1701ParamMap.hpp} if block names moved (fader/EQ addresses
must match \texttt{DigiRadio\_IC\_1\_PARAM.h}).
\item Rebuild firmware; the ESP32 will replay the new download on next boot.
\item Update Chapter~\ref{ch:sigmastudio} tables if band frequencies or the
signal chain changed.
\end{enumerate}
\begin{drnote}[No USBi download on DigiRadio]
Do \emph{not} rely on SigmaStudio \emph{Link Compile Download} on this
hardware path. Export-only workflow is sufficient: the ESP32 is the programmer
at every power-up.
\end{drnote}
\section{Boot sequence (I\textsuperscript{2}C RAM load)}
\label{sec:adau1701-boot}
DigiRadio has no ADAU1701 self-boot EEPROM. The ESP32 holds the only copy of
the DSP program and writes it into RAM after each reset.
\begin{figure}[htbp]
\centering
\begin{tikzpicture}[
drstep/.style={draw, rounded corners, minimum width=9.5cm,
minimum height=0.9cm, align=center, font=\small},
>={Latex}, node distance=3mm]
\node[drstep, fill=black!6] (rst) {Assert RESET\# (10\,ms), release};
\node[drstep, fill=black!6, below=of rst] (i2c)
{Create I\textsuperscript{2}C master @ 100\,kHz, address 0x34};
\node[drstep, fill=black!8, below=of i2c] (dl)
{Replay \texttt{DspProgram} from flash or embedded export};
\node[drstep, fill=black!6, below=of dl] (ready)
{\texttt{Adau1701Driver::isBooted()} = true};
\node[drstep, fill=black!10, below=of ready] (prof)
{\texttt{AudioService::loadAndApply()} --- safeload user profile};
\foreach \a/\b in {rst/i2c, i2c/dl, dl/ready, ready/prof} {
\draw[->] (\a) -- (\b);
}
\end{tikzpicture}
\caption{ADAU1701 boot flow on DigiRadio (host RAM load).}
\label{fig:adau1701-boot}
\end{figure}
\texttt{SIGMA\_WRITE\_REGISTER\_BLOCK} streams each write in 64-byte I\textsuperscript{2}C
chunks (payloads may exceed 255 bytes). The active script comes from the
\texttt{dsp} data partition when a valid \texttt{DRAD} blob is present; otherwise
the embedded SigmaStudio export is used (Section~\ref{sec:api-dsp-program}).
After download, \texttt{loadAndApply()} restores the saved
\texttt{core::AudioProfile} from NVS (or factory defaults) so user settings
survive power cycles without re-writing the whole program.
\section{Software architecture}
\label{sec:adau1701-stack}
Application code never includes \texttt{DigiRadio\_IC\_1\_PARAM.h}. The stack
separates concerns as follows:
\begin{table}[htbp]
\centering
\small
\begin{tabular}{@{}L{2.5cm}L{3.8cm}L{6.2cm}@{}}
\drhead Layer & Type & Responsibility \\
\midrule
HTTP / UI & \texttt{SetupWebServer}, web UI &
JSON $\rightarrow$ \texttt{AudioService} \\
Service & \texttt{audio::AudioService} &
Profile in RAM, NVS, enhancement overlay \\
Domain & \texttt{core::AudioProfile}, \texttt{IDsp} &
Host-testable types, no ESP-IDF \\
Adapter & \texttt{adau1701::Adau1701Dsp} &
Maps \texttt{DspError} $\leftrightarrow$ \texttt{Adau1701Error} \\
Driver & \texttt{adau1701::Adau1701Driver} &
I\textsuperscript{2}C, boot, safeload \\
Generated + glue & \texttt{DigiRadio\_IC\_1.h}, \texttt{SigmaStudioFW.c} &
SigmaStudio export, register writes \\
\bottomrule
\end{tabular}
\caption{ADAU1701 control stack (Slice~5).}
\label{tab:adau1701-stack}
\end{table}
\texttt{AudioService} merges \texttt{AudioProfile::enhancements} into the
effective EQ via \texttt{core::applyEnhancementsToEq()} before calling
\texttt{IDsp::applyEq()} or \texttt{applyProfile()}. Base EQ settings in NVS
stay unchanged; enhancement bands are overlays (Section~\ref{sec:ss-enhancements}).
\section{Adau1701Driver API}
\label{sec:adau1701-driver}
\texttt{Adau1701Driver} owns the I\textsuperscript{2}C bus (RAII), drives
RESET\#, and exposes intent-level methods. All return
\texttt{std::expected<T, Adau1701Error>}. Runtime methods require a prior
successful \texttt{boot()}.
\subsection{Bring-up and state}
\begin{table}[htbp]
\centering
\begin{tabular}{@{}L{4.2cm}L{9.2cm}@{}}
\toprule
\textbf{Method} & \textbf{Purpose} \\
\midrule
\texttt{boot()} & Reset chip, init I\textsuperscript{2}C, run
\texttt{default\_download\_IC\_1()} \\
\texttt{isBooted()} & \texttt{true} after successful download \\
\bottomrule
\end{tabular}
\caption{ADAU1701 bring-up.}
\end{table}
\subsection{Full profile and partial updates}
\begin{table}[htbp]
\centering
\begin{tabular}{@{}L{4.2cm}L{9.2cm}@{}}
\toprule
\textbf{Method} & \textbf{Purpose} \\
\midrule
\texttt{applyProfile(profile)} &
Safeload mixer + EQ + master in one call \\
\texttt{applyMixer(mixer)} &
Si4684/ESP32 faders + St Mixer1 L/R \\
\texttt{applyEq(eq)} &
All six PEQ bands (band~0 skipped, see below) \\
\texttt{setInputVolume(source, L, R)} &
One source path (\texttt{MixSource::Si4684} or \texttt{Esp32}) \\
\texttt{setMasterVolume(L, R)} &
Multiple~1 master output \\
\texttt{setEqBand(band, gain, center, q)} &
Design biquad in core, safeload five coefficients \\
\bottomrule
\end{tabular}
\caption{Runtime safeload entry points.}
\label{tab:adau1701-api}
\end{table}
\begin{drnote}[High-pass band fixed]
\texttt{setEqBand} and \texttt{applyEq} \emph{skip} band index~0 (SigmaStudio
band~1, 20\,Hz Butterworth high-pass). Calls with index~0 return
\texttt{Adau1701Error::InvalidParameter}. Only peaking bands 1--5 are
runtime-adjustable, matching Table~\ref{tab:ss-runtime}.
\end{drnote}
\subsection{Gain and EQ coefficient path}
Volume cells (faders, master) store linear gain as ADAU \textbf{8.23 fixpoint}.
\texttt{core::gainDbTo\-LinearFixpoint()} converts \texttt{GainDb} before
\texttt{sigma\_safeload\_param()}.
PEQ bands use \texttt{core::designPeakingEq()} at 48\,kHz sample rate to
produce five biquad coefficients (\texttt{B0..B2, A0..A1}), converted to
fixpoint and written atomically with \texttt{sigma\_safeload\_block()} (up to
five parameters per transfer --- one band per call).
Host tests in \texttt{components/core/test/biquad\_design\_test.cpp} lock the
coefficient math; \texttt{enhancements\_design\_test.cpp} locks enhancement
curves.
\section{Parameter address map}
\label{sec:adau1701-parammap}
SigmaStudio assigns small integer indices to parameter RAM cells. The export
header \texttt{DigiRadio\_IC\_1\_PARAM.h} defines \texttt{ADDR\_*} symbols;
\texttt{Adau1701ParamMap.hpp} maps domain concepts to those addresses:
\begin{table}[htbp]
\centering
\small
\begin{tabular}{@{}L{2.4cm}L{3.6cm}L{7.2cm}@{}}
\drhead Domain & \texttt{ADDR\_*} & SigmaStudio block \\
\midrule
Si4684 L/R & \texttt{SI4674}, \texttt{SI4674\_1} & Si4674 fader \\
ESP32 L/R & \texttt{ESP32}, \texttt{ESP32\_1} & ESP32 fader \\
Mix L/R & {\footnotesize\texttt{STMIXER1\_ST0/1\_VOLUME}} & St Mixer1 \\
EQ band $n$ & {\footnotesize\texttt{PARAMEQ1\_ST$n$\_B0} + 0..4} & Param EQ1 coeffs \\
Master L/R & \texttt{MULTIPLE1}, \texttt{MULTIPLE1\_1} & Multiple 1 \\
\bottomrule
\end{tabular}
\caption{Runtime parameter map (current export). Re-verify after
SigmaStudio re-export.}
\label{tab:adau1701-addr}
\end{table}
If a schematic rename changes cell names, update \texttt{Adau1701ParamMap.hpp}
and this table together with \texttt{DigiRadio\_IC\_1\_PARAM.h}.
\section{Safeload mechanism}
\label{sec:adau1701-safeload}
Direct parameter RAM writes during playback cause audible clicks. The ADAU1701
provides \emph{safeload} registers: the host stages up to five
(address, value) pairs, then triggers a single atomic update between
samples.
DigiRadio implements this in \texttt{SigmaStudioFW.c}:
\begin{enumerate}
\item Write each fixpoint value to \texttt{SAFELOAD\_DATA\_0..4}.
\item Write each target parameter index to \texttt{SAFELOAD\_ADDR\_0..4}.
\item Pulse \texttt{CORE\_CONTROL} with \texttt{IST\_TRIGGER}.
\end{enumerate}
\texttt{sigma\_safeload\_param()} handles one cell (volume fader).
\texttt{sigma\_safeload\_block()} handles up to five (full PEQ band).
\texttt{Adau1701Driver} never writes parameter data registers directly for
runtime control.
\begin{drcaution}[Safeload only for live changes]
Boot-time programming uses \texttt{SIGMA\_WRITE\_REGISTER\_BLOCK} to fill
program and parameter RAM from the export. After audio is running, mixer and
EQ updates must use safeload only.
\end{drcaution}
\section{Domain model and persistence}
\label{sec:adau1701-domain}
\texttt{core::AudioProfile} is the unit of persistence and HTTP serialisation:
\begin{itemize}
\item \texttt{MixerState} --- six \texttt{GainDb} fields (Si4684 L/R,
ESP32 L/R, St Mixer1 L/R).
\item \texttt{EqProfile} --- six bands; index~0 fixed high-pass, 1--5
peaking (100\,Hz--8\,kHz defaults per Chapter~\ref{sec:ss-eq}).
\item \texttt{masterLeft}, \texttt{masterRight} --- Multiple~1.
\item \texttt{AudioEnhancements} --- \texttt{stereo\_level} and
\texttt{bass\_level} (0--100), mapped to PEQ overlays at apply time.
\end{itemize}
JSON parse/serialise lives in \texttt{core::AudioProfileJson} (pure core, host
tested). NVS key \texttt{audio\_profile\_json} in namespace \texttt{digiradio}
via \texttt{secure\_store::NvsAudioProfileStore}.
\texttt{AudioProfile::factoryDefault()} is flat EQ, 0\,dB gains, enhancements
off. \texttt{POST /api/audio/reset} restores and persists this snapshot.
\section{audio::AudioService}
\label{sec:adau1701-service}
\texttt{AudioService} is the only entry point HTTP and future UI code should
use for audio path control:
\begin{table}[htbp]
\centering
\small
\begin{tabular}{@{}L{4.2cm}L{9.2cm}@{}}
\toprule
\textbf{Method} & \textbf{Effect} \\
\midrule
\texttt{loadAndApply()} & Load NVS (or default), safeload full profile \\
\texttt{applyProfile(p, persist)} & Replace profile, safeload, optional NVS \\
\texttt{setInputVolume(source, L, R, persist)} & One input path \\
\texttt{setMasterVolume(L, R, persist)} & Master fader \\
\texttt{setEqBand(band, ...)} & Update base EQ + effective overlay \\
\texttt{setStereoEnhance(level, persist)} & PEQ overlay bands 3--5 \\
\texttt{setBassEnhance(level, persist)} & PEQ overlay bands 1--2 \\
\texttt{currentProfile()} & Snapshot for \texttt{GET /api/audio/profile} \\
\bottomrule
\end{tabular}
\caption{\texttt{AudioService} intent-level API.}
\label{tab:adau1701-service}
\end{table}
On boot, \texttt{HardwareBootstrap::boot()} runs Si4684 boot, then
\texttt{Adau1701Driver::boot()}, then \texttt{loadAndApply()}. ADAU boot
failure is fail-closed (no network start); profile apply failure is logged as
warning but boot continues.
\section{HTTP and web UI}
\label{sec:adau1701-http}
REST routes (Chapter~\ref{ch:api}, Section~\ref{sec:api-audio-profile-get})
delegate to \texttt{AudioService}:
\begin{itemize}
\item \texttt{GET/PUT /api/audio/profile} --- full JSON profile.
\item \texttt{POST /api/audio/reset} --- factory flat profile.
\item \texttt{POST /api/audio/stereo-enhance} --- body
\texttt{\{"level":0..100\}}.
\item \texttt{POST /api/audio/bass-enhance} --- same schema.
\end{itemize}
The gzipped setup page (\texttt{components/net/www/index.html}) is a tabbed
SPA: a \emph{Now playing} view (RDS/DLS metadata), tuner and preset panels,
master/path levels, six PEQ band gain sliders, enhancement overlays, Bluetooth
controls, and Wi-Fi provisioning. Saving sends \texttt{PUT /api/audio/profile};
enhancement sliders call the dedicated POST routes for immediate safeload +
persist. Regenerate \texttt{index.html.gz} with \texttt{tools/gzip-www.sh}
after editing the HTML source.
\section{Typical usage patterns}
\label{sec:adau1701-patterns}
\subsection{C++ firmware (direct service access)}
\begin{enumerate}
\item After \texttt{HardwareBootstrap::boot()}, obtain
\texttt{HardwareBootstrap::audioService()}.
\item Adjust levels:
\texttt{setMasterVolume(GainDb::tryFromDb(-3), ..., true)}.
\item Tune one EQ band (index 2 = 400\,Hz peaking in default map):
\texttt{setEqBand(band, gain, center, q, true)}.
\item Read back: \texttt{currentProfile()} for logging or display.
\end{enumerate}
\subsection{HTTP client}
\begin{verbatim}
# Read current snapshot:
curl http://digiradio.local/api/audio/profile
# Boost bass enhance to 60%:
curl -X POST http://digiradio.local/api/audio/bass-enhance \
-H "Content-Type: application/json" -d '{"level":60}'
# Restore factory flat:
curl -X POST http://digiradio.local/api/audio/reset
\end{verbatim}
\subsection{After SigmaStudio EQ layout change}
Re-export, rebuild firmware, flash ESP32. Existing NVS profiles remain valid
only if band indices and centre frequencies still match; otherwise reset via
\texttt{POST /api/audio/reset} or migrate JSON manually.
\section{Error handling}
\label{sec:adau1701-errors}
\texttt{Adau1701Error} values and typical causes:
\begin{table}[htbp]
\centering
\small
\begin{tabular}{@{}L{4.2cm}L{9.2cm}@{}}
\drhead Error & Typical cause \\
\midrule
\texttt{ResetFailed} & GPIO reset line configuration \\
\texttt{I2cInitFailed} & Bus or device add on ESP32 \\
\texttt{DownloadFailed} & I\textsuperscript{2}C failure during boot download \\
\texttt{NotBooted} & Runtime call before \texttt{boot()} \\
\texttt{SafeloadFailed} & I\textsuperscript{2}C or safeload trigger failure \\
\texttt{InvalidParameter} & EQ band~0 or out-of-range domain input \\
\bottomrule
\end{tabular}
\caption{ADAU1701 driver errors.}
\label{tab:adau1701-errors}
\end{table}
\texttt{Adau1701Dsp} maps these to \texttt{core::DspError} for
\texttt{AudioService}. HTTP handlers map store/DSP failures to
\texttt{\{"status":"error","reason":"store\_failed"\}} with HTTP~500.
\section{Integration at power-up}
\label{sec:adau1701-integration}
Static instances in \texttt{main/hardware\_bootstrap.cpp}:
\begin{verbatim}
Adau1701Driver -> Adau1701Dsp -> AudioService(NvsAudioProfileStore)
\end{verbatim}
Boot order: Si4684 \texttt{boot(Dab)} $\rightarrow$ ADAU1701 \texttt{boot()}
$\rightarrow$ \texttt{loadAndApply()} $\rightarrow$ network stack.
\texttt{main/main.cpp} passes \texttt{audioService()} to
\texttt{NetBootstrap::start()} for HTTP registration.
\section{Further reading}
\label{sec:adau1701-reading}
\begin{itemize}
\item Chapter~\ref{ch:sigmastudio} --- schematic, EQ bands, export workflow.
\item Chapter~\ref{ch:api} --- JSON schemas for \texttt{/api/audio/*}.
\item \texttt{Firmware/ADAU1701-Firmware/README.md} --- export file cheatsheet.
\item Analog Devices ADAU1701 datasheet --- safeload and I\textsuperscript{2}C protocol.
\item \texttt{components/core/test/} --- biquad and profile JSON host tests.
\end{itemize}