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
View File
@@ -22,6 +22,7 @@ idf_component_register(
"src/EnhancementsDesign.cpp"
"src/AudioProfile.cpp"
"src/AudioProfileJson.cpp"
"src/Bt1035At.cpp"
INCLUDE_DIRS "include"
)
@@ -0,0 +1,99 @@
/**
* @file Bt1035At.hpp
* @brief Typed AT command builder and response parser for FSC-BT1035.
*
* 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 "core/ParseError.hpp"
#include <array>
#include <cstddef>
#include <expected>
#include <string>
#include <string_view>
namespace core {
/**
* @brief Bt1035AtCommand — supported AT commands (enumerated subset).
*
* @dname Bt1035AtCommand
* @return n/a (type)
* @pubstate Each variant maps to one line via buildBt1035AtLine().
*
* @author Michele Bigi
* @date 2026-07-06
*/
enum class Bt1035AtCommand {
Ping, ///< AT — link check.
AuxLineIn, ///< AT+AUXCFG=1 — wired Line-In from ADAU1701 (mandatory).
};
/**
* @brief Bt1035AtResponseKind — parsed module reply class.
*
* @dname Bt1035AtResponseKind
* @return n/a (type)
* @pubstate Unknown payloads map to Unexpected, never ignored.
*
* @author Michele Bigi
* @date 2026-07-06
*/
enum class Bt1035AtResponseKind {
Ok,
Error,
Unexpected,
};
/** Number of commands in bootInitSequence(). */
inline constexpr std::size_t kBt1035BootInitCommandCount = 2U;
/**
* @brief buildBt1035AtLine — serialise a command with CRLF terminator.
*
* @dname buildBt1035AtLine
* @param command Typed AT command.
* @return Full line including \r\n suffix.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::string buildBt1035AtLine(Bt1035AtCommand command);
/**
* @brief bootInitSequence — mandatory bring-up commands in order.
*
* @dname bootInitSequence
* @return Ping then AuxLineIn (AT+AUXCFG=1).
* @pubstate none
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] std::array<Bt1035AtCommand, kBt1035BootInitCommandCount>
bootInitSequence() noexcept;
/**
* @brief parseBt1035AtResponse — classify a single response line.
*
* @dname parseBt1035AtResponse
* @param line Untrusted UART payload (may include whitespace).
* @return Ok, Error, or Unexpected.
* @pubstate none
*
* @author Michele Bigi
* @date 2026-07-06
*/
[[nodiscard]] Bt1035AtResponseKind parseBt1035AtResponse(
std::string_view line) noexcept;
} // namespace core
+68
View File
@@ -0,0 +1,68 @@
/**
* @file Bt1035At.cpp
* @brief Bt1035At 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 "core/Bt1035At.hpp"
namespace core {
namespace {
[[nodiscard]] std::string_view trimAscii(std::string_view text) noexcept
{
while (!text.empty()
&& (text.front() == ' ' || text.front() == '\r'
|| text.front() == '\n' || text.front() == '\t')) {
text.remove_prefix(1U);
}
while (!text.empty()
&& (text.back() == ' ' || text.back() == '\r' || text.back() == '\n'
|| text.back() == '\t')) {
text.remove_suffix(1U);
}
return text;
}
} // namespace
std::string buildBt1035AtLine(Bt1035AtCommand command)
{
switch (command) {
case Bt1035AtCommand::Ping:
return "AT\r\n";
case Bt1035AtCommand::AuxLineIn:
return "AT+AUXCFG=1\r\n";
}
return "AT\r\n";
}
std::array<Bt1035AtCommand, kBt1035BootInitCommandCount> bootInitSequence() noexcept
{
return std::array<Bt1035AtCommand, kBt1035BootInitCommandCount>{
Bt1035AtCommand::Ping,
Bt1035AtCommand::AuxLineIn,
};
}
Bt1035AtResponseKind parseBt1035AtResponse(std::string_view line) noexcept
{
const std::string_view trimmed = trimAscii(line);
if (trimmed == "OK") {
return Bt1035AtResponseKind::Ok;
}
if (trimmed == "ERROR" || trimmed.starts_with("ERROR")) {
return Bt1035AtResponseKind::Error;
}
return Bt1035AtResponseKind::Unexpected;
}
} // namespace core
@@ -34,6 +34,7 @@ add_library(digiradio_core STATIC
"${CORE_SRC_DIR}/EnhancementsDesign.cpp"
"${CORE_SRC_DIR}/AudioProfile.cpp"
"${CORE_SRC_DIR}/AudioProfileJson.cpp"
"${CORE_SRC_DIR}/Bt1035At.cpp"
)
target_include_directories(digiradio_core PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/../include"
@@ -70,3 +71,7 @@ add_test(NAME audio_profile_json_test COMMAND audio_profile_json_test)
add_executable(enhancements_design_test enhancements_design_test.cpp)
target_link_libraries(enhancements_design_test PRIVATE digiradio_core)
add_test(NAME enhancements_design_test COMMAND enhancements_design_test)
add_executable(bt1035_at_test bt1035_at_test.cpp)
target_link_libraries(bt1035_at_test PRIVATE digiradio_core)
add_test(NAME bt1035_at_test COMMAND bt1035_at_test)
@@ -0,0 +1,71 @@
/**
* @file bt1035_at_test.cpp
* @brief Host tests for FSC-BT1035 AT command builder and parser.
*
* 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 "core/Bt1035At.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
namespace {
[[nodiscard]] int runInitSequenceTest()
{
const auto sequence = core::bootInitSequence();
if (sequence.size() != core::kBt1035BootInitCommandCount) {
std::cerr << "init sequence size mismatch\n";
return EXIT_FAILURE;
}
if (sequence[1U] != core::Bt1035AtCommand::AuxLineIn) {
std::cerr << "AUXCFG=1 must be in init sequence\n";
return EXIT_FAILURE;
}
const std::string aux = core::buildBt1035AtLine(core::Bt1035AtCommand::AuxLineIn);
if (aux != "AT+AUXCFG=1\r\n") {
std::cerr << "AUXCFG command line mismatch\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
[[nodiscard]] int runParseTest()
{
if (core::parseBt1035AtResponse("OK\r\n") != core::Bt1035AtResponseKind::Ok) {
std::cerr << "OK parse failed\n";
return EXIT_FAILURE;
}
if (core::parseBt1035AtResponse("ERROR\r\n")
!= core::Bt1035AtResponseKind::Error) {
std::cerr << "ERROR parse failed\n";
return EXIT_FAILURE;
}
if (core::parseBt1035AtResponse("garbage")
!= core::Bt1035AtResponseKind::Unexpected) {
std::cerr << "unexpected parse failed\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
} // namespace
int main()
{
if (runInitSequenceTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
if (runParseTest() != EXIT_SUCCESS) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}