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

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.
parent f46d7cc8
...@@ -72,65 +72,90 @@ SerialUART::SerialUART(uart_inst_t *uart, pin_size_t tx, pin_size_t rx) { ...@@ -72,65 +72,90 @@ SerialUART::SerialUART(uart_inst_t *uart, pin_size_t tx, pin_size_t rx) {
mutex_init(&_mutex); mutex_init(&_mutex);
} }
static void _uart0IRQ();
static void _uart1IRQ();
void SerialUART::begin(unsigned long baud, uint16_t config) { void SerialUART::begin(unsigned long baud, uint16_t config) {
_baud = baud; _baud = baud;
uart_init(_uart, baud); uart_init(_uart, baud);
int bits, stop; int bits, stop;
uart_parity_t parity; uart_parity_t parity;
switch (config & SERIAL_PARITY_MASK) { switch (config & SERIAL_PARITY_MASK) {
case SERIAL_PARITY_EVEN: parity = UART_PARITY_EVEN; break; case SERIAL_PARITY_EVEN:
case SERIAL_PARITY_ODD: parity = UART_PARITY_ODD; break; parity = UART_PARITY_EVEN;
default: parity = UART_PARITY_NONE; break; break;
case SERIAL_PARITY_ODD:
parity = UART_PARITY_ODD;
break;
default:
parity = UART_PARITY_NONE;
break;
} }
switch (config & SERIAL_STOP_BIT_MASK) { switch (config & SERIAL_STOP_BIT_MASK) {
case SERIAL_STOP_BIT_1: stop = 1; break; case SERIAL_STOP_BIT_1:
default: stop = 2; break; stop = 1;
break;
default:
stop = 2;
break;
} }
switch (config & SERIAL_DATA_MASK) { switch (config & SERIAL_DATA_MASK) {
case SERIAL_DATA_5: bits = 5; break; case SERIAL_DATA_5:
case SERIAL_DATA_6: bits = 6; break; bits = 5;
case SERIAL_DATA_7: bits = 7; break; break;
default: bits = 8; break; case SERIAL_DATA_6:
bits = 6;
break;
case SERIAL_DATA_7:
bits = 7;
break;
default:
bits = 8;
break;
} }
uart_set_format(_uart, bits, stop, parity); uart_set_format(_uart, bits, stop, parity);
gpio_set_function(_tx, GPIO_FUNC_UART); gpio_set_function(_tx, GPIO_FUNC_UART);
gpio_set_function(_rx, GPIO_FUNC_UART); gpio_set_function(_rx, GPIO_FUNC_UART);
_running = true; _writer = 0;
while (!_swFIFO.empty()) { _reader = 0;
(void)_swFIFO.pop(); // Just throw out anything in our old FIFO
if (_uart == uart0) {
irq_set_exclusive_handler(UART0_IRQ, _uart0IRQ);
irq_set_enabled(UART0_IRQ, true);
} else {
irq_set_exclusive_handler(UART1_IRQ, _uart1IRQ);
irq_set_enabled(UART1_IRQ, true);
} }
uart_get_hw(_uart)->imsc |= UART_UARTIMSC_RXIM_BITS | UART_UARTIMSC_RTIM_BITS;
_running = true;
} }
void SerialUART::end() { void SerialUART::end() {
if (!_running) { if (!_running) {
return; return;
} }
if (_uart == uart0) {
irq_set_enabled(UART0_IRQ, false);
} else {
irq_set_enabled(UART1_IRQ, false);
}
uart_deinit(_uart); uart_deinit(_uart);
_running = false; _running = false;
} }
// Transfers any data in the HW FIFO into our SW one, up to 32 bytes
void SerialUART::_pumpFIFO() {
while ((_swFIFO.size() < 32) && (uart_is_readable(_uart))) {
_swFIFO.push(uart_getc(_uart));
}
}
int SerialUART::peek() { int SerialUART::peek() {
CoreMutex m(&_mutex); CoreMutex m(&_mutex);
if (!_running || !m) { if (!_running || !m) {
return -1; return -1;
} }
_pumpFIFO(); uint32_t start = millis();
// If there's something in the FIFO now, just peek at it uint32_t now = millis();
if (_swFIFO.size()) { while ((now - start) < _timeout) {
return _swFIFO.front(); if (_writer != _reader) {
} return _queue[_reader];
// The SW FIFO is empty, read the HW one until the timeout }
if (uart_is_readable_within_us(_uart, _timeout * 1000)) { delay(1);
// We got one char, put it in the FIFO (which will now have exactly 1 byte) and return it now = millis();
_swFIFO.push(uart_getc(_uart));
return _swFIFO.front();
} }
return -1; // Nothing available before timeout return -1; // Nothing available before timeout
} }
...@@ -140,16 +165,16 @@ int SerialUART::read() { ...@@ -140,16 +165,16 @@ int SerialUART::read() {
if (!_running || !m) { if (!_running || !m) {
return -1; return -1;
} }
_pumpFIFO(); uint32_t start = millis();
if (_swFIFO.size()) { uint32_t now = millis();
auto ret = _swFIFO.front(); while ((now - start) < _timeout) {
_swFIFO.pop(); if (_writer != _reader) {
return ret; auto ret = _queue[_reader];
} _reader = (_reader + 1) % sizeof(_queue);
// The SW FIFO is empty, read the HW one until the timeout return ret;
if (uart_is_readable_within_us(_uart, _timeout * 1000)) { }
// We got one char, return it (FIFO will still be empty delay(1);
return uart_getc(_uart); now = millis();
} }
return -1; // Timeout return -1; // Timeout
} }
...@@ -159,8 +184,7 @@ int SerialUART::available() { ...@@ -159,8 +184,7 @@ int SerialUART::available() {
if (!_running || !m) { if (!_running || !m) {
return 0; return 0;
} }
_pumpFIFO(); return (_writer - _reader) % sizeof(_queue);
return _swFIFO.size();
} }
int SerialUART::availableForWrite() { int SerialUART::availableForWrite() {
...@@ -168,7 +192,6 @@ int SerialUART::availableForWrite() { ...@@ -168,7 +192,6 @@ int SerialUART::availableForWrite() {
if (!_running || !m) { if (!_running || !m) {
return 0; return 0;
} }
_pumpFIFO();
return (uart_is_writable(_uart)) ? 1 : 0; return (uart_is_writable(_uart)) ? 1 : 0;
} }
...@@ -177,7 +200,6 @@ void SerialUART::flush() { ...@@ -177,7 +200,6 @@ void SerialUART::flush() {
if (!_running || !m) { if (!_running || !m) {
return; return;
} }
_pumpFIFO();
uart_tx_wait_blocking(_uart); uart_tx_wait_blocking(_uart);
} }
...@@ -186,7 +208,6 @@ size_t SerialUART::write(uint8_t c) { ...@@ -186,7 +208,6 @@ size_t SerialUART::write(uint8_t c) {
if (!_running || !m) { if (!_running || !m) {
return 0; return 0;
} }
_pumpFIFO();
uart_putc_raw(_uart, c); uart_putc_raw(_uart, c);
return 1; return 1;
} }
...@@ -196,7 +217,6 @@ size_t SerialUART::write(const uint8_t *p, size_t len) { ...@@ -196,7 +217,6 @@ size_t SerialUART::write(const uint8_t *p, size_t len) {
if (!_running || !m) { if (!_running || !m) {
return 0; return 0;
} }
_pumpFIFO();
size_t cnt = len; size_t cnt = len;
while (cnt) { while (cnt) {
uart_putc_raw(_uart, *p); uart_putc_raw(_uart, *p);
...@@ -224,3 +244,21 @@ void arduino::serialEvent2Run(void) { ...@@ -224,3 +244,21 @@ void arduino::serialEvent2Run(void) {
serialEvent2(); serialEvent2();
} }
} }
// IRQ handler, called when FIFO > 1/4 full or when it had held unread data for >32 bit times
void __not_in_flash_func(SerialUART::_handleIRQ)() {
uart_get_hw(_uart)->icr |= UART_UARTICR_RTIC_BITS | UART_UARTICR_RXIC_BITS;
while ((uart_is_readable(_uart)) && ((_writer + 1) % sizeof(_queue) != _reader)) {
_queue[_writer] = uart_getc(_uart);
asm volatile("" ::: "memory"); // Ensure the queue is written before the written count advances
_writer = (_writer + 1) % sizeof(_queue);
}
}
static void __not_in_flash_func(_uart0IRQ)() {
Serial1._handleIRQ();
}
static void __not_in_flash_func(_uart1IRQ)() {
Serial2._handleIRQ();
}
...@@ -57,6 +57,9 @@ public: ...@@ -57,6 +57,9 @@ public:
using Print::write; using Print::write;
operator bool() override; operator bool() override;
// Not to be called by users, only from the IRQ handler. In public so that the C-language IQR callback can access it
void _handleIRQ();
private: private:
bool _running = false; bool _running = false;
uart_inst_t *_uart; uart_inst_t *_uart;
...@@ -64,8 +67,10 @@ private: ...@@ -64,8 +67,10 @@ private:
int _baud; int _baud;
mutex_t _mutex; mutex_t _mutex;
void _pumpFIFO(); // Lockless, IRQ-handled circular queue
std::queue<uint8_t> _swFIFO; uint32_t _writer;
uint32_t _reader;
uint8_t _queue[32];
}; };
extern SerialUART Serial1; // HW UART 0 extern SerialUART Serial1; // HW UART 0
......
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