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:
2026-07-06 17:07:37 +02:00
co-authored by Cursor
parent d6fa41e944
commit d49b2011dd
22 changed files with 979 additions and 48 deletions
@@ -1,6 +1,7 @@
idf_component_register(
SRCS "src/component_stub.cpp"
SRCS "src/Bt1035Driver.cpp"
INCLUDE_DIRS "include"
REQUIRES core driver esp_driver_gpio esp_driver_uart
)
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
@@ -0,0 +1,134 @@
/**
* @file Bt1035Driver.hpp
* @brief FSC-BT1035 Bluetooth transmitter — UART AT control.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-07-06
*/
#pragma once
#include "bt1035/Bt1035Error.hpp"
#include "core/Bt1035At.hpp"
#include <expected>
namespace bt1035 {
/**
* @brief Bt1035Pins — board GPIO/UART identifiers for the module.
*
* @dname Bt1035Pins
* @return n/a (type)
* @pubstate Immutable wiring snapshot from board_pins.hpp.
*
* @author Michele Bigi
* @date 2026-07-06
*/
struct Bt1035Pins {
int uartTx; ///< ESP32 TX -> module RX.
int uartRx; ///< ESP32 RX <- module TX.
int rtsGpio; ///< RTS (flow control).
int ctsGpio; ///< CTS (flow control).
int resetGpio; ///< Module RESET (active level per schematic).
int sysCtlGpio; ///< SYS_CTL (optional module enable).
};
/**
* @brief Bt1035Driver — owns UART + reset, runs mandatory AT init.
*
* @dname Bt1035Driver
* @return n/a (type)
* @pubstate Owns UART port after boot(). booted_ true after init sequence
* including AT+AUXCFG=1 (Line-In from ADAU1701).
*
* @author Michele Bigi
* @date 2026-07-06
*/
class Bt1035Driver {
public:
/**
* @brief Bt1035Driver — construct with board pin map.
*
* @dname Bt1035Driver
* @param pins UART, flow control, reset, SYS_CTL wiring.
* @pubstate stores pins_; not booted until boot().
*
* @author Michele Bigi
* @date 2026-07-06
*/
explicit Bt1035Driver(Bt1035Pins pins);
/**
* @brief ~Bt1035Driver — release UART resources.
*
* @dname ~Bt1035Driver
* @pubstate deletes UART driver when installed.
*
* @author Michele Bigi
* @date 2026-07-06
*/
~Bt1035Driver();
Bt1035Driver(const Bt1035Driver&) = delete;
Bt1035Driver& operator=(const Bt1035Driver&) = delete;
/**
* @brief boot — reset module and run mandatory AT init sequence.
*
* @dname boot
* @return Ok on success, or Bt1035Error.
* @pubstate sets booted_ after Ping + AT+AUXCFG=1 both return OK.
*
* Sequence: hardware reset, UART @ 115200 with RTS/CTS, then
* core::bootInitSequence() (Chapter~\ref{ch:bt1035}).
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<void, Bt1035Error> boot();
/**
* @brief isBooted — query whether Line-In init succeeded.
*
* @dname isBooted
* @return true after successful boot().
* @pubstate reads booted_.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] bool isBooted() const noexcept;
/**
* @brief sendCommand — transmit one typed AT command and expect OK.
*
* @dname sendCommand
* @param command Enumerated AT command.
* @return Ok on OK response, or Bt1035Error.
* @pubstate writes UART; reads until OK/ERROR/timeout.
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::expected<void, Bt1035Error> sendCommand(
core::Bt1035AtCommand command);
private:
[[nodiscard]] std::expected<void, Bt1035Error> ensureBooted() const;
[[nodiscard]] std::expected<void, Bt1035Error> runInitSequence();
[[nodiscard]] std::expected<void, Bt1035Error> transmitAndExpectOk(
std::string_view commandLine);
Bt1035Pins pins_;
bool booted_;
bool uartInstalled_;
int uartPort_;
};
} // namespace bt1035
@@ -0,0 +1,36 @@
/**
* @file Bt1035Error.hpp
* @brief Typed errors for FSC-BT1035 driver operations.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-07-06
*/
#pragma once
namespace bt1035 {
/**
* @brief Bt1035Error — failure causes for BT1035 bring-up and AT I/O.
*
* @dname Bt1035Error
* @return n/a (type)
* @pubstate n/a
*
* @author Michele Bigi
* @date 2026-07-06
*/
enum class Bt1035Error {
ResetFailed,
UartInitFailed,
NotBooted,
AtTimeout,
AtError,
UnexpectedResponse,
};
} // namespace bt1035
@@ -0,0 +1,193 @@
/**
* @file Bt1035Driver.cpp
* @brief Bt1035Driver implementation.
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* @author Michele Bigi
* @date 2026-07-06
*/
#include "bt1035/Bt1035Driver.hpp"
#include "driver/gpio.h"
#include "driver/uart.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <array>
#include <string>
namespace bt1035 {
namespace {
constexpr char kTag[] = "Bt1035";
constexpr int kUartPort = 2;
constexpr int kBaudRate = 115200;
constexpr int kUartRxBuffer = 512;
constexpr int kUartTxBuffer = 256;
constexpr int kResponseTimeoutMs = 2000;
constexpr int kPostResetMs = 300;
constexpr int kPostUartMs = 100;
} // namespace
Bt1035Driver::Bt1035Driver(Bt1035Pins pins)
: pins_(pins)
, booted_(false)
, uartInstalled_(false)
, uartPort_(kUartPort)
{
}
Bt1035Driver::~Bt1035Driver()
{
if (uartInstalled_) {
uart_driver_delete(static_cast<uart_port_t>(uartPort_));
uartInstalled_ = false;
}
}
bool Bt1035Driver::isBooted() const noexcept
{
return booted_;
}
std::expected<void, Bt1035Error> Bt1035Driver::ensureBooted() const
{
if (!booted_) {
return std::unexpected(Bt1035Error::NotBooted);
}
return {};
}
std::expected<void, Bt1035Error> Bt1035Driver::transmitAndExpectOk(
std::string_view commandLine)
{
const int written = uart_write_bytes(static_cast<uart_port_t>(uartPort_),
commandLine.data(),
commandLine.size());
if (written < 0
|| static_cast<std::size_t>(written) != commandLine.size()) {
return std::unexpected(Bt1035Error::UartInitFailed);
}
std::array<char, 128> buffer{};
std::string accumulated;
const TickType_t deadline =
xTaskGetTickCount() + pdMS_TO_TICKS(kResponseTimeoutMs);
while (xTaskGetTickCount() < deadline) {
const int received = uart_read_bytes(static_cast<uart_port_t>(uartPort_),
buffer.data(), buffer.size(),
pdMS_TO_TICKS(50));
if (received > 0) {
accumulated.append(buffer.data(), static_cast<std::size_t>(received));
const core::Bt1035AtResponseKind kind =
core::parseBt1035AtResponse(accumulated);
if (kind == core::Bt1035AtResponseKind::Ok) {
return {};
}
if (kind == core::Bt1035AtResponseKind::Error) {
return std::unexpected(Bt1035Error::AtError);
}
}
}
return std::unexpected(Bt1035Error::AtTimeout);
}
std::expected<void, Bt1035Error> Bt1035Driver::sendCommand(
core::Bt1035AtCommand command)
{
if (auto ready = ensureBooted(); !ready) {
return ready;
}
return transmitAndExpectOk(core::buildBt1035AtLine(command));
}
std::expected<void, Bt1035Error> Bt1035Driver::runInitSequence()
{
for (const core::Bt1035AtCommand command : core::bootInitSequence()) {
if (auto result = transmitAndExpectOk(core::buildBt1035AtLine(command));
!result) {
return result;
}
}
return {};
}
std::expected<void, Bt1035Error> Bt1035Driver::boot()
{
if (booted_) {
return {};
}
gpio_config_t resetCfg = {};
resetCfg.pin_bit_mask = 1ULL << pins_.resetGpio;
resetCfg.mode = GPIO_MODE_OUTPUT;
if (gpio_config(&resetCfg) != ESP_OK) {
return std::unexpected(Bt1035Error::ResetFailed);
}
gpio_config_t sysCfg = {};
sysCfg.pin_bit_mask = 1ULL << pins_.sysCtlGpio;
sysCfg.mode = GPIO_MODE_OUTPUT;
if (gpio_config(&sysCfg) != ESP_OK) {
return std::unexpected(Bt1035Error::ResetFailed);
}
gpio_set_level(static_cast<gpio_num_t>(pins_.sysCtlGpio), 1);
gpio_set_level(static_cast<gpio_num_t>(pins_.resetGpio), 0);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(static_cast<gpio_num_t>(pins_.resetGpio), 1);
vTaskDelay(pdMS_TO_TICKS(kPostResetMs));
if (!uartInstalled_) {
const uart_config_t uartCfg = {
.baud_rate = kBaudRate,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_CTS_RTS,
.rx_flow_ctrl_thresh = 122,
.source_clk = UART_SCLK_DEFAULT,
};
if (uart_driver_install(static_cast<uart_port_t>(uartPort_), kUartRxBuffer,
kUartTxBuffer, 0, nullptr, 0)
!= ESP_OK) {
return std::unexpected(Bt1035Error::UartInitFailed);
}
if (uart_param_config(static_cast<uart_port_t>(uartPort_), &uartCfg)
!= ESP_OK) {
return std::unexpected(Bt1035Error::UartInitFailed);
}
if (uart_set_pin(static_cast<uart_port_t>(uartPort_), pins_.uartTx,
pins_.uartRx, pins_.rtsGpio, pins_.ctsGpio)
!= ESP_OK) {
return std::unexpected(Bt1035Error::UartInitFailed);
}
uartInstalled_ = true;
}
uart_flush_input(static_cast<uart_port_t>(uartPort_));
vTaskDelay(pdMS_TO_TICKS(kPostUartMs));
if (auto init = runInitSequence(); !init) {
ESP_LOGE(kTag, "AT init failed");
return init;
}
booted_ = true;
ESP_LOGI(kTag, "Line-In mode enabled (AT+AUXCFG=1)");
return {};
}
} // namespace bt1035
@@ -1,34 +0,0 @@
/**
* @file component_stub.cpp
* @brief FSC-BT1035 driver component placeholder (Slice 6).
*
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
*
* Copyright 2026 Michele Bigi
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* @author Michele Bigi
* @date 2026-07-06
*/
namespace bt1035::detail {
/**
* @brief bt1035ComponentLinked — ensures the driver component links.
*
* @dname bt1035ComponentLinked
* @return n/a (type)
* @pubstate n/a
*
* @author Michele Bigi
* @date 2026-07-06
*/
void bt1035ComponentLinked() noexcept {}
} // namespace bt1035::detail