Skip to content

Commit

Permalink
samples: usb: passthrough sample application using only irq handlers
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
SynchronicIT committed Sep 9, 2024
1 parent 8c32a82 commit 00ad9ca
Show file tree
Hide file tree
Showing 7 changed files with 314 additions and 0 deletions.
8 changes: 8 additions & 0 deletions samples/subsys/usb/passthrough_irq_usb/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
9 changes: 9 additions & 0 deletions samples/subsys/usb/passthrough_irq_usb/Kconfig
Original file line number Diff line number Diff line change
@@ -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"
50 changes: 50 additions & 0 deletions samples/subsys/usb/passthrough_irq_usb/README.rst
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions samples/subsys/usb/passthrough_irq_usb/app.overlay
Original file line number Diff line number Diff line change
@@ -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";
};
};
9 changes: 9 additions & 0 deletions samples/subsys/usb/passthrough_irq_usb/prj.conf
Original file line number Diff line number Diff line change
@@ -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"
12 changes: 12 additions & 0 deletions samples/subsys/usb/passthrough_irq_usb/sample.yml
Original file line number Diff line number Diff line change
@@ -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
215 changes: 215 additions & 0 deletions samples/subsys/usb/passthrough_irq_usb/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/*
* Copyright (c) 2024 Argentum Systems Ltd.
* Copyright (c) 2024 SynchronicIT BV
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/ring_buffer.h>

#include <stdio.h>
#include <string.h>


#include <zephyr/usb/usb_device.h>
#include <zephyr/usb/usbd.h>
#include <zephyr/logging/log.h>
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;
}

0 comments on commit 00ad9ca

Please sign in to comment.