From 226a3188975b21d29d32fe4423a6c929766192a5 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Sat, 31 Aug 2024 07:46:11 -0700 Subject: [PATCH] Add serial inversion for UART and SerialPIO (#2395) Use real GPIO pad inversion to allow inverted RX, TX, and controls for the hardware UART and software PIO-emulated serial ports. Adds ``setInvertTX(bool)`` and ``setInvertRX(bool)`` calls to both ports, with ``setInvertControl(bool)`` for the HW UARTS. --- cores/rp2040/SerialPIO.cpp | 41 +++++++++---------- cores/rp2040/SerialPIO.h | 21 ++++++++-- cores/rp2040/SerialUART.cpp | 11 +++++ cores/rp2040/SerialUART.h | 21 ++++++++++ cores/rp2040/SoftwareSerial.h | 10 +---- cores/rp2040/pio_uart.pio | 44 -------------------- cores/rp2040/pio_uart.pio.h | 76 ----------------------------------- docs/piouart.rst | 7 +++- docs/serial.rst | 8 ++++ keywords.txt | 3 ++ 10 files changed, 89 insertions(+), 153 deletions(-) diff --git a/cores/rp2040/SerialPIO.cpp b/cores/rp2040/SerialPIO.cpp index 96244f7..f4657e1 100644 --- a/cores/rp2040/SerialPIO.cpp +++ b/cores/rp2040/SerialPIO.cpp @@ -46,24 +46,22 @@ static pio_program_t *pio_make_uart_prog(int repl, const pio_program_t *pg) { return p; } -static PIOProgram *_getTxProgram(int bits, bool inverted) { - int key = inverted ? -bits : bits; - auto f = _txMap.find(key); +static PIOProgram *_getTxProgram(int bits) { + auto f = _txMap.find(bits); if (f == _txMap.end()) { - pio_program_t * p = pio_make_uart_prog(bits, inverted ? &pio_tx_inv_program : &pio_tx_program); - _txMap.insert({key, new PIOProgram(p)}); - f = _txMap.find(key); + pio_program_t * p = pio_make_uart_prog(bits, &pio_tx_program); + _txMap.insert({bits, new PIOProgram(p)}); + f = _txMap.find(bits); } return f->second; } -static PIOProgram *_getRxProgram(int bits, bool inverted) { - int key = inverted ? -bits : bits; - auto f = _rxMap.find(key); +static PIOProgram *_getRxProgram(int bits) { + auto f = _rxMap.find(bits); if (f == _rxMap.end()) { - pio_program_t * p = pio_make_uart_prog(bits, inverted ? &pio_rx_inv_program : &pio_rx_program); - _rxMap.insert({key, new PIOProgram(p)}); - f = _rxMap.find(key); + pio_program_t * p = pio_make_uart_prog(bits, &pio_rx_program); + _rxMap.insert({bits, new PIOProgram(p)}); + f = _rxMap.find(bits); } return f->second; } @@ -98,7 +96,7 @@ void __not_in_flash_func(SerialPIO::_handleIRQ)() { return; } while (!pio_sm_is_rx_fifo_empty(_rxPIO, _rxSM)) { - uint32_t decode = _rxPIO->rxf[_rxSM] ^ (_rxInverted ? 0xffffffff : 0); + uint32_t decode = _rxPIO->rxf[_rxSM]; decode >>= 33 - _rxBits; uint32_t val = 0; for (int b = 0; b < _bits + 1; b++) { @@ -140,6 +138,8 @@ SerialPIO::SerialPIO(pin_size_t tx, pin_size_t rx, size_t fifoSize) { _fifoSize = fifoSize + 1; // Always one unused entry _queue = new uint8_t[_fifoSize]; mutex_init(&_mutex); + _invertTX = false; + _invertRX = false; } SerialPIO::~SerialPIO() { @@ -191,7 +191,7 @@ void SerialPIO::begin(unsigned long baud, uint16_t config) { if (_tx != NOPIN) { _txBits = _bits + _stop + (_parity != UART_PARITY_NONE ? 1 : 0) + 1/*start bit*/; - _txPgm = _getTxProgram(_txBits, _txInverted); + _txPgm = _getTxProgram(_txBits); int off; if (!_txPgm->prepare(&_txPIO, &_txSM, &off)) { DEBUGCORE("ERROR: Unable to allocate PIO TX UART, out of PIO resources\n"); @@ -201,6 +201,7 @@ void SerialPIO::begin(unsigned long baud, uint16_t config) { digitalWrite(_tx, HIGH); pinMode(_tx, OUTPUT); + gpio_set_outover(_tx, _invertTX); pio_tx_program_init(_txPIO, _txSM, off, _tx); pio_sm_clear_fifos(_txPIO, _txSM); // Remove any existing data @@ -218,7 +219,7 @@ void SerialPIO::begin(unsigned long baud, uint16_t config) { _reader = 0; _rxBits = 2 * (_bits + _stop + (_parity != UART_PARITY_NONE ? 1 : 0) + 1) - 1; - _rxPgm = _getRxProgram(_rxBits, _rxInverted); + _rxPgm = _getRxProgram(_rxBits); int off; if (!_rxPgm->prepare(&_rxPIO, &_rxSM, &off)) { DEBUGCORE("ERROR: Unable to allocate PIO RX UART, out of PIO resources\n"); @@ -249,6 +250,7 @@ void SerialPIO::begin(unsigned long baud, uint16_t config) { irq_set_exclusive_handler(irqno, _fifoIRQ); irq_set_enabled(irqno, true); + gpio_set_inover(_rx, _invertRX); pio_sm_set_enabled(_rxPIO, _rxSM, true); } @@ -262,6 +264,7 @@ void SerialPIO::end() { if (_tx != NOPIN) { pio_sm_set_enabled(_txPIO, _txSM, false); pio_sm_unclaim(_txPIO, _txSM); + gpio_set_outover(_tx, 0); } if (_rx != NOPIN) { pio_sm_set_enabled(_rxPIO, _rxSM, false); @@ -277,6 +280,7 @@ void SerialPIO::end() { auto irqno = pioNum == 0 ? PIO0_IRQ_0 : PIO1_IRQ_0; irq_set_enabled(irqno, false); } + gpio_set_inover(_rx, 0); } _running = false; } @@ -348,11 +352,6 @@ void SerialPIO::flush() { delay((1000 * (_txBits + 1)) / _baud); } -void SerialPIO::setInverted(bool invTx, bool invRx) { - _txInverted = invTx; - _rxInverted = invRx; -} - size_t SerialPIO::write(uint8_t c) { CoreMutex m(&_mutex); if (!_running || !m || (_tx == NOPIN)) { @@ -371,7 +370,7 @@ size_t SerialPIO::write(uint8_t c) { } val <<= 1; // Start bit = low - pio_sm_put_blocking(_txPIO, _txSM, _txInverted ? ~val : val); + pio_sm_put_blocking(_txPIO, _txSM, val); return 1; } diff --git a/cores/rp2040/SerialPIO.h b/cores/rp2040/SerialPIO.h index cf843ea..1f02967 100644 --- a/cores/rp2040/SerialPIO.h +++ b/cores/rp2040/SerialPIO.h @@ -41,7 +41,22 @@ public: void begin(unsigned long baud, uint16_t config) override; void end() override; - void setInverted(bool invTx = true, bool invRx = true); + void setInverted(bool invTx = true, bool invRx = true) { + setInvertTX(invTx); + setInvertRX(invRx); + } + bool setInvertTX(bool invert = true) { + if (!_running) { + _invertTX = invert; + } + return !_running; + } + bool setInvertRX(bool invert = true) { + if (!_running) { + _invertRX = invert; + } + return !_running; + } virtual int peek() override; virtual int read() override; @@ -65,8 +80,8 @@ protected: int _stop; bool _overflow; mutex_t _mutex; - bool _txInverted = false; - bool _rxInverted = false; + bool _invertTX; + bool _invertRX; PIOProgram *_txPgm; PIO _txPIO; diff --git a/cores/rp2040/SerialUART.cpp b/cores/rp2040/SerialUART.cpp index eee982c..4ebba4b 100644 --- a/cores/rp2040/SerialUART.cpp +++ b/cores/rp2040/SerialUART.cpp @@ -138,6 +138,9 @@ SerialUART::SerialUART(uart_inst_t *uart, pin_size_t tx, pin_size_t rx, pin_size _cts = cts; mutex_init(&_mutex); mutex_init(&_fifoMutex); + _invertTX = false; + _invertRX = false; + _invertControl = false; } static void _uart0IRQ(); @@ -154,14 +157,18 @@ void SerialUART::begin(unsigned long baud, uint16_t config) { _fcnTx = gpio_get_function(_tx); _fcnRx = gpio_get_function(_rx); gpio_set_function(_tx, GPIO_FUNC_UART); + gpio_set_outover(_tx, _invertTX ? 1 : 0); gpio_set_function(_rx, GPIO_FUNC_UART); + gpio_set_inover(_rx, _invertRX ? 1 : 0); if (_rts != UART_PIN_NOT_DEFINED) { _fcnRts = gpio_get_function(_rts); gpio_set_function(_rts, GPIO_FUNC_UART); + gpio_set_outover(_rts, _invertControl ? 1 : 0); } if (_cts != UART_PIN_NOT_DEFINED) { _fcnCts = gpio_get_function(_cts); gpio_set_function(_cts, GPIO_FUNC_UART); + gpio_set_inover(_cts, _invertControl ? 1 : 0); } uart_init(_uart, baud); @@ -246,12 +253,16 @@ void SerialUART::end() { // Restore pin functions gpio_set_function(_tx, _fcnTx); + gpio_set_outover(_tx, 0); gpio_set_function(_rx, _fcnRx); + gpio_set_inover(_rx, 0); if (_rts != UART_PIN_NOT_DEFINED) { gpio_set_function(_rts, _fcnRts); + gpio_set_outover(_rts, 0); } if (_cts != UART_PIN_NOT_DEFINED) { gpio_set_function(_cts, _fcnCts); + gpio_set_inover(_cts, 0); } } diff --git a/cores/rp2040/SerialUART.h b/cores/rp2040/SerialUART.h index 4964f04..a29ac09 100644 --- a/cores/rp2040/SerialUART.h +++ b/cores/rp2040/SerialUART.h @@ -43,6 +43,26 @@ public: ret &= setTX(tx); return ret; } + + bool setInvertTX(bool invert = true) { + if (!_running) { + _invertTX = invert; + } + return !_running; + } + bool setInvertRX(bool invert = true) { + if (!_running) { + _invertRX = invert; + } + return !_running; + } + bool setInvertControl(bool invert = true) { + if (!_running) { + _invertControl = invert; + } + return !_running; + } + bool setFIFOSize(size_t size); bool setPollingMode(bool mode = true); @@ -86,6 +106,7 @@ private: bool _polling = false; bool _overflow; bool _break; + bool _invertTX, _invertRX, _invertControl; // Lockless, IRQ-handled circular queue uint32_t _writer; diff --git a/cores/rp2040/SoftwareSerial.h b/cores/rp2040/SoftwareSerial.h index aef3a63..deaf4a9 100644 --- a/cores/rp2040/SoftwareSerial.h +++ b/cores/rp2040/SoftwareSerial.h @@ -30,10 +30,6 @@ public: } ~SoftwareSerial() { - if (_invert) { - gpio_set_outover(_tx, 0); - gpio_set_outover(_rx, 0); - } } virtual void begin(unsigned long baud = 115200) override { @@ -41,11 +37,9 @@ public: }; void begin(unsigned long baud, uint16_t config) override { + setInvertTX(invert); + setInvertRX(invert); SerialPIO::begin(baud, config); - if (_invert) { - gpio_set_outover(_tx, GPIO_OVERRIDE_INVERT); - gpio_set_inover(_rx, GPIO_OVERRIDE_INVERT); - } } void listen() { /* noop */ } diff --git a/cores/rp2040/pio_uart.pio b/cores/rp2040/pio_uart.pio index b0b1a67..9a7a24b 100644 --- a/cores/rp2040/pio_uart.pio +++ b/cores/rp2040/pio_uart.pio @@ -39,28 +39,6 @@ wait_bit: jmp y-- wait_bit jmp x-- bitloop - - -; inverted-logic version (inverts the stop bit) - -.program pio_tx_inv -.side_set 1 opt - - -; We shift out the start and stop bit as part of the FIFO - set x, 9 - - pull side 0 ; Force stop bit low - -; Send the bits -bitloop: - out pins, 1 - mov y, isr ; ISR is loaded by the setup routine with the period-1 count -wait_bit: - jmp y-- wait_bit - jmp x-- bitloop - - % c-sdk { static inline void pio_tx_program_init(PIO pio, uint sm, uint offset, uint pin_tx) { @@ -110,28 +88,6 @@ wait_half: push ; Stuff it and wait for next start - -.program pio_rx_inv - -; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX. - -start: - set x, 18 ; Preload bit counter...we'll shift in the start bit and stop bit, and each bit will be double-recorded (to be fixed by RP2040 code) - wait 1 pin 0 ; Stall until start bit is asserted - -bitloop: - ; Delay until 1/2 way into the bit time - mov y, osr -wait_half: - jmp y-- wait_half - - ; Read in the bit - in pins, 1 ; Shift data bit into ISR - jmp x-- bitloop ; Loop all bits - - push ; Stuff it and wait for next start - - % c-sdk { static inline void pio_rx_program_init(PIO pio, uint sm, uint offset, uint pin) { pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false); diff --git a/cores/rp2040/pio_uart.pio.h b/cores/rp2040/pio_uart.pio.h index 08a83c6..394c172 100644 --- a/cores/rp2040/pio_uart.pio.h +++ b/cores/rp2040/pio_uart.pio.h @@ -44,44 +44,6 @@ static inline pio_sm_config pio_tx_program_get_default_config(uint offset) { sm_config_set_sideset(&c, 2, true, false); return c; } -#endif - -// ---------- // -// pio_tx_inv // -// ---------- // - -#define pio_tx_inv_wrap_target 0 -#define pio_tx_inv_wrap 5 -#define pio_tx_inv_pio_version 0 - -static const uint16_t pio_tx_inv_program_instructions[] = { - // .wrap_target - 0xe029, // 0: set x, 9 - 0x90a0, // 1: pull block side 0 - 0x6001, // 2: out pins, 1 - 0xa046, // 3: mov y, isr - 0x0084, // 4: jmp y--, 4 - 0x0042, // 5: jmp x--, 2 - // .wrap -}; - -#if !PICO_NO_HARDWARE -static const struct pio_program pio_tx_inv_program = { - .instructions = pio_tx_inv_program_instructions, - .length = 6, - .origin = -1, - .pio_version = 0, -#if PICO_PIO_VERSION > 0 - .used_gpio_ranges = 0x0 -#endif -}; - -static inline pio_sm_config pio_tx_inv_program_get_default_config(uint offset) { - pio_sm_config c = pio_get_default_sm_config(); - sm_config_set_wrap(&c, offset + pio_tx_inv_wrap_target, offset + pio_tx_inv_wrap); - sm_config_set_sideset(&c, 2, true, false); - return c; -} static inline void pio_tx_program_init(PIO pio, uint sm, uint offset, uint pin_tx) { // Tell PIO to initially drive output-high on the selected pin, then map PIO @@ -140,44 +102,6 @@ static inline pio_sm_config pio_rx_program_get_default_config(uint offset) { sm_config_set_wrap(&c, offset + pio_rx_wrap_target, offset + pio_rx_wrap); return c; } -#endif - -// ---------- // -// pio_rx_inv // -// ---------- // - -#define pio_rx_inv_wrap_target 0 -#define pio_rx_inv_wrap 6 -#define pio_rx_inv_pio_version 0 - -static const uint16_t pio_rx_inv_program_instructions[] = { - // .wrap_target - 0xe032, // 0: set x, 18 - 0x20a0, // 1: wait 1 pin, 0 - 0xa047, // 2: mov y, osr - 0x0083, // 3: jmp y--, 3 - 0x4001, // 4: in pins, 1 - 0x0042, // 5: jmp x--, 2 - 0x8020, // 6: push block - // .wrap -}; - -#if !PICO_NO_HARDWARE -static const struct pio_program pio_rx_inv_program = { - .instructions = pio_rx_inv_program_instructions, - .length = 7, - .origin = -1, - .pio_version = 0, -#if PICO_PIO_VERSION > 0 - .used_gpio_ranges = 0x0 -#endif -}; - -static inline pio_sm_config pio_rx_inv_program_get_default_config(uint offset) { - pio_sm_config c = pio_get_default_sm_config(); - sm_config_set_wrap(&c, offset + pio_rx_inv_wrap_target, offset + pio_rx_inv_wrap); - return c; -} static inline void pio_rx_program_init(PIO pio, uint sm, uint offset, uint pin) { pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false); diff --git a/docs/piouart.rst b/docs/piouart.rst index 1b3ff40..7812881 100644 --- a/docs/piouart.rst +++ b/docs/piouart.rst @@ -23,6 +23,12 @@ For example, to make a transmit-only port on GP16 For detailed information about the Serial ports, see the Arduino `Serial Reference `_ . +Inversion +--------- + +``SoftwareSerial`` and ``SerialPIO`` can both support inverted input and/or outputs via the methods +``setInvertRX(bool invert)`` and ``setInvertTX(bool invert)``. + SoftwareSerial Emulation ======================== @@ -31,7 +37,6 @@ with the Arduino `Software Serial `` to include it. The following differences from the Arduino standard are present: -* Inverted mode is not supported * All ports are always listening * ``listen`` call is a no-op * ``isListening()`` always returns ``true`` diff --git a/docs/serial.rst b/docs/serial.rst index c5281f8..0e41358 100644 --- a/docs/serial.rst +++ b/docs/serial.rst @@ -47,6 +47,14 @@ For detailed information about the Serial ports, see the Arduino `Serial Reference `_ . +Inversion +--------- + +``Serial1`` and ``Serial2`` can both support inverted input and/or outputs via the methods +``Serial1/2::setInvertRX(bool invert)`` and ``Serial1/2::setInvertTX(bool invert)`` and +``Serial1/2::serInvertControl(bool invert)``. + + RP2040 Specific SerialUSB methods --------------------------------- diff --git a/keywords.txt b/keywords.txt index 820376a..1516ef5 100644 --- a/keywords.txt +++ b/keywords.txt @@ -73,6 +73,9 @@ prepare KEYWORD2 SerialPIO KEYWORD2 setFIFOSize KEYWORD2 setPollingMode KEYWORD2 +setInvertTX KEYWORD2 +setInvertRX KEYWORD2 +setInvertControl KEYWORD2 digitalWriteFast KEYWORD2 digitalReadFast KEYWORD2