usb: unify companion + repeater CDC ACM init, drop boot waits

Single ZephyrUSBCDC module owns the usbd context, 1200-baud DFU
detection, and DTR transitions for both roles. The boot banner
now blocks on a k_event signalled by the usbd_msg_callback when
DTR transitions high — host attached → wakes immediately; no host
→ bounded timeout (2 s repeater, 1 s companion). Replaces the
fixed k_sleep delays in both mains.

Deletes the companion's 10 s DTR-polling work — line state changes
arrive as events now, same callback handles disconnect (resets V3
parser, flips active_iface) and DFU touch (reboots to bootloader).

Side effect: prod companion no longer enumerates a phantom CDC ACM
port (CONFIG_LOG=n skips the whole stack instead of auto-initing
an unused device).
This commit is contained in:
liquidraver
2026-05-27 09:04:32 +02:00
parent 99279fd9fa
commit f06c472e87
10 changed files with 363 additions and 317 deletions
+10 -4
View File
@@ -493,11 +493,11 @@ if(CONFIG_ZEPHCORE_ROLE_REPEATER)
helpers/TransportKeyStore.cpp
helpers/CommonCLI.cpp
)
# USB CDC with 1200 baud touch detection (nRF boards with USB CDC ACM only).
# ESP32 boards use usb_serial (no UDC/CDC ACM), so skip custom USB init.
# Shared USBD CDC ACM init + 1200-baud DFU + DTR event/callback module.
# ESP32 boards use usb_serial (no UDC/CDC ACM), so skip when not present.
if(NOT CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT AND (CONFIG_USB_CDC_ACM OR CONFIG_USBD_CDC_ACM_CLASS))
target_sources(app PRIVATE
adapters/usb/ZephyrRepeaterUSB.cpp
adapters/usb/ZephyrUSBCDC.cpp
)
endif()
target_include_directories(app PRIVATE
@@ -563,11 +563,17 @@ else()
target_include_directories(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/adapters/transport
)
# USB CDC companion transport (only in debug builds with logging)
# USB CDC companion transport (only in debug builds with logging).
# Pulls in the shared ZephyrUSBCDC module too (USBD lifecycle + DTR callback).
if(CONFIG_LOG)
target_sources(app PRIVATE
adapters/usb/ZephyrCompanionUSB.cpp
)
if(NOT CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT AND (CONFIG_USB_CDC_ACM OR CONFIG_USBD_CDC_ACM_CLASS))
target_sources(app PRIVATE
adapters/usb/ZephyrUSBCDC.cpp
)
endif()
endif()
target_include_directories(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/adapters/usb
+21 -61
View File
@@ -4,6 +4,10 @@
*
* V3-framed USB CDC for companion mode. Extracted from main_companion.cpp.
* Only compiled when CONFIG_LOG is enabled (debug builds).
*
* USBD lifecycle + 1200-baud DFU detection + DTR state tracking live in
* the shared ZephyrUSBCDC module; this file just runs the V3 frame parser
* on top of the CDC ACM UART and reacts to DTR-drop events from there.
*/
#include <zephyr/kernel.h>
@@ -16,14 +20,13 @@
LOG_MODULE_REGISTER(zephcore_usb, CONFIG_ZEPHCORE_USB_LOG_LEVEL);
#include <ZephyrBLE.h>
#include <adapters/board/ZephyrBoard.h>
#include <app/CompanionMesh.h>
#include "ZephyrCompanionUSB.h"
#include "ZephyrUSBCDC.h"
/* MAX_FRAME_SIZE defined in CompanionMesh.h */
#define USB_DTR_CHECK_MS 10000 /* USB DTR poll interval - 10s for power savings */
#define USB_RING_BUF_SIZE 512 /* USB RX ring buffer size */
#define USB_FRAME_TIMEOUT_MS 2000 /* Partial frame timeout - reset parser after 2s of no completion */
@@ -35,31 +38,16 @@ static uint8_t usb_rx_buf[MAX_FRAME_SIZE + 2]; /* +2 for length prefix */
static uint16_t usb_rx_idx;
static uint16_t usb_frame_len; /* Expected frame length (0 = waiting for header) */
static uint32_t usb_frame_start_time; /* Timestamp of first byte in current frame */
static bool usb_dtr_active;
/* Pointers to mesh event infrastructure (set by init) */
static struct k_event *s_mesh_events;
static struct k_work *s_rx_work;
static uint32_t s_mesh_event_ble_rx;
static mesh::ZephyrBoard *s_board;
/* Work items */
static void usb_rx_work_fn(struct k_work *work);
static void usb_dtr_check_work_fn(struct k_work *work);
K_WORK_DEFINE(usb_rx_work, usb_rx_work_fn);
K_WORK_DELAYABLE_DEFINE(usb_dtr_check_work, usb_dtr_check_work_fn);
/**
* Reboot into the Adafruit nRF52 bootloader DFU mode.
* Triggered by host opening USB CDC at 1200 baud then dropping DTR.
*/
static void reboot_to_bootloader_dfu(void)
{
LOG_INF("*** REBOOTING TO BOOTLOADER DFU ***");
s_board->rebootToBootloader();
CODE_UNREACHABLE;
}
/* USB CDC UART interrupt callback - puts bytes in ring buffer */
static void usb_uart_isr(const struct device *dev, void *user_data)
@@ -160,51 +148,21 @@ static void usb_rx_work_fn(struct k_work *work)
}
}
/* USB DTR check - monitors DTR signal for disconnect detection
* AND detects 1200bps baud rate for bootloader DFU trigger. */
static void usb_dtr_check_work_fn(struct k_work *work)
/* DTR-transition callback from the shared ZephyrUSBCDC module.
* On drop: host closed the port → reset parser, hand control back to BLE. */
static void on_dtr_change(bool dtr_active)
{
ARG_UNUSED(work);
if (!usb_dev) {
if (dtr_active) {
return;
}
uint32_t dtr = 0;
int err = uart_line_ctrl_get(usb_dev, UART_LINE_CTRL_DTR, &dtr);
if (err) {
/* Line control not supported or error */
k_work_schedule(&usb_dtr_check_work, K_MSEC(USB_DTR_CHECK_MS));
return;
LOG_INF("usb_dtr: DTR dropped, USB disconnected");
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");
}
bool dtr_now = (dtr != 0);
/* 1200bps DFU trigger: when host opens port at 1200 baud then
* drops DTR, reboot into bootloader DFU mode.
* This is the Arduino convention used by Adafruit nRF52 boards. */
if (usb_dtr_active && !dtr_now) {
uint32_t baud = 0;
uart_line_ctrl_get(usb_dev, UART_LINE_CTRL_BAUD_RATE, &baud);
if (baud == 1200) {
reboot_to_bootloader_dfu();
/* Never returns */
}
/* Normal DTR drop - USB disconnected */
LOG_INF("usb_dtr: DTR dropped, USB disconnected");
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");
}
/* Reset RX state */
ring_buf_reset(&usb_ring_buf);
usb_frame_len = 0;
usb_rx_idx = 0;
}
usb_dtr_active = dtr_now;
k_work_schedule(&usb_dtr_check_work, K_MSEC(USB_DTR_CHECK_MS));
ring_buf_reset(&usb_ring_buf);
usb_frame_len = 0;
usb_rx_idx = 0;
}
/* Send frame over USB with V3 length prefix */
@@ -242,10 +200,11 @@ void zephcore_usb_companion_init(struct k_event *mesh_events,
uint32_t mesh_event_ble_rx,
void *board)
{
ARG_UNUSED(board);
s_mesh_events = mesh_events;
s_rx_work = rx_work;
s_mesh_event_ble_rx = mesh_event_ble_rx;
s_board = static_cast<mesh::ZephyrBoard *>(board);
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_cdc_acm_uart)
usb_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
@@ -257,8 +216,9 @@ void zephcore_usb_companion_init(struct k_event *mesh_events,
uart_irq_callback_set(usb_dev, usb_uart_isr);
uart_irq_rx_enable(usb_dev);
/* Start DTR monitoring to detect terminal disconnect */
k_work_schedule(&usb_dtr_check_work, K_MSEC(USB_DTR_CHECK_MS));
/* DTR state changes (including disconnect) reach us via the
* shared usbd_msg_callback — no polling work needed. */
zephcore_usbd_set_dtr_cb(on_dtr_change);
} else {
LOG_WRN("USB CDC device not ready");
usb_dev = NULL;
-203
View File
@@ -1,203 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
* ZephCore - USB CDC ACM with 1200 baud touch detection (Repeater)
*
* This module initializes USB CDC ACM with a message callback to detect
* 1200 baud touch for entering UF2 bootloader mode.
*/
#include <stdint.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/usb/usbd.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/drivers/hwinfo.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(zephcore_usb_repeater, CONFIG_ZEPHCORE_BOARD_LOG_LEVEL);
#include <adapters/board/ZephyrBoard.h>
#include "ZephyrRepeaterUSB.h"
/*
* USB Device Configuration
* Since we're not using CDC_ACM_SERIAL_INITIALIZE_AT_BOOT, we define our own values.
*/
#define ZEPHCORE_USB_VID 0x2fe3 /* Zephyr Project VID */
#define ZEPHCORE_USB_PID 0x0004 /* CDC ACM PID */
#define ZEPHCORE_USB_MAX_POWER 125 /* 250mA in 2mA units */
/*
* USB Device Context - based on Zephyr's cdc_acm_serial.c
* but with message callback for 1200 baud touch detection.
*/
USBD_DEVICE_DEFINE(zephcore_usbd,
DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)),
ZEPHCORE_USB_VID, ZEPHCORE_USB_PID);
USBD_DESC_LANG_DEFINE(zephcore_lang);
USBD_DESC_MANUFACTURER_DEFINE(zephcore_mfr, "ZephCore");
USBD_DESC_PRODUCT_DEFINE(zephcore_product, "ZephCore Repeater");
USBD_DESC_SERIAL_NUMBER_DEFINE(zephcore_sn);
USBD_DESC_CONFIG_DEFINE(zephcore_fs_cfg_desc, "FS Configuration");
USBD_DESC_CONFIG_DEFINE(zephcore_hs_cfg_desc, "HS Configuration");
USBD_CONFIGURATION_DEFINE(zephcore_fs_config,
0, /* attributes: bus-powered */
ZEPHCORE_USB_MAX_POWER, &zephcore_fs_cfg_desc);
USBD_CONFIGURATION_DEFINE(zephcore_hs_config,
0, /* attributes: bus-powered */
ZEPHCORE_USB_MAX_POWER, &zephcore_hs_cfg_desc);
/* ZephyrBoard instance for bootloader entry */
static mesh::ZephyrBoard usb_board;
/* Track DTR state for Arduino-style 1200 baud touch detection.
* Arduino triggers on DTR drop (high→low) while baud rate is 1200.
* adafruit-nrfutil --touch 1200 opens port at 1200 baud (DTR high),
* then closes it (DTR low). We detect both the baud rate change and
* the DTR drop to cover all host tool implementations. */
static bool dtr_was_active;
static void enter_bootloader(void)
{
LOG_WRN("Entering bootloader (1200 baud touch)");
/* Small delay to let USB disconnect cleanly */
k_msleep(100);
usb_board.rebootToBootloader();
/* Never returns */
}
/*
* USB message callback - detect 1200 baud touch for bootloader entry.
* Two detection methods (matching Arduino behavior):
* 1. Baud rate set to 1200 (SET_LINE_CODING)
* 2. DTR drop while baud is 1200 (SET_CONTROL_LINE_STATE)
*/
static void usbd_msg_callback(struct usbd_context *const ctx, const struct usbd_msg *msg)
{
if (msg->type == USBD_MSG_CDC_ACM_LINE_CODING) {
uint32_t baudrate = 0;
int ret = uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_BAUD_RATE, &baudrate);
if (ret == 0) {
LOG_DBG("CDC ACM baud rate: %u", baudrate);
if (baudrate == 1200) {
enter_bootloader();
}
}
} else if (msg->type == USBD_MSG_CDC_ACM_CONTROL_LINE_STATE) {
uint32_t dtr = 0;
uint32_t baudrate = 0;
uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_DTR, &dtr);
uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_BAUD_RATE, &baudrate);
LOG_DBG("CDC ACM DTR=%u baud=%u", dtr, baudrate);
/* Arduino method: DTR drop (high→low) while baud is 1200 */
if (dtr_was_active && !dtr && baudrate == 1200) {
enter_bootloader();
}
dtr_was_active = (dtr != 0);
}
}
static int register_cdc_acm(struct usbd_context *const uds_ctx,
const enum usbd_speed speed)
{
struct usbd_config_node *cfg_nd;
int err;
if (speed == USBD_SPEED_HS) {
cfg_nd = &zephcore_hs_config;
} else {
cfg_nd = &zephcore_fs_config;
}
err = usbd_add_configuration(uds_ctx, speed, cfg_nd);
if (err) {
LOG_ERR("Failed to add configuration");
return err;
}
err = usbd_register_class(&zephcore_usbd, "cdc_acm_0", speed, 1);
if (err) {
LOG_ERR("Failed to register CDC ACM class");
return err;
}
return usbd_device_set_code_triple(uds_ctx, speed,
USB_BCC_MISCELLANEOUS, 0x02, 0x01);
}
extern "C" int zephcore_usbd_init(void)
{
int err;
LOG_INF("Initializing ZephCore USB CDC with 1200 baud touch detection");
/* Add string descriptors */
err = usbd_add_descriptor(&zephcore_usbd, &zephcore_lang);
if (err) {
LOG_ERR("Failed to add language descriptor (%d)", err);
return err;
}
err = usbd_add_descriptor(&zephcore_usbd, &zephcore_mfr);
if (err) {
LOG_ERR("Failed to add manufacturer descriptor (%d)", err);
return err;
}
err = usbd_add_descriptor(&zephcore_usbd, &zephcore_product);
if (err) {
LOG_ERR("Failed to add product descriptor (%d)", err);
return err;
}
err = usbd_add_descriptor(&zephcore_usbd, &zephcore_sn);
if (err) {
LOG_ERR("Failed to add serial number descriptor (%d)", err);
return err;
}
/* Register configurations */
if (USBD_SUPPORTS_HIGH_SPEED &&
usbd_caps_speed(&zephcore_usbd) == USBD_SPEED_HS) {
err = register_cdc_acm(&zephcore_usbd, USBD_SPEED_HS);
if (err) {
return err;
}
}
err = register_cdc_acm(&zephcore_usbd, USBD_SPEED_FS);
if (err) {
return err;
}
/* Register message callback for 1200 baud detection */
err = usbd_msg_register_cb(&zephcore_usbd, usbd_msg_callback);
if (err) {
LOG_ERR("Failed to register message callback (%d)", err);
return err;
}
/* Initialize USB device */
err = usbd_init(&zephcore_usbd);
if (err) {
LOG_ERR("Failed to initialize USB device (%d)", err);
return err;
}
/* Enable USB device */
err = usbd_enable(&zephcore_usbd);
if (err) {
LOG_ERR("Failed to enable USB device (%d)", err);
return err;
}
LOG_INF("ZephCore USB CDC initialized");
return 0;
}
-32
View File
@@ -1,32 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
* ZephCore - USB CDC ACM with 1200 baud touch detection (Repeater)
*
* Replaces Zephyr's CDC_ACM_SERIAL_INITIALIZE_AT_BOOT with custom init
* that includes a message callback for detecting 1200 baud touch to
* enter UF2 bootloader mode.
*/
#ifndef ZEPHCORE_ZEPHYR_REPEATER_USB_H
#define ZEPHCORE_ZEPHYR_REPEATER_USB_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Initialize USB CDC ACM with 1200 baud touch detection.
*
* This replaces Zephyr's default CDC_ACM_SERIAL_INITIALIZE_AT_BOOT
* initialization with our own that includes a message callback for
* detecting 1200 baud touch to enter bootloader mode.
*
* @return 0 on success, negative error code on failure.
*/
int zephcore_usbd_init(void);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHCORE_ZEPHYR_REPEATER_USB_H */
+255
View File
@@ -0,0 +1,255 @@
/*
* SPDX-License-Identifier: Apache-2.0
* ZephCore — Unified USB CDC ACM init (companion + repeater + observer)
*
* See ZephyrUSBCDC.h for the contract. This implementation:
* - Defines a single USBD context + CDC ACM class
* - Registers usbd_msg_callback so the host's SET_CONTROL_LINE_STATE and
* SET_LINE_CODING requests reach us as events (no DTR polling)
* - Emits a k_event when DTR transitions high — the boot path waits on it
* instead of a fixed sleep
* - Fires a user callback on every DTR transition (companion: reset RX +
* flip active_iface on drop)
* - Handles the Arduino-style 1200-baud touch → reboot-to-bootloader flow
* for nRF52 boards
*/
#include "ZephyrUSBCDC.h"
#include <stdint.h>
#include <errno.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/usb/usbd.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(zephcore_usbd, CONFIG_ZEPHCORE_BOARD_LOG_LEVEL);
#include <adapters/board/ZephyrBoard.h>
/* ===== Per-role descriptor strings ====== */
#define ZEPHCORE_USB_VID 0x2fe3 /* Zephyr Project VID */
#if defined(ZEPHCORE_REPEATER)
#define ZEPHCORE_USB_PID 0x0004
#define ZEPHCORE_USB_PRODUCT "ZephCore Repeater"
#elif defined(ZEPHCORE_OBSERVER)
#define ZEPHCORE_USB_PID 0x0005
#define ZEPHCORE_USB_PRODUCT "ZephCore Observer"
#else
#define ZEPHCORE_USB_PID 0x0006
#define ZEPHCORE_USB_PRODUCT "ZephCore Companion"
#endif
#define ZEPHCORE_USB_MAX_POWER 125 /* 250 mA in 2 mA units */
/* ===== USBD device + descriptors ===== */
USBD_DEVICE_DEFINE(zephcore_usbd,
DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)),
ZEPHCORE_USB_VID, ZEPHCORE_USB_PID);
USBD_DESC_LANG_DEFINE(zephcore_lang);
USBD_DESC_MANUFACTURER_DEFINE(zephcore_mfr, "ZephCore");
USBD_DESC_PRODUCT_DEFINE(zephcore_product, ZEPHCORE_USB_PRODUCT);
USBD_DESC_SERIAL_NUMBER_DEFINE(zephcore_sn);
USBD_DESC_CONFIG_DEFINE(zephcore_fs_cfg_desc, "FS Configuration");
USBD_DESC_CONFIG_DEFINE(zephcore_hs_cfg_desc, "HS Configuration");
USBD_CONFIGURATION_DEFINE(zephcore_fs_config,
0, ZEPHCORE_USB_MAX_POWER, &zephcore_fs_cfg_desc);
USBD_CONFIGURATION_DEFINE(zephcore_hs_config,
0, ZEPHCORE_USB_MAX_POWER, &zephcore_hs_cfg_desc);
/* ===== Module state ===== */
#define USB_CDC_DTR_BIT BIT(0)
static K_EVENT_DEFINE(usb_cdc_events);
static bool s_dtr_active;
static bool s_initialized;
static zephcore_usbd_cdc_dtr_cb_t s_dtr_cb;
/* ZephyrBoard for bootloader-magic write before reset. The class is stateless
* (only static methods over GPREGRET + sys_reboot), so a local instance here
* is fine even when other roles also have their own instance. */
static mesh::ZephyrBoard usb_board;
static void enter_bootloader(void)
{
LOG_WRN("Entering bootloader (1200 baud touch)");
/* Let USB disconnect cleanly before the reset */
k_msleep(100);
usb_board.rebootToBootloader();
CODE_UNREACHABLE;
}
/* ===== USBD message callback ===== */
static void usbd_msg_callback(struct usbd_context *const ctx,
const struct usbd_msg *msg)
{
ARG_UNUSED(ctx);
if (msg->type == USBD_MSG_CDC_ACM_LINE_CODING) {
uint32_t baudrate = 0;
if (uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_BAUD_RATE,
&baudrate) == 0) {
LOG_DBG("CDC ACM baud rate: %u", baudrate);
if (baudrate == 1200) {
/* Host explicitly set 1200 baud — DFU intent.
* Adafruit nrfutil --touch 1200 closes the
* port immediately after; do not wait for
* the DTR drop to fire. */
enter_bootloader();
}
}
} else if (msg->type == USBD_MSG_CDC_ACM_CONTROL_LINE_STATE) {
uint32_t dtr = 0;
uint32_t baudrate = 0;
uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_DTR, &dtr);
uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_BAUD_RATE,
&baudrate);
LOG_DBG("CDC ACM DTR=%u baud=%u", dtr, baudrate);
const bool dtr_now = (dtr != 0);
/* Arduino-style DFU: DTR drop while baud is 1200. Some host
* tools change baud then close (handled by LINE_CODING above),
* others just toggle DTR with baud still at 1200 — catch both. */
if (s_dtr_active && !dtr_now && baudrate == 1200) {
enter_bootloader();
}
s_dtr_active = dtr_now;
if (dtr_now) {
/* Wake the boot-time waiter; subsequent transitions
* leave the bit set, which is what callers expect
* (a "have we ever seen DTR?" gate). */
k_event_set(&usb_cdc_events, USB_CDC_DTR_BIT);
}
if (s_dtr_cb) {
s_dtr_cb(dtr_now);
}
}
}
/* ===== Setup helpers ===== */
static int register_cdc_acm(struct usbd_context *const uds_ctx,
const enum usbd_speed speed)
{
struct usbd_config_node *cfg_nd;
int err;
if (speed == USBD_SPEED_HS) {
cfg_nd = &zephcore_hs_config;
} else {
cfg_nd = &zephcore_fs_config;
}
err = usbd_add_configuration(uds_ctx, speed, cfg_nd);
if (err) {
LOG_ERR("Failed to add configuration");
return err;
}
err = usbd_register_class(&zephcore_usbd, "cdc_acm_0", speed, 1);
if (err) {
LOG_ERR("Failed to register CDC ACM class");
return err;
}
return usbd_device_set_code_triple(uds_ctx, speed,
USB_BCC_MISCELLANEOUS, 0x02, 0x01);
}
/* ===== Public API ===== */
extern "C" int zephcore_usbd_init(void)
{
if (s_initialized) {
return 0;
}
int err;
LOG_INF("Initializing ZephCore USB CDC ACM (" ZEPHCORE_USB_PRODUCT ")");
err = usbd_add_descriptor(&zephcore_usbd, &zephcore_lang);
if (err) {
LOG_ERR("usbd_add_descriptor(lang) failed: %d", err);
return err;
}
err = usbd_add_descriptor(&zephcore_usbd, &zephcore_mfr);
if (err) {
LOG_ERR("usbd_add_descriptor(mfr) failed: %d", err);
return err;
}
err = usbd_add_descriptor(&zephcore_usbd, &zephcore_product);
if (err) {
LOG_ERR("usbd_add_descriptor(product) failed: %d", err);
return err;
}
err = usbd_add_descriptor(&zephcore_usbd, &zephcore_sn);
if (err) {
LOG_ERR("usbd_add_descriptor(sn) failed: %d", err);
return err;
}
if (USBD_SUPPORTS_HIGH_SPEED &&
usbd_caps_speed(&zephcore_usbd) == USBD_SPEED_HS) {
err = register_cdc_acm(&zephcore_usbd, USBD_SPEED_HS);
if (err) {
return err;
}
}
err = register_cdc_acm(&zephcore_usbd, USBD_SPEED_FS);
if (err) {
return err;
}
err = usbd_msg_register_cb(&zephcore_usbd, usbd_msg_callback);
if (err) {
LOG_ERR("usbd_msg_register_cb failed: %d", err);
return err;
}
err = usbd_init(&zephcore_usbd);
if (err) {
LOG_ERR("usbd_init failed: %d", err);
return err;
}
err = usbd_enable(&zephcore_usbd);
if (err) {
LOG_ERR("usbd_enable failed: %d", err);
return err;
}
s_initialized = true;
LOG_INF("ZephCore USB CDC initialized");
return 0;
}
extern "C" int zephcore_usbd_wait_dtr(uint32_t timeout_ms)
{
if (!s_initialized) {
return -ENODEV;
}
uint32_t got = k_event_wait(&usb_cdc_events, USB_CDC_DTR_BIT,
false, K_MSEC(timeout_ms));
return got ? 0 : -EAGAIN;
}
extern "C" bool zephcore_usbd_is_dtr_active(void)
{
return s_dtr_active;
}
extern "C" void zephcore_usbd_set_dtr_cb(zephcore_usbd_cdc_dtr_cb_t cb)
{
s_dtr_cb = cb;
}
+47
View File
@@ -0,0 +1,47 @@
/*
* SPDX-License-Identifier: Apache-2.0
* ZephCore — Unified USB CDC ACM init (companion + repeater + observer)
*
* Owns the USBD device + CDC ACM class registration, the usbd_msg_callback
* for 1200-baud touch DFU + DTR state tracking, and the boot-waiter event.
* Replaces CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT auto-init so we control
* the message callback path and can deliver event-driven DTR notifications
* to consumers (no DTR polling work).
*/
#ifndef ZEPHCORE_ZEPHYR_USB_CDC_H
#define ZEPHCORE_ZEPHYR_USB_CDC_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Per-transition DTR callback. Fires on every host CONTROL_LINE_STATE change,
* value = current DTR level. NULL is allowed (cb cleared). */
typedef void (*zephcore_usbd_cdc_dtr_cb_t)(bool dtr_active);
/* Initialize USB device + CDC ACM class and enumerate.
* Idempotent; subsequent calls are no-ops.
* Returns 0 on success or a negative errno. */
int zephcore_usbd_init(void);
/* Block until the host opens the CDC ACM port (DTR transitions high) or the
* timeout elapses. Returns 0 on DTR-high, -EAGAIN on timeout, -ENODEV if
* zephcore_usbd_init() has not been called. */
int zephcore_usbd_wait_dtr(uint32_t timeout_ms);
/* Last-known DTR state from the most recent CONTROL_LINE_STATE message. */
bool zephcore_usbd_is_dtr_active(void);
/* Register a callback fired on every DTR transition.
* Companion uses this to reset its RX state and flip active_iface on drop. */
void zephcore_usbd_set_dtr_cb(zephcore_usbd_cdc_dtr_cb_t cb);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHCORE_ZEPHYR_USB_CDC_H */
+6 -2
View File
@@ -9,9 +9,13 @@ CONFIG_BUILD_OUTPUT_UF2=y
CONFIG_USE_DT_CODE_PARTITION=y
# ========== USB CDC ACM (nRF52 USB) ==========
# We own usbd init via adapters/usb/ZephyrUSBCDC.cpp so the same
# usbd_msg_callback can handle 1200-baud DFU detection and signal DTR
# transitions to the boot-waiter event + companion disconnect callback.
# Without =n here the Zephyr sample-style auto-init runs at SYS_INIT
# with no public hook for us.
CONFIG_USB_DEVICE_STACK_NEXT=y
CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=y
CONFIG_CDC_ACM_SERIAL_PRODUCT_STRING="ZephCore"
CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=n
CONFIG_UART_LINE_CTRL=y
# ========== BLE: nRF52-specific controller settings ==========
-5
View File
@@ -18,11 +18,6 @@ CONFIG_BT=n
# for Ed25519 key generation, LoRa random delays, etc.
CONFIG_ENTROPY_GENERATOR=y
# ========== USB CDC ==========
# Custom USB init with 1200 baud touch detection for UF2 bootloader (nRF52 only).
# ESP32 boards use usb_serial — this setting is ignored there.
CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=n
# ========== Optional ESP32 repeater uplink ==========
# Observer-style WiFi+MQTT reporting from repeater role.
# Runtime-configured and reboot-applied.
+10 -2
View File
@@ -35,6 +35,7 @@ LOG_MODULE_REGISTER(zephcore_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
#if IS_ENABLED(CONFIG_LOG)
#include <ZephyrCompanionUSB.h>
#include <ZephyrUSBCDC.h>
#endif
#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK)
@@ -525,8 +526,15 @@ int main(void)
/* Clear any stale bootloader magic from previous sessions */
zephyr_board.clearBootloaderMagic();
/* Brief settle — deferred logging will catch up once USB CDC enumerates */
k_sleep(K_MSEC(100));
/* 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) && \
!IS_ENABLED(CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT)
zephcore_usbd_init();
zephcore_usbd_wait_dtr(1000);
#endif
LOG_INF("=== ZephCore starting ===");
/* Log reset reason so we can diagnose random reboots */
+14 -8
View File
@@ -33,9 +33,9 @@ extern "C" void bt_ctlr_assert_handle(char *file, uint32_t line)
}
#endif
/* USB CDC with 1200 baud touch detection (when not using auto-init) */
/* USB CDC ACM init + 1200-baud DFU + DTR callbacks (shared with companion) */
#if !IS_ENABLED(CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT)
#include <ZephyrRepeaterUSB.h>
#include <ZephyrUSBCDC.h>
#endif
#include <app/RepeaterDataStore.h>
@@ -384,8 +384,16 @@ int main(void)
zephyr_board.clearBootloaderMagic();
#endif
/* Wait for USB CDC to enumerate before any logging */
k_sleep(K_MSEC(2000));
/* USB CDC init up front so the host can enumerate, then wait for the
* host to open the port (DTR asserted) — event-driven via the usbd
* message callback. Unplugged → 2 s timeout, no banner; attached →
* banner reaches the user the moment the port opens. */
#if !IS_ENABLED(CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT) && DT_HAS_COMPAT_STATUS_OKAY(zephyr_cdc_acm_uart)
zephcore_usbd_init();
#endif
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_cdc_acm_uart)
zephcore_usbd_wait_dtr(2000);
#endif
LOG_INF("=== ZephCore Repeater starting ===");
#if IS_ENABLED(CONFIG_ZEPHCORE_WIFI_OTA)
@@ -515,10 +523,8 @@ int main(void)
k_work_schedule(&initial_advert_work, K_SECONDS(10));
#endif
/* Initialize USB serial for CLI */
#if !IS_ENABLED(CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT) && DT_HAS_COMPAT_STATUS_OKAY(zephyr_cdc_acm_uart)
zephcore_usbd_init();
#endif
/* USB CDC was initialized earlier (right after clearBootloaderMagic).
* Just acquire the device handle for the CLI's UART IRQ binding below. */
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_cdc_acm_uart)
usb_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
#else