Skip to content

Commit dc1198b

Browse files
Use interrupts to capture Serial UART data, not polling (#393)
PR #379 was an ugly hack which works only if you poll the Serial port more frequently than ~8 byte times. This PR replaces the polling with an IRQ based lockless writer/reader queue so that even if you only read every 32 byte times, the Serial1/2 port should not lose data. It also should use less CPU and allow for somewhat higher throughput.
1 parent f46d7cc commit dc1198b

File tree

2 files changed

+90
-47
lines changed

2 files changed

+90
-47
lines changed

cores/rp2040/SerialUART.cpp

Lines changed: 83 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -72,65 +72,90 @@ SerialUART::SerialUART(uart_inst_t *uart, pin_size_t tx, pin_size_t rx) {
7272
mutex_init(&_mutex);
7373
}
7474

75+
static void _uart0IRQ();
76+
static void _uart1IRQ();
77+
7578
void SerialUART::begin(unsigned long baud, uint16_t config) {
7679
_baud = baud;
7780
uart_init(_uart, baud);
7881
int bits, stop;
7982
uart_parity_t parity;
8083
switch (config & SERIAL_PARITY_MASK) {
81-
case SERIAL_PARITY_EVEN: parity = UART_PARITY_EVEN; break;
82-
case SERIAL_PARITY_ODD: parity = UART_PARITY_ODD; break;
83-
default: parity = UART_PARITY_NONE; break;
84+
case SERIAL_PARITY_EVEN:
85+
parity = UART_PARITY_EVEN;
86+
break;
87+
case SERIAL_PARITY_ODD:
88+
parity = UART_PARITY_ODD;
89+
break;
90+
default:
91+
parity = UART_PARITY_NONE;
92+
break;
8493
}
8594
switch (config & SERIAL_STOP_BIT_MASK) {
86-
case SERIAL_STOP_BIT_1: stop = 1; break;
87-
default: stop = 2; break;
95+
case SERIAL_STOP_BIT_1:
96+
stop = 1;
97+
break;
98+
default:
99+
stop = 2;
100+
break;
88101
}
89102
switch (config & SERIAL_DATA_MASK) {
90-
case SERIAL_DATA_5: bits = 5; break;
91-
case SERIAL_DATA_6: bits = 6; break;
92-
case SERIAL_DATA_7: bits = 7; break;
93-
default: bits = 8; break;
103+
case SERIAL_DATA_5:
104+
bits = 5;
105+
break;
106+
case SERIAL_DATA_6:
107+
bits = 6;
108+
break;
109+
case SERIAL_DATA_7:
110+
bits = 7;
111+
break;
112+
default:
113+
bits = 8;
114+
break;
94115
}
95116
uart_set_format(_uart, bits, stop, parity);
96117
gpio_set_function(_tx, GPIO_FUNC_UART);
97118
gpio_set_function(_rx, GPIO_FUNC_UART);
98-
_running = true;
99-
while (!_swFIFO.empty()) {
100-
(void)_swFIFO.pop(); // Just throw out anything in our old FIFO
119+
_writer = 0;
120+
_reader = 0;
121+
122+
if (_uart == uart0) {
123+
irq_set_exclusive_handler(UART0_IRQ, _uart0IRQ);
124+
irq_set_enabled(UART0_IRQ, true);
125+
} else {
126+
irq_set_exclusive_handler(UART1_IRQ, _uart1IRQ);
127+
irq_set_enabled(UART1_IRQ, true);
101128
}
129+
uart_get_hw(_uart)->imsc |= UART_UARTIMSC_RXIM_BITS | UART_UARTIMSC_RTIM_BITS;
130+
_running = true;
102131
}
103132

104133
void SerialUART::end() {
105134
if (!_running) {
106135
return;
107136
}
137+
if (_uart == uart0) {
138+
irq_set_enabled(UART0_IRQ, false);
139+
} else {
140+
irq_set_enabled(UART1_IRQ, false);
141+
}
108142
uart_deinit(_uart);
109143
_running = false;
110144
}
111145

112-
// Transfers any data in the HW FIFO into our SW one, up to 32 bytes
113-
void SerialUART::_pumpFIFO() {
114-
while ((_swFIFO.size() < 32) && (uart_is_readable(_uart))) {
115-
_swFIFO.push(uart_getc(_uart));
116-
}
117-
}
118-
119146
int SerialUART::peek() {
120147
CoreMutex m(&_mutex);
121148
if (!_running || !m) {
122149
return -1;
123150
}
124-
_pumpFIFO();
125-
// If there's something in the FIFO now, just peek at it
126-
if (_swFIFO.size()) {
127-
return _swFIFO.front();
128-
}
129-
// The SW FIFO is empty, read the HW one until the timeout
130-
if (uart_is_readable_within_us(_uart, _timeout * 1000)) {
131-
// We got one char, put it in the FIFO (which will now have exactly 1 byte) and return it
132-
_swFIFO.push(uart_getc(_uart));
133-
return _swFIFO.front();
151+
uint32_t start = millis();
152+
uint32_t now = millis();
153+
while ((now - start) < _timeout) {
154+
if (_writer != _reader) {
155+
return _queue[_reader];
156+
}
157+
delay(1);
158+
now = millis();
134159
}
135160
return -1; // Nothing available before timeout
136161
}
@@ -140,16 +165,16 @@ int SerialUART::read() {
140165
if (!_running || !m) {
141166
return -1;
142167
}
143-
_pumpFIFO();
144-
if (_swFIFO.size()) {
145-
auto ret = _swFIFO.front();
146-
_swFIFO.pop();
147-
return ret;
148-
}
149-
// The SW FIFO is empty, read the HW one until the timeout
150-
if (uart_is_readable_within_us(_uart, _timeout * 1000)) {
151-
// We got one char, return it (FIFO will still be empty
152-
return uart_getc(_uart);
168+
uint32_t start = millis();
169+
uint32_t now = millis();
170+
while ((now - start) < _timeout) {
171+
if (_writer != _reader) {
172+
auto ret = _queue[_reader];
173+
_reader = (_reader + 1) % sizeof(_queue);
174+
return ret;
175+
}
176+
delay(1);
177+
now = millis();
153178
}
154179
return -1; // Timeout
155180
}
@@ -159,16 +184,14 @@ int SerialUART::available() {
159184
if (!_running || !m) {
160185
return 0;
161186
}
162-
_pumpFIFO();
163-
return _swFIFO.size();
187+
return (_writer - _reader) % sizeof(_queue);
164188
}
165189

166190
int SerialUART::availableForWrite() {
167191
CoreMutex m(&_mutex);
168192
if (!_running || !m) {
169193
return 0;
170194
}
171-
_pumpFIFO();
172195
return (uart_is_writable(_uart)) ? 1 : 0;
173196
}
174197

@@ -177,7 +200,6 @@ void SerialUART::flush() {
177200
if (!_running || !m) {
178201
return;
179202
}
180-
_pumpFIFO();
181203
uart_tx_wait_blocking(_uart);
182204
}
183205

@@ -186,7 +208,6 @@ size_t SerialUART::write(uint8_t c) {
186208
if (!_running || !m) {
187209
return 0;
188210
}
189-
_pumpFIFO();
190211
uart_putc_raw(_uart, c);
191212
return 1;
192213
}
@@ -196,7 +217,6 @@ size_t SerialUART::write(const uint8_t *p, size_t len) {
196217
if (!_running || !m) {
197218
return 0;
198219
}
199-
_pumpFIFO();
200220
size_t cnt = len;
201221
while (cnt) {
202222
uart_putc_raw(_uart, *p);
@@ -224,3 +244,21 @@ void arduino::serialEvent2Run(void) {
224244
serialEvent2();
225245
}
226246
}
247+
248+
// IRQ handler, called when FIFO > 1/4 full or when it had held unread data for >32 bit times
249+
void __not_in_flash_func(SerialUART::_handleIRQ)() {
250+
uart_get_hw(_uart)->icr |= UART_UARTICR_RTIC_BITS | UART_UARTICR_RXIC_BITS;
251+
while ((uart_is_readable(_uart)) && ((_writer + 1) % sizeof(_queue) != _reader)) {
252+
_queue[_writer] = uart_getc(_uart);
253+
asm volatile("" ::: "memory"); // Ensure the queue is written before the written count advances
254+
_writer = (_writer + 1) % sizeof(_queue);
255+
}
256+
}
257+
258+
static void __not_in_flash_func(_uart0IRQ)() {
259+
Serial1._handleIRQ();
260+
}
261+
262+
static void __not_in_flash_func(_uart1IRQ)() {
263+
Serial2._handleIRQ();
264+
}

cores/rp2040/SerialUART.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,20 @@ class SerialUART : public HardwareSerial {
5757
using Print::write;
5858
operator bool() override;
5959

60+
// Not to be called by users, only from the IRQ handler. In public so that the C-language IQR callback can access it
61+
void _handleIRQ();
62+
6063
private:
6164
bool _running = false;
6265
uart_inst_t *_uart;
6366
pin_size_t _tx, _rx;
6467
int _baud;
6568
mutex_t _mutex;
6669

67-
void _pumpFIFO();
68-
std::queue<uint8_t> _swFIFO;
70+
// Lockless, IRQ-handled circular queue
71+
uint32_t _writer;
72+
uint32_t _reader;
73+
uint8_t _queue[32];
6974
};
7075

7176
extern SerialUART Serial1; // HW UART 0

0 commit comments

Comments
 (0)