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>
279 lines
11 KiB
TeX
279 lines
11 KiB
TeX
% ============================================================
|
|
% 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 I\textsuperscript{2}S slave 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
|
|
I\textsuperscript{2}S bus from the ADAU1701. The mandatory
|
|
\texttt{AT+AUXCFG=3} and \texttt{AT+I2SCFG=67} commands select I\textsuperscript{2}S
|
|
slave input at 48\,kHz --- omitting them silently breaks the entire wireless
|
|
output (Section~\ref{sec:bt1035-i2s}).
|
|
|
|
\section{Control interface}
|
|
\label{sec:bt1035-uart}
|
|
|
|
\subsection{UART parameters}
|
|
|
|
\begin{table}[htbp]
|
|
\centering
|
|
\small
|
|
\begin{tabular}{@{}L{4.2cm}L{9.2cm}@{}}
|
|
\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 \drpath{components/core/test/bt1035_at_test.cpp} lock the
|
|
init sequence (\texttt{AT+AUXCFG=3}, \texttt{AT+I2SCFG=67}) and the parser.
|
|
|
|
\section{Mandatory I\textsuperscript{2}S slave mode}
|
|
\label{sec:bt1035-i2s}
|
|
|
|
\begin{drcaution}[I\textsuperscript{2}S init is not optional]
|
|
The board routes ADAU1701 \texttt{SDATA\_OUT0} (MP6) to the module PCM input
|
|
with shared BCLK/LRCLK (see Chapter~\ref{ch:hardware}). The init sequence
|
|
must use:
|
|
\begin{itemize}
|
|
\item \texttt{AT+AUXCFG=3} --- I\textsuperscript{2}S mode (§5.1.25)
|
|
\item \texttt{AT+I2SCFG=67} --- I\textsuperscript{2}S slave, 48\,kHz, 32-bit (§5.1.4)
|
|
\end{itemize}
|
|
\texttt{AT+AUXCFG=1} (Line-In) does \textbf{not} match the schematic.
|
|
AGENTS.md treats skipping I\textsuperscript{2}S init 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
|
|
I\textsuperscript{2}S path is ready before Wi-Fi starts.
|
|
|
|
\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] (sys) {SYS\_CTL high, RESET\# pulse};
|
|
\node[drstep, fill=black!6, below=of sys] (uart)
|
|
{Install UART2 @ 115200, RTS/CTS};
|
|
\node[drstep, fill=black!8, below=of uart] (at)
|
|
{Send \texttt{AT} --- expect OK};
|
|
\node[drstep, fill=black!10, below=of at] (aux)
|
|
{Send \texttt{AT+AUXCFG=3} --- expect OK (I\textsuperscript{2}S)};
|
|
\node[drstep, fill=black!10, below=of aux] (i2s)
|
|
{Send \texttt{AT+I2SCFG=67} --- expect OK (slave 48\,kHz)};
|
|
\node[drstep, fill=black!6, below=of i2s] (done)
|
|
{\texttt{Bt1035Driver::isBooted()} = true};
|
|
\foreach \a/\b in {sys/uart, uart/at, at/aux, aux/i2s, i2s/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 I\textsuperscript{2}S bring-up required for the wired audio path.
|
|
|
|
\section{Software architecture}
|
|
\label{sec:bt1035-stack}
|
|
|
|
\begin{table}[htbp]
|
|
\centering
|
|
\small
|
|
\begin{tabular}{@{}L{2.5cm}L{3.8cm}L{6.2cm}@{}}
|
|
\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--7). Pairing and A2DP status are
|
|
exposed on \texttt{/api/bluetooth/*} via \texttt{BluetoothService}.}
|
|
\label{tab:bt1035-stack}
|
|
\end{table}
|
|
|
|
\section{Supported AT command subset}
|
|
\label{sec:bt1035-at}
|
|
|
|
The firmware enumerates every command it sends. Wire formats follow
|
|
\drpath{Hardware/DATASHEET/FSC-BT1035_programming_user_guide_1.1.1.pdf}
|
|
(§5 commands, §6 events). Extending the subset requires updating
|
|
\texttt{core::Bt1035AtCommand}, the manual, and a host test.
|
|
|
|
\begin{table}[htbp]
|
|
\centering
|
|
\small
|
|
\begin{tabular}{@{}L{2.4cm}L{3.4cm}L{5.5cm}@{}}
|
|
\drhead Enum & Line sent & Programming guide \\
|
|
\midrule
|
|
\texttt{Ping} & \texttt{AT} & link check \\
|
|
\texttt{I2sMode} & \texttt{AT+AUXCFG=3} & §5.1.25 Param=3 I2S \\
|
|
\texttt{I2sSlave48k32} & \texttt{AT+I2SCFG=67} & §5.1.4 slave 48\,kHz 32-bit \\
|
|
\texttt{PairDiscoverable} & \texttt{AT+PAIR=1} & §5.1.20 enter discoverable \\
|
|
\texttt{PairHidden} & \texttt{AT+PAIR=0} & §5.1.20 leave discoverable \\
|
|
\texttt{A2dpStat} & \texttt{AT+A2DPSTAT} & §5.3.1; states 1--5 \\
|
|
\texttt{A2dpDisconnect} & \texttt{AT+A2DPDISC} & §5.3.3 \\
|
|
\texttt{QueryName} & \texttt{AT+NAME} & §5.1.16 read \texttt{+NAME=} \\
|
|
\texttt{QueryAutoConn} & \texttt{AT+AUTOCONN} & §5.1.11 read \texttt{+AUTOCONN=} \\
|
|
\texttt{QueryPairedList} & \texttt{AT+PLIST} & §5.1.22; ends with \texttt{+PLIST=E} \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\caption{Enumerated AT commands (\texttt{core::Bt1035AtCommand}). Boot
|
|
uses Ping + I2sMode + I2sSlave48k32; pairing commands are runtime.}
|
|
\label{tab:bt1035-at}
|
|
\end{table}
|
|
|
|
Boot also calls \texttt{AT+NAME=<identity>,0} and \texttt{AT+AUTOCONN=3}
|
|
from \texttt{hardware\_bootstrap.cpp} (§5.1.16 suffix disabled, §5.1.11
|
|
reconnect count).
|
|
|
|
\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
|
|
\small
|
|
\begin{tabular}{@{}L{4.2cm}L{9.2cm}@{}}
|
|
\toprule
|
|
\textbf{Method} & \textbf{Purpose} \\
|
|
\midrule
|
|
\texttt{boot()} & Reset, UART init, run \texttt{bootInitSequence()} \\
|
|
\texttt{isBooted()} & \texttt{true} after I\textsuperscript{2}S init succeeded \\
|
|
\texttt{sendCommand(cmd)} & Send one typed command, expect OK \\
|
|
\texttt{enterPairingMode()} & \texttt{AT+PAIR=1} \\
|
|
\texttt{leavePairingMode()} & \texttt{AT+PAIR=0} \\
|
|
\texttt{queryA2dpState()} & \texttt{AT+A2DPSTAT}, parse \texttt{+A2DPSTAT=} \\
|
|
\texttt{disconnectA2dp()} & \texttt{AT+A2DPDISC} \\
|
|
\texttt{queryDeviceName()} & \texttt{AT+NAME}, parse \texttt{+NAME=} \\
|
|
\texttt{queryAutoReconnect()} & \texttt{AT+AUTOCONN}, parse \texttt{+AUTOCONN=} \\
|
|
\texttt{setAutoReconnect(times)} & \texttt{AT+AUTOCONN=n} (0--15) \\
|
|
\texttt{queryPairedList()} & \texttt{AT+PLIST}, parse \texttt{+PLIST=} lines \\
|
|
\bottomrule
|
|
\end{tabular}
|
|
\caption{Public driver API.}
|
|
\label{tab:bt1035-api}
|
|
\end{table}
|
|
|
|
\subsection{Error codes}
|
|
|
|
\begin{table}[htbp]
|
|
\centering
|
|
\small
|
|
\begin{tabular}{@{}L{4.2cm}L{9.2cm}@{}}
|
|
\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()} --- I\textsuperscript{2}S slave 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 I\textsuperscript{2}S config
|
|
after a module reset:
|
|
|
|
\begin{verbatim}
|
|
bt1035::Bt1035Driver& bt = ...;
|
|
if (auto r = bt.sendCommand(core::Bt1035AtCommand::I2sMode); !r) {
|
|
// handle Bt1035Error
|
|
}
|
|
if (auto r = bt.sendCommand(core::Bt1035AtCommand::I2sSlave48k32); !r) {
|
|
// handle Bt1035Error
|
|
}
|
|
\end{verbatim}
|
|
|
|
\section{Further reading}
|
|
\label{sec:bt1035-reading}
|
|
|
|
\begin{itemize}
|
|
\item \texttt{Hardware/DATASHEET/FSC-BT1035\_programming\_user\_guide\_1.1.1.pdf}
|
|
--- authoritative AT command and event reference (§5--§6).
|
|
\item \texttt{Hardware/DATASHEET/FSC-BT1035\_Datasheet\_EN.pdf} --- module
|
|
electrical and pinout specification.
|
|
\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
|
|
and parser host tests.
|
|
\end{itemize}
|