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>
This commit is contained in:
@@ -98,7 +98,9 @@ persistence fails.
|
||||
|
||||
Returns a tuner snapshot serialised by
|
||||
\texttt{core::serializeTunerStatusJson()} from \texttt{core::TunerStatus}.
|
||||
The handler calls \texttt{tuner::TunerService::refreshStatus()}.
|
||||
The handler calls \texttt{tuner::TunerService::refreshStatus()}. DAB and FM
|
||||
tuning workflows are in Chapter~\ref{ch:si4684}, Sections~\ref{sec:si4684-dab-session}
|
||||
and~\ref{sec:si4684-fm-session}.
|
||||
|
||||
\begin{drnote}[Response schema (DAB example)]
|
||||
\begin{drcode}[JSON]
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
% ============================================================
|
||||
% DigiRadio — Manual chapter: FSC-BT1035 Bluetooth
|
||||
% ============================================================
|
||||
|
||||
\chapter{FSC-BT1035 Bluetooth Transmitter}
|
||||
\label{ch:bt1035}
|
||||
|
||||
The Feasycom FSC-BT1035 (Qualcomm QCC3056) is the wireless output stage of
|
||||
DigiRadio: it receives PCM from the ADAU1701 over I\textsuperscript{2}S and
|
||||
streams Bluetooth audio with aptX, aptX~HD, and aptX~Adaptive. This chapter
|
||||
documents how the ESP32-S3 controls the module over UART (AT commands with
|
||||
RTS/CTS), why Line-In mode is mandatory, and how \texttt{bt1035::Bt1035Driver}
|
||||
implements the bring-up sequence.
|
||||
|
||||
\begin{drref}[Hardware context]
|
||||
Board wiring (UART pins, I\textsuperscript{2}S to the module, flow control)
|
||||
is in Chapter~\ref{ch:hardware}, Section~\ref{sec:hw-bt1035}. The ADAU1701
|
||||
master clock and limiter settings that feed the module are in
|
||||
Chapter~\ref{ch:adau1701} and Chapter~\ref{ch:sigmastudio}.
|
||||
\end{drref}
|
||||
|
||||
\section{Role in the audio chain}
|
||||
\label{sec:bt1035-role}
|
||||
|
||||
The BT1035 is an I\textsuperscript{2}S \textbf{slave} at 48\,kHz: LRCLK and
|
||||
BCLK come from the ADAU1701; PCM arrives on \texttt{SDATA\_OUT0}
|
||||
(ADAU MP6 $\rightarrow$ module pin~5). The ESP32 does not process audio
|
||||
samples for Bluetooth; it only configures the module so the wired path is
|
||||
accepted and encoded for transmission.
|
||||
|
||||
Without firmware init the module may stay in a default mode that ignores the
|
||||
Line-In from the DSP. The mandatory \texttt{AT+AUXCFG=1} command selects
|
||||
auxiliary/Line-In input --- omitting it silently breaks the entire wireless
|
||||
output (Section~\ref{sec:bt1035-linein}).
|
||||
|
||||
\section{Control interface}
|
||||
\label{sec:bt1035-uart}
|
||||
|
||||
\subsection{UART parameters}
|
||||
|
||||
\begin{table}[htbp]
|
||||
\centering
|
||||
\begin{tabular}{@{}ll@{}}
|
||||
\drhead Setting & Value \\
|
||||
\midrule
|
||||
Port & UART2 (not the console UART) \\
|
||||
Baud rate & 115200 \\
|
||||
Data format & 8N1 \\
|
||||
Flow control & Hardware RTS/CTS (required) \\
|
||||
Reset & GPIO active-low pulse at boot \\
|
||||
SYS\_CTL & Held active to enable the module \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{FSC-BT1035 UART and control lines (\texttt{board\_pins.hpp}).}
|
||||
\label{tab:bt1035-uart}
|
||||
\end{table}
|
||||
|
||||
\subsection{Response handling}
|
||||
|
||||
Every command expects a module reply containing \texttt{OK} or
|
||||
\texttt{ERROR}. The driver:
|
||||
|
||||
\begin{itemize}
|
||||
\item builds lines in the pure core via \texttt{core::buildBt1035AtLine()};
|
||||
\item classifies replies with \texttt{core::parseBt1035AtResponse()};
|
||||
\item treats timeouts and unexpected payloads as errors (never ignored).
|
||||
\end{itemize}
|
||||
|
||||
Host tests in \texttt{components/core/test/bt1035\_at\_test.cpp} lock the
|
||||
init sequence (including \texttt{AT+AUXCFG=1}) and the parser.
|
||||
|
||||
\section{Mandatory Line-In mode}
|
||||
\label{sec:bt1035-linein}
|
||||
|
||||
\begin{drcaution}[AT+AUXCFG=1 is not optional]
|
||||
The documented init sequence must include \texttt{AT+AUXCFG=1} after a
|
||||
successful \texttt{AT} ping. This tells the QCC3056 firmware to take audio
|
||||
from the wired I\textsuperscript{2}S/Line-In port (the ADAU1701 output)
|
||||
rather than an internal source. AGENTS.md and the hardware manual both treat
|
||||
skipping this step as a production bug.
|
||||
\end{drcaution}
|
||||
|
||||
\section{Boot sequence}
|
||||
\label{sec:bt1035-boot}
|
||||
|
||||
At power-up \texttt{HardwareBootstrap::boot()} runs the Si4684 and ADAU1701
|
||||
first, applies the saved audio profile, then initialises the BT1035 so the
|
||||
Line-In path is ready before Wi-Fi starts.
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\begin{tikzpicture}[
|
||||
step/.style={draw, rounded corners, minimum width=9.5cm,
|
||||
minimum height=0.9cm, align=center, font=\small},
|
||||
>={Latex}, node distance=3mm]
|
||||
\node[step, fill=black!6] (sys) {SYS\_CTL high, RESET\# pulse};
|
||||
\node[step, fill=black!6, below=of sys] (uart)
|
||||
{Install UART2 @ 115200, RTS/CTS};
|
||||
\node[step, fill=black!8, below=of uart] (at)
|
||||
{Send \texttt{AT} --- expect OK};
|
||||
\node[step, fill=black!10, below=of at] (aux)
|
||||
{Send \texttt{AT+AUXCFG=1} --- expect OK (Line-In)};
|
||||
\node[step, fill=black!6, below=of aux] (done)
|
||||
{\texttt{Bt1035Driver::isBooted()} = true};
|
||||
\foreach \a/\b in {sys/uart, uart/at, at/aux, aux/done} {
|
||||
\draw[->] (\a) -- (\b);
|
||||
}
|
||||
\end{tikzpicture}
|
||||
\caption{FSC-BT1035 AT init on DigiRadio.}
|
||||
\label{fig:bt1035-boot}
|
||||
\end{figure}
|
||||
|
||||
Pairing, codec selection, and volume over Bluetooth are handled by the
|
||||
module's own firmware and NVS; DigiRadio firmware currently implements
|
||||
only the Line-In bring-up required for the wired audio path.
|
||||
|
||||
\section{Software architecture}
|
||||
\label{sec:bt1035-stack}
|
||||
|
||||
\begin{table}[htbp]
|
||||
\centering
|
||||
\small
|
||||
\begin{tabular}{@{}llp{5.8cm}@{}}
|
||||
\drhead Layer & Type & Responsibility \\
|
||||
\midrule
|
||||
Bootstrap & \texttt{HardwareBootstrap} &
|
||||
Constructs driver, calls \texttt{boot()} after ADAU1701 \\
|
||||
Driver & \texttt{bt1035::Bt1035Driver} &
|
||||
UART, reset, AT init sequence \\
|
||||
Pure core & \texttt{core::Bt1035AtCommand}, parser &
|
||||
Host-testable command strings and OK/ERROR classification \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{BT1035 control stack (Slice~6). Future HTTP/UI for pairing will
|
||||
sit above the driver without changing the init contract.}
|
||||
\label{tab:bt1035-stack}
|
||||
\end{table}
|
||||
|
||||
\section{Supported AT command subset}
|
||||
\label{sec:bt1035-at}
|
||||
|
||||
The firmware enumerates every command it sends. Extending the subset requires
|
||||
updating \texttt{core::Bt1035AtCommand}, the manual, and a host test.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\centering
|
||||
\begin{tabular}{@{}lll@{}}
|
||||
\drhead Enum & Line sent & Purpose \\
|
||||
\midrule
|
||||
\texttt{Ping} & \texttt{AT} & Verify UART link \\
|
||||
\texttt{AuxLineIn} & \texttt{AT+AUXCFG=1} & Enable Line-In from ADAU \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{AT commands used at boot (\texttt{core::bootInitSequence()}).}
|
||||
\label{tab:bt1035-at}
|
||||
\end{table}
|
||||
|
||||
\section{Bt1035Driver API}
|
||||
\label{sec:bt1035-driver}
|
||||
|
||||
\texttt{Bt1035Driver} owns UART and GPIO reset. All methods return
|
||||
\texttt{std::expected<T, Bt1035Error>}.
|
||||
|
||||
\begin{table}[htbp]
|
||||
\centering
|
||||
\begin{tabular}{@{}ll@{}}
|
||||
\toprule
|
||||
\textbf{Method} & \textbf{Purpose} \\
|
||||
\midrule
|
||||
\texttt{boot()} & Reset, UART init, run \texttt{bootInitSequence()} \\
|
||||
\texttt{isBooted()} & \texttt{true} after Line-In init succeeded \\
|
||||
\texttt{sendCommand(cmd)} & Send one typed command, expect OK \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Public driver API.}
|
||||
\label{tab:bt1035-api}
|
||||
\end{table}
|
||||
|
||||
\subsection{Error codes}
|
||||
|
||||
\begin{table}[htbp]
|
||||
\centering
|
||||
\small
|
||||
\begin{tabular}{@{}ll@{}}
|
||||
\drhead \texttt{Bt1035Error} & Typical cause \\
|
||||
\midrule
|
||||
\texttt{ResetFailed} & GPIO configuration failure \\
|
||||
\texttt{UartInitFailed} & \texttt{uart\_driver\_install} / pins \\
|
||||
\texttt{NotBooted} & \texttt{sendCommand} before \texttt{boot()} \\
|
||||
\texttt{AtTimeout} & No OK/ERROR within 2\,s \\
|
||||
\texttt{AtError} & Module returned ERROR \\
|
||||
\texttt{UnexpectedResponse} & Unrecognised payload (future use) \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Driver error enumeration.}
|
||||
\label{tab:bt1035-errors}
|
||||
\end{table}
|
||||
|
||||
\section{Integration at power-up}
|
||||
\label{sec:bt1035-integration}
|
||||
|
||||
\texttt{main/hardware\_bootstrap.cpp} constructs a static
|
||||
\texttt{Bt1035Driver} and calls \texttt{boot()} after
|
||||
\texttt{AudioService::loadAndApply()}. Failure returns
|
||||
\texttt{HardwareBootError::Bt1035BootFailed} and \texttt{app\_main} halts
|
||||
before network bring-up (fail-closed, same as Si4684/ADAU1701).
|
||||
|
||||
Boot order:
|
||||
\begin{enumerate}
|
||||
\item Si4684 \texttt{boot(Dab)} --- tuner image in RAM.
|
||||
\item ADAU1701 \texttt{boot()} --- SigmaStudio program in RAM.
|
||||
\item \texttt{AudioService::loadAndApply()} --- user mixer/EQ profile.
|
||||
\item BT1035 \texttt{boot()} --- Line-In enabled for wireless output.
|
||||
\end{enumerate}
|
||||
|
||||
\section{Typical usage (firmware developer)}
|
||||
\label{sec:bt1035-usage}
|
||||
|
||||
After a successful \texttt{HardwareBootstrap::boot()}, the module is ready;
|
||||
no further calls are required for basic listening. To re-send Line-In config
|
||||
after a module reset:
|
||||
|
||||
\begin{verbatim}
|
||||
bt1035::Bt1035Driver& bt = ...;
|
||||
if (auto r = bt.sendCommand(core::Bt1035AtCommand::AuxLineIn); !r) {
|
||||
// handle Bt1035Error
|
||||
}
|
||||
\end{verbatim}
|
||||
|
||||
\section{Further reading}
|
||||
\label{sec:bt1035-reading}
|
||||
|
||||
\begin{itemize}
|
||||
\item Feasycom FSC-BT1035 AT command manual (vendor) --- full command set
|
||||
for pairing, name, and codec options not yet wrapped by firmware.
|
||||
\item Chapter~\ref{ch:hardware} --- pin map and I\textsuperscript{2}S routing.
|
||||
\item Chapter~\ref{ch:adau1701} --- DSP output that feeds the module.
|
||||
\item \texttt{components/core/test/bt1035\_at\_test.cpp} --- init sequence test.
|
||||
\end{itemize}
|
||||
@@ -130,7 +130,13 @@ 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.
|
||||
DTOs. Full DAB/FM tuning guide: Chapter~\ref{ch:si4684}.
|
||||
|
||||
\section{Bt1035Driver}\label{cls:Bt1035Driver}
|
||||
UART driver for the FSC-BT1035 (Chapter~\ref{ch:bt1035}). \texttt{boot()}
|
||||
pulses RESET\#, opens UART2 with RTS/CTS, and runs
|
||||
\texttt{core::bootInitSequence()} (Ping + \texttt{AT+AUXCFG=1}). Returns
|
||||
\texttt{Bt1035Error} on timeout, ERROR response, or UART failure.
|
||||
|
||||
\section{Adau1701Driver}\label{cls:Adau1701Driver}
|
||||
RAII I\textsuperscript{2}C driver for the ADAU1701 SigmaDSP
|
||||
|
||||
@@ -147,10 +147,11 @@ 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.
|
||||
The module is controlled by AT commands over UART with hardware flow control.
|
||||
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.
|
||||
Full driver API, boot flow, and error codes are in Chapter~\ref{ch:bt1035}.
|
||||
|
||||
\section{Configuration, storage, and user interface}
|
||||
\label{sec:fw-config}
|
||||
@@ -203,6 +204,9 @@ bring-up:
|
||||
\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.
|
||||
\item \textbf{FSC-BT1035} (UART): \texttt{bt1035::Bt1035Driver::boot()}
|
||||
enables Line-In (\texttt{AT+AUXCFG=1}) after the DSP path is configured
|
||||
(Chapter~\ref{ch:bt1035}).
|
||||
\end{enumerate}
|
||||
|
||||
If either driver returns an error, the firmware logs the failure and stops
|
||||
|
||||
@@ -131,7 +131,8 @@ in the module's own non-volatile memory.
|
||||
\begin{drcaution}[Line-In mode]
|
||||
The initialisation sequence must enable Line-In mode (\texttt{AT+AUXCFG=1})
|
||||
so the module accepts the wired audio coming from the DSP. Omitting it
|
||||
silently breaks the audio path.
|
||||
silently breaks the audio path. Driver boot flow and AT subset are documented
|
||||
in Chapter~\ref{ch:bt1035}.
|
||||
\end{drcaution}
|
||||
|
||||
\section{The host: ESP32-S3}
|
||||
|
||||
@@ -30,7 +30,8 @@ extending DigiRadio: the hardware at a block level
|
||||
(Chapter~\ref{ch:hardware}), the firmware architecture
|
||||
(Chapter~\ref{ch:firmware}), dedicated companion-chip guides for the
|
||||
Si4684 tuner (Chapter~\ref{ch:si4684}) and ADAU1701 DSP
|
||||
(Chapter~\ref{ch:adau1701}), the HTTP JSON API exposed by the web UI
|
||||
(Chapter~\ref{ch:adau1701}), FSC-BT1035 Bluetooth
|
||||
(Chapter~\ref{ch:bt1035}), the HTTP JSON API exposed by the web UI
|
||||
(Chapter~\ref{ch:api}), the per-class design reference that grows with
|
||||
the code (Chapter~\ref{ch:classes}), and how to build and flash
|
||||
(Chapter~\ref{ch:build}). Exact C++ signatures are generated by Doxygen
|
||||
|
||||
@@ -224,18 +224,104 @@ after boot.
|
||||
\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.
|
||||
\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; audio appears on I\textsuperscript{2}S.
|
||||
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}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
\include{ch-firmware}
|
||||
\include{ch-si4684}
|
||||
\include{ch-adau1701}
|
||||
\include{ch-bt1035}
|
||||
\include{ch-api}
|
||||
\include{ch-classes}
|
||||
\include{ch-build}
|
||||
|
||||
Reference in New Issue
Block a user