Files
earlephilhower_arduino-pico/cores/rp2040/SerialUART.cpp
T
Earle F. Philhower, III 44cd697b29 Add SerialUART::setPollingMode() (#473)
Fixes #472

Instead of using interrupts, explicitly call the IRQ handler dueing Serial
read/peek/available calls.

Add to keywords.txt for syntax hilighting.

Add poll calls in the SerialUART::write-like calls (write,
flush, etc.)

Really remove division from IRQ routines/
2022-02-10 12:22:27 -08:00

313 lines
8.1 KiB
C++

/*
Serial-over-UART for the Raspberry Pi Pico RP2040
Copyright (c) 2021 Earle F. Philhower, III <earlephilhower@yahoo.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "SerialUART.h"
#include "CoreMutex.h"
#include <hardware/uart.h>
#include <hardware/gpio.h>
// SerialEvent functions are weak, so when the user doesn't define them,
// the linker just sets their address to 0 (which is checked below).
// The Serialx_available is just a wrapper around Serialx.available(),
// but we can refer to it weakly so we don't pull in the entire
// HardwareSerial instance if the user doesn't also refer to it.
extern void serialEvent1() __attribute__((weak));
extern void serialEvent2() __attribute__((weak));
bool SerialUART::setRX(pin_size_t pin) {
constexpr uint32_t valid[2] = { __bitset({1, 13, 17, 29}) /* UART0 */,
__bitset({5, 9, 21, 25}) /* UART1 */
};
if ((!_running) && ((1 << pin) & valid[uart_get_index(_uart)])) {
_rx = pin;
return true;
}
if (_running) {
panic("FATAL: Attempting to set Serial%d.RX while running", uart_get_index(_uart) + 1);
} else {
panic("FATAL: Attempting to set Serial%d.RX to illegal pin %d", uart_get_index(_uart) + 1, pin);
}
return false;
}
bool SerialUART::setTX(pin_size_t pin) {
constexpr uint32_t valid[2] = { __bitset({0, 12, 16, 28}) /* UART0 */,
__bitset({4, 8, 20, 24}) /* UART1 */
};
if ((!_running) && ((1 << pin) & valid[uart_get_index(_uart)])) {
_tx = pin;
return true;
}
if (_running) {
panic("FATAL: Attempting to set Serial%d.TX while running", uart_get_index(_uart) + 1);
} else {
panic("FATAL: Attempting to set Serial%d.TX to illegal pin %d", uart_get_index(_uart) + 1, pin);
}
return false;
}
bool SerialUART::setPollingMode(bool mode) {
if (_running) {
return false;
}
_polling = mode;
return true;
}
bool SerialUART::setFIFOSize(size_t size) {
if (!size || _running) {
return false;
}
_fifoSize = size + 1; // Always 1 unused entry
return true;
}
SerialUART::SerialUART(uart_inst_t *uart, pin_size_t tx, pin_size_t rx) {
_uart = uart;
_tx = tx;
_rx = rx;
mutex_init(&_mutex);
}
static void _uart0IRQ();
static void _uart1IRQ();
void SerialUART::begin(unsigned long baud, uint16_t config) {
_queue = new uint8_t[_fifoSize];
_baud = baud;
uart_init(_uart, baud);
int bits, stop;
uart_parity_t parity;
switch (config & SERIAL_PARITY_MASK) {
case SERIAL_PARITY_EVEN:
parity = UART_PARITY_EVEN;
break;
case SERIAL_PARITY_ODD:
parity = UART_PARITY_ODD;
break;
default:
parity = UART_PARITY_NONE;
break;
}
switch (config & SERIAL_STOP_BIT_MASK) {
case SERIAL_STOP_BIT_1:
stop = 1;
break;
default:
stop = 2;
break;
}
switch (config & SERIAL_DATA_MASK) {
case SERIAL_DATA_5:
bits = 5;
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);
gpio_set_function(_tx, GPIO_FUNC_UART);
gpio_set_function(_rx, GPIO_FUNC_UART);
_writer = 0;
_reader = 0;
if (!_polling) {
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);
}
// Set the IRQ enables and FIFO level to minimum
uart_set_irq_enables(_uart, true, false);
} else {
// Polling mode has no IRQs used
}
_running = true;
}
void SerialUART::end() {
if (!_running) {
return;
}
if (!_polling) {
if (_uart == uart0) {
irq_set_enabled(UART0_IRQ, false);
} else {
irq_set_enabled(UART1_IRQ, false);
}
}
uart_deinit(_uart);
delete[] _queue;
_running = false;
}
int SerialUART::peek() {
CoreMutex m(&_mutex);
if (!_running || !m) {
return -1;
}
if (_polling) {
_handleIRQ();
}
if (_writer != _reader) {
return _queue[_reader];
}
return -1;
}
int SerialUART::read() {
CoreMutex m(&_mutex);
if (!_running || !m) {
return -1;
}
if (_polling) {
_handleIRQ();
}
if (_writer != _reader) {
auto ret = _queue[_reader];
asm volatile("" ::: "memory"); // Ensure the value is read before advancing
auto next_reader = (_reader + 1) % _fifoSize;
asm volatile("" ::: "memory"); // Ensure the reader value is only written once, correctly
_reader = next_reader;
return ret;
}
return -1;
}
int SerialUART::available() {
CoreMutex m(&_mutex);
if (!_running || !m) {
return 0;
}
if (_polling) {
_handleIRQ();
}
return (_writer - _reader) % _fifoSize;
}
int SerialUART::availableForWrite() {
CoreMutex m(&_mutex);
if (!_running || !m) {
return 0;
}
if (_polling) {
_handleIRQ();
}
return (uart_is_writable(_uart)) ? 1 : 0;
}
void SerialUART::flush() {
CoreMutex m(&_mutex);
if (!_running || !m) {
return;
}
if (_polling) {
_handleIRQ();
}
uart_tx_wait_blocking(_uart);
}
size_t SerialUART::write(uint8_t c) {
CoreMutex m(&_mutex);
if (!_running || !m) {
return 0;
}
if (_polling) {
_handleIRQ();
}
uart_putc_raw(_uart, c);
return 1;
}
size_t SerialUART::write(const uint8_t *p, size_t len) {
CoreMutex m(&_mutex);
if (!_running || !m) {
return 0;
}
if (_polling) {
_handleIRQ();
}
size_t cnt = len;
while (cnt) {
uart_putc_raw(_uart, *p);
cnt--;
p++;
}
return len;
}
SerialUART::operator bool() {
return _running;
}
SerialUART Serial1(uart0, PIN_SERIAL1_TX, PIN_SERIAL1_RX);
SerialUART Serial2(uart1, PIN_SERIAL2_TX, PIN_SERIAL2_RX);
void arduino::serialEvent1Run(void) {
if (serialEvent1 && Serial1.available()) {
serialEvent1();
}
}
void arduino::serialEvent2Run(void) {
if (serialEvent2 && Serial2.available()) {
serialEvent2();
}
}
// IRQ handler, called when FIFO > 1/8 full or when it had held unread data for >32 bit times
void __not_in_flash_func(SerialUART::_handleIRQ)() {
// ICR is write-to-clear
uart_get_hw(_uart)->icr = UART_UARTICR_RTIC_BITS | UART_UARTICR_RXIC_BITS;
while (uart_is_readable(_uart)) {
auto val = uart_getc(_uart);
auto next_writer = _writer + 1;
if (next_writer == _fifoSize) {
next_writer = 0;
}
if (next_writer != _reader) {
_queue[_writer] = val;
asm volatile("" ::: "memory"); // Ensure the queue is written before the written count advances
// Avoid using division or mod because the HW divider could be in use
_writer = next_writer;
} else {
// TODO: Overflow
}
}
}
static void __not_in_flash_func(_uart0IRQ)() {
Serial1._handleIRQ();
}
static void __not_in_flash_func(_uart1IRQ)() {
Serial2._handleIRQ();
}