Unverified Commit 226a3188 authored by Earle F. Philhower, III's avatar Earle F. Philhower, III Committed by GitHub

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.
parent 729163d0
...@@ -46,24 +46,22 @@ static pio_program_t *pio_make_uart_prog(int repl, const pio_program_t *pg) { ...@@ -46,24 +46,22 @@ static pio_program_t *pio_make_uart_prog(int repl, const pio_program_t *pg) {
return p; return p;
} }
static PIOProgram *_getTxProgram(int bits, bool inverted) { static PIOProgram *_getTxProgram(int bits) {
int key = inverted ? -bits : bits; auto f = _txMap.find(bits);
auto f = _txMap.find(key);
if (f == _txMap.end()) { if (f == _txMap.end()) {
pio_program_t * p = pio_make_uart_prog(bits, inverted ? &pio_tx_inv_program : &pio_tx_program); pio_program_t * p = pio_make_uart_prog(bits, &pio_tx_program);
_txMap.insert({key, new PIOProgram(p)}); _txMap.insert({bits, new PIOProgram(p)});
f = _txMap.find(key); f = _txMap.find(bits);
} }
return f->second; return f->second;
} }
static PIOProgram *_getRxProgram(int bits, bool inverted) { static PIOProgram *_getRxProgram(int bits) {
int key = inverted ? -bits : bits; auto f = _rxMap.find(bits);
auto f = _rxMap.find(key);
if (f == _rxMap.end()) { if (f == _rxMap.end()) {
pio_program_t * p = pio_make_uart_prog(bits, inverted ? &pio_rx_inv_program : &pio_rx_program); pio_program_t * p = pio_make_uart_prog(bits, &pio_rx_program);
_rxMap.insert({key, new PIOProgram(p)}); _rxMap.insert({bits, new PIOProgram(p)});
f = _rxMap.find(key); f = _rxMap.find(bits);
} }
return f->second; return f->second;
} }
...@@ -98,7 +96,7 @@ void __not_in_flash_func(SerialPIO::_handleIRQ)() { ...@@ -98,7 +96,7 @@ void __not_in_flash_func(SerialPIO::_handleIRQ)() {
return; return;
} }
while (!pio_sm_is_rx_fifo_empty(_rxPIO, _rxSM)) { 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; decode >>= 33 - _rxBits;
uint32_t val = 0; uint32_t val = 0;
for (int b = 0; b < _bits + 1; b++) { 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) { ...@@ -140,6 +138,8 @@ SerialPIO::SerialPIO(pin_size_t tx, pin_size_t rx, size_t fifoSize) {
_fifoSize = fifoSize + 1; // Always one unused entry _fifoSize = fifoSize + 1; // Always one unused entry
_queue = new uint8_t[_fifoSize]; _queue = new uint8_t[_fifoSize];
mutex_init(&_mutex); mutex_init(&_mutex);
_invertTX = false;
_invertRX = false;
} }
SerialPIO::~SerialPIO() { SerialPIO::~SerialPIO() {
...@@ -191,7 +191,7 @@ void SerialPIO::begin(unsigned long baud, uint16_t config) { ...@@ -191,7 +191,7 @@ void SerialPIO::begin(unsigned long baud, uint16_t config) {
if (_tx != NOPIN) { if (_tx != NOPIN) {
_txBits = _bits + _stop + (_parity != UART_PARITY_NONE ? 1 : 0) + 1/*start bit*/; _txBits = _bits + _stop + (_parity != UART_PARITY_NONE ? 1 : 0) + 1/*start bit*/;
_txPgm = _getTxProgram(_txBits, _txInverted); _txPgm = _getTxProgram(_txBits);
int off; int off;
if (!_txPgm->prepare(&_txPIO, &_txSM, &off)) { if (!_txPgm->prepare(&_txPIO, &_txSM, &off)) {
DEBUGCORE("ERROR: Unable to allocate PIO TX UART, out of PIO resources\n"); 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) { ...@@ -201,6 +201,7 @@ void SerialPIO::begin(unsigned long baud, uint16_t config) {
digitalWrite(_tx, HIGH); digitalWrite(_tx, HIGH);
pinMode(_tx, OUTPUT); pinMode(_tx, OUTPUT);
gpio_set_outover(_tx, _invertTX);
pio_tx_program_init(_txPIO, _txSM, off, _tx); pio_tx_program_init(_txPIO, _txSM, off, _tx);
pio_sm_clear_fifos(_txPIO, _txSM); // Remove any existing data pio_sm_clear_fifos(_txPIO, _txSM); // Remove any existing data
...@@ -218,7 +219,7 @@ void SerialPIO::begin(unsigned long baud, uint16_t config) { ...@@ -218,7 +219,7 @@ void SerialPIO::begin(unsigned long baud, uint16_t config) {
_reader = 0; _reader = 0;
_rxBits = 2 * (_bits + _stop + (_parity != UART_PARITY_NONE ? 1 : 0) + 1) - 1; _rxBits = 2 * (_bits + _stop + (_parity != UART_PARITY_NONE ? 1 : 0) + 1) - 1;
_rxPgm = _getRxProgram(_rxBits, _rxInverted); _rxPgm = _getRxProgram(_rxBits);
int off; int off;
if (!_rxPgm->prepare(&_rxPIO, &_rxSM, &off)) { if (!_rxPgm->prepare(&_rxPIO, &_rxSM, &off)) {
DEBUGCORE("ERROR: Unable to allocate PIO RX UART, out of PIO resources\n"); 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) { ...@@ -249,6 +250,7 @@ void SerialPIO::begin(unsigned long baud, uint16_t config) {
irq_set_exclusive_handler(irqno, _fifoIRQ); irq_set_exclusive_handler(irqno, _fifoIRQ);
irq_set_enabled(irqno, true); irq_set_enabled(irqno, true);
gpio_set_inover(_rx, _invertRX);
pio_sm_set_enabled(_rxPIO, _rxSM, true); pio_sm_set_enabled(_rxPIO, _rxSM, true);
} }
...@@ -262,6 +264,7 @@ void SerialPIO::end() { ...@@ -262,6 +264,7 @@ void SerialPIO::end() {
if (_tx != NOPIN) { if (_tx != NOPIN) {
pio_sm_set_enabled(_txPIO, _txSM, false); pio_sm_set_enabled(_txPIO, _txSM, false);
pio_sm_unclaim(_txPIO, _txSM); pio_sm_unclaim(_txPIO, _txSM);
gpio_set_outover(_tx, 0);
} }
if (_rx != NOPIN) { if (_rx != NOPIN) {
pio_sm_set_enabled(_rxPIO, _rxSM, false); pio_sm_set_enabled(_rxPIO, _rxSM, false);
...@@ -277,6 +280,7 @@ void SerialPIO::end() { ...@@ -277,6 +280,7 @@ void SerialPIO::end() {
auto irqno = pioNum == 0 ? PIO0_IRQ_0 : PIO1_IRQ_0; auto irqno = pioNum == 0 ? PIO0_IRQ_0 : PIO1_IRQ_0;
irq_set_enabled(irqno, false); irq_set_enabled(irqno, false);
} }
gpio_set_inover(_rx, 0);
} }
_running = false; _running = false;
} }
...@@ -348,11 +352,6 @@ void SerialPIO::flush() { ...@@ -348,11 +352,6 @@ void SerialPIO::flush() {
delay((1000 * (_txBits + 1)) / _baud); delay((1000 * (_txBits + 1)) / _baud);
} }
void SerialPIO::setInverted(bool invTx, bool invRx) {
_txInverted = invTx;
_rxInverted = invRx;
}
size_t SerialPIO::write(uint8_t c) { size_t SerialPIO::write(uint8_t c) {
CoreMutex m(&_mutex); CoreMutex m(&_mutex);
if (!_running || !m || (_tx == NOPIN)) { if (!_running || !m || (_tx == NOPIN)) {
...@@ -371,7 +370,7 @@ size_t SerialPIO::write(uint8_t c) { ...@@ -371,7 +370,7 @@ size_t SerialPIO::write(uint8_t c) {
} }
val <<= 1; // Start bit = low val <<= 1; // Start bit = low
pio_sm_put_blocking(_txPIO, _txSM, _txInverted ? ~val : val); pio_sm_put_blocking(_txPIO, _txSM, val);
return 1; return 1;
} }
......
...@@ -41,7 +41,22 @@ public: ...@@ -41,7 +41,22 @@ public:
void begin(unsigned long baud, uint16_t config) override; void begin(unsigned long baud, uint16_t config) override;
void end() 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 peek() override;
virtual int read() override; virtual int read() override;
...@@ -65,8 +80,8 @@ protected: ...@@ -65,8 +80,8 @@ protected:
int _stop; int _stop;
bool _overflow; bool _overflow;
mutex_t _mutex; mutex_t _mutex;
bool _txInverted = false; bool _invertTX;
bool _rxInverted = false; bool _invertRX;
PIOProgram *_txPgm; PIOProgram *_txPgm;
PIO _txPIO; PIO _txPIO;
......
...@@ -138,6 +138,9 @@ SerialUART::SerialUART(uart_inst_t *uart, pin_size_t tx, pin_size_t rx, pin_size ...@@ -138,6 +138,9 @@ SerialUART::SerialUART(uart_inst_t *uart, pin_size_t tx, pin_size_t rx, pin_size
_cts = cts; _cts = cts;
mutex_init(&_mutex); mutex_init(&_mutex);
mutex_init(&_fifoMutex); mutex_init(&_fifoMutex);
_invertTX = false;
_invertRX = false;
_invertControl = false;
} }
static void _uart0IRQ(); static void _uart0IRQ();
...@@ -154,14 +157,18 @@ void SerialUART::begin(unsigned long baud, uint16_t config) { ...@@ -154,14 +157,18 @@ void SerialUART::begin(unsigned long baud, uint16_t config) {
_fcnTx = gpio_get_function(_tx); _fcnTx = gpio_get_function(_tx);
_fcnRx = gpio_get_function(_rx); _fcnRx = gpio_get_function(_rx);
gpio_set_function(_tx, GPIO_FUNC_UART); gpio_set_function(_tx, GPIO_FUNC_UART);
gpio_set_outover(_tx, _invertTX ? 1 : 0);
gpio_set_function(_rx, GPIO_FUNC_UART); gpio_set_function(_rx, GPIO_FUNC_UART);
gpio_set_inover(_rx, _invertRX ? 1 : 0);
if (_rts != UART_PIN_NOT_DEFINED) { if (_rts != UART_PIN_NOT_DEFINED) {
_fcnRts = gpio_get_function(_rts); _fcnRts = gpio_get_function(_rts);
gpio_set_function(_rts, GPIO_FUNC_UART); gpio_set_function(_rts, GPIO_FUNC_UART);
gpio_set_outover(_rts, _invertControl ? 1 : 0);
} }
if (_cts != UART_PIN_NOT_DEFINED) { if (_cts != UART_PIN_NOT_DEFINED) {
_fcnCts = gpio_get_function(_cts); _fcnCts = gpio_get_function(_cts);
gpio_set_function(_cts, GPIO_FUNC_UART); gpio_set_function(_cts, GPIO_FUNC_UART);
gpio_set_inover(_cts, _invertControl ? 1 : 0);
} }
uart_init(_uart, baud); uart_init(_uart, baud);
...@@ -246,12 +253,16 @@ void SerialUART::end() { ...@@ -246,12 +253,16 @@ void SerialUART::end() {
// Restore pin functions // Restore pin functions
gpio_set_function(_tx, _fcnTx); gpio_set_function(_tx, _fcnTx);
gpio_set_outover(_tx, 0);
gpio_set_function(_rx, _fcnRx); gpio_set_function(_rx, _fcnRx);
gpio_set_inover(_rx, 0);
if (_rts != UART_PIN_NOT_DEFINED) { if (_rts != UART_PIN_NOT_DEFINED) {
gpio_set_function(_rts, _fcnRts); gpio_set_function(_rts, _fcnRts);
gpio_set_outover(_rts, 0);
} }
if (_cts != UART_PIN_NOT_DEFINED) { if (_cts != UART_PIN_NOT_DEFINED) {
gpio_set_function(_cts, _fcnCts); gpio_set_function(_cts, _fcnCts);
gpio_set_inover(_cts, 0);
} }
} }
......
...@@ -43,6 +43,26 @@ public: ...@@ -43,6 +43,26 @@ public:
ret &= setTX(tx); ret &= setTX(tx);
return ret; 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 setFIFOSize(size_t size);
bool setPollingMode(bool mode = true); bool setPollingMode(bool mode = true);
...@@ -86,6 +106,7 @@ private: ...@@ -86,6 +106,7 @@ private:
bool _polling = false; bool _polling = false;
bool _overflow; bool _overflow;
bool _break; bool _break;
bool _invertTX, _invertRX, _invertControl;
// Lockless, IRQ-handled circular queue // Lockless, IRQ-handled circular queue
uint32_t _writer; uint32_t _writer;
......
...@@ -30,10 +30,6 @@ public: ...@@ -30,10 +30,6 @@ public:
} }
~SoftwareSerial() { ~SoftwareSerial() {
if (_invert) {
gpio_set_outover(_tx, 0);
gpio_set_outover(_rx, 0);
}
} }
virtual void begin(unsigned long baud = 115200) override { virtual void begin(unsigned long baud = 115200) override {
...@@ -41,11 +37,9 @@ public: ...@@ -41,11 +37,9 @@ public:
}; };
void begin(unsigned long baud, uint16_t config) override { void begin(unsigned long baud, uint16_t config) override {
setInvertTX(invert);
setInvertRX(invert);
SerialPIO::begin(baud, config); SerialPIO::begin(baud, config);
if (_invert) {
gpio_set_outover(_tx, GPIO_OVERRIDE_INVERT);
gpio_set_inover(_rx, GPIO_OVERRIDE_INVERT);
}
} }
void listen() { /* noop */ } void listen() { /* noop */ }
......
...@@ -39,28 +39,6 @@ wait_bit: ...@@ -39,28 +39,6 @@ wait_bit:
jmp y-- wait_bit jmp y-- wait_bit
jmp x-- bitloop 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 { % c-sdk {
static inline void pio_tx_program_init(PIO pio, uint sm, uint offset, uint pin_tx) { static inline void pio_tx_program_init(PIO pio, uint sm, uint offset, uint pin_tx) {
...@@ -110,28 +88,6 @@ wait_half: ...@@ -110,28 +88,6 @@ wait_half:
push ; Stuff it and wait for next start 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 { % c-sdk {
static inline void pio_rx_program_init(PIO pio, uint sm, uint offset, uint pin) { 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); pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
......
...@@ -44,44 +44,6 @@ static inline pio_sm_config pio_tx_program_get_default_config(uint offset) { ...@@ -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); sm_config_set_sideset(&c, 2, true, false);
return c; 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) { 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 // 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) { ...@@ -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); sm_config_set_wrap(&c, offset + pio_rx_wrap_target, offset + pio_rx_wrap);
return c; 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) { 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); pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
......
...@@ -23,6 +23,12 @@ For example, to make a transmit-only port on GP16 ...@@ -23,6 +23,12 @@ For example, to make a transmit-only port on GP16
For detailed information about the Serial ports, see the For detailed information about the Serial ports, see the
Arduino `Serial Reference <https://www.arduino.cc/reference/en/language/functions/communication/serial/>`_ . Arduino `Serial Reference <https://www.arduino.cc/reference/en/language/functions/communication/serial/>`_ .
Inversion
---------
``SoftwareSerial`` and ``SerialPIO`` can both support inverted input and/or outputs via the methods
``setInvertRX(bool invert)`` and ``setInvertTX(bool invert)``.
SoftwareSerial Emulation SoftwareSerial Emulation
======================== ========================
...@@ -31,7 +37,6 @@ with the Arduino `Software Serial <https://docs.arduino.cc/learn/built-in-librar ...@@ -31,7 +37,6 @@ with the Arduino `Software Serial <https://docs.arduino.cc/learn/built-in-librar
library. Use the normal ``#include <SoftwareSerial.h>`` to include it. The following library. Use the normal ``#include <SoftwareSerial.h>`` to include it. The following
differences from the Arduino standard are present: differences from the Arduino standard are present:
* Inverted mode is not supported
* All ports are always listening * All ports are always listening
* ``listen`` call is a no-op * ``listen`` call is a no-op
* ``isListening()`` always returns ``true`` * ``isListening()`` always returns ``true``
...@@ -47,6 +47,14 @@ For detailed information about the Serial ports, see the ...@@ -47,6 +47,14 @@ For detailed information about the Serial ports, see the
Arduino `Serial Reference <https://www.arduino.cc/reference/en/language/functions/communication/serial/>`_ . Arduino `Serial Reference <https://www.arduino.cc/reference/en/language/functions/communication/serial/>`_ .
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 RP2040 Specific SerialUSB methods
--------------------------------- ---------------------------------
......
...@@ -73,6 +73,9 @@ prepare KEYWORD2 ...@@ -73,6 +73,9 @@ prepare KEYWORD2
SerialPIO KEYWORD2 SerialPIO KEYWORD2
setFIFOSize KEYWORD2 setFIFOSize KEYWORD2
setPollingMode KEYWORD2 setPollingMode KEYWORD2
setInvertTX KEYWORD2
setInvertRX KEYWORD2
setInvertControl KEYWORD2
digitalWriteFast KEYWORD2 digitalWriteFast KEYWORD2
digitalReadFast KEYWORD2 digitalReadFast KEYWORD2
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment