Skip to content

Commit

Permalink
samples: drivers: uart: Add a 'passthrough' example
Browse files Browse the repository at this point in the history
This sample can be used as a "virtual wire", allowing direct access to
devices connected to the target processor - for example: GPS, Cellular,
etc...

This is also useful as a utility - no other connectivity options exist
for UART, where other interfaces like SPI and I2C have shell commands.

Signed-off-by: Attie Grande <attie.grande@argentum-systems.co.uk>
  • Loading branch information
attie-argentum authored and henrikbrixandersen committed Jan 27, 2024
1 parent 018dbcf commit cf14d4f
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 0 deletions.
7 changes: 7 additions & 0 deletions samples/drivers/uart/passthrough/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(passthrough)

target_sources(app PRIVATE src/main.c)
50 changes: 50 additions & 0 deletions samples/drivers/uart/passthrough/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
.. zephyr:code-sample:: uart-passthrough
:name: UART Passthrough
:relevant-api: uart_interface

Pass data directly between the console and another UART interface.

Overview
********

This sample will virtually connect two UART interfaces together, 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/drivers/uart/passthrough`.

Requirements
************

#. One UART interface, identified as Zephyr's console.
#. 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
12 changes: 12 additions & 0 deletions samples/drivers/uart/passthrough/boards/nucleo_l476rg.overlay
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/ {
chosen {
uart,passthrough = &uart4;
};
};

&uart4 {
pinctrl-0 = <&uart4_tx_pa0 &uart4_rx_pa1>;
pinctrl-names = "default";
current-speed = <9600>;
status = "okay";
};
3 changes: 3 additions & 0 deletions samples/drivers/uart/passthrough/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CONFIG_SERIAL=y
CONFIG_RING_BUFFER=y
CONFIG_UART_INTERRUPT_DRIVEN=y
14 changes: 14 additions & 0 deletions samples/drivers/uart/passthrough/sample.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
sample:
name: UART Passthrough
tests:
sample.drivers.uart:
integration_platforms:
- qemu_x86
tags:
- serial
- uart
filter: CONFIG_SERIAL and
CONFIG_UART_INTERRUPT_DRIVEN and
dt_chosen_enabled("zephyr,console") and
dt_chosen_enabled("uart,passthrough")
harness: keyboard
150 changes: 150 additions & 0 deletions samples/drivers/uart/passthrough/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* Copyright (c) 2024 Argentum Systems Ltd.
*
* 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>

struct patch_info {
const uint8_t * const name;

const struct device *rx_dev;
struct ring_buf *rx_ring_buf;
bool rx_error;
bool rx_overflow;

const struct device *tx_dev;
};

#define DEV_CONSOLE DEVICE_DT_GET(DT_CHOSEN(zephyr_console))
#define DEV_OTHER DEVICE_DT_GET(DT_CHOSEN(uart_passthrough))

#define RING_BUF_SIZE 64

RING_BUF_DECLARE(rb_console, RING_BUF_SIZE);
struct patch_info patch_c2o = {
.name = "c2o",

.rx_dev = DEV_CONSOLE,
.rx_ring_buf = &rb_console,
.rx_error = false,
.rx_overflow = false,

.tx_dev = DEV_OTHER,
};

RING_BUF_DECLARE(rb_other, RING_BUF_SIZE);
struct patch_info patch_o2c = {
.name = "o2c",

.rx_dev = DEV_OTHER,
.rx_ring_buf = &rb_other,
.rx_error = false,
.rx_overflow = false,

.tx_dev = DEV_CONSOLE,
};

static void uart_cb(const struct device *dev, void *ctx)
{
struct patch_info *patch = (struct patch_info *)ctx;
int ret;
uint8_t *buf;
uint32_t len;

while (uart_irq_update(patch->rx_dev) > 0) {
ret = uart_irq_rx_ready(patch->rx_dev);
if (ret < 0) {
patch->rx_error = true;
}
if (ret <= 0) {
break;
}

len = ring_buf_put_claim(patch->rx_ring_buf, &buf, RING_BUF_SIZE);
if (len == 0) {
/* no space for Rx, disable the IRQ */
uart_irq_rx_disable(patch->rx_dev);
patch->rx_overflow = true;
break;
}

ret = uart_fifo_read(patch->rx_dev, buf, len);
if (ret < 0) {
patch->rx_error = true;
}
if (ret <= 0) {
break;
}
len = ret;

ret = ring_buf_put_finish(patch->rx_ring_buf, len);
if (ret != 0) {
patch->rx_error = true;
break;
}
}
}

static void passthrough(struct patch_info *patch)
{
int ret;
uint8_t *buf;
uint32_t len;

if (patch->rx_error) {
printk("<<%s: Rx Error!>>\n", patch->name);
patch->rx_error = false;
}

if (patch->rx_overflow) {
printk("<<%s: Rx Overflow!>>\n", patch->name);
patch->rx_overflow = false;
}

len = ring_buf_get_claim(patch->rx_ring_buf, &buf, RING_BUF_SIZE);
if (len == 0) {
goto done;
}

ret = uart_fifo_fill(patch->tx_dev, buf, len);
if (ret < 0) {
goto error;
}
len = ret;

ret = ring_buf_get_finish(patch->rx_ring_buf, len);
if (ret < 0) {
goto error;
}

done:
uart_irq_rx_enable(patch->rx_dev);
return;

error:
printk("<<%s: Tx Error!>>\n", patch->name);
}

int main(void)
{
printk("Console Device: %p\n", patch_c2o.rx_dev);
printk("Other Device: %p\n", patch_o2c.rx_dev);

uart_irq_callback_user_data_set(patch_c2o.rx_dev, uart_cb, (void *)&patch_c2o);
uart_irq_callback_user_data_set(patch_o2c.rx_dev, uart_cb, (void *)&patch_o2c);

for (;;) {
passthrough(&patch_c2o);
passthrough(&patch_o2c);
}

return 0;
}

0 comments on commit cf14d4f

Please sign in to comment.