From 34f247019a6d2c0dfcb15dd51f40d57bc01fc2a8 Mon Sep 17 00:00:00 2001 From: Michele Bigi Date: Wed, 5 Aug 2026 08:57:43 +0200 Subject: [PATCH] Si4684 boot fix: flash encryption off, 16MB flash, NVS enc off, main task stack 8KB, CTS timeout 10s (fixes BootCmd error 6), DMA-safe HOST_LOAD buffer --- .gitignore | 7 + Hardware/.DS_Store | Bin 12292 -> 14340 bytes .../adau1701/src/FlashDspProgramSource.cpp | 3 +- .../bt1035/include/bt1035/Bt1035Driver.hpp | 2 + .../drivers/bt1035/src/Bt1035Driver.cpp | 3 +- .../drivers/si4684/src/Si4684Driver.cpp | 60 ++++-- .../drivers/si4684/src/Si4684Tuner.cpp | 6 +- .../net/include/net/NetBootstrap.hpp | 20 +- .../net/include/net/SetupWebServer.hpp | 5 +- .../components/net/src/SetupWebServer.cpp | 30 +-- Software/i2c_scanner.ino | 51 +++++ Software/main/CMakeLists.txt | 5 + Software/main/hardware_bootstrap.cpp | 4 +- Software/main/idf_component.yml | 17 ++ Software/main/main.cpp | 10 + Software/main/test_firmware.cpp | 194 ++++++++++++++++++ Software/main/test_firmware.hpp | 16 ++ Software/partitions.csv | 8 +- Software/sdkconfig.defaults | 8 +- 19 files changed, 396 insertions(+), 53 deletions(-) create mode 100644 Software/i2c_scanner.ino create mode 100644 Software/main/idf_component.yml create mode 100644 Software/main/test_firmware.cpp create mode 100644 Software/main/test_firmware.hpp diff --git a/.gitignore b/.gitignore index 8ca3efd..0bc1c85 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,10 @@ Software/docs/manual/*.out Software/docs/manual/*.pdf Software/docs/manual/*.synctex.gz Software/docs/manual/*.toc + +# Debug logs e file temporanei (DigiRadio Si4684 debug) +Software/boot*.log +Software/sdkconfig 2 +Software/sdkconfig 3 +Software/sdkconfig 4 +.DS_Store diff --git a/Hardware/.DS_Store b/Hardware/.DS_Store index f94c383ad2010fb7f2c081a2ef99f82d8d6be557..5d54729fa3b91c4580f55334fc5bc8dc8f68c8a1 100644 GIT binary patch literal 14340 zcmeHNYit}>6+UMi$20M!8M}6qxY_R3n+7Yfla1pPCuy7YBTY!6T6$J#eJS2Xg3MfcOC_n{jQPnE&1HvOhpr{g+S`hpo@B{b}ex#@g=gyt6 zXUAiwDiK6x?rP@T$2oWJJ@7C`?mM z7(xw%8VEHIY9Q1=sDbyU0ru^sAXai3zC#U!8VEJ8qyaWR#OYx)<bdggo4#>p;oqtAWhl2LM zlU&?p2TXAqzC#U!8d#};2Pvxp6v%)H^F;HI>C%i`Rx`3xLSVvrqI+-I!4~&%XXngm zhx?`Mw5FO?GWj|RWgE&jR*0flDV|MF8gpqYW6hZDnc@gb^%;hib>o@r8C97ZlWRH* z-O8xCQnUrsyh1Fa1FD`k=Gu)JJ#X5wX+Y``lkd%Da#IsU*4z$5Gm7onj50D&RP*WSOitA&1})`D zE3KYa#^h=r>uDwb#Bx^O{50{@EN9r&9WN@1Zr9f{K5i=3peH^wCT}rwMMKki4O6vL zLmwJ7N!eYNQs~mh4f@AfQgqKWD>Bb0MU&_XnZcn+{ z%^5mx`4+ocO7<2NR%PPoKe#dzsgrJ`fk$LW@CYq4YuuPaF>xt($&UBwhOS77R4*@) z=v%Odyg}V?2BzRVJPTiiZ^J8a8Qz4y!wsxJ8S8Nmw&0_96i;9m4r2}{QN<#!KyZgT z%G2%}O^W%3y2Pgx>;3dfj)KwI=~y`X$lU*nTR&?H;>y1ZuIWjS2W!hBRpW0@iPsCD z93}{rS$c{-Ts}v#UR-)O*9Rs&9SZTde(~rT<`OKH?Ul&FJ*7Sz-uOAkVF^bG1_L-s z@W;?s@((`jZH8%3AqP$Lb=2w0*hFcK;yF-Cu_&;`s27WCNUjfTuF=5|tLqo5YYck{ zcHtn~hONO}isi*J=bO5wgcsZIMj`;~Yt-+<{w7)v=U|e)y9GkLWzPeq5A|%d;YEF* z%=*B4VAt|yPRr6#p40$=4B=F;ey4)K0WX;ZHeLJYYKGy3e@*pIcRTP8tg~P&1OIls zrI3%WYM4H-Grs@Ru#Ng{pRJe9ZO~5n-nnhX`MYr7v!`8I?{HGy$oM<<4#}4B4xhiQ zbhxOL-6P0?0R>-cUZfIlYaFiiF}FIM@y@1BRycpI-}%!S;*uE?nnso(-~-jszw+&l zBjmT%?@xOU@%9-9ch<8l85NO)lQvg44lX$k?|_>yQ&6O3KjCfNgxgnf z6P~k;0oHUA1`M|stJu`AJ=kV6bGC!K3(L|j8s0$1A#Qt)qXUCO!;#S?#jMoXK+e!+ z3c5KaSLHHVZbr*kN}HxJ_bcW3-Mk*Bvae`x*ffe3xsIu_F{KZC%1P0oSL^L zhsiasTk5!~6eXz&8~bz`udCI}HNGPnbsM3MhTv_2t;@()Mz z)W~il&uYxuCDCa8`$Tbs<#!Yndh_f^bWbA*Wco}}kkicMeo2zez9~9C;G&U(4~vqV z@3+0$PS)Wgly!1omOI0p%-W-(G)UJB3)C5t=S4d@MNz)|1pDJt8Pn>SR`jJNb(|2z zVRJH5RC?y<^um=mC5raIQa%$omRBvKXlvtx_0i}MtEw%ZSMt>9x^3I5qv>eBDF2pZ zWL2M4>AZy8^ZeLET=JnOQoH%scwFZU6DwTn1dS-{f>ub;oyu`qQ9T8pf#=}|@G86p zSKxJc3$DT6;0C-6|AL!Xh7qjB9k>&la33adKOV*-_y|6RC$SIv@eF2h3N=!ZzIrewKg6j55)Qj z8p@N)faCymVJTNI2#Z%4_k)o{OfDzz@Z_#YLrmUCz~RX~v8I?T5MFsQ8EcNnWrRk_ zmOU72ipVJP#G%NMh`fPt$&-&o9*D>w@kD#9GbS^XiFexGe*v9_qgL^dkHz9L<(F#vc%?R zQVq;NcQH(Ml#@Ww{?K%B0$4jxV)9x!9yW-;|!YDSGN5MiC zXbK20F&HzrAUh=LrBqqe(0x40U); [[nodiscard]] const esp_partition_t* dspPartition() { diff --git a/Software/components/drivers/bt1035/include/bt1035/Bt1035Driver.hpp b/Software/components/drivers/bt1035/include/bt1035/Bt1035Driver.hpp index 2c62d70..c967d6e 100644 --- a/Software/components/drivers/bt1035/include/bt1035/Bt1035Driver.hpp +++ b/Software/components/drivers/bt1035/include/bt1035/Bt1035Driver.hpp @@ -244,6 +244,8 @@ private: [[nodiscard]] std::expected transmitAndExpectOk( std::string_view commandLine); + static constexpr int kResponseTimeoutMs = 2000; + Bt1035Pins pins_; bool booted_; bool uartInstalled_; diff --git a/Software/components/drivers/bt1035/src/Bt1035Driver.cpp b/Software/components/drivers/bt1035/src/Bt1035Driver.cpp index 4be7b23..3ebdfe9 100644 --- a/Software/components/drivers/bt1035/src/Bt1035Driver.cpp +++ b/Software/components/drivers/bt1035/src/Bt1035Driver.cpp @@ -105,7 +105,8 @@ std::expected Bt1035Driver::transmitAndCollect( std::expected Bt1035Driver::transmitAndExpectOk( std::string_view commandLine) { - if (auto collected = transmitAndCollect(commandLine); collected) { + auto collected = transmitAndCollect(commandLine); + if (collected) { return {}; } return std::unexpected(collected.error()); diff --git a/Software/components/drivers/si4684/src/Si4684Driver.cpp b/Software/components/drivers/si4684/src/Si4684Driver.cpp index 102f402..fc4d704 100644 --- a/Software/components/drivers/si4684/src/Si4684Driver.cpp +++ b/Software/components/drivers/si4684/src/Si4684Driver.cpp @@ -23,6 +23,7 @@ #include #include #include +#include "esp_heap_caps.h" namespace si4684 { @@ -31,7 +32,7 @@ namespace { constexpr char kTag[] = "Si4684"; constexpr std::size_t kSpiBufferSize = 4096U; constexpr int kCtsPollMs = 2; -constexpr int kCtsRetries = 200; +constexpr int kCtsRetries = 5000; constexpr int kStcRetries = 250; constexpr int kStcPollMs = 20; @@ -213,36 +214,57 @@ std::expected Si4684Driver::writeCommand( std::expected Si4684Driver::hostLoadBlob( const core::IFirmwareBlobReader& blob, std::size_t chunkPayload) { + // Buffer DMA-capable e allineato: obbligatorio per spi_device_transmit + // con trasferimenti grandi. Un buffer sullo stack non e' DMA-safe e puo' + // corrompere i dati sui blob grossi (patch/firmware). + const std::size_t txSize = 4U + chunkPayload; + auto* tx = static_cast( + heap_caps_malloc(txSize, MALLOC_CAP_DMA | MALLOC_CAP_8BIT)); + if (tx == nullptr) { + return std::unexpected(Si4684Error::ImageLoadFailed); + } + std::array payload = {}; std::size_t offset = 0U; + Si4684Error err = Si4684Error::ImageLoadFailed; + bool failed = false; while (offset < blob.size()) { const std::size_t maxChunk = std::min(chunkPayload, payload.size()); const std::size_t copied = blob.read(offset, std::span(payload.data(), maxChunk)); if (copied == 0U) { - return std::unexpected(Si4684Error::ImageLoadFailed); + failed = true; + break; } - std::array tx = {}; + std::memset(tx, 0, txSize); tx[0] = static_cast(Command::HostLoad); tx[1] = 0x00U; tx[2] = 0x00U; tx[3] = 0x00U; - std::memcpy(tx.data() + 4U, payload.data(), copied); + std::memcpy(tx + 4U, payload.data(), copied); spi_transaction_t txn = {}; - txn.length = (4U + copied) * 8U; - txn.tx_buffer = tx.data(); + txn.length = txSize * 8U; + txn.tx_buffer = tx; if (spi_device_transmit(static_cast(spiDevice_), &txn) != ESP_OK) { - return std::unexpected(Si4684Error::ImageLoadFailed); + failed = true; + break; } if (auto cts = waitCts(); !cts) { - return cts; + err = cts.error(); + failed = true; + break; } offset += copied; } + + heap_caps_free(tx); + if (failed) { + return std::unexpected(err); + } return {}; } @@ -396,7 +418,7 @@ std::expected Si4684Driver::boot(Si4684Band band) gpio_set_level(static_cast(pins_.rstbGpio), 0); vTaskDelay(pdMS_TO_TICKS(5)); gpio_set_level(static_cast(pins_.rstbGpio), 1); - vTaskDelay(pdMS_TO_TICKS(3)); + vTaskDelay(pdMS_TO_TICKS(20)); if (!spiBusActive_) { spi_bus_config_t busCfg = {}; @@ -442,7 +464,7 @@ std::expected Si4684Driver::boot(Si4684Band band) return std::unexpected(Si4684Error::PowerUpFailed); } - vTaskDelay(pdMS_TO_TICKS(1)); + vTaskDelay(pdMS_TO_TICKS(20)); if (auto li = writeCommand(Command::LoadInit, nullptr, 0U); !li) { return std::unexpected(Si4684Error::PatchLoadFailed); @@ -557,18 +579,18 @@ std::expected Si4684Driver::readFmRsq() return std::unexpected(rd.error()); } - Si4684FmRsq rsq = {}; const auto khz = chipFmFreqToKHz(readLe16(raw.data() + 6)); if (auto freq = core::FrequencyKHz::tryFromKhz(khz); freq) { - rsq.frequency = *freq; - } else { - return std::unexpected(Si4684Error::CommandFailed); + Si4684FmRsq rsq{ + *freq, + static_cast(raw[8]), + static_cast(raw[9]), + (raw[4] & 0x01U) != 0U, + (raw[4] & 0x02U) != 0U, + }; + return rsq; } - rsq.valid = (raw[4] & 0x01U) != 0U; - rsq.stereo = (raw[4] & 0x02U) != 0U; - rsq.rssiDbuV = static_cast(raw[8]); - rsq.snrDb = static_cast(raw[9]); - return rsq; + return std::unexpected(Si4684Error::CommandFailed); } std::expected Si4684Driver::readFmRds() diff --git a/Software/components/drivers/si4684/src/Si4684Tuner.cpp b/Software/components/drivers/si4684/src/Si4684Tuner.cpp index 0919ef3..7450d14 100644 --- a/Software/components/drivers/si4684/src/Si4684Tuner.cpp +++ b/Software/components/drivers/si4684/src/Si4684Tuner.cpp @@ -172,7 +172,8 @@ std::expected Si4684Tuner::tuneFm( std::expected Si4684Tuner::seekFm( core::SeekDirection direction) { - if (auto result = driver_.seekFm(direction, SeekBandWrap::Wrap); !result) { + const auto result = driver_.seekFm(direction, SeekBandWrap::Wrap); + if (!result) { return std::unexpected(mapError(result.error())); } fmFrequency_ = *result; @@ -191,7 +192,8 @@ Si4684Tuner::listDabServices() return std::unexpected(mapError(events.error())); } - if (auto list = driver_.fetchDabServiceList(); list) { + const auto list = driver_.fetchDabServiceList(); + if (list) { std::vector out; out.reserve(list->size()); for (const auto& item : *list) { diff --git a/Software/components/net/include/net/NetBootstrap.hpp b/Software/components/net/include/net/NetBootstrap.hpp index 29c36de..9fd24e2 100644 --- a/Software/components/net/include/net/NetBootstrap.hpp +++ b/Software/components/net/include/net/NetBootstrap.hpp @@ -99,6 +99,21 @@ public: NetBootstrap(const NetBootstrap&) = delete; NetBootstrap& operator=(const NetBootstrap&) = delete; + /** + * @brief NetBootstrap — construct a started bootstrap. + * + * @dname NetBootstrap + * @param softAp Optional SoftAP mode host. + * @param sta Optional STA client instance. + * @param webServer HTTP server instance. + * @param state Initial network state. + * @pubstate Transfers ownership of optional network resources. + */ + NetBootstrap(std::optional softAp, + std::optional sta, + SetupWebServer webServer, + NetState state); + /** * @brief NetBootstrap — move-construct from a started bootstrap. * @@ -148,11 +163,6 @@ public: [[nodiscard]] NetState state() const noexcept; private: - NetBootstrap(std::optional softAp, - std::optional sta, - SetupWebServer webServer, - NetState state); - std::optional softAp_; std::optional sta_; SetupWebServer webServer_; diff --git a/Software/components/net/include/net/SetupWebServer.hpp b/Software/components/net/include/net/SetupWebServer.hpp index 924f3c2..b35c5b8 100644 --- a/Software/components/net/include/net/SetupWebServer.hpp +++ b/Software/components/net/include/net/SetupWebServer.hpp @@ -23,10 +23,9 @@ #include "net/NetError.hpp" #include "net/NetState.hpp" +#include "esp_http_server.h" #include -struct httpd_req; - namespace audio { class AudioService; } // namespace audio @@ -172,7 +171,7 @@ public: const core::DeviceIdentity& deviceIdentity); private: - httpd_handle* server_; + httpd_handle_t server_; core::ISecureStore* store_; NetState netState_; tuner::TunerService* tuner_; diff --git a/Software/components/net/src/SetupWebServer.cpp b/Software/components/net/src/SetupWebServer.cpp index c8685aa..4d3afe0 100644 --- a/Software/components/net/src/SetupWebServer.cpp +++ b/Software/components/net/src/SetupWebServer.cpp @@ -63,10 +63,10 @@ constexpr char kTag[] = "SetupWebServer"; constexpr char kFirmwareVersion[] = "0.8.5"; constexpr unsigned kRebootDelaySec = 3; -extern const uint8_t www_index_html_gz_start[] asm( - "_binary_www_index_html_gz_start"); -extern const uint8_t www_index_html_gz_end[] asm( - "_binary_www_index_html_gz_end"); +extern const uint8_t index_html_gz_start[] asm( + "_binary_index_html_gz_start"); +extern const uint8_t index_html_gz_end[] asm( + "_binary_index_html_gz_end"); /** * @brief routeContextFrom — read handler dependencies from user_ctx. @@ -81,7 +81,7 @@ extern const uint8_t www_index_html_gz_end[] asm( */ [[nodiscard]] HttpRouteContext* routeContextFrom(httpd_req_t* req) noexcept { - return static_cast(httpd_req_get_user_ctx(req)); + return static_cast(req->user_ctx); } /** @@ -421,7 +421,7 @@ esp_err_t tunerSeekPostHandler(httpd_req_t* req) } std::array body{}; - readRequestBody(req, body); + (void)readRequestBody(req, body); const auto direction = core::parseTunerSeekJson(std::string_view(body.data())); if (!direction) { const std::string json = @@ -620,11 +620,11 @@ esp_err_t audioBassEnhancePostHandler(httpd_req_t* req) esp_err_t indexGetHandler(httpd_req_t* req) { const size_t length = - static_cast(www_index_html_gz_end - www_index_html_gz_start); + static_cast(index_html_gz_end - index_html_gz_start); httpd_resp_set_type(req, "text/html"); httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); return httpd_resp_send(req, - reinterpret_cast(www_index_html_gz_start), + reinterpret_cast(index_html_gz_start), length); } @@ -935,7 +935,7 @@ esp_err_t bluetoothAutoReconnectPostHandler(httpd_req_t* req) } std::array body{}; - readRequestBody(req, body); + (void)readRequestBody(req, body); const auto times = core::parseBluetoothAutoReconnectJson(std::string_view(body.data())); if (!times) { @@ -1112,7 +1112,8 @@ SetupWebServer::SetupWebServer() , stations_(nullptr) , integration_(nullptr) , routeContext_{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, {}} + nullptr, core::CompanionChipStatus{false, false, false}, + core::DeviceIdentity::unknown()} { } @@ -1163,7 +1164,8 @@ SetupWebServer& SetupWebServer::operator=(SetupWebServer&& other) noexcept other.stations_ = nullptr; other.integration_ = nullptr; other.routeContext_ = {nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, {}, core::DeviceIdentity::unknown()}; + nullptr, nullptr, core::CompanionChipStatus{false, false, false}, + core::DeviceIdentity::unknown()}; } return *this; } @@ -1175,7 +1177,8 @@ SetupWebServer::~SetupWebServer() server_ = nullptr; } routeContext_ = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, {}, core::DeviceIdentity::unknown()}; + nullptr, core::CompanionChipStatus{false, false, false}, + core::DeviceIdentity::unknown()}; } std::expected SetupWebServer::start( @@ -1218,7 +1221,8 @@ std::expected SetupWebServer::start( if (httpd_start(&server_, &config) != ESP_OK) { ESP_LOGE(kTag, "httpd_start failed"); routeContext_ = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, {}, core::DeviceIdentity::unknown()}; + nullptr, core::CompanionChipStatus{false, false, false}, + core::DeviceIdentity::unknown()}; return std::unexpected(NetError::HttpServerStartFailed); } diff --git a/Software/i2c_scanner.ino b/Software/i2c_scanner.ino new file mode 100644 index 0000000..a77c0fb --- /dev/null +++ b/Software/i2c_scanner.ino @@ -0,0 +1,51 @@ +#include + +// I2C scanner for Arduino IDE +// - Apri il Monitor Seriale a 115200 baud +// - Collega SDA/SCL al bus I2C del dispositivo che vuoi testare +// - Carica lo sketch e osserva gli indirizzi trovati + +void setup() { + Serial.begin(115200); + while (!Serial) { + delay(10); + } + + Serial.println("I2C Scanner - Arduino IDE"); + Wire.begin(); + Serial.println("Scanning for I2C devices..."); +} + +void loop() { + byte error, address; + int count = 0; + + Serial.println("\nScanning..."); + + for (address = 1; address < 127; address++) { + Wire.beginTransmission(address); + error = Wire.endTransmission(); + + if (error == 0) { + Serial.print("Found I2C device at 0x"); + if (address < 16) Serial.print("0"); + Serial.print(address, HEX); + Serial.println(" !"); + count++; + } else if (error == 4) { + Serial.print("Unknown error at address 0x"); + if (address < 16) Serial.print("0"); + Serial.println(address, HEX); + } + } + + if (count == 0) { + Serial.println("No I2C devices found."); + } else { + Serial.print("Done. "); + Serial.print(count); + Serial.println(" device(s) found."); + } + + delay(5000); +} diff --git a/Software/main/CMakeLists.txt b/Software/main/CMakeLists.txt index 84f6f90..27f5185 100644 --- a/Software/main/CMakeLists.txt +++ b/Software/main/CMakeLists.txt @@ -2,6 +2,11 @@ idf_component_register( SRCS "main.cpp" "hardware_bootstrap.cpp" + "$<$:test_firmware.cpp>" INCLUDE_DIRS "." REQUIRES core net secure_store adau1701 si4684 tuner audio bt1035 bluetooth station integration ota eeprom24aa ) + +if(CONFIG_TEST_FIRMWARE) + set(EXTRA_COMPONENT_DIRS "${EXTRA_COMPONENT_DIRS}" "${CMAKE_CURRENT_SOURCE_DIR}") +endif() diff --git a/Software/main/hardware_bootstrap.cpp b/Software/main/hardware_bootstrap.cpp index 2c2f822..c713ec0 100644 --- a/Software/main/hardware_bootstrap.cpp +++ b/Software/main/hardware_bootstrap.cpp @@ -92,7 +92,7 @@ std::expected HardwareBootstrap::boot() } if (auto tunerResult = gSi4684.boot(si4684::Si4684Band::Dab); !tunerResult) { - ESP_LOGE(kTag, "Si4684 boot failed"); + ESP_LOGE(kTag, "Si4684 boot failed: error %d", static_cast(tunerResult.error())); return std::unexpected(HardwareBootError::Si4684BootFailed); } @@ -104,7 +104,7 @@ std::expected HardwareBootstrap::boot() } } - const auto* busHandle = + auto* busHandle = static_cast(gAdau1701.i2cBusHandle()); eeprom24aa::Eeprom24aa eeprom(busHandle, static_cast( diff --git a/Software/main/idf_component.yml b/Software/main/idf_component.yml new file mode 100644 index 0000000..e5dcd75 --- /dev/null +++ b/Software/main/idf_component.yml @@ -0,0 +1,17 @@ +## IDF Component Manager Manifest File +dependencies: + ## Required IDF version + idf: + version: '>=4.1.0' + # # Put list of dependencies here + # # For components maintained by Espressif: + # component: "~1.0.0" + # # For 3rd party components: + # username/component: ">=1.0.0,<2.0.0" + # username2/component2: + # version: "~1.0.0" + # # For transient dependencies `public` flag can be set. + # # `public` flag doesn't have an effect dependencies of the `main` component. + # # All dependencies of `main` are public by default. + # public: true + espressif/mdns: '*' diff --git a/Software/main/main.cpp b/Software/main/main.cpp index 1c11fe8..e2e0d56 100644 --- a/Software/main/main.cpp +++ b/Software/main/main.cpp @@ -17,6 +17,7 @@ #include "net/NetBootstrap.hpp" #include "ota/OtaService.hpp" #include "secure_store/NvsSecureStore.hpp" +#include "si4684/Si4684Tuner.hpp" #include "station/StationService.hpp" #include "tuner/TunerService.hpp" @@ -24,6 +25,10 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#if CONFIG_TEST_FIRMWARE +#include "test_firmware.hpp" +#endif + namespace { constexpr char kTag[] = "digiradio"; @@ -59,6 +64,11 @@ extern "C" void app_main() return; } +#if CONFIG_TEST_FIRMWARE + test_firmware::runTestFirmware(); + return; +#endif + static secure_store::NvsSecureStore store; static tuner::TunerService tunerService( diff --git a/Software/main/test_firmware.cpp b/Software/main/test_firmware.cpp new file mode 100644 index 0000000..8d11a53 --- /dev/null +++ b/Software/main/test_firmware.cpp @@ -0,0 +1,194 @@ +/** + * @file test_firmware.cpp + * @brief Minimal firmware sequence for board, ADAU1701, Si4684 and BT1035 tests. + * + * DigiRadio firmware — https://github.com/manvalan/DigiRadio + * + * Copyright 2026 Michele Bigi + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "adau1701/Adau1701Dsp.hpp" +#include "adau1701/Adau1701Driver.hpp" +#include "adau1701/EmbeddedDspProgramSource.hpp" +#include "adau1701/FallbackDspProgramSource.hpp" +#include "adau1701/FlashDspProgramSource.hpp" +#include "board_pins.hpp" +#include "bt1035/Bt1035Driver.hpp" +#include "core/Bt1035At.hpp" +#include "core/FrequencyKHz.hpp" +#include "esp_log.h" +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "si4684/Si4684Band.hpp" +#include "si4684/Si4684Driver.hpp" +#include "si4684/Si4684EmbeddedImages.hpp" + +#include "test_firmware.hpp" + +namespace test_firmware { + +namespace { +constexpr char kTag[] = "testfw"; + +void logChipInfo() +{ + esp_chip_info_t info; + esp_chip_info(&info); + ESP_LOGI(kTag, "ESP32 chip: %d cores, rev %d, features=0x%x", + info.cores, info.revision, info.features); +} + +void logError(const char* stage, int code) +{ + ESP_LOGE(kTag, "%s failed (err=%d)", stage, code); +} + +void logStage(const char* stage) +{ + ESP_LOGI(kTag, "==== %s ====", stage); +} + +const char* a2dpStateToken(core::Bt1035A2dpState state) noexcept +{ + using core::Bt1035A2dpState; + switch (state) { + case Bt1035A2dpState::Unsupported: + return "unsupported"; + case Bt1035A2dpState::Standby: + return "standby"; + case Bt1035A2dpState::Connecting: + return "connecting"; + case Bt1035A2dpState::Connected: + return "connected"; + case Bt1035A2dpState::Streaming: + return "streaming"; + case Bt1035A2dpState::Paused: + return "paused"; + } + return "unknown"; +} + +} // namespace + +void runTestFirmware() +{ + logStage("Board check"); + logChipInfo(); + + logStage("Boot ADAU1701"); + adau1701::EmbeddedDspProgramSource embeddedDspProgram; + adau1701::FlashDspProgramSource flashDspProgram; + adau1701::FallbackDspProgramSource dspProgramSource( + flashDspProgram, embeddedDspProgram); + adau1701::Adau1701Driver adau( + adau1701::Adau1701Pins{ + .i2cSda = board::pins::Adau1701Sda, + .i2cScl = board::pins::Adau1701Scl, + .resetGpio = board::pins::Adau1701Reset, + .i2cAddr7 = board::pins::Adau1701Addr, + }, + dspProgramSource); + + if (auto result = adau.boot(); !result) { + logError("ADAU1701 boot", static_cast(result.error())); + return; + } + ESP_LOGI(kTag, "ADAU1701 boot succeeded"); + + logStage("Boot Si4684"); + si4684::Si4684EmbeddedImages images; + si4684::Si4684Driver si4684( + si4684::Si4684Pins{ + .spiHost = SPI2_HOST, + .csGpio = board::pins::Si4684Cs, + .misoGpio = board::pins::Si4684Miso, + .mosiGpio = board::pins::Si4684Mosi, + .sclkGpio = board::pins::Si4684Sclk, + .rstbGpio = board::pins::Si4684Rstb, + .intbGpio = board::pins::Si4684Intb, + }, + images.romPatch(), + images.dabFirmware(), + images.fmFirmware()); + + if (auto result = si4684.boot(si4684::Si4684Band::Fm); !result) { + logError("Si4684 boot", static_cast(result.error())); + return; + } + ESP_LOGI(kTag, "Si4684 FM boot succeeded"); + + logStage("BT1035 boot"); + bt1035::Bt1035Driver bt( + bt1035::Bt1035Pins{ + .uartTx = board::pins::Bt1035UartTx, + .uartRx = board::pins::Bt1035UartRx, + .rtsGpio = board::pins::Bt1035Rts, + .ctsGpio = board::pins::Bt1035Cts, + .resetGpio = board::pins::Bt1035Reset, + .sysCtlGpio = board::pins::Bt1035SysCtl, + }); + + if (auto result = bt.boot(); !result) { + logError("BT1035 boot", static_cast(result.error())); + return; + } + ESP_LOGI(kTag, "BT1035 boot succeeded"); + + if (auto nameResult = bt.queryDeviceName(); nameResult) { + ESP_LOGI(kTag, "BT1035 name=%s", nameResult->c_str()); + } + + if (auto result = bt.setDeviceName("DigiRadio-Test"); !result) { + logError("BT1035 setDeviceName", static_cast(result.error())); + } + + if (auto result = bt.enterPairingMode(); !result) { + logError("BT1035 pairing mode", static_cast(result.error())); + } else { + ESP_LOGI(kTag, "BT1035 pairing mode enabled"); + } + + if (auto states = bt.queryA2dpState(); states) { + ESP_LOGI(kTag, "BT1035 A2DP state=%s", + a2dpStateToken(states.value())); + } + + if (auto listResult = bt.queryPairedList(); listResult) { + ESP_LOGI(kTag, "BT1035 paired devices=%zu", listResult->size()); + } + + logStage("Radio test: tune FM"); + auto frequency = core::FrequencyKHz::tryFromKhz(100700U); + if (!frequency) { + ESP_LOGE(kTag, "Invalid FM frequency"); + return; + } + + if (auto result = si4684.tuneFm(*frequency); !result) { + logError("Si4684 tune FM", static_cast(result.error())); + } else { + ESP_LOGI(kTag, "Si4684 tuned FM to %u kHz", frequency->value()); + } + + if (auto result = si4684.setVolume(16); !result) { + logError("Si4684 setVolume", static_cast(result.error())); + } + + if (auto rsqResult = si4684.readFmRsq(); rsqResult) { + ESP_LOGI(kTag, + "FM RSQ: snr=%u, afc=%d, blend=%u, stere=%u, rssi=%d", + rsqResult->snr, rsqResult->afc, rsqResult->blend, + rsqResult->stere, rsqResult->rssi); + } else { + logError("Si4684 read FM RSQ", static_cast(rsqResult.error())); + } + + ESP_LOGI(kTag, "Test firmware finished. Keep running for manual pairing and audio verification."); + while (true) { + vTaskDelay(pdMS_TO_TICKS(10000)); + } +} + +} // namespace test_firmware diff --git a/Software/main/test_firmware.hpp b/Software/main/test_firmware.hpp new file mode 100644 index 0000000..f0b8958 --- /dev/null +++ b/Software/main/test_firmware.hpp @@ -0,0 +1,16 @@ +/** + * @file test_firmware.hpp + * @brief Minimal DigiRadio hardware test firmware entrypoint. + * + * DigiRadio firmware — https://github.com/manvalan/DigiRadio + * + * Copyright 2026 Michele Bigi + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +namespace test_firmware { + +void runTestFirmware(); + +} // namespace test_firmware diff --git a/Software/partitions.csv b/Software/partitions.csv index ef30d74..622266a 100644 --- a/Software/partitions.csv +++ b/Software/partitions.csv @@ -1,4 +1,4 @@ -# DigiRadio partition table (4 MB flash — ESP32-S3-WROOM-1) +# DigiRadio partition table (16 MB flash — ESP32-S3-WROOM-1) # ota_0 + ota_1: dual OTA app slots (equal size, 64 KiB aligned). # dsp: updatable ADAU1701 program blob (task 3.1). # nvs_keys: NVS encryption keys (secure-store slice). @@ -8,6 +8,6 @@ nvs, data, nvs, 0x9000, 0x6000, otadata, data, ota, 0xf000, 0x2000, nvs_keys, data, nvs_keys, 0x11000, 0x1000, phy_init, data, phy, 0x12000, 0x1000, -ota_0, app, ota_0, 0x20000, 0x1B0000, -ota_1, app, ota_1, 0x1D0000, 0x1B0000, -dsp, data, 0x40, 0x380000, 0x40000, +ota_0, app, ota_0, 0x20000, 0x400000, +ota_1, app, ota_1, 0x420000, 0x400000, +dsp, data, 0x40, 0x820000, 0x40000, diff --git a/Software/sdkconfig.defaults b/Software/sdkconfig.defaults index 68b9a6b..2342860 100644 --- a/Software/sdkconfig.defaults +++ b/Software/sdkconfig.defaults @@ -12,11 +12,11 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y # Flash encryption — DEVELOPMENT mode (re-flash plaintext until eFuse policy set) -CONFIG_SECURE_FLASH_ENC_ENABLED=y -CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT=y +# CONFIG_SECURE_FLASH_ENC_ENABLED=y +# CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT=y # NVS encryption at rest (XTS-AES; keys in nvs_keys, protected by flash encryption) -CONFIG_NVS_ENCRYPTION=y +# CONFIG_NVS_ENCRYPTION=y # C++23, exceptions and RTTI off (AGENTS.md §2) CONFIG_COMPILER_CXX_EXCEPTIONS=n @@ -25,3 +25,5 @@ CONFIG_COMPILER_CXX_STD=23 # Logging CONFIG_LOG_DEFAULT_LEVEL_INFO=y +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192