I was lazy and used AI. LOL. No doubt, it's still highly contentious as to whether AI driver LLM's can ever do it "right" but here goes.Would certainly be interested to see your code, if only to see whether I've done it "right". (Mine's certainly a mess ATM). I do at least feel fairly comfortable with the basics of serial ports now.
Note that I created my own (odd?) logic... Characters sent via either of the 2 UART ports get passed through to the PIO driven UART channel and characters sent via the PIO UART channel get passed through to both UART channels.
Starting with the overlay:
Code:
/* The Pico and Pico 2 are pin compatible. *//* Defining pin control for 2nd standard UART port and the PIO driven UART port */&pinctrl {uart1_default: uart1_default {group1 {pinmux = <UART1_TX_P4>;};group2 {pinmux = <UART1_RX_P5>;input-enable;};};uart2_pio0_default: uart2_pio0_default {rx_pins {pinmux = <PIO0_P3>;input-enable;bias-pull-up;};tx_pins {pinmux = <PIO0_P2>;};};};/* UART settings for the PIO channel */&pio0 {status = "okay";uart2_pio0: uart2_pio0 {pinctrl-0 = <&uart2_pio0_default>;pinctrl-names = "default";compatible = "raspberrypi,pico-uart-pio";current-speed = <115200>;status = "okay";};};&i2c0 {status = "disabled";// done to ensure no mixed use of pins 4 and 5};/* Settings for the 2nd standard UART port */&uart1 {current-speed = <115200>;status = "okay";pinctrl-0 = <&uart1_default>;pinctrl-names = "default";};/ {chosen {uart,passthrough = &uart2_pio0;};aliases {uart-second = &uart1;};};Code:
/* * Copyright (c) 2025 Gerrikoio (AI assisted). * * 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;const struct device *tx_dev2;};#define DEV_CONSOLE DEVICE_DT_GET(DT_CHOSEN(zephyr_console))#define DEV_PASSED DEVICE_DT_GET(DT_CHOSEN(uart_passthrough))#define DEV_SECOND DEVICE_DT_GET(DT_ALIAS(uart_second))#define RING_BUF_SIZE 64RING_BUF_DECLARE(rb_console, RING_BUF_SIZE);struct patch_info patch_c2o_1 = {.name = "c2o-1",.rx_dev = DEV_CONSOLE,.rx_ring_buf = &rb_console,.rx_error = false,.rx_overflow = false,.tx_dev = DEV_PASSED,.tx_dev2 = NULL,};RING_BUF_DECLARE(rb_second, RING_BUF_SIZE);struct patch_info patch_c2o_2 = {.name = "c2o-2",.rx_dev = DEV_SECOND,.rx_ring_buf = &rb_second,.rx_error = false,.rx_overflow = false,.tx_dev = DEV_PASSED,.tx_dev2 = NULL,};RING_BUF_DECLARE(rb_other, RING_BUF_SIZE);struct patch_info patch_o2c = {.name = "o2c",.rx_dev = DEV_PASSED,.rx_ring_buf = &rb_other,.rx_error = false,.rx_overflow = false,.tx_dev = DEV_CONSOLE,.tx_dev2 = DEV_SECOND,};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 poll_other_rx(struct patch_info *patch){uint8_t c;int ret;ret = uart_poll_in(patch->rx_dev, &c);if (ret == 0) {/* Character received */ret = ring_buf_put(patch->rx_ring_buf, &c, 1);if (ret != 1) {patch->rx_overflow = true;}} else if (ret != -1) {/* A real error occurred */if (uart_err_check(patch->rx_dev)) {/* This will clear the error flags */patch->rx_error = true;}}}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;}if (patch->tx_dev == DEV_PASSED) {/* The PIO UART does not support uart_fifo_fill */for (uint32_t i = 0; i < len; i++) {uart_poll_out(patch->tx_dev, buf[i]);}ret = len;} else {/* Also send to the second Tx device if it exists */if (patch->tx_dev2) {ret = uart_fifo_fill(patch->tx_dev2, buf, len);if (ret < 0) {goto error;}}ret = uart_fifo_fill(patch->tx_dev, buf, len);if (ret < 0) {goto error;}}len = (ret > 0) ? ret : 0;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){if (!device_is_ready(patch_c2o_1.rx_dev) || !device_is_ready(patch_c2o_2.rx_dev) || !device_is_ready(patch_o2c.rx_dev)) {printk("UART devices not ready\n");return 0;}printk("Console Port: %s (%p)\n", patch_c2o_1.name, patch_c2o_1.rx_dev);printk("Second Port: %s (%p)\n", patch_c2o_2.name, patch_c2o_2.rx_dev);printk("Passthrough Port: %s (%p)\n", patch_o2c.name, patch_o2c.rx_dev);/* Use interrupt-driven API for the console (hardware UART) */uart_irq_callback_user_data_set(patch_c2o_1.rx_dev, uart_cb, (void *)&patch_c2o_1);/* Use interrupt-driven API for the second UART (hardware UART) */uart_irq_callback_user_data_set(patch_c2o_2.rx_dev, uart_cb, (void *)&patch_c2o_2);for (;;) {/* Poll the "other" device (PIO UART) for received data */poll_other_rx(&patch_o2c);passthrough(&patch_c2o_1);passthrough(&patch_c2o_2);passthrough(&patch_o2c);}return 0;}Statistics: Posted by gerrikoio — Tue Sep 30, 2025 9:17 am