From 00ad9ca0395efc259051f8b063d7707783dad406 Mon Sep 17 00:00:00 2001 From: Vincent van der Locht Date: Mon, 9 Sep 2024 11:22:06 +0200 Subject: [PATCH] samples: usb: passthrough sample application using only irq handlers sample application which connects a physical uart to a usb cdc acm uart The complete process in handled in a single interrupt handler without interference of the application code. Signed-off-by: Vincent van der Locht --- .../usb/passthrough_irq_usb/CMakeLists.txt | 8 + .../subsys/usb/passthrough_irq_usb/Kconfig | 9 + .../subsys/usb/passthrough_irq_usb/README.rst | 50 ++++ .../usb/passthrough_irq_usb/app.overlay | 11 + .../subsys/usb/passthrough_irq_usb/prj.conf | 9 + .../subsys/usb/passthrough_irq_usb/sample.yml | 12 + .../subsys/usb/passthrough_irq_usb/src/main.c | 215 ++++++++++++++++++ 7 files changed, 314 insertions(+) create mode 100644 samples/subsys/usb/passthrough_irq_usb/CMakeLists.txt create mode 100644 samples/subsys/usb/passthrough_irq_usb/Kconfig create mode 100644 samples/subsys/usb/passthrough_irq_usb/README.rst create mode 100644 samples/subsys/usb/passthrough_irq_usb/app.overlay create mode 100644 samples/subsys/usb/passthrough_irq_usb/prj.conf create mode 100644 samples/subsys/usb/passthrough_irq_usb/sample.yml create mode 100644 samples/subsys/usb/passthrough_irq_usb/src/main.c diff --git a/samples/subsys/usb/passthrough_irq_usb/CMakeLists.txt b/samples/subsys/usb/passthrough_irq_usb/CMakeLists.txt new file mode 100644 index 00000000000000..92829b4622e64d --- /dev/null +++ b/samples/subsys/usb/passthrough_irq_usb/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(passthrough) + +include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake) +target_sources(app PRIVATE src/main.c) diff --git a/samples/subsys/usb/passthrough_irq_usb/Kconfig b/samples/subsys/usb/passthrough_irq_usb/Kconfig new file mode 100644 index 00000000000000..96c5455894806d --- /dev/null +++ b/samples/subsys/usb/passthrough_irq_usb/Kconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Source common USB sample options used to initialize new experimental USB +# device stack. The scope of these options is limited to USB samples in project +# tree, you cannot use them in your own application. +source "samples/subsys/usb/common/Kconfig.sample_usbd" + +source "Kconfig.zephyr" diff --git a/samples/subsys/usb/passthrough_irq_usb/README.rst b/samples/subsys/usb/passthrough_irq_usb/README.rst new file mode 100644 index 00000000000000..d24cd5c92c1e97 --- /dev/null +++ b/samples/subsys/usb/passthrough_irq_usb/README.rst @@ -0,0 +1,50 @@ +.. zephyr:code-sample:: passthrough_irq_usb + :name: USB-UART Passthrough + :relevant-api: uart_interface + + Pass data directly between the console and another UART interface. + +Overview +******** + +This sample will connect a physical UART interfaces to a USB uart, as if Zephyr +and the processor were not present. Data read from the console is transmitted +to the "*other*" interface, and data read from the "*other*" interface is +relayed to the console. + +The source code for this sample application can be found at: +:zephyr_file:`samples/subsys/usb/passthrough_irq_usb`. + +Requirements +************ + +#. One USB UART interface, identified as Zephyr's first usb acm interface. +#. A second UART connected to something interesting (e.g: GPS), identified as + the chosen ``uart,passthrough`` device - the pins and baudrate will need to + be configured correctly. + +Building and Running +******************** + +Build and flash the sample as follows, changing ``nucleo_l476rg`` for your +board: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/uart/passthrough + :board: nucleo_l476rg + :goals: build flash + :compact: + +Sample Output +============= + +.. code-block:: console + + *** Booting Zephyr OS build zephyr-v3.5.0-2988-gb84bab36b941 *** + Console Device: 0x8003940 + Other Device: 0x800392c + $GNGSA,A,3,31,29,25,26,,,,,,,,,11.15,10.66,3.29,1*06 + $GNGSA,A,3,,,,,,,,,,,,,11.15,10.66,3.29,2*0F + $GNGSA,A,3,,,,,,,,,,,,,11.15,10.66,3.29,3*0E + $GNGSA,A,3,,,,,,,,,,,,,11.15,10.66,3.29,4*09 + $GNGSA,A,3,,,,,,,,,,,,,11.15,10.66,3.29,5*08 diff --git a/samples/subsys/usb/passthrough_irq_usb/app.overlay b/samples/subsys/usb/passthrough_irq_usb/app.overlay new file mode 100644 index 00000000000000..c50d0fd9d69e70 --- /dev/null +++ b/samples/subsys/usb/passthrough_irq_usb/app.overlay @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&zephyr_udc0 { + cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; diff --git a/samples/subsys/usb/passthrough_irq_usb/prj.conf b/samples/subsys/usb/passthrough_irq_usb/prj.conf new file mode 100644 index 00000000000000..30cdfbc26d0ddc --- /dev/null +++ b/samples/subsys/usb/passthrough_irq_usb/prj.conf @@ -0,0 +1,9 @@ +CONFIG_SERIAL=y +CONFIG_RING_BUFFER=y +CONFIG_UART_INTERRUPT_DRIVEN=y + +CONFIG_USB_DEVICE_STACK_NEXT=y +CONFIG_USBD_CDC_ACM_CLASS=y + +CONFIG_SAMPLE_USBD_PID=0x0001 +CONFIG_SAMPLE_USBD_PRODUCT="USBD CDC ACM sample" diff --git a/samples/subsys/usb/passthrough_irq_usb/sample.yml b/samples/subsys/usb/passthrough_irq_usb/sample.yml new file mode 100644 index 00000000000000..c6d7cf0e1739fc --- /dev/null +++ b/samples/subsys/usb/passthrough_irq_usb/sample.yml @@ -0,0 +1,12 @@ +sample: + name: USB-UART Passthrough +tests: + sample.drivers.uart: + tags: + - serial + - uart + - usb + filter: CONFIG_SERIAL and + CONFIG_UART_INTERRUPT_DRIVEN and + dt_chosen_enabled("uart,passthrough") + harness: keyboard diff --git a/samples/subsys/usb/passthrough_irq_usb/src/main.c b/samples/subsys/usb/passthrough_irq_usb/src/main.c new file mode 100644 index 00000000000000..ccc428a910ed73 --- /dev/null +++ b/samples/subsys/usb/passthrough_irq_usb/src/main.c @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2024 Argentum Systems Ltd. + * Copyright (c) 2024 SynchronicIT BV + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +#include + + +#include +#include +#include +LOG_MODULE_REGISTER(passthrough, LOG_LEVEL_INF); + + +const struct device *const usb_dev = DEVICE_DT_GET(DT_NODELABEL(cdc_acm_uart0)); +const struct device *const uart_dev = DEVICE_DT_GET(DT_CHOSEN(uart_passthrough)); + +#define RING_BUF_SIZE 1024 + +uint8_t ring_buffer[2][RING_BUF_SIZE]; + +struct ring_buf usb_ringbuf; +struct ring_buf uart_ringbuf; + +static inline void print_baudrate(const struct device *dev) +{ + uint32_t baudrate; + int ret; + + ret = uart_line_ctrl_get(dev, UART_LINE_CTRL_BAUD_RATE, &baudrate); + if (ret) { + LOG_WRN("Failed to get baudrate, ret code %d", ret); + } else { + LOG_INF("Baudrate %u", baudrate); + } +} + +#if defined(CONFIG_USB_DEVICE_STACK_NEXT) +static struct usbd_context *sample_usbd; +K_SEM_DEFINE(dtr_sem, 0, 1); + +static void sample_msg_cb(struct usbd_context *const ctx, const struct usbd_msg *msg) +{ + LOG_INF("USBD message: %s", usbd_msg_type_string(msg->type)); + + if (usbd_can_detect_vbus(ctx)) { + if (msg->type == USBD_MSG_VBUS_READY) { + if (usbd_enable(ctx)) { + LOG_ERR("Failed to enable device support"); + } + } + + if (msg->type == USBD_MSG_VBUS_REMOVED) { + if (usbd_disable(ctx)) { + LOG_ERR("Failed to disable device support"); + } + } + } + + if (msg->type == USBD_MSG_CDC_ACM_CONTROL_LINE_STATE) { + uint32_t dtr = 0U; + + uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_DTR, &dtr); + if (dtr) { + k_sem_give(&dtr_sem); + } + } + + if (msg->type == USBD_MSG_CDC_ACM_LINE_CODING) { + print_baudrate(msg->dev); + } +} + +static int enable_usb_device_next(void) +{ + int err; + + sample_usbd = sample_usbd_init_device(sample_msg_cb); + if (sample_usbd == NULL) { + LOG_ERR("Failed to initialize USB device"); + return -ENODEV; + } + + if (!usbd_can_detect_vbus(sample_usbd)) { + err = usbd_enable(sample_usbd); + if (err) { + LOG_ERR("Failed to enable device support"); + return err; + } + } + + LOG_INF("USB device support enabled"); + + return 0; +} +#endif /* defined(CONFIG_USB_DEVICE_STACK_NEXT) */ + + +static void interrupt_handler(const struct device *dev, void *user_data) +{ + + struct ring_buf *ringbuf_rx; + struct ring_buf *ringbuf_tx; + const struct device *peer = user_data; + + if(dev == usb_dev) { + ringbuf_rx = &uart_ringbuf; + ringbuf_tx = &usb_ringbuf; + + } else { + ringbuf_rx = &usb_ringbuf; + ringbuf_tx = &uart_ringbuf; + } + + while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + if (uart_irq_rx_ready(dev)) { + int recv_len, rb_len; + uint8_t buffer[64]; + size_t len = MIN(ring_buf_space_get(ringbuf_rx), sizeof(buffer)); + + if (len == 0) { + ring_buf_get(ringbuf_rx, NULL, buffer); + len = MIN(ring_buf_space_get(ringbuf_rx), sizeof(buffer)); + } + + recv_len = uart_fifo_read(dev, buffer, len); + if (recv_len < 0) { + LOG_ERR("Failed to read UART FIFO"); + recv_len = 0; + }; + + rb_len = ring_buf_put(ringbuf_rx, buffer, recv_len); + if (rb_len < recv_len) { + LOG_ERR("Drop %u bytes", recv_len - rb_len); + } + + LOG_DBG("tty fifo -> ringbuf %d bytes", rb_len); + if (rb_len) { + uart_irq_tx_enable(peer); + } + } + + if (uart_irq_tx_ready(dev)) { + uint8_t buffer[64]; + int rb_len, send_len; + + rb_len = ring_buf_get(ringbuf_tx, buffer, sizeof(buffer)); + if (!rb_len) { + LOG_DBG("Ring buffer empty, disable TX IRQ"); + uart_irq_tx_disable(dev); + continue; + } + + send_len = uart_fifo_fill(dev, buffer, rb_len); + if (send_len < rb_len) { + LOG_ERR("Drop %d bytes", rb_len - send_len); + } + + LOG_DBG("ringbuf -> tty fifo %d bytes", send_len); + } + } +} + +int main(void) +{ + int ret; + + if (!device_is_ready(uart_dev)) { + LOG_ERR("UART device not ready"); + return 0; + } + + if (!device_is_ready(usb_dev)) { + LOG_ERR("CDC ACM device not ready"); + return 0; + } + +#if defined(CONFIG_USB_DEVICE_STACK_NEXT) + ret = enable_usb_device_next(); +#else + ret = usb_enable(NULL); +#endif + + if (ret != 0) { + LOG_ERR("Failed to enable USB"); + return 0; + } + + ring_buf_init(&uart_ringbuf, sizeof(ring_buffer[0]), ring_buffer[0]); + ring_buf_init(&usb_ringbuf, sizeof(ring_buffer[1]), ring_buffer[1]); + + printk("UART Device: %p\n", uart_dev); + printk("USB Device: %p\n", usb_dev); + + uart_irq_callback_user_data_set(uart_dev, interrupt_handler, usb_dev); + uart_irq_callback_user_data_set(usb_dev, interrupt_handler, uart_dev); + + /* Enable rx interrupts */ + uart_irq_rx_enable(uart_dev); + uart_irq_rx_enable(usb_dev); + + for (;;) { + k_msleep(1000); + } + + return 0; +}