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.
This commit is contained in:
parent
729163d0cc
commit
226a318897
10 changed files with 89 additions and 153 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 */ }
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 <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
|
||||
========================
|
||||
|
|
@ -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
|
||||
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``
|
||||
|
|
|
|||
|
|
@ -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/>`_ .
|
||||
|
||||
|
||||
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
|
||||
---------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,9 @@ prepare KEYWORD2
|
|||
SerialPIO KEYWORD2
|
||||
setFIFOSize KEYWORD2
|
||||
setPollingMode KEYWORD2
|
||||
setInvertTX KEYWORD2
|
||||
setInvertRX KEYWORD2
|
||||
setInvertControl KEYWORD2
|
||||
|
||||
digitalWriteFast KEYWORD2
|
||||
digitalReadFast KEYWORD2
|
||||
|
|
|
|||
Loading…
Reference in a new issue