diff --git a/README.md b/README.md index 3b47d4d..2107e43 100644 --- a/README.md +++ b/README.md @@ -73,20 +73,20 @@ cd west init -l zephcore west update -# Companion (with logging) +# Companion (production — default, no extra conf needed) west build -b wio_tracker_l1 zephcore --pristine -# Companion (production, no logging, no USB) +# Companion (debug logging) west build -b wio_tracker_l1 zephcore --pristine -- \ - -DEXTRA_CONF_FILE="boards/common/prod.conf" + -DEXTRA_CONF_FILE="boards/common/debug.conf" -# Repeater (with logging) +# Repeater west build -b rak4631 zephcore --pristine -- \ -DEXTRA_CONF_FILE="boards/common/repeater.conf" -# Repeater (production) +# Repeater (debug logging) west build -b rak4631 zephcore --pristine -- \ - -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/prod.conf" + -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/debug.conf" # Repeater with packet logging (clean RAW/RX/TX lines only, no debug spam) west build -b rak4631 zephcore --pristine -- \ @@ -175,7 +175,7 @@ The old `txdelay`, `rxdelay`, and `direct.txdelay` commands are still accepted f - **LoRa RX duty cycle**: CAD-based receive windowing reduces LoRa RX current from ~10-15mA to ~3-5mA. Auto-enabled for SX1262 companion and repeater builds (toggleable at runtime via `set rxduty on/off`). Disabled for LR1110 (mid-preamble lock issue) and SX127x. - **Adaptive Power Control (APC)**: compiled in by default but disabled at runtime. Enable per-node with `set tx apc` -- automatically reduces TX power when echo SNR shows excess margin, ramping back up if echoes drop. See [apc.md](zephcore/apc.md). -- **USB disabled in production**: Saves ~2-5mA and 62KB flash when logging is off +- **Production by default**: No logging, no asserts, reboot-on-fatal. Add `debug.conf` to enable logging. - **GPIO-gated GPS**: Powered on only during fix acquisition ## Configuration diff --git a/build.sh b/build.sh index ef4343a..649478c 100644 --- a/build.sh +++ b/build.sh @@ -37,19 +37,19 @@ if [[ $1 == "nrf" ]]; then for board in "${nRF_boards[@]}"; do board_clean_for_path=$(echo "$board" | sed -e 's/\//-/g') - # build nRF companions + # build nRF companions (production is the default — no extra conf needed) echo "Now building $board companion" if [[ $board == "wio_tracker_l1" ]]; then - west build -b "$board" zephcore --pristine -- -DEXTRA_CONF_FILE="boards/common/prod.conf" -DCONFIG_ZEPHCORE_EASTER_EGG_DOOM=y + west build -b "$board" zephcore --pristine -- -DCONFIG_ZEPHCORE_EASTER_EGG_DOOM=y else - west build -b "$board" zephcore --pristine -- -DEXTRA_CONF_FILE="boards/common/prod.conf" + west build -b "$board" zephcore --pristine fi mv build/zephyr/zephyr.uf2 firmware/"$board"-companion-"$COMMIT_HASH".uf2 mv build/zephyr/zephyr.zip firmware/"$board"-companion-"$COMMIT_HASH".zip - + # build nRF repeaters echo "Now building $board repeater" - west build -b "$board" zephcore --pristine -- -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/prod.conf" + west build -b "$board" zephcore --pristine -- -DEXTRA_CONF_FILE="boards/common/repeater.conf" mv build/zephyr/zephyr.uf2 firmware/"$board"-repeater-"$COMMIT_HASH".uf2 mv build/zephyr/zephyr.zip firmware/"$board"-repeater-"$COMMIT_HASH".zip @@ -58,12 +58,12 @@ if [[ $1 == "nrf" ]]; then # Heltec_t114_without_display_* PIO envs. if [[ $board == "heltec_t114" ]]; then echo "Now building $board companion (noscreen)" - west build -b "$board" zephcore --pristine -- -DEXTRA_CONF_FILE="boards/common/prod.conf;boards/nrf52840/heltec_t114/no_display.conf" + west build -b "$board" zephcore --pristine -- -DEXTRA_CONF_FILE="boards/nrf52840/heltec_t114/no_display.conf" mv build/zephyr/zephyr.uf2 firmware/"$board"-companion-noscreen-"$COMMIT_HASH".uf2 mv build/zephyr/zephyr.zip firmware/"$board"-companion-noscreen-"$COMMIT_HASH".zip echo "Now building $board repeater (noscreen)" - west build -b "$board" zephcore --pristine -- -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/prod.conf;boards/nrf52840/heltec_t114/no_display.conf" + west build -b "$board" zephcore --pristine -- -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/nrf52840/heltec_t114/no_display.conf" mv build/zephyr/zephyr.uf2 firmware/"$board"-repeater-noscreen-"$COMMIT_HASH".uf2 mv build/zephyr/zephyr.zip firmware/"$board"-repeater-noscreen-"$COMMIT_HASH".zip fi @@ -82,9 +82,9 @@ if [[ $1 == "esp32" ]]; then fi if [[ $2 == "companions" ]]; then - # build ESP32 companions + # build ESP32 companions (production is the default) echo "Now building $board companion" - west build -b "$board" zephcore --pristine --sysbuild -- -DEXTRA_CONF_FILE="boards/common/prod.conf" + west build -b "$board" zephcore --pristine --sysbuild FLASH_SIZE=$( python3 -c ' import re @@ -127,7 +127,7 @@ print(str(size // 1048576) + "MB") if [[ $2 == "repeaters" ]]; then # build ESP32 repeaters echo "Now building $board repeater" - west build -b "$board" zephcore --pristine --sysbuild -- -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/prod.conf" + west build -b "$board" zephcore --pristine --sysbuild -- -DEXTRA_CONF_FILE="boards/common/repeater.conf" FLASH_SIZE=$( python3 -c ' import re diff --git a/zephcore/ARCHITECTURE.md b/zephcore/ARCHITECTURE.md index 3395486..1ea2fc6 100644 --- a/zephcore/ARCHITECTURE.md +++ b/zephcore/ARCHITECTURE.md @@ -511,11 +511,11 @@ Wolf3D-style raycaster on OLED: textured walls, 2 enemy types, shooting, HUD. By ### 9.1 Config Layering ``` -prj.conf (base: console, logging) +prj.conf (base: console; production defaults — LOG=n, ASSERT=n) → boards/common/zephcore_common.conf (ALL boards: BLE, crypto, FS, LoRa, sensors) → boards/common/_common.conf (nrf52/esp32/nrf54l/mg24 specifics) → boards///board.conf (board-specific pins, features) - → [optional] repeater.conf, prod.conf (user extras, LAST = highest priority) + → [optional] repeater.conf, debug.conf (user extras, LAST = highest priority) ``` ### 9.2 Key Kconfig Choices diff --git a/zephcore/CMakeLists.txt b/zephcore/CMakeLists.txt index b58a9b2..668ce0b 100644 --- a/zephcore/CMakeLists.txt +++ b/zephcore/CMakeLists.txt @@ -306,17 +306,7 @@ if(ZEPHCORE_BOARD_CONF AND EXISTS ${ZEPHCORE_BOARD_CONF}) zephcore_auto_pair_overlay("${ZEPHCORE_BOARD_CONF}") endif() -# Include logging tuning only for debug builds (when prod.conf is NOT active). -# This avoids Kconfig warnings from log settings that depend on CONFIG_LOG. -set(ZEPHCORE_LOGGING_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/logging.conf") -if(EXTRA_CONF_FILE MATCHES "prod\\.conf") - message(STATUS " Production build — skipping logging.conf") -else() - if(EXISTS ${ZEPHCORE_LOGGING_CONF}) - list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_LOGGING_CONF}) - zephcore_auto_pair_overlay("${ZEPHCORE_LOGGING_CONF}") - endif() -endif() +# Production is the default. Debug logging is opt-in via debug.conf. # Auto-include WiFi OTA for ESP32 repeater builds. # Enables WiFi AP + HTTP server + MCUboot image management. diff --git a/zephcore/Kconfig b/zephcore/Kconfig index 6194f25..75f9450 100644 --- a/zephcore/Kconfig +++ b/zephcore/Kconfig @@ -66,6 +66,27 @@ config ZEPHCORE_ROLE_COMPANION BLE companion device that connects to ZephCore mobile app. Includes BLE stack, contact management, offline queue, channels. +config ZEPHCORE_COMPANION_USB + bool "Enable USB CDC companion transport" + depends on USB_DEVICE_STACK_NEXT + default y if ZEPHCORE_ROLE_COMPANION + help + Compile and init the USB CDC-ACM companion protocol stack + (ZephyrCompanionUSB / ZephyrUSBCDC) independently of CONFIG_LOG. + The full ~60-opcode binary protocol is available over USB even in + production builds (CONFIG_LOG=n). USB and BLE arbitrate the active + interface first-come-first-served: CMD_APP_START claims it for USB only + if no BLE session is already active (and is ignored while BLE is live); + on USB unplug the interface is handed back to BLE or released to idle. + + Enabled by default for all companion builds on platforms that already + have CONFIG_USB_DEVICE_STACK_NEXT=y (all nRF52840 boards). On + platforms without USB hardware (MG24, ESP32 classic, C3/C6) the + depends forces this to n automatically — no conf change needed. + + For ESP32-S3 boards, USB_DEVICE_STACK_NEXT must be enabled first via + boards/common/esp32s3_usb.conf before this becomes active. + config ZEPHCORE_ROLE_REPEATER bool "Repeater" help diff --git a/zephcore/adapters/ble/ZephyrBLE.cpp b/zephcore/adapters/ble/ZephyrBLE.cpp index 89065b4..8818730 100644 --- a/zephcore/adapters/ble/ZephyrBLE.cpp +++ b/zephcore/adapters/ble/ZephyrBLE.cpp @@ -122,6 +122,44 @@ static int64_t ble_tx_start_time = 0; /* Active interface tracking */ static enum zephcore_iface active_iface = ZEPHCORE_IFACE_NONE; +/* active_iface is mutated from two threads — the Bluetooth callback thread + * (connect / security / pairing / disconnect) and the USB workqueue + * (CMD_APP_START / DTR drop). This mutex makes the check-then-act claim and + * release sequences atomic so the two transports can never both believe they + * own the interface (or lose a write to it). */ +K_MUTEX_DEFINE(ble_iface_lock); + +/* Atomically claim the interface for `who` iff it is currently idle. + * Returns true only if this call performed the NONE -> who transition. */ +static bool iface_claim_if_idle(enum zephcore_iface who) +{ + k_mutex_lock(&ble_iface_lock, K_FOREVER); + bool claimed = (active_iface == ZEPHCORE_IFACE_NONE); + if (claimed) { + active_iface = who; + } + k_mutex_unlock(&ble_iface_lock); + return claimed; +} + +/* Atomically release the interface if `who` currently owns it. */ +static void iface_release(enum zephcore_iface who) +{ + k_mutex_lock(&ble_iface_lock, K_FOREVER); + if (active_iface == who) { + active_iface = ZEPHCORE_IFACE_NONE; + } + k_mutex_unlock(&ble_iface_lock); +} + +/* Claim the active interface for BLE, but only if nothing else owns it. + * First-come-first-served: a live USB session must not be evicted by BLE + * connecting/pairing in the background. Returns true if BLE now owns it. */ +static bool ble_claim_iface_if_idle(void) +{ + return iface_claim_if_idle(ZEPHCORE_IFACE_BLE); +} + /* DLE tracking — set after successful DLE request to avoid double-request */ static bool dle_requested; @@ -298,6 +336,16 @@ static void connected(struct bt_conn *conn, uint8_t err) return; } LOG_INF("connected: %s", addr); + + /* Reject BLE connections while USB is the active transport. + * The user connected via BLE expecting to exchange messages, but + * USB owns the interface — they would get nothing and be confused. */ + if (active_iface == ZEPHCORE_IFACE_USB) { + LOG_INF("connected: USB active — rejecting BLE connection from %s", addr); + bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + return; + } + current_conn = bt_conn_ref(conn); /* Zephyr stops advertising internally when the conn slot is consumed @@ -349,9 +397,7 @@ static void disconnected(struct bt_conn *conn, uint8_t reason) conn_params_pending = false; /* Clear interface state if BLE was active */ - if (active_iface == ZEPHCORE_IFACE_BLE) { - active_iface = ZEPHCORE_IFACE_NONE; - } + iface_release(ZEPHCORE_IFACE_BLE); /* Clear queues, retry state, and congestion */ k_msgq_purge(&ble_send_queue); @@ -397,7 +443,7 @@ static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_ if (level >= BT_SECURITY_L2 && !ble_tx_ready) { LOG_INF("security established, enabling TX"); ble_tx_ready = true; - active_iface = ZEPHCORE_IFACE_BLE; + ble_claim_iface_if_idle(); /* If CCC was already subscribed (bonded reconnect — phone writes * CCC before security_changed fires), kick TX now. On fresh @@ -532,14 +578,12 @@ static void pairing_complete(struct bt_conn *conn, bool bonded) ARG_UNUSED(conn); LOG_INF("pairing complete: bonded=%d", bonded); - /* Switch to BLE interface (fresh pairing only). - * Conn params and TX enable are handled in security_changed(), - * which fires for both fresh pairing and bonded reconnects. */ - if (active_iface == ZEPHCORE_IFACE_USB) { - LOG_INF("switching from USB to BLE"); - /* Main handles USB state clearing via on_connected callback */ + if (ble_claim_iface_if_idle()) { + LOG_INF("pairing complete, activating BLE interface"); + } else { + LOG_INF("pairing complete, %s already active — BLE paired but not promoted", + active_iface == ZEPHCORE_IFACE_USB ? "USB" : "BLE"); } - active_iface = ZEPHCORE_IFACE_BLE; } static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason) @@ -985,12 +1029,28 @@ uint32_t zephcore_ble_get_passkey(void) enum zephcore_iface zephcore_ble_get_active_iface(void) { - return active_iface; + k_mutex_lock(&ble_iface_lock, K_FOREVER); + enum zephcore_iface iface = active_iface; + k_mutex_unlock(&ble_iface_lock); + return iface; } void zephcore_ble_set_active_iface(enum zephcore_iface iface) { + k_mutex_lock(&ble_iface_lock, K_FOREVER); active_iface = iface; + k_mutex_unlock(&ble_iface_lock); +} + +bool zephcore_ble_iface_try_claim(enum zephcore_iface who) +{ + k_mutex_lock(&ble_iface_lock, K_FOREVER); + bool ok = (active_iface == ZEPHCORE_IFACE_NONE || active_iface == who); + if (ok) { + active_iface = who; + } + k_mutex_unlock(&ble_iface_lock); + return ok; } struct k_msgq *zephcore_ble_get_recv_queue(void) diff --git a/zephcore/adapters/ble/ZephyrBLE.h b/zephcore/adapters/ble/ZephyrBLE.h index f8a18ab..b1df197 100644 --- a/zephcore/adapters/ble/ZephyrBLE.h +++ b/zephcore/adapters/ble/ZephyrBLE.h @@ -65,10 +65,17 @@ bool zephcore_ble_is_advertising(void); void zephcore_ble_set_passkey(uint32_t passkey); uint32_t zephcore_ble_get_passkey(void); -/** Get/set active interface (BLE/USB coexistence). */ +/** Get/set active interface (BLE/USB coexistence). Both are thread-safe — + * active_iface is mutated from the BLE callback thread and the USB workqueue. */ enum zephcore_iface zephcore_ble_get_active_iface(void); void zephcore_ble_set_active_iface(enum zephcore_iface iface); +/** Atomically claim the active interface for `who` unless the other transport + * already owns it. Succeeds (returns true) if the interface is idle or already + * held by `who`; fails if a different interface is active. Thread-safe — use + * this instead of a get-then-set sequence to avoid a check-then-act race. */ +bool zephcore_ble_iface_try_claim(enum zephcore_iface who); + /** Get recv/send queues for USB path sharing. */ struct k_msgq *zephcore_ble_get_recv_queue(void); struct k_msgq *zephcore_ble_get_send_queue(void); diff --git a/zephcore/adapters/usb/ZephyrCompanionUSB.cpp b/zephcore/adapters/usb/ZephyrCompanionUSB.cpp index 5a1a64a..f535b6b 100644 --- a/zephcore/adapters/usb/ZephyrCompanionUSB.cpp +++ b/zephcore/adapters/usb/ZephyrCompanionUSB.cpp @@ -115,13 +115,17 @@ static void usb_rx_work_fn(struct k_work *work) * is actually CMD_SEND_CHANNEL_TXT_MSG and meant the USB * handshake silently dropped the app's first frame.) */ if (payload_len >= 1 && payload[0] == 0x01 /* CMD_APP_START */) { - if (zephcore_ble_get_active_iface() == ZEPHCORE_IFACE_BLE && - zephcore_ble_is_connected()) { - LOG_INF("usb_rx: CMD_APP_START, disconnecting BLE"); - zephcore_ble_disconnect(); + /* Atomically claim the interface for USB unless BLE + * already owns it. try_claim succeeds when idle or + * already USB (reconnect) and fails only while a BLE + * session is live, so USB can't steal it — and the + * compare-and-set can't race a concurrent BLE claim. */ + if (zephcore_ble_iface_try_claim(ZEPHCORE_IFACE_USB)) { + zephcore_ble_set_enabled(false); + LOG_INF("usb_rx: CMD_APP_START → IFACE_USB"); + } else { + LOG_INF("usb_rx: CMD_APP_START ignored, BLE is active"); } - zephcore_ble_set_active_iface(ZEPHCORE_IFACE_USB); - LOG_INF("usb_rx: active_iface = IFACE_USB"); } /* Only process if USB is active interface */ @@ -155,9 +159,21 @@ static void on_dtr_change(bool dtr_active) return; } LOG_INF("usb_dtr: DTR dropped, USB disconnected"); + /* While USB owns the interface, BLE claims are rejected (see connected()), + * so this thread is the only writer of active_iface here — the get/set + * pair below needs no extra locking beyond the thread-safe accessors. */ if (zephcore_ble_get_active_iface() == ZEPHCORE_IFACE_USB) { - zephcore_ble_set_active_iface(ZEPHCORE_IFACE_NONE); - LOG_INF("usb_dtr: active_iface = IFACE_NONE"); + if (zephcore_ble_is_connected()) { + /* BLE client is physically connected — hand off to it. */ + zephcore_ble_set_active_iface(ZEPHCORE_IFACE_BLE); + LOG_INF("usb_dtr: → IFACE_BLE (BLE was connected)"); + } else { + /* Nobody connected — go idle and restart advertising + * so the phone can find the companion again. */ + zephcore_ble_set_active_iface(ZEPHCORE_IFACE_NONE); + zephcore_ble_set_enabled(true); + LOG_INF("usb_dtr: → IFACE_NONE, BLE advertising restarted"); + } } ring_buf_reset(&usb_ring_buf); usb_frame_len = 0; diff --git a/zephcore/boards/common/debug.conf b/zephcore/boards/common/debug.conf new file mode 100644 index 0000000..261c2ac --- /dev/null +++ b/zephcore/boards/common/debug.conf @@ -0,0 +1,40 @@ +# Debug Config — opt-in logging and diagnostics. +# +# Production is the default. Pass this conf to get the full debug experience: +# +# west build -b rak4631 zephcore --pristine -- \ +# -DEXTRA_CONF_FILE="boards/common/debug.conf" +# +# # With repeater role: +# west build -b rak4631 zephcore --pristine -- \ +# -DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/debug.conf" + +# ========== Core debug features ========== +CONFIG_LOG=y +CONFIG_ASSERT=y +CONFIG_USE_SEGGER_RTT=y +CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=4096 +CONFIG_THREAD_NAME=y + +# Halt-and-inspect on fatal faults instead of the production reboot-on-fatal +# (prj.conf sets CONFIG_RESET_ON_FATAL_ERROR=y). A debugger needs the core +# stopped at the fault with a stack dump, not rebooted out from under it. +CONFIG_RESET_ON_FATAL_ERROR=n + +# ========== Log tuning ========== +# ZephCore BLE modules at INF level (connection lifecycle, pairing, TX/RX). +CONFIG_ZEPHCORE_BLE_LOG_LEVEL_INF=y + +# Deferred logging — non-blocking so BLE/LoRa timing is not disturbed. +CONFIG_LOG_BUFFER_SIZE=8192 +CONFIG_LOG_PROCESS_TRIGGER_THRESHOLD=1 +CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=2048 + +# RTT backend only — no UART backend on nRF52. +# UART backend stalls on uart_poll_out() when the USB host disconnects; +# RTT DROP mode discards messages without blocking. +CONFIG_LOG_BACKEND_RTT=y +CONFIG_LOG_BACKEND_RTT_MODE_DROP=y + +# Silence noisy subsystems +CONFIG_CFB_LOG_LEVEL_ERR=y diff --git a/zephcore/boards/common/esp32s3_console_uart0.dtsi b/zephcore/boards/common/esp32s3_console_uart0.dtsi new file mode 100644 index 0000000..deb4bc2 --- /dev/null +++ b/zephcore/boards/common/esp32s3_console_uart0.dtsi @@ -0,0 +1,45 @@ +/* + * Reroute console + shell off USB Serial/JTAG onto uart0 (physical TX/RX). + * + * ESP32-S3 USB Serial/JTAG shares the D+/D- pins with USB OTG and only one + * controller can own the port at a time. Boards that default their console to + * USB Serial/JTAG include this alongside esp32s3_usb_otg.dtsi so that, when + * esp32s3_usb.conf disables USB Serial/JTAG and hands the port to USB OTG, the + * console still has a home. Boards with a dedicated UART console (e.g. Heltec) + * do not need this. + * + * uart0 is disabled by default in the ESP32-S3 SoC dtsi, so we enable it and + * pin it to the chip's ROM-default UART0 pads (GPIO43 TX / GPIO44 RX) — the + * same pins Heltec uses. Without this the console would point at a disabled, + * un-pinned node and produce no output at all. + */ +#include +#include +#include + +&pinctrl { + uart0_default: uart0_default { + group1 { + pinmux = ; + output-high; + }; + group2 { + pinmux = ; + bias-pull-up; + }; + }; +}; + +&uart0 { + status = "okay"; + current-speed = <115200>; + pinctrl-0 = <&uart0_default>; + pinctrl-names = "default"; +}; + +/ { + chosen { + zephyr,console = &uart0; + zephyr,shell-uart = &uart0; + }; +}; diff --git a/zephcore/boards/common/esp32s3_usb.conf b/zephcore/boards/common/esp32s3_usb.conf new file mode 100644 index 0000000..9b76185 --- /dev/null +++ b/zephcore/boards/common/esp32s3_usb.conf @@ -0,0 +1,24 @@ +# ESP32-S3 USB CDC-ACM companion support +# +# ESP32-S3 has a DWC2 USB OTG peripheral (snps,dwc2 compatible) that supports +# the Zephyr USB NEXT stack. This conf enables it and turns on the companion +# USB transport on S3 boards. Do NOT use on ESP32 (original) or C3/C6 — those +# chips have no DWC2 controller. +# +# Build with: +# -DEXTRA_CONF_FILE="boards/common/esp32s3_usb.conf" +# +# Also add the cdc_acm_uart DTS node to the board overlay (the overlays include +# boards/common/esp32s3_usb_otg.dtsi). Without the DT node, +# DT_HAS_COMPAT_STATUS_OKAY(zephyr_cdc_acm_uart) is false and +# zephcore_usbd_init() is compiled out at the #if check. + +CONFIG_USB_DEVICE_STACK_NEXT=y +CONFIG_ZEPHCORE_COMPANION_USB=y +CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=n +CONFIG_UART_LINE_CTRL=y + +# Disable USB Serial/JTAG so it doesn't fight USB OTG for the D+/D- pins. +# ESP32-S3 has an internal mux — only one controller can own the port at a time. +# With USB companion active, the port IS the companion protocol channel. +CONFIG_ESP_USB_SERIAL_JTAG_ENABLED=n diff --git a/zephcore/boards/common/esp32s3_usb_otg.dtsi b/zephcore/boards/common/esp32s3_usb_otg.dtsi new file mode 100644 index 0000000..1e8dbcb --- /dev/null +++ b/zephcore/boards/common/esp32s3_usb_otg.dtsi @@ -0,0 +1,21 @@ +/* + * ESP32-S3 USB OTG (DWC2) — companion USB transport. + * + * Enables the snps,dwc2 USB OTG controller and attaches a CDC-ACM UART node + * for the binary companion protocol. The DWC2 driver is only compiled when + * CONFIG_USB_DEVICE_STACK_NEXT=y (set by boards/common/esp32s3_usb.conf); + * without that conf this node is inert. + * + * Boards whose default console rides USB Serial/JTAG (shares D+/D- with USB + * OTG) must also reroute the console off the shared pins — see + * esp32s3_console_uart0.dtsi. + */ +zephyr_udc0: &usb_otg { + status = "okay"; +}; + +&zephyr_udc0 { + cdc_acm_uart: cdc_acm_uart { + compatible = "zephyr,cdc-acm-uart"; + }; +}; diff --git a/zephcore/boards/common/logging.conf b/zephcore/boards/common/logging.conf deleted file mode 100644 index c828825..0000000 --- a/zephcore/boards/common/logging.conf +++ /dev/null @@ -1,23 +0,0 @@ -# Logging Tuning — included automatically for debug builds only. -# Prod builds (CONFIG_LOG=n via prod.conf) skip this file entirely, -# avoiding Kconfig warnings from log settings that depend on CONFIG_LOG. - -# ZephCore BLE modules at INF level (connection lifecycle, pairing, TX/RX) -# Default level is WRN — this ensures our INF messages come through. -# Avoid DBG level — it causes system workqueue stack overflow. -CONFIG_ZEPHCORE_BLE_LOG_LEVEL_INF=y - -# Deferred mode (non-blocking) — buffer/thread config -CONFIG_LOG_BUFFER_SIZE=8192 -CONFIG_LOG_PROCESS_TRIGGER_THRESHOLD=1 -CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=2048 - -# Backends -# NOTE: UART backend on nRF52 routes through USB CDC ACM (zephyr,console = &cdc_acm_uart). -# There is no LOG_BACKEND_UART_MODE_DROP in Zephyr — if the USB host disconnects, the log -# processing thread stalls on uart_poll_out(). Disable UART backend on nRF52 to avoid this; -# RTT (below) is the reliable debug path and already configured with drop mode. -CONFIG_LOG_BACKEND_RTT=y - -# Suppress noisy subsystem logs -CONFIG_CFB_LOG_LEVEL_ERR=y diff --git a/zephcore/boards/common/mg24_common.conf b/zephcore/boards/common/mg24_common.conf index 8e7b4ef..9d07191 100644 --- a/zephcore/boards/common/mg24_common.conf +++ b/zephcore/boards/common/mg24_common.conf @@ -16,10 +16,13 @@ CONFIG_BT_TX_PROCESSOR_STACK_SIZE=2048 # Controller assert handler — without this, a controller assert silently # freezes the CPU at highest IRQ priority with zero log output. +# Harmless when CONFIG_ASSERT=n, so it stays on for prod and debug. CONFIG_BT_CTLR_ASSERT_HANDLER=y # ========== Debug ========== -CONFIG_ASSERT=y +# CONFIG_ASSERT is debug-only (boards/common/debug.conf, opt-in). Setting it +# here would override the prj.conf production default (CONFIG_ASSERT=n) on every +# MG24 build, since this file loads after prj.conf. # ========== Heap ========== # Silabs BLE stack requires larger heap (override zephcore_common 2KB default) diff --git a/zephcore/boards/common/nrf52_common.conf b/zephcore/boards/common/nrf52_common.conf index eb0ce10..588cd07 100644 --- a/zephcore/boards/common/nrf52_common.conf +++ b/zephcore/boards/common/nrf52_common.conf @@ -44,11 +44,12 @@ CONFIG_SOC_FLASH_NRF_PARTIAL_ERASE=y # Silently ignored when QSPI is not present (Kconfig guard: if NORDIC_QSPI_NOR). CONFIG_NORDIC_QSPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096 -# ========== SEGGER RTT (J-Link debug) ========== -# RTT log backend is in boards/common/logging.conf (only included for debug builds). -CONFIG_USE_SEGGER_RTT=y -CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=4096 - # ========== Debug ========== -CONFIG_ASSERT=y +# SEGGER RTT and CONFIG_ASSERT are debug-only and live in boards/common/debug.conf +# (opt-in). Keeping them here would override the production defaults from +# prj.conf (CONFIG_USE_SEGGER_RTT=n, CONFIG_ASSERT=n) on every nRF52 build, +# since this file loads after prj.conf. +# +# BT_CTLR_ASSERT_HANDLER is harmless when CONFIG_ASSERT=n (no RAM/flash cost, +# the handler is simply never invoked) so it stays on for both prod and debug. CONFIG_BT_CTLR_ASSERT_HANDLER=y diff --git a/zephcore/boards/common/nrf54l_common.conf b/zephcore/boards/common/nrf54l_common.conf index 756a255..67e5989 100644 --- a/zephcore/boards/common/nrf54l_common.conf +++ b/zephcore/boards/common/nrf54l_common.conf @@ -23,11 +23,9 @@ CONFIG_BT_CTLR_DATA_LENGTH_MAX=251 # Coded PHY — enable in nRF LL controller (host auto-request in zephcore_common.conf) CONFIG_BT_CTLR_PHY_CODED=y -# ========== SEGGER RTT (J-Link / CMSIS-DAP debug) ========== -CONFIG_LOG_BACKEND_RTT=y -CONFIG_USE_SEGGER_RTT=y -CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=4096 - # ========== Debug ========== -CONFIG_ASSERT=y +# SEGGER RTT and CONFIG_ASSERT are debug-only and live in boards/common/debug.conf +# (opt-in). Keeping them here would override the production defaults from +# prj.conf on every nRF54L build, since this file loads after prj.conf. +# BT_CTLR_ASSERT_HANDLER is harmless when CONFIG_ASSERT=n, so it stays on. CONFIG_BT_CTLR_ASSERT_HANDLER=y diff --git a/zephcore/boards/common/prod.conf b/zephcore/boards/common/prod.conf deleted file mode 100644 index 7ff951a..0000000 --- a/zephcore/boards/common/prod.conf +++ /dev/null @@ -1,22 +0,0 @@ -# Production Config - Common to ALL boards (Companion & Repeater) -# Build: west build -b zephcore --pristine -- \ -# -DEXTRA_CONF_FILE="boards/common/prod.conf" -# -# Disables logging for smaller binary and lower power consumption. -# USB CDC still works for CLI commands on repeaters. - -# Disable logging subsystem (saves ~62KB flash, ~2-5mA power) -CONFIG_LOG=n - -# Disable asserts (saves flash, removes CMake warning) -CONFIG_ASSERT=n - -# Disable SEGGER RTT (saves ~4KB RAM ring buffer + init code) -CONFIG_USE_SEGGER_RTT=n - -# Disable thread name strings (saves flash — only useful for debug) -CONFIG_THREAD_NAME=n - -# Max contacts: Kconfig default is 350 — fits nRF52840 RAM with ~10% headroom -# and is safe for 100KB ExtraFS. RAM-limited boards (xiao_nrf54l15=450, -# xiao_esp32c3=300) override in their own board.conf. Do not set here. diff --git a/zephcore/boards/common/zephcore_common.conf b/zephcore/boards/common/zephcore_common.conf index df18959..667e0e6 100644 --- a/zephcore/boards/common/zephcore_common.conf +++ b/zephcore/boards/common/zephcore_common.conf @@ -29,7 +29,6 @@ CONFIG_PSA_WANT_KEY_TYPE_HMAC=y # ========== Stack Protection ========== CONFIG_HW_STACK_PROTECTION=y -CONFIG_THREAD_NAME=y # ========== Hardware Basics ========== CONFIG_ADC=y @@ -195,10 +194,7 @@ CONFIG_INPUT_EVENT_DUMP=n CONFIG_INPUT_MODE_SYNCHRONOUS=y # ========== Logging ========== -# RTT log backend: DROP mode (not BLOCK) — when no RTT host is connected the -# buffer is always full; BLOCK mode sleeps 20ms per message with irq_lock held, -# disrupting BLE timing and cascading into buffer exhaustion deadlocks. -CONFIG_LOG_BACKEND_RTT_MODE_DROP=y +# Log backend tuning lives in boards/common/debug.conf (opt-in). # Silence USB CH9 control-transfer spam (dozens of INF lines during enumeration). # WRN keeps real USB errors visible. diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.overlay b/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.overlay index f51dc54..ce6e5c9 100644 --- a/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.overlay +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.overlay @@ -101,3 +101,6 @@ /* LittleFS auto-mount — standard /lfs mount point */ #include "../../common/filesystem.dtsi" +/* USB CDC-ACM — companion USB transport (esp32s3_usb.conf). Heltec has a + * dedicated UART console, so no console reroute is needed. */ +#include "../../common/esp32s3_usb_otg.dtsi" diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.overlay b/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.overlay index 6e42ee2..7047afd 100644 --- a/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.overlay +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.overlay @@ -101,3 +101,6 @@ /* LittleFS auto-mount — standard /lfs mount point */ #include "../../common/filesystem.dtsi" +/* USB CDC-ACM — companion USB transport (esp32s3_usb.conf). Heltec has a + * dedicated UART console, so no console reroute is needed. */ +#include "../../common/esp32s3_usb_otg.dtsi" diff --git a/zephcore/boards/esp32/station_g2/board.overlay b/zephcore/boards/esp32/station_g2/board.overlay index 8b8174e..16b14a8 100644 --- a/zephcore/boards/esp32/station_g2/board.overlay +++ b/zephcore/boards/esp32/station_g2/board.overlay @@ -109,3 +109,8 @@ /* LittleFS auto-mount — standard /lfs mount point */ #include "../../common/filesystem.dtsi" + +/* USB OTG companion transport. station_g2 consoles over USB Serial/JTAG, which + * shares D+/D- with USB OTG, so the console is rerouted to uart0. */ +#include "../../common/esp32s3_console_uart0.dtsi" +#include "../../common/esp32s3_usb_otg.dtsi" diff --git a/zephcore/boards/esp32/xiao_esp32s3/board.overlay b/zephcore/boards/esp32/xiao_esp32s3/board.overlay index 3ef121f..d433717 100644 --- a/zephcore/boards/esp32/xiao_esp32s3/board.overlay +++ b/zephcore/boards/esp32/xiao_esp32s3/board.overlay @@ -86,3 +86,8 @@ /* LittleFS auto-mount — standard /lfs mount point */ #include "../../common/filesystem.dtsi" + +/* USB OTG companion transport. Consoles over USB Serial/JTAG (shares D+/D- + * with USB OTG), so the console is rerouted to uart0. */ +#include "../../common/esp32s3_console_uart0.dtsi" +#include "../../common/esp32s3_usb_otg.dtsi" diff --git a/zephcore/boards/nrf52840/xiao_nrf52840/board.conf b/zephcore/boards/nrf52840/xiao_nrf52840/board.conf index 9103fc0..db0b700 100644 --- a/zephcore/boards/nrf52840/xiao_nrf52840/board.conf +++ b/zephcore/boards/nrf52840/xiao_nrf52840/board.conf @@ -7,7 +7,7 @@ CONFIG_BT_DIS_MODEL_NUMBER_STR="Seeed XIAO nRF52840" CONFIG_ZEPHCORE_SD_FWID=0x0123 -# Default debug (logging.conf) sends LOG_* to RTT only. Plain XIAO is usually +# Default debug (debug.conf) sends LOG_* to RTT only. Plain XIAO is usually # flashed/debugged over USB without an SWD J-Link — enable UART backend so # LOG_* appears on the CDC ACM serial port (same as zephyr,console). # RTT remains enabled for J-Link; use DROP mode (see zephcore_common.conf). diff --git a/zephcore/prj.conf b/zephcore/prj.conf index 474cb97..51fbf55 100644 --- a/zephcore/prj.conf +++ b/zephcore/prj.conf @@ -16,10 +16,16 @@ CONFIG_UART_CONSOLE=y CONFIG_UART_INTERRUPT_DRIVEN=y CONFIG_RING_BUFFER=y -# Logging subsystem — enabled by default (prod.conf disables with CONFIG_LOG=n). -# Log tuning (buffer size, backends, etc.) is in boards/common/logging.conf, -# which CMake auto-includes only when prod.conf is NOT active. -CONFIG_LOG=y +# Production by default — add boards/common/debug.conf to enable logging. +CONFIG_LOG=n +CONFIG_ASSERT=n +CONFIG_USE_SEGGER_RTT=n +CONFIG_THREAD_NAME=n + +# Reboot on any fatal error (stack overflow, hard fault, etc.) instead of +# spinning forever. Asserts are disabled so they won't reach here, but all +# other fatal paths (k_panic, CPU exceptions) still get a clean reboot. +CONFIG_RESET_ON_FATAL_ERROR=y # ========== Power Management ========== # Note: nRF52840 automatically uses WFI (Wait For Interrupt) in idle thread. diff --git a/zephcore/src/main_companion.cpp b/zephcore/src/main_companion.cpp index 201d72f..7176bb6 100644 --- a/zephcore/src/main_companion.cpp +++ b/zephcore/src/main_companion.cpp @@ -34,7 +34,13 @@ LOG_MODULE_REGISTER(zephcore_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL); #include -#if IS_ENABLED(CONFIG_LOG) +/* The USB CDC companion stack is compiled when either logging needs the CDC + * console (debug builds) or the binary companion transport is explicitly + * enabled (CONFIG_ZEPHCORE_COMPANION_USB, default-y on USB-capable boards). */ +#define ZEPHCORE_USB_STACK \ + (IS_ENABLED(CONFIG_LOG) || IS_ENABLED(CONFIG_ZEPHCORE_COMPANION_USB)) + +#if ZEPHCORE_USB_STACK #include #include #endif @@ -181,7 +187,7 @@ static void ble_on_connected(void) /* BLE disconnected callback — clean up state and notify UI */ static void ble_on_disconnected(void) { -#if IS_ENABLED(CONFIG_LOG) +#if ZEPHCORE_USB_STACK zephcore_usb_companion_reset_rx(); #endif #ifdef ZEPHCORE_LORA @@ -562,8 +568,8 @@ int main(void) /* USB CDC init up front so the host can enumerate, then block until the * host opens the port (DTR-high) — event-driven via the shared usbd * message callback. Unplugged → 1 s timeout, banner is buffered for any - * later attach. CONFIG_LOG=n prod builds skip the whole stack. */ -#if IS_ENABLED(CONFIG_LOG) && DT_HAS_COMPAT_STATUS_OKAY(zephyr_cdc_acm_uart) && \ + * later attach. */ +#if ZEPHCORE_USB_STACK && DT_HAS_COMPAT_STATUS_OKAY(zephyr_cdc_acm_uart) && \ !IS_ENABLED(CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT) zephcore_usbd_init(); zephcore_usbd_wait_dtr(1000); @@ -780,10 +786,11 @@ int main(void) zephcore_ble_init(&ble_cbs); /* - * USB CDC ACM - only enabled when logging is enabled (debug builds). - * Production builds (CONFIG_LOG=n) skip USB entirely, saving ~2-5mA. + * USB CDC ACM companion transport. + * Enabled when logging is on (debug builds) OR when explicitly requested + * via CONFIG_ZEPHCORE_COMPANION_USB (e.g. prod builds on USB-capable boards). */ -#if IS_ENABLED(CONFIG_LOG) +#if ZEPHCORE_USB_STACK zephcore_usb_companion_init(&mesh_events, &rx_process_work, MESH_EVENT_BLE_RX, &zephyr_board); #endif