├── .gitignore ├── serial.h ├── .gitmodules ├── tusb_config.h ├── CMakeLists.txt ├── LICENSE.md ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── README.md ├── uart_tx.pio ├── usb-descriptors.c ├── uart_rx.pio └── uart-bridge.c /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build 3 | -------------------------------------------------------------------------------- /serial.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern char serial[17]; -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pico-sdk"] 2 | path = pico-sdk 3 | url = https://github.com/raspberrypi/pico-sdk.git 4 | -------------------------------------------------------------------------------- /tusb_config.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Copyright (c) 2021 Álvaro Fernández Rojas 4 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 5 | * Copyright (c) 2020 Damien P. George 6 | */ 7 | 8 | #if !defined(_TUSB_CONFIG_H_) 9 | #define _TUSB_CONFIG_H_ 10 | 11 | #include 12 | 13 | #define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE 14 | 15 | #define CFG_TUD_CDC 6 16 | //#define CFG_TUD_CDC 5 17 | #define CFG_TUD_CDC_RX_BUFSIZE 1024 // no harm making these bigger I guess 18 | #define CFG_TUD_CDC_TX_BUFSIZE 1024 19 | 20 | #endif /* _TUSB_CONFIG_H_ */ 21 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | 3 | cmake_minimum_required(VERSION 3.13) 4 | 5 | include(pico-sdk/pico_sdk_init.cmake) 6 | 7 | project(pico_uart_bridge) 8 | 9 | pico_sdk_init() 10 | 11 | add_executable(uart_bridge uart-bridge.c usb-descriptors.c) 12 | 13 | pico_generate_pio_header(uart_bridge ${CMAKE_CURRENT_LIST_DIR}/uart_rx.pio) 14 | pico_generate_pio_header(uart_bridge ${CMAKE_CURRENT_LIST_DIR}/uart_tx.pio) 15 | 16 | target_include_directories(uart_bridge PUBLIC 17 | ./ 18 | ../pico-sdk/lib/tinyusb/src) 19 | 20 | target_link_libraries(uart_bridge 21 | pico_multicore 22 | pico_stdlib 23 | hardware_flash 24 | tinyusb_device 25 | hardware_pio) 26 | 27 | pico_add_extra_outputs(uart_bridge) 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2021 Álvaro Fernández Rojas 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | # Set your workflow to run on push events to the develop and all feature branches 5 | push: 6 | branches: 7 | - develop 8 | - feature/* 9 | # Set your workflow to run on pull request events that target the master branch 10 | pull_request: 11 | branches: 12 | - master 13 | 14 | jobs: 15 | pico: 16 | name: RPi Pico compilation 17 | runs-on: ubuntu-20.04 18 | steps: 19 | - name: 'Check out code' 20 | uses: actions/checkout@v3 21 | 22 | - name: 'Install dependencies' 23 | run: | 24 | sudo apt-get install cmake gcc-arm-none-eabi libnewlib-arm-none-eabi 25 | - name: 'Update Submodules' 26 | run: | 27 | git submodule sync --recursive 28 | git submodule update --init --recursive 29 | - name: 'Configure' 30 | run: | 31 | mkdir -p build 32 | cmake -B build 33 | - name: 'Build' 34 | run: | 35 | make -C build 36 | - name: 'Upload binary' 37 | uses: actions/upload-artifact@v3 38 | with: 39 | name: pico-uart-bridge.uf2 40 | path: build/uart_bridge.uf2 41 | retention-days: 5 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Raspberry Pi Pico USB-UART Bridge 2 | ================================= 3 | This project converts the Raspberry Pi Pico(or any RP2040) into a USB to 6 UART board. 4 | 5 | History 6 | ---------- 7 | This expands [Noltari's](https://github.com/Noltari/pico-uart-bridge) project to add 4 additional UARTs using the pico PIOs. And expands on [harrywalsh's](https://github.com/harrywalsh/pico-hw_and_pio-uart-gridge) project to provide better SEO and remove some data loss when using all 6 UARTs concurrently. 8 | 9 | Disclaimer 10 | ---------- 11 | 12 | This software is provided without warranty, according to the MIT License, and should therefore not be used where it may endanger life, financial stakes, or cause discomfort and inconvenience to others. 13 | 14 | Raspberry Pi Pico Pinout 15 | ------------------------ 16 | The pinout can easily be modified in uart-bridge.c but below is the default 17 | 18 | | Raspberry Pi Pico GPIO | Function | 19 | |:----------------------:|:--------:| 20 | | GPIO0 (Pin 1) | UART0 TX | 21 | | GPIO1 (Pin 2) | UART0 RX | 22 | | GPIO4 (Pin 6) | UART1 TX | 23 | | GPIO5 (Pin 7) | UART1 RX | 24 | | GPIO8 (Pin 11) | UART2 TX | 25 | | GPIO9 (Pin 12) | UART2 RX | 26 | | GPIO12 (Pin 16) | UART3 TX | 27 | | GPIO13 (Pin 17) | UART3 RX | 28 | | GPIO16 (Pin 21) | UART4 TX | 29 | | GPIO17 (Pin 22) | UART4 RX | 30 | | GPIO20 (Pin 26) | UART5 TX | 31 | | GPIO21 (Pin 27) | UART5 RX | 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | tag: 6 | description: 'New tag to create' 7 | required: true 8 | type: string 9 | jobs: 10 | Release: 11 | runs-on: ubuntu-20.04 12 | steps: 13 | - name: 'Check out code' 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | - name: Create Tag 18 | uses: rickstaa/action-create-tag@v1 19 | with: 20 | tag: "${{ inputs.tag }}" 21 | message: "" 22 | - name: Create Release 23 | uses: fregante/release-with-changelog@v3 24 | id: create_release 25 | with: 26 | token: ${{ secrets.GITHUB_TOKEN }} 27 | - name: 'Install dependencies' 28 | run: | 29 | sudo apt-get install cmake gcc-arm-none-eabi libnewlib-arm-none-eabi 30 | - name: 'Update Submodules' 31 | run: | 32 | git submodule sync --recursive 33 | git submodule update --init --recursive 34 | - name: 'Configure' 35 | run: | 36 | mkdir -p build 37 | cmake -B build 38 | - name: 'Build' 39 | run: | 40 | make -C build 41 | - name: Upload uf2 42 | uses: actions/upload-release-asset@v1 43 | env: 44 | GITHUB_TOKEN: ${{ github.token }} 45 | with: 46 | upload_url: ${{ steps.create_release.outputs.upload_url }} 47 | asset_name: pico-sexa-uart-bridge-${{ inputs.tag }}.uf2 48 | asset_path: build/uart_bridge.uf2 49 | asset_content_type: application/octet-stream -------------------------------------------------------------------------------- /uart_tx.pio: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | ; 4 | ; SPDX-License-Identifier: BSD-3-Clause 5 | ; 6 | 7 | .program uart_tx 8 | .side_set 1 opt 9 | 10 | ; An 8n1 UART transmit program. 11 | ; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin. 12 | 13 | pull side 1 [7] ; Assert stop bit, or stall with line in idle state 14 | set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks 15 | bitloop: ; This loop will run 8 times (8n1 UART) 16 | out pins, 1 ; Shift 1 bit from OSR to the first OUT pin 17 | jmp x-- bitloop [6] ; Each loop iteration is 8 cycles. 18 | 19 | .program uart_txp 20 | .side_set 1 opt 21 | 22 | ; An 8n1 UART transmit program. 23 | ; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin. 24 | 25 | pull side 1 [7] ; Assert stop bit, or stall with line in idle state 26 | set x, 8 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks 27 | bitloop: ; This loop will run 9 times (8p1 UART) 28 | out pins, 1 ; Shift 1 bit from OSR to the first OUT pin 29 | jmp x-- bitloop [6] ; Each loop iteration is 8 cycles. 30 | 31 | 32 | % c-sdk { 33 | #include "hardware/clocks.h" 34 | #include 35 | 36 | static inline void uart_tx_program_init(PIO pio, uint sm, uint offset, uint pin_tx, uint baud) { 37 | // Tell PIO to initially drive output-high on the selected pin, then map PIO 38 | // onto that pin with the IO muxes. 39 | pio_sm_set_pins_with_mask(pio, sm, 1u << pin_tx, 1u << pin_tx); 40 | pio_sm_set_pindirs_with_mask(pio, sm, 1u << pin_tx, 1u << pin_tx); 41 | pio_gpio_init(pio, pin_tx); 42 | 43 | pio_sm_config c = uart_tx_program_get_default_config(offset); 44 | 45 | // OUT shifts to right, no autopull 46 | sm_config_set_out_shift(&c, true, false, 32); 47 | 48 | // We are mapping both OUT and side-set to the same pin, because sometimes 49 | // we need to assert user data onto the pin (with OUT) and sometimes 50 | // assert constant values (start/stop bit) 51 | sm_config_set_out_pins(&c, pin_tx, 1); 52 | sm_config_set_sideset_pins(&c, pin_tx); 53 | 54 | // We only need TX, so get an 8-deep FIFO! 55 | sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); 56 | 57 | // SM transmits 1 bit per 8 execution cycles. 58 | float div = (float)clock_get_hz(clk_sys) / (8 * baud); 59 | sm_config_set_clkdiv(&c, div); 60 | 61 | pio_sm_init(pio, sm, offset, &c); 62 | pio_sm_set_enabled(pio, sm, true); 63 | } 64 | 65 | static inline void uart_tx_program_putc(PIO pio, uint sm, char c, char parity_setting) { 66 | uint32_t cp; 67 | 68 | if (parity_setting!=UART_PARITY_NONE) { 69 | uint16_t parity = c^(c>>1); 70 | parity=parity^(parity>>2); 71 | parity=parity^(parity>>4); 72 | if (parity_setting==UART_PARITY_EVEN) 73 | cp = ((parity&0x01)<<8)|c; 74 | else 75 | cp = ((parity^0x01)<<8)|c; 76 | } else { 77 | cp=(uint32_t)c; 78 | } 79 | pio_sm_put_blocking(pio, sm, cp); 80 | } 81 | 82 | static inline void uart_tx_program_puts(PIO pio, uint sm, const char *s, char parity_setting) { 83 | while (*s) 84 | uart_tx_program_putc(pio, sm, *s++, parity_setting); 85 | } 86 | 87 | %} 88 | -------------------------------------------------------------------------------- /usb-descriptors.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Copyright (c) 2021 Álvaro Fernández Rojas 4 | * 5 | * This file is based on a file originally part of the 6 | * MicroPython project, http://micropython.org/ 7 | * 8 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 9 | * Copyright (c) 2019 Damien P. George 10 | */ 11 | 12 | #include 13 | 14 | #include "serial.h" 15 | 16 | #define DESC_STR_MAX 20 17 | 18 | #define USBD_VID 0x2E8A /* Raspberry Pi */ 19 | #define USBD_PID 0x000A /* Raspberry Pi Pico SDK CDC */ 20 | 21 | #define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN * CFG_TUD_CDC) 22 | #define USBD_MAX_POWER_MA 500 23 | 24 | #define USBD_ITF_CDC_0 0 25 | #define USBD_ITF_CDC_1 2 26 | #define USBD_ITF_CDC_2 4 27 | #define USBD_ITF_CDC_3 6 28 | #define USBD_ITF_CDC_4 8 29 | //#define USBD_ITF_MAX 10 30 | #define USBD_ITF_CDC_5 10 31 | #define USBD_ITF_MAX 12 32 | 33 | #define USBD_CDC_0_EP_CMD 0x81 34 | #define USBD_CDC_1_EP_CMD 0x83 35 | #define USBD_CDC_2_EP_CMD 0x85 36 | #define USBD_CDC_3_EP_CMD 0x87 37 | #define USBD_CDC_4_EP_CMD 0x89 38 | #define USBD_CDC_5_EP_CMD 0x8B 39 | 40 | #define USBD_CDC_0_EP_OUT 0x01 41 | #define USBD_CDC_1_EP_OUT 0x03 42 | #define USBD_CDC_2_EP_OUT 0x05 43 | #define USBD_CDC_3_EP_OUT 0x07 44 | #define USBD_CDC_4_EP_OUT 0x09 45 | #define USBD_CDC_5_EP_OUT 0x0B 46 | 47 | 48 | 49 | #define USBD_CDC_0_EP_IN 0x82 50 | #define USBD_CDC_1_EP_IN 0x84 51 | #define USBD_CDC_2_EP_IN 0x86 52 | #define USBD_CDC_3_EP_IN 0x88 53 | #define USBD_CDC_4_EP_IN 0x8E // 8D works at 9600, 8E seems to work best 54 | #define USBD_CDC_5_EP_IN 0x8C 55 | 56 | 57 | 58 | #define USBD_CDC_CMD_MAX_SIZE 8 59 | #define USBD_CDC_IN_OUT_MAX_SIZE 64 60 | 61 | #define USBD_STR_0 0x00 62 | #define USBD_STR_MANUF 0x01 63 | #define USBD_STR_PRODUCT 0x02 64 | #define USBD_STR_SERIAL 0x03 65 | #define USBD_STR_CDC 0x04 66 | 67 | static const tusb_desc_device_t usbd_desc_device = { 68 | .bLength = sizeof(tusb_desc_device_t), 69 | .bDescriptorType = TUSB_DESC_DEVICE, 70 | .bcdUSB = 0x0200, 71 | .bDeviceClass = TUSB_CLASS_MISC, 72 | .bDeviceSubClass = MISC_SUBCLASS_COMMON, 73 | .bDeviceProtocol = MISC_PROTOCOL_IAD, 74 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 75 | .idVendor = USBD_VID, 76 | .idProduct = USBD_PID, 77 | .bcdDevice = 0x0100, 78 | .iManufacturer = USBD_STR_MANUF, 79 | .iProduct = USBD_STR_PRODUCT, 80 | .iSerialNumber = USBD_STR_SERIAL, 81 | .bNumConfigurations = 1, 82 | }; 83 | 84 | static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { 85 | TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_MAX, USBD_STR_0, USBD_DESC_LEN, 86 | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USBD_MAX_POWER_MA), 87 | 88 | TUD_CDC_DESCRIPTOR(USBD_ITF_CDC_0, USBD_STR_CDC, USBD_CDC_0_EP_CMD, 89 | USBD_CDC_CMD_MAX_SIZE, USBD_CDC_0_EP_OUT, USBD_CDC_0_EP_IN, 90 | USBD_CDC_IN_OUT_MAX_SIZE), 91 | 92 | TUD_CDC_DESCRIPTOR(USBD_ITF_CDC_1, USBD_STR_CDC, USBD_CDC_1_EP_CMD, 93 | USBD_CDC_CMD_MAX_SIZE, USBD_CDC_1_EP_OUT, USBD_CDC_1_EP_IN, 94 | USBD_CDC_IN_OUT_MAX_SIZE), 95 | 96 | TUD_CDC_DESCRIPTOR(USBD_ITF_CDC_2, USBD_STR_CDC, USBD_CDC_2_EP_CMD, 97 | USBD_CDC_CMD_MAX_SIZE, USBD_CDC_2_EP_OUT, USBD_CDC_2_EP_IN, 98 | USBD_CDC_IN_OUT_MAX_SIZE), 99 | 100 | TUD_CDC_DESCRIPTOR(USBD_ITF_CDC_3, USBD_STR_CDC, USBD_CDC_3_EP_CMD, 101 | USBD_CDC_CMD_MAX_SIZE, USBD_CDC_3_EP_OUT, USBD_CDC_3_EP_IN, 102 | USBD_CDC_IN_OUT_MAX_SIZE), 103 | 104 | TUD_CDC_DESCRIPTOR(USBD_ITF_CDC_4, USBD_STR_CDC, USBD_CDC_4_EP_CMD, 105 | USBD_CDC_CMD_MAX_SIZE, USBD_CDC_4_EP_OUT, USBD_CDC_4_EP_IN, 106 | USBD_CDC_IN_OUT_MAX_SIZE), 107 | 108 | TUD_CDC_DESCRIPTOR(USBD_ITF_CDC_5, USBD_STR_CDC, USBD_CDC_5_EP_CMD, 109 | USBD_CDC_CMD_MAX_SIZE, USBD_CDC_5_EP_OUT, USBD_CDC_5_EP_IN, 110 | USBD_CDC_IN_OUT_MAX_SIZE), 111 | 112 | 113 | }; 114 | 115 | char serial[17]; 116 | 117 | static char *const usbd_desc_str[] = { 118 | [USBD_STR_MANUF] = "Raspberry Pi", 119 | [USBD_STR_PRODUCT] = "Pico", 120 | [USBD_STR_SERIAL] = serial, 121 | [USBD_STR_CDC] = "Board CDC", 122 | }; 123 | 124 | const uint8_t *tud_descriptor_device_cb(void) 125 | { 126 | return (const uint8_t *) &usbd_desc_device; 127 | } 128 | 129 | const uint8_t *tud_descriptor_configuration_cb(uint8_t index) 130 | { 131 | return usbd_desc_cfg; 132 | } 133 | 134 | const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) 135 | { 136 | static uint16_t desc_str[DESC_STR_MAX]; 137 | uint8_t len; 138 | 139 | if (index == 0) { 140 | desc_str[1] = 0x0409; 141 | len = 1; 142 | } else { 143 | const char *str; 144 | 145 | if (index >= sizeof(usbd_desc_str) / sizeof(usbd_desc_str[0])) 146 | return NULL; 147 | 148 | str = usbd_desc_str[index]; 149 | for (len = 0; len < DESC_STR_MAX - 1 && str[len]; ++len) 150 | desc_str[1 + len] = str[len]; 151 | } 152 | 153 | desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * len + 2); 154 | 155 | return desc_str; 156 | } 157 | -------------------------------------------------------------------------------- /uart_rx.pio: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | ; 4 | ; SPDX-License-Identifier: BSD-3-Clause 5 | ; 6 | 7 | .program uart_rx_mini 8 | 9 | ; Minimum viable 8n1 UART receiver. Wait for the start bit, then sample 8 bits 10 | ; with the correct timing. 11 | ; IN pin 0 is mapped to the GPIO used as UART RX. 12 | ; Autopush must be enabled, with a threshold of 8. 13 | 14 | wait 0 pin 0 ; Wait for start bit 15 | set x, 7 [10] ; Preload bit counter, delay until eye of first data bit 16 | bitloop: ; Loop 8 times 17 | in pins, 1 ; Sample data 18 | jmp x-- bitloop [6] ; Each iteration is 8 cycles 19 | 20 | % c-sdk { 21 | #include "hardware/clocks.h" 22 | #include "hardware/gpio.h" 23 | 24 | static inline void uart_rx_mini_program_init(PIO pio, uint sm, uint offset, uint pin, uint baud) { 25 | pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false); 26 | pio_gpio_init(pio, pin); 27 | gpio_pull_up(pin); 28 | 29 | pio_sm_config c = uart_rx_mini_program_get_default_config(offset); 30 | sm_config_set_in_pins(&c, pin); // for WAIT, IN 31 | // Shift to right, autopush enabled 32 | sm_config_set_in_shift(&c, true, true, 8); 33 | sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); 34 | // SM transmits 1 bit per 8 execution cycles. 35 | float div = (float)clock_get_hz(clk_sys) / (8 * baud); 36 | sm_config_set_clkdiv(&c, div); 37 | 38 | pio_sm_init(pio, sm, offset, &c); 39 | pio_sm_set_enabled(pio, sm, true); 40 | } 41 | %} 42 | 43 | .program uart_rx 44 | 45 | ; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and 46 | ; break conditions more gracefully. 47 | ; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX. 48 | 49 | start: 50 | wait 0 pin 0 ; Stall until start bit is asserted 51 | set x, 7 [10] ; Preload bit counter, then delay until halfway through 52 | bitloop: ; the first data bit (12 cycles incl wait, set). 53 | in pins, 1 ; Shift data bit into ISR 54 | jmp x-- bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles 55 | jmp pin good_stop ; Check stop bit (should be high) 56 | 57 | irq 4 rel ; Either a framing error or a break. Set a sticky flag, 58 | wait 1 pin 0 ; and wait for line to return to idle state. 59 | jmp start ; Don't push data if we didn't see good framing. 60 | 61 | good_stop: ; No delay before returning to start; a little slack is 62 | in null,24 63 | push ; important in case the TX clock is slightly too fast. 64 | 65 | .program uart_rxp 66 | start: 67 | wait 0 pin 0 ; Stall until start bit is asserted 68 | set x, 8 [10] ; Preload bit counter, then delay until halfway through 69 | bitloop: ; the first data bit (12 cycles incl wait, set). 70 | in pins, 1 ; Shift data bit into ISR 71 | jmp x-- bitloop [6] ; Loop 9 times, each loop iteration is 8 cycles 72 | jmp pin good_stop ; Check stop bit (should be high) 73 | 74 | irq 4 rel ; Either a framing error or a break. Set a sticky flag, 75 | wait 1 pin 0 ; and wait for line to return to idle state. 76 | jmp start ; Don't push data if we didn't see good framing. 77 | 78 | good_stop: ; No delay before returning to start; a little slack is 79 | in null, 23 80 | push ; important in case the TX clock is slightly too fast. 81 | 82 | % c-sdk { 83 | static inline void uart_rx_program_init(PIO pio, uint sm, uint offset, uint pin, uint baud) { 84 | pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false); 85 | pio_gpio_init(pio, pin); 86 | gpio_pull_up(pin); 87 | 88 | pio_sm_config c = uart_rx_program_get_default_config(offset); 89 | sm_config_set_in_pins(&c, pin); // for WAIT, IN 90 | sm_config_set_jmp_pin(&c, pin); // for JMP 91 | // Shift to right, autopull disabled 92 | sm_config_set_in_shift(&c, true, false, 32); 93 | // Deeper FIFO as we're not doing any TX 94 | sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); 95 | // SM transmits 1 bit per 8 execution cycles. 96 | float div = (float)clock_get_hz(clk_sys) / (8 * baud); 97 | sm_config_set_clkdiv(&c, div); 98 | 99 | pio_sm_init(pio, sm, offset, &c); 100 | pio_sm_set_enabled(pio, sm, true); 101 | } 102 | 103 | static inline void uart_baud(PIO pio, uint sm, uint baud) { 104 | float div = (float)clock_get_hz(clk_sys) / (8 * baud); 105 | pio_sm_set_clkdiv(pio,sm,div); 106 | } 107 | 108 | static inline char uart_rx_program_getc(PIO pio, uint sm) { 109 | // 8-bit read from the uppermost byte of the FIFO, as data is left-justified 110 | io_rw_8 *rxfifo_shift = (io_rw_8*)&pio->rxf[sm]; 111 | while (pio_sm_is_rx_fifo_empty(pio, sm)) 112 | tight_loop_contents(); 113 | return (char)*rxfifo_shift; 114 | } 115 | 116 | %} 117 | -------------------------------------------------------------------------------- /uart-bridge.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | /* 3 | * Copyright 2021 Álvaro Fernández Rojas 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "serial.h" 18 | 19 | #if !defined(MIN) 20 | #define MIN(a, b) ((a > b) ? b : a) 21 | #endif /* MIN */ 22 | 23 | #define LED_PIN 25 24 | 25 | // might as well use our RAM 26 | #define BUFFER_SIZE 2560 27 | 28 | #define DEF_BIT_RATE 9600 29 | #define DEF_STOP_BITS 1 30 | #define DEF_PARITY 0 31 | #define DEF_DATA_BITS 8 32 | 33 | typedef struct { 34 | uart_inst_t *const inst; 35 | uint8_t tx_pin; 36 | uint8_t rx_pin; 37 | uint sm; 38 | } uart_id_t; 39 | 40 | typedef struct { 41 | cdc_line_coding_t usb_lc; 42 | cdc_line_coding_t uart_lc; 43 | mutex_t lc_mtx; 44 | uint8_t uart_rx_buffer[BUFFER_SIZE]; 45 | uint32_t uart_rx_pos; 46 | uint8_t uart_to_usb_buffer[BUFFER_SIZE]; 47 | uint32_t uart_to_usb_pos; 48 | mutex_t uart_mtx; 49 | uint8_t usb_to_uart_buffer[BUFFER_SIZE]; 50 | uint32_t usb_to_uart_pos; 51 | uint32_t usb_to_uart_snd; 52 | mutex_t usb_mtx; 53 | } uart_data_t; 54 | 55 | uart_id_t UART_ID[CFG_TUD_CDC] = { 56 | { 57 | .inst = uart0, 58 | .tx_pin = 0, 59 | .rx_pin = 1, 60 | },{ 61 | .inst = uart1, 62 | .tx_pin = 4, 63 | .rx_pin = 5, 64 | },{ 65 | .inst = 0, 66 | .tx_pin = 8, 67 | .rx_pin = 9, 68 | .sm = 0, 69 | },{ 70 | .inst = 0, 71 | .tx_pin = 12, 72 | .rx_pin = 13, 73 | .sm = 1, 74 | },{ 75 | .inst = 0, 76 | .tx_pin = 16, 77 | .rx_pin = 17, 78 | .sm = 2, 79 | },{ 80 | .inst = 0, 81 | .tx_pin = 20, 82 | .rx_pin = 21, 83 | .sm = 3, 84 | } 85 | }; 86 | 87 | uart_data_t UART_DATA[CFG_TUD_CDC]; 88 | 89 | uint rx_offset=0; 90 | uint rxp_offset=0; 91 | 92 | uint tx_offset=0; 93 | uint txp_offset=0; 94 | 95 | static inline uint databits_usb2uart(uint8_t data_bits) 96 | { 97 | switch (data_bits) { 98 | case 5: 99 | return 5; 100 | case 6: 101 | return 6; 102 | case 7: 103 | return 7; 104 | default: 105 | return 8; 106 | } 107 | } 108 | 109 | static inline uart_parity_t parity_usb2uart(uint8_t usb_parity) 110 | { 111 | switch (usb_parity) { 112 | case 1: 113 | return UART_PARITY_ODD; 114 | case 2: 115 | return UART_PARITY_EVEN; 116 | default: 117 | return UART_PARITY_NONE; 118 | } 119 | } 120 | 121 | static inline uint stopbits_usb2uart(uint8_t stop_bits) 122 | { 123 | switch (stop_bits) { 124 | case 2: 125 | return 2; 126 | default: 127 | return 1; 128 | } 129 | } 130 | 131 | void update_uart_cfg(uint8_t itf) 132 | { 133 | uart_id_t *ui = &UART_ID[itf]; 134 | uart_data_t *ud = &UART_DATA[itf]; 135 | 136 | if (mutex_try_enter(&ud->lc_mtx, NULL)) { 137 | if (ui->inst != 0) { //regular uart 138 | if (ud->usb_lc.bit_rate != ud->uart_lc.bit_rate) { 139 | uart_set_baudrate(ui->inst, ud->usb_lc.bit_rate); 140 | ud->uart_lc.bit_rate = ud->usb_lc.bit_rate; 141 | } 142 | 143 | if ((ud->usb_lc.stop_bits != ud->uart_lc.stop_bits) || 144 | (ud->usb_lc.parity != ud->uart_lc.parity) || 145 | (ud->usb_lc.data_bits != ud->uart_lc.data_bits)) { 146 | uart_set_format(ui->inst, 147 | databits_usb2uart(ud->usb_lc.data_bits), 148 | stopbits_usb2uart(ud->usb_lc.stop_bits), 149 | parity_usb2uart(ud->usb_lc.parity)); 150 | ud->uart_lc.data_bits = ud->usb_lc.data_bits; 151 | ud->uart_lc.parity = ud->usb_lc.parity; 152 | ud->uart_lc.stop_bits = ud->usb_lc.stop_bits; 153 | } 154 | } else { 155 | if (ud->usb_lc.bit_rate != ud->uart_lc.bit_rate) { 156 | uart_baud(pio0,ui->sm,ud->usb_lc.bit_rate); 157 | uart_baud(pio1,ui->sm,ud->usb_lc.bit_rate); 158 | ud->uart_lc.bit_rate = ud->usb_lc.bit_rate; 159 | } 160 | if (ud->usb_lc.parity != ud->uart_lc.parity) { 161 | ud->uart_lc.parity = ud->usb_lc.parity; 162 | if (ud->usb_lc.parity == UART_PARITY_NONE) { 163 | uart_rx_program_init(pio0, ui->sm, rx_offset, ui->rx_pin, ud->uart_lc.bit_rate); 164 | uart_tx_program_init(pio1, ui->sm, tx_offset, ui->tx_pin, ud->uart_lc.bit_rate); 165 | } else { 166 | uart_rx_program_init(pio0, ui->sm, rxp_offset, ui->rx_pin, ud->uart_lc.bit_rate); 167 | uart_tx_program_init(pio1, ui->sm, txp_offset, ui->tx_pin, ud->uart_lc.bit_rate); 168 | } 169 | } 170 | } 171 | mutex_exit(&ud->lc_mtx); 172 | } 173 | } 174 | 175 | void usb_read_bytes(uint8_t itf) { 176 | uint32_t len = tud_cdc_n_available(itf); 177 | 178 | if (len) { 179 | uart_data_t *ud = &UART_DATA[itf]; 180 | 181 | mutex_enter_blocking(&ud->usb_mtx); 182 | 183 | len = MIN(len, BUFFER_SIZE - ud->usb_to_uart_pos); 184 | if (len) { 185 | uint32_t count; 186 | 187 | count = tud_cdc_n_read(itf, &ud->usb_to_uart_buffer[ud->usb_to_uart_pos], len); 188 | ud->usb_to_uart_pos += count; 189 | } 190 | 191 | mutex_exit(&ud->usb_mtx); 192 | } 193 | } 194 | 195 | void usb_write_bytes(uint8_t itf) { 196 | uart_data_t *ud = &UART_DATA[itf]; 197 | 198 | if (ud->uart_to_usb_pos && mutex_try_enter(&ud->uart_mtx, NULL)) { 199 | uint32_t count; 200 | 201 | count = tud_cdc_n_write(itf, ud->uart_to_usb_buffer, ud->uart_to_usb_pos); 202 | if (count < ud->uart_to_usb_pos) 203 | memcpy(ud->uart_to_usb_buffer, &ud->uart_to_usb_buffer[count], 204 | ud->uart_to_usb_pos - count); 205 | ud->uart_to_usb_pos -= count; 206 | 207 | mutex_exit(&ud->uart_mtx); 208 | 209 | if (count) 210 | tud_cdc_n_write_flush(itf); 211 | } 212 | } 213 | 214 | void usb_cdc_process(uint8_t itf) 215 | { 216 | uart_data_t *ud = &UART_DATA[itf]; 217 | 218 | mutex_enter_blocking(&ud->lc_mtx); 219 | tud_cdc_n_get_line_coding(itf, &ud->usb_lc); 220 | mutex_exit(&ud->lc_mtx); 221 | 222 | usb_read_bytes(itf); 223 | usb_write_bytes(itf); 224 | } 225 | 226 | void core1_entry(void) 227 | { 228 | tusb_init(); 229 | 230 | while (1) { 231 | int itf; 232 | int con = 0; 233 | 234 | tud_task(); 235 | 236 | for (itf = 0; itf < CFG_TUD_CDC; itf++) { 237 | if (tud_cdc_n_connected(itf)) { 238 | con = 1; 239 | usb_cdc_process(itf); 240 | } 241 | } 242 | 243 | gpio_put(LED_PIN, con); 244 | } 245 | } 246 | 247 | void uart_read_bytes(uint8_t itf) 248 | { 249 | const uart_id_t *ui = &UART_ID[itf]; 250 | uart_data_t *ud = &UART_DATA[itf]; 251 | 252 | if (ui->inst != 0) { 253 | if (uart_is_readable(ui->inst)) { 254 | while (uart_is_readable(ui->inst) && 255 | ud->uart_rx_pos < BUFFER_SIZE) { 256 | ud->uart_rx_buffer[ud->uart_rx_pos] = uart_getc(ui->inst); 257 | ud->uart_rx_pos++; 258 | } 259 | } 260 | } else { 261 | if (!pio_sm_is_rx_fifo_empty(pio0, ui->sm)) { 262 | while (!pio_sm_is_rx_fifo_empty(pio0, ui->sm) && 263 | ud->uart_rx_pos < BUFFER_SIZE) { 264 | ud->uart_rx_buffer[ud->uart_rx_pos] = uart_rx_program_getc(pio0, ui->sm); 265 | ud->uart_rx_pos++; 266 | } 267 | } 268 | } 269 | // If we can get the uart mutex then copy the UART data to the uart USB sender, otherwise we'll get it next time around 270 | if (mutex_try_enter(&ud->uart_mtx, NULL)) { 271 | // Ensure we don't overflow the uart_to_usb_buffer 272 | uint32_t len = MIN(ud->uart_rx_pos, BUFFER_SIZE - ud->uart_to_usb_pos); 273 | memcpy(&ud->uart_to_usb_buffer[ud->uart_to_usb_pos], ud->uart_rx_buffer, len); 274 | ud->uart_to_usb_pos += len; 275 | ud->uart_rx_pos = 0; 276 | mutex_exit(&ud->uart_mtx); 277 | } 278 | } 279 | 280 | void uart_write_bytes(uint8_t itf) { 281 | uart_data_t *ud = &UART_DATA[itf]; 282 | 283 | // Try to get the usb_mutex and don't block if we cannot get it, we'll TX the data next passs 284 | if ((ud->usb_to_uart_pos) && (ud->usb_to_uart_snd < ud->usb_to_uart_pos) && 285 | mutex_try_enter(&ud->usb_mtx, NULL)) { 286 | const uart_id_t *ui = &UART_ID[itf]; 287 | 288 | if (ui->inst != 0){ 289 | while (uart_is_writable(ui->inst)&&(ud->usb_to_uart_snd < ud->usb_to_uart_pos)) { 290 | uart_putc(ui->inst, ud->usb_to_uart_buffer[ud->usb_to_uart_snd++]); 291 | } 292 | } else { 293 | size_t bufspace=7-pio_sm_get_tx_fifo_level(pio1,ui->sm); 294 | size_t tosend=ud->usb_to_uart_pos-ud->usb_to_uart_snd; 295 | tosend = MIN(tosend,bufspace); 296 | 297 | for (size_t i = 0; ism, ud->usb_to_uart_buffer[ud->usb_to_uart_snd+i],ud->usb_lc.parity); 299 | } 300 | ud->usb_to_uart_snd+=tosend; 301 | } 302 | // only reset buffers if we've sent everything 303 | if (ud->usb_to_uart_snd == ud->usb_to_uart_pos) { 304 | ud->usb_to_uart_pos = 0; 305 | ud->usb_to_uart_snd = 0; 306 | } 307 | mutex_exit(&ud->usb_mtx); 308 | } 309 | } 310 | 311 | static inline void init_usb_cdc_serial_num() { 312 | uint8_t id[8]; 313 | flash_get_unique_id(id); 314 | for (int i = 0; i < 8; ++i) { 315 | sprintf(serial + 2 * i, "%X", id[i]); 316 | } 317 | serial[16] = '\0'; 318 | } 319 | 320 | void init_uart_data(uint8_t itf) { 321 | uart_id_t *ui = &UART_ID[itf]; 322 | uart_data_t *ud = &UART_DATA[itf]; 323 | 324 | if (ui->inst != 0) { 325 | /* Pinmux */ 326 | gpio_set_function(ui->tx_pin, GPIO_FUNC_UART); 327 | gpio_set_function(ui->rx_pin, GPIO_FUNC_UART); 328 | } 329 | 330 | /* USB CDC LC */ 331 | ud->usb_lc.bit_rate = DEF_BIT_RATE; 332 | ud->usb_lc.data_bits = DEF_DATA_BITS; 333 | ud->usb_lc.parity = DEF_PARITY; 334 | ud->usb_lc.stop_bits = DEF_STOP_BITS; 335 | 336 | /* UART LC */ 337 | ud->uart_lc.bit_rate = DEF_BIT_RATE; 338 | ud->uart_lc.data_bits = DEF_DATA_BITS; 339 | ud->uart_lc.parity = DEF_PARITY; 340 | ud->uart_lc.stop_bits = DEF_STOP_BITS; 341 | 342 | /* Buffer */ 343 | ud->uart_rx_pos = 0; 344 | ud->uart_to_usb_pos = 0; 345 | ud->usb_to_uart_pos = 0; 346 | ud->usb_to_uart_snd = 0; 347 | 348 | /* Mutex */ 349 | mutex_init(&ud->lc_mtx); 350 | mutex_init(&ud->uart_mtx); 351 | mutex_init(&ud->usb_mtx); 352 | 353 | if (ui->inst != 0){ 354 | /* UART start */ 355 | uart_init(ui->inst, ud->usb_lc.bit_rate); 356 | uart_set_hw_flow(ui->inst, false, false); 357 | uart_set_format(ui->inst, databits_usb2uart(ud->usb_lc.data_bits), 358 | stopbits_usb2uart(ud->usb_lc.stop_bits), 359 | parity_usb2uart(ud->usb_lc.parity)); 360 | } else { 361 | // Set up the state machine we're going to use to for rx/tx 362 | uart_rx_program_init(pio0, ui->sm, rx_offset, ui->rx_pin, ud->uart_lc.bit_rate); 363 | uart_tx_program_init(pio1, ui->sm, tx_offset, ui->tx_pin, ud->uart_lc.bit_rate); 364 | } 365 | } 366 | 367 | int main(void) 368 | { 369 | int itf; 370 | 371 | // store our PIO programs in tbe instruction registers 372 | // we'll use pio0 for RX and pio1 for tx so only one copy of each is needed 373 | // however we'll use a different program to send/receive with parity 374 | rx_offset = pio_add_program(pio0, &uart_rx_program); 375 | tx_offset = pio_add_program(pio1, &uart_tx_program); 376 | rxp_offset = pio_add_program(pio0, &uart_rxp_program); 377 | txp_offset = pio_add_program(pio1, &uart_txp_program); 378 | 379 | init_usb_cdc_serial_num(); 380 | 381 | for (itf = 0; itf < CFG_TUD_CDC; itf++) 382 | init_uart_data(itf); 383 | 384 | gpio_init(LED_PIN); 385 | gpio_set_dir(LED_PIN, GPIO_OUT); 386 | 387 | multicore_launch_core1(core1_entry); 388 | 389 | while (1) { 390 | for (itf = 0; itf < CFG_TUD_CDC; itf++) { 391 | update_uart_cfg(itf); 392 | uart_read_bytes(itf); 393 | uart_write_bytes(itf); 394 | } 395 | } 396 | 397 | return 0; 398 | } 399 | --------------------------------------------------------------------------------