diff --git a/Software/components/drivers/bt1035/include/bt1035/Bt1035Driver.hpp b/Software/components/drivers/bt1035/include/bt1035/Bt1035Driver.hpp index 3d65d06..169852f 100644 --- a/Software/components/drivers/bt1035/include/bt1035/Bt1035Driver.hpp +++ b/Software/components/drivers/bt1035/include/bt1035/Bt1035Driver.hpp @@ -37,8 +37,19 @@ namespace bt1035 { struct Bt1035Pins { int uartTx; ///< ESP32 TX -> module RX. int uartRx; ///< ESP32 RX <- module TX. - int resetGpio; ///< Module RESET (active level per schematic). - int sysCtlGpio; ///< SYS_CTL (optional module enable). + int resetGpio; ///< Module RESET# (pin 8). Read-only: configured as a + ///< floating input (2026-08-22), never driven — relies + ///< entirely on the module's own internal pull-up. + int sysCtlGpio; ///< SYS_CTL (pin 34). Driven HIGH once at boot, then + ///< never touched again for the process lifetime. + int ctsGpio; ///< Host->module UART_CTS (module pin 15). Diagnostic + ///< only (2026-08-22): read-only floating input, never + ///< driven — this driver does not implement hardware + ///< flow control. See boot()'s comment. + int rtsGpio; ///< Module UART_RTS/PIO2 (module pin 16), factory + ///< default function is PA_MUTE, not flow control + ///< (Feasycom programming guide). Diagnostic only + ///< (2026-08-22): read-only floating input. }; /** diff --git a/Software/components/drivers/bt1035/src/Bt1035Driver.cpp b/Software/components/drivers/bt1035/src/Bt1035Driver.cpp index 60d8ba7..6866f7d 100644 --- a/Software/components/drivers/bt1035/src/Bt1035Driver.cpp +++ b/Software/components/drivers/bt1035/src/Bt1035Driver.cpp @@ -36,14 +36,18 @@ constexpr int kBaudRate = 115200; constexpr int kUartRxBuffer = 4096; constexpr int kUartTxBuffer = 256; constexpr int kResponseTimeoutMs = 2000; -constexpr int kPostResetMs = 500; constexpr int kPostUartMs = 100; -/** Feasycom BT1035 programming user guide §2.2 (pin 34 SYS_CTRL): "Delay - * 100ms, pull high". */ -constexpr int kSysCtlLeadInMs = 100; -/** Margin beyond the datasheet's own >20ms SYS_CTRL-assertion-to-power-up - * minimum (§4.7), for regulator/crystal settling before RESET releases. */ -constexpr int kSysCtlSettleMs = 50; +/** Datasheet §4.7: "From the OFF state, SYS_CTRL must be asserted for + * >20 ms to start power up." */ +constexpr int kSysCtlAssertMs = 20; +/** Datasheet §4.8: "Reset Protection timeout (typically greater than + * ~1.8 s) causes the device to power down if VCHG is not present and + * SYS_CTRL is low." Held comfortably longer than that before each + * power-up assert, to guarantee a genuine full power-down rather than a + * pulse too short for the module's own protection timer to act on — see + * resetAndInitOnce()'s comment for why this now runs on every attempt, + * not just the first. */ +constexpr int kSysCtlDeassertMs = 2500; /** Measured live (2026-08-20, power/wiring confirmed sound with a * multimeter — VBAT_IN/SYS_CTRL/1.8V_OUT/VDD_IO all correct, TX/RX pins * verified via continuity): the module's spontaneous boot banner @@ -970,35 +974,47 @@ std::expected Bt1035Driver::runInitSequence() std::expected Bt1035Driver::resetAndInitOnce() { - // Feasycom BT1035 programming user guide §2.2, pin 34 SYS_CTRL: - // "Delay 100ms, pull high" — the datasheet's own OFF-state timing spec - // (§4.7) says SYS_CTRL must be asserted >20ms before the internal - // regulators start powering up at all, so pulling it high with no - // lead-in delay (the previous sequence here) races the chip's own - // power-on requirement. Held low with RESET already asserted, then a - // 100ms lead-in exactly matching the guide, then SYS_CTRL high, then - // extra settle time before releasing RESET into a chip that's had a - // chance to actually power up first. + // RESET# (pin 8) is deliberately never driven by this driver (see + // boot()'s GPIO config below): it's left a floating input, relying + // entirely on the BT1035's own "fixed strong pull-up to VDD_IO" + // (datasheet §4.8), which the datasheet explicitly says means the pin + // "can therefore be left unconnected". Diagnostic test (2026-08-22) to + // check whether the previous external RESET# drive was contributing to + // the intermittent boot failures. + // + // SYS_CTRL (pin 34) alternative usage (2026-08-22 experiment): drive a + // genuine LOW-then-HIGH cycle every time this function runs, not just + // once ever. With RESET# now Hi-Z, SYS_CTRL is the only pin this + // driver can still use to force a real power-cycle — previously it + // was asserted HIGH exactly once at first boot and never touched + // again, which meant every later retry (hardware::bt1035RetryTask + // calls boot() again on failure) just re-listened on an + // already-asserted line without ever actually power-cycling the + // module. Held LOW for kSysCtlDeassertMs first so the module's own + // Reset Protection timeout actually elapses (a shorter pulse risks + // the module staying "protected" on, per datasheet §4.8, so the + // retry wouldn't be a real fresh power-on at all), then HIGH for the + // datasheet's own >=20ms minimum. const auto sysCtlPin = static_cast(pins_.sysCtlGpio); const auto resetPin = static_cast(pins_.resetGpio); + const auto ctsPin = static_cast(pins_.ctsGpio); + const auto rtsPin = static_cast(pins_.rtsGpio); gpio_set_level(sysCtlPin, 0); - gpio_set_level(resetPin, 0); - vTaskDelay(pdMS_TO_TICKS(kSysCtlLeadInMs)); - ESP_LOGI(kTag, "pre-power: SYS_CTRL=%d RESET=%d (want 0,0)", - gpio_get_level(sysCtlPin), gpio_get_level(resetPin)); - + vTaskDelay(pdMS_TO_TICKS(kSysCtlDeassertMs)); gpio_set_level(sysCtlPin, 1); - vTaskDelay(pdMS_TO_TICKS(kSysCtlSettleMs)); - ESP_LOGI(kTag, "post-syscl: SYS_CTRL=%d RESET=%d (want 1,0)", - gpio_get_level(sysCtlPin), gpio_get_level(resetPin)); - - gpio_set_level(resetPin, 1); - vTaskDelay(pdMS_TO_TICKS(kPostResetMs)); - ESP_LOGI(kTag, "post-reset: SYS_CTRL=%d RESET=%d (want 1,1)", + vTaskDelay(pdMS_TO_TICKS(kSysCtlAssertMs)); + ESP_LOGI(kTag, "power-up: SYS_CTRL=%d (want 1, driven) RESET#=%d " + "(want 1, Hi-Z + internal pull-up, not driven by us)", gpio_get_level(sysCtlPin), gpio_get_level(resetPin)); + ESP_LOGI(kTag, "before banner wait: CTS=%d (module's flow-control " + "input, host side floating) RTS/PIO2=%d (module's " + "PA_MUTE by factory default)", + gpio_get_level(ctsPin), gpio_get_level(rtsPin)); logRawUartBoot(uartPort_); + ESP_LOGI(kTag, "after banner wait: CTS=%d RTS/PIO2=%d", + gpio_get_level(ctsPin), gpio_get_level(rtsPin)); uart_flush_input(static_cast(uartPort_)); vTaskDelay(pdMS_TO_TICKS(kPostUartMs)); @@ -1011,18 +1027,25 @@ std::expected Bt1035Driver::boot() return {}; } - // GPIO_MODE_INPUT_OUTPUT (not plain GPIO_MODE_OUTPUT): gpio_config() - // only enables the pad's input buffer when the INPUT bit is set, so a - // pure-output config leaves gpio_get_level() reading a stale/always-0 - // register instead of the real driven level — needed for the readback - // diagnostic below to be meaningful. + // RESET# (pin 8) is never driven by this driver (2026-08-22 diagnostic + // change): configured as a pure floating input, pull-up/pull-down both + // explicitly disabled, so nothing on the ESP32 side influences this + // net electrically — the BT1035's own internal RESET# pull-up (§4.8) + // is the only thing holding it high. GPIO_MODE_INPUT (not OUTPUT) still + // lets gpio_get_level() read it back for the diagnostic log below, + // without ever driving it. gpio_config_t resetCfg = {}; resetCfg.pin_bit_mask = 1ULL << pins_.resetGpio; - resetCfg.mode = GPIO_MODE_INPUT_OUTPUT; + resetCfg.mode = GPIO_MODE_INPUT; + resetCfg.pull_up_en = GPIO_PULLUP_DISABLE; + resetCfg.pull_down_en = GPIO_PULLDOWN_DISABLE; if (gpio_config(&resetCfg) != ESP_OK) { return std::unexpected(Bt1035Error::ResetFailed); } + // SYS_CTRL (pin 34) stays actively driven (GPIO_MODE_INPUT_OUTPUT: + // the INPUT bit is what makes gpio_get_level() read the real driven + // level instead of a stale register, for the diagnostic log). gpio_config_t sysCfg = {}; sysCfg.pin_bit_mask = 1ULL << pins_.sysCtlGpio; sysCfg.mode = GPIO_MODE_INPUT_OUTPUT; @@ -1030,6 +1053,35 @@ std::expected Bt1035Driver::boot() return std::unexpected(Bt1035Error::ResetFailed); } + // CTS (module pin 15, host->module) and RTS (module pin 16/PIO2, + // factory default function PA_MUTE per the Feasycom programming + // guide) — diagnostic only (2026-08-22): this driver does not + // implement UART hardware flow control (UART_HW_FLOWCTRL_DISABLE + // below), so these are wired but otherwise unused. Configured as + // floating inputs, pull-up/pull-down both disabled, purely to read + // back their level for the diagnostic log — never driven. Exploring + // whether the module's CTS input floating could be gating its own + // UART TX (a common hardware-flow-control behavior), and whether RTS + // toggles at all (would indicate the module's internal firmware is + // alive even when UART TX is silent). + gpio_config_t ctsCfg = {}; + ctsCfg.pin_bit_mask = 1ULL << pins_.ctsGpio; + ctsCfg.mode = GPIO_MODE_INPUT; + ctsCfg.pull_up_en = GPIO_PULLUP_DISABLE; + ctsCfg.pull_down_en = GPIO_PULLDOWN_DISABLE; + if (gpio_config(&ctsCfg) != ESP_OK) { + return std::unexpected(Bt1035Error::ResetFailed); + } + + gpio_config_t rtsCfg = {}; + rtsCfg.pin_bit_mask = 1ULL << pins_.rtsGpio; + rtsCfg.mode = GPIO_MODE_INPUT; + rtsCfg.pull_up_en = GPIO_PULLUP_DISABLE; + rtsCfg.pull_down_en = GPIO_PULLDOWN_DISABLE; + if (gpio_config(&rtsCfg) != ESP_OK) { + return std::unexpected(Bt1035Error::ResetFailed); + } + if (!uartInstalled_) { const uart_config_t uartCfg = { .baud_rate = kBaudRate, diff --git a/Software/docs/TODO.md b/Software/docs/TODO.md index 092cb7f..bb70865 100644 --- a/Software/docs/TODO.md +++ b/Software/docs/TODO.md @@ -158,6 +158,31 @@ Short version: known deviation from the historically validated design removed), not because this sample proved a better success rate. Root cause of the underlying intermittent silence is still open (see entry above). +- **BT1035 — RESET#/SYS_CTRL redesign, CTS/RTS diagnostics, Feasycom + escalation (2026-08-22).** A sibling project's PinScope netlist report + (RESET# pulled to GND, SYS_CTRL pulled HIGH by stray resistors) was + checked against our own schematic and does **not** apply to us — our + RESET#/SYS_CTRL wiring is correct, verified via netlist. Its RF_OUT/pin + 51 floating finding **does** also apply to us, but is a separate, + RF-range-only concern (datasheet documents both internal- and + external-antenna variants; can't tell which we have), not the cause of + the digital/UART boot silence. As a diagnostic test, RESET# is now + never driven at all (floating input, relying on the module's own + internal pull-up per §4.8) and SYS_CTRL now does a genuine LOW(2.5s)→ + HIGH power-cycle on every retry attempt (previously asserted once ever + and left alone, meaning retries never actually power-cycled the + module). Also added read-only diagnostics on the previously-unused + CTS/RTS pins (physically wired, named in `board_pins.hpp`, never + configured by any driver code, host flow control disabled) — live + readings were perfectly stable (CTS=HIGH, RTS=LOW) across ~12 samples + over 30+ minutes, arguing against pure floating-noise. **Across all of + today's changes combined, zero successful boots were observed in + cumulative 45+ minutes of live testing** — inconclusive-to-negative, + not proof any change helped or hurt. Escalated to Feasycom support with + a detailed email (drafted, kept outside the repo) covering the + symptom, everything ruled out, the CTS/RTS open question, and the + antenna-variant question; paused further live experimentation pending + their reply rather than keep permuting timing parameters blind. - **Still open**: intermittent multi-second HTTP unresponsiveness under load; DAB signal quality still antenna-limited; 24 KB `nvs` partition may be undersized (`saveProfile()` `store_failed` seen intermittently, diff --git a/Software/docs/si4684-rf-investigation-report.md b/Software/docs/si4684-rf-investigation-report.md index 8937890..b1527a7 100644 --- a/Software/docs/si4684-rf-investigation-report.md +++ b/Software/docs/si4684-rf-investigation-report.md @@ -998,3 +998,116 @@ sample proves it improved the success rate. The underlying intermittent root cause (most likely the module's internal, sealed 32MHz crystal startup margin — see the 2026-08-21 entry above) remains unresolved and would need an oscilloscope to pin down further. + +## 2026-08-22 update: RESET#/SYS_CTRL redesign, CTS/RTS diagnostics, +## PinScope report from a sibling project, Feasycom support escalation + +**PinScope findings from a sibling "DigiRadio evolution" project with the +same BT1035 wiring pattern** were reviewed for transferability. Checked +each finding against our own schematic netlist (exact BT1035 pin numbers +cross-referenced against the datasheet's own pin table) rather than +assuming they apply: +- **U16-001 (RESET# pulled to GND by a stray R67) and U16-002 (SYS_CTRL + pulled HIGH by a stray R69): do NOT apply to our board.** Verified via + netlist: our RESET# net (`GPIO17`) has only the ESP32 and the BT1035, + no resistor; our SYS_CTRL pull-down (R12) genuinely goes to GND (pins + 1/22, confirmed GND in the datasheet), not a stray pull-up. +- **U16-003 (VCHG/VCHG_SENSE unconnected): same on our board, presumably + intentional** (no USB charging via the BT1035). +- **U16-004 (RF_OUT/pin 51 floating): also true on our board** — but this + is a genuinely open question, not a confirmed defect: the BT1035 + datasheet documents both an "Internal Antenna" (§9.2, on-board antenna, + no RF_OUT routing needed, PCB keep-out area required instead) and + "External Antenna" (§9.3, RF_OUT routed out) layout option, and we + cannot tell from the datasheet alone which variant this specific module + part/order uses. This affects actual Bluetooth RF range once the module + boots — separate from, and does not explain, the intermittent + total-silence boot symptom (RF_OUT is downstream of the digital + baseband processor that generates the boot banner and answers AT + commands). +- The sibling report's "RESET#/SYS_CTRL/VCHG compound badly" warning + does not transfer to us, since our RESET#/SYS_CTRL wiring is correct. + +**RESET#/SYS_CTRL hardware-management redesign (diagnostic test, +requested explicitly to see if external RESET# control was itself +contributing to the intermittent failures):** +- RESET# (pin 8) is no longer driven by this driver at all: reconfigured + as a floating input (`GPIO_MODE_INPUT`, pull-up/pull-down both + explicitly disabled), relying entirely on the BT1035's own datasheet- + documented "fixed strong pull-up to VDD_IO" (§4.8). GPIO17 confirmed + reading HIGH via this internal pull-up, live. +- SYS_CTRL (pin 34) redesigned to perform a genuine LOW→HIGH power-cycle + on *every* `resetAndInitOnce()` call (held LOW 2.5s — comfortably + longer than the datasheet's "~1.8s typical" Reset Protection timeout — + then HIGH for >=20ms per §4.7), rather than being asserted once ever at + first boot and left alone: previously, every later retry from + `bt1035RetryTask` was silently reusing an already-HIGH SYS_CTRL line + and never actually power-cycling the module at all. +- Both changes build/test clean and were confirmed live to behave exactly + as designed (RESET# reads HIGH via internal pull-up; SYS_CTRL cadence + matches the 2.5s+20ms design on every retry). + +**Result: inconclusive-to-negative on hit rate.** Across a cumulative +~45+ minutes of live monitoring after these changes (two separate +sessions, dozens of retry attempts), **zero successful boots were +observed** — no banner, ever, in this window. This is not better than, +and arguably worse than, the small sample seen the same week under the +*previous* (RESET#-driven) design, which did show at least one clean +success among fewer attempts. This should not be read as proof the new +design is wrong — the previous design also produced a 31-attempt/0-success +streak in one session this same week — but it is also not evidence the +redesign helped. Kept anyway because it is independently correct per the +datasheet (RESET# genuinely can be left unconnected; SYS_CTRL retries +should be genuine power-cycles), not because it demonstrably fixed +anything. + +**New CTS/RTS diagnostic instrumentation.** Discovered, previously +unexamined this entire investigation: the board physically wires +`board::pins::Bt1035Cts` (GPIO21 -> BT1035 pin 15, UART_CTS) and +`board::pins::Bt1035Rts` (BT1035 pin 16, UART_RTS -> GPIO14) — both +**named in `board_pins.hpp` since the pin's original definition, but +never configured or used by any driver code**, and the UART is +initialized with `UART_HW_FLOWCTRL_DISABLE`. Added both as read-only +floating-input diagnostics (`Bt1035Pins::ctsGpio`/`rtsGpio`, logged +before and after the banner-wait window in `resetAndInitOnce()`), purely +to observe — never driven. + +Datasheet research (not just speculation) on what this could mean: +- §4.1 Table 4-1 lists flow control as one of several **configurable** + UART settings ("Supports Automatic Flow Control (CTS and RTS lines)"), + not stated as active by default. +- No AT command to explicitly enable/disable flow control was found in + the programming guide. +- Pin 16 (UART_RTS/PIO2)'s documented **factory-default alternate + function is "PA mute pin"** (`AT+MUTEPIO`'s own default parameter is + PIO2) — i.e., out of the box this pin most likely isn't acting as RTS + at all. +- Live readings, across ~12 samples over 30+ minutes and multiple + power-cycles: **CTS=HIGH, RTS=LOW, perfectly stable, zero variation.** + This argues against a genuinely floating/noisy input (which would be + expected to show at least some jitter across dozens of samples) — + something is holding both at a fixed level, whether that's incidental + ESP32 GPIO leakage, an internal pull inside the module, or the module + actively driving its own RTS/PA_MUTE output. We do not yet have a + reading from a *successful* boot to compare against, since none + occurred in this session's remaining test window. + +**Escalated to Feasycom support** (email drafted, not yet sent by the +user) with: the full symptom description, everything ruled out this +session (power rails, RESET#/SYS_CTRL sequencing variants), the CTS/RTS +finding reframed as an open question rather than a confirmed cause (per +the datasheet nuance above), and the RF_OUT/antenna-variant question. +Decided to pause further live hardware experimentation until a reply is +received, rather than keep varying RESET#/SYS_CTRL/timing parameters +without new information — see the email draft (kept outside the repo, in +the session's scratch directory) for the exact wording sent. + +**How to apply, for a future session**: don't re-propose "try removing +RESET# drive" or "try a genuine SYS_CTRL power-cycle on retry" as fresh +ideas — both were tried this session, both are justified independently, +neither showed a measurable improvement in a non-trivial sample. Don't +assume CTS/RTS floating is confirmed as the cause either — the CTS=1/ +RTS=0 stability argues against pure floating-noise, and flow control may +not even be engaged by default per the datasheet. The single most +valuable next input is Feasycom's own answer, not another round of +timing-parameter permutation. diff --git a/Software/main/hardware_bootstrap.cpp b/Software/main/hardware_bootstrap.cpp index c2cbf71..5e11de0 100644 --- a/Software/main/hardware_bootstrap.cpp +++ b/Software/main/hardware_bootstrap.cpp @@ -79,6 +79,8 @@ bt1035::Bt1035Driver gBt1035( .uartRx = board::pins::Bt1035UartRx, .resetGpio = board::pins::Bt1035Reset, .sysCtlGpio = board::pins::Bt1035SysCtl, + .ctsGpio = board::pins::Bt1035Cts, + .rtsGpio = board::pins::Bt1035Rts, }); core::DeviceIdentity gDeviceIdentity = core::DeviceIdentity::unknown();