mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 21:08:19 +00:00
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:
+10
-4
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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 ==========
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user