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:
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user