Skip to content

Commit 5e74bbb

Browse files
Rewrite SerialPIO receive path, ensure proper edge (#2929)
The SerialPIO(SoftwareSerial) receive path was convoluted and required a lot of work on the host to get the actual data out. It also wasn't always sampling on the proper edge leading to errors whenever clocks or hold times shifted slightly. Rewrite the SerialPIO RX path to explicitily wait for start bit, pause 1/2 bit time, then idle for a full bit time for all bits. Takes more PIO instruction memory but works flawlessly even with mismatched clocks. Tested with a loopback from HW UART to SW UART with a 5% clock mismatch @ 19200 baud without reception errors, whereas the original code would fail with less than a 0.5% variation. Fixes #2928 ```` SoftwareSerial s(15, -1); void setup() { Serial1.setTX(0); Serial1.begin(19200 + 1920/2, SERIAL_8N1); s.begin(19200, SERIAL_8N1); } void loop() { Serial.println("---"); Serial1.write("Had we but world enough and time", 32); uint32_t now = millis(); while (millis() - now < 500) { while (s.available()) { auto c = s.read(); Serial.printf("%02x '%c'\n", c, c); } } } ````
1 parent 8d58a92 commit 5e74bbb

File tree

3 files changed

+27
-22
lines changed

3 files changed

+27
-22
lines changed

cores/rp2040/SerialPIO.cpp

+4-8
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ static PIOProgram *_getRxProgram(int bits) {
6969
// ------------------------------------------------------------------------
7070

7171
// TODO - this works, but there must be a faster/better way...
72-
static int _parity(int bits, int data) {
72+
static int __not_in_flash_func(_parity)(int bits, int data) {
7373
int p = 0;
7474
for (int b = 0; b < bits; b++) {
7575
p ^= (data & (1 << b)) ? 1 : 0;
@@ -98,11 +98,7 @@ void __not_in_flash_func(SerialPIO::_handleIRQ)() {
9898
}
9999
while (!pio_sm_is_rx_fifo_empty(_rxPIO, _rxSM)) {
100100
uint32_t decode = _rxPIO->rxf[_rxSM];
101-
decode >>= 33 - _rxBits;
102-
uint32_t val = 0;
103-
for (int b = 0; b < _bits + 1; b++) {
104-
val |= (decode & (1 << (b * 2))) ? 1 << b : 0;
105-
}
101+
uint32_t val = decode >> (32 - _rxBits - 1);
106102
if (_parity == UART_PARITY_EVEN) {
107103
int p = ::_parity(_bits, val);
108104
int r = (val & (1 << _bits)) ? 1 : 0;
@@ -234,7 +230,7 @@ void SerialPIO::begin(unsigned long baud, uint16_t config) {
234230
_writer = 0;
235231
_reader = 0;
236232

237-
_rxBits = 2 * (_bits + _stop + (_parity != UART_PARITY_NONE ? 1 : 0) + 1) - 1;
233+
_rxBits = _bits + (_parity != UART_PARITY_NONE ? 1 : 0);
238234
_rxPgm = _getRxProgram(_rxBits);
239235
int off;
240236
if (!_rxPgm->prepare(&_rxPIO, &_rxSM, &off, _rx, 1)) {
@@ -249,7 +245,7 @@ void SerialPIO::begin(unsigned long baud, uint16_t config) {
249245
pio_sm_clear_fifos(_rxPIO, _rxSM); // Remove any existing data
250246

251247
// Put phase divider into OSR w/o using add'l program memory
252-
pio_sm_put_blocking(_rxPIO, _rxSM, clock_get_hz(clk_sys) / (_baud * 2) - 7 /* insns in PIO halfbit loop */);
248+
pio_sm_put_blocking(_rxPIO, _rxSM, clock_get_hz(clk_sys) / (_baud * 2) - 3);
253249
pio_sm_exec(_rxPIO, _rxSM, pio_encode_pull(false, false));
254250

255251
// Join the TX FIFO to the RX one now that we don't need it

cores/rp2040/pio_uart.pio

+14-9
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,25 @@ static inline void pio_tx_program_init(PIO pio, uint sm, uint offset, uint pin_t
7373
; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX.
7474

7575
start:
76-
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)
76+
set x, 18 ; Preload bit counter...overwritten by the app
7777
wait 0 pin 0 ; Stall until start bit is asserted
7878

79-
bitloop:
8079
; Delay until 1/2 way into the bit time
8180
mov y, osr
82-
wait_half:
83-
jmp y-- wait_half
81+
wait_mid_start:
82+
jmp y-- wait_mid_start
8483

85-
; Read in the bit
86-
in pins, 1 ; Shift data bit into ISR
87-
jmp x-- bitloop ; Loop all bits
88-
89-
push ; Stuff it and wait for next start
84+
bitloop:
85+
mov y, osr
86+
bitloop1:
87+
jmp y-- bitloop1
88+
mov y, osr
89+
bitloop2:
90+
jmp y-- bitloop2
91+
92+
in pins, 1
93+
jmp x-- bitloop
94+
push
9095

9196
% c-sdk {
9297
static inline void pio_rx_program_init(PIO pio, uint sm, uint offset, uint pin) {

cores/rp2040/pio_uart.pio.h

+9-5
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ static inline void pio_tx_program_init(PIO pio, uint sm, uint offset, uint pin_t
7171
// ------ //
7272

7373
#define pio_rx_wrap_target 0
74-
#define pio_rx_wrap 6
74+
#define pio_rx_wrap 10
7575
#define pio_rx_pio_version 0
7676

7777
static const uint16_t pio_rx_program_instructions[] = {
@@ -80,16 +80,20 @@ static const uint16_t pio_rx_program_instructions[] = {
8080
0x2020, // 1: wait 0 pin, 0
8181
0xa047, // 2: mov y, osr
8282
0x0083, // 3: jmp y--, 3
83-
0x4001, // 4: in pins, 1
84-
0x0042, // 5: jmp x--, 2
85-
0x8020, // 6: push block
83+
0xa047, // 4: mov y, osr
84+
0x0085, // 5: jmp y--, 5
85+
0xa047, // 6: mov y, osr
86+
0x0087, // 7: jmp y--, 7
87+
0x4001, // 8: in pins, 1
88+
0x0044, // 9: jmp x--, 4
89+
0x8020, // 10: push block
8690
// .wrap
8791
};
8892

8993
#if !PICO_NO_HARDWARE
9094
static const struct pio_program pio_rx_program = {
9195
.instructions = pio_rx_program_instructions,
92-
.length = 7,
96+
.length = 11,
9397
.origin = -1,
9498
.pio_version = 0,
9599
#if PICO_PIO_VERSION > 0

0 commit comments

Comments
 (0)