Skip to content

Commit ce7d337

Browse files
Add SoftwareSerial-like PIO based UARTs (#391)
Adds support to the core for PIO-based, software-created UARTs, up to 8 (the number of PIO state machines) possible. By using a custom program on the PIO state machines, it allows for very high bit rates and does not require CPU or interrupts. Bit widths from 5- to 8-bits, 1 or 2 stop bits, and even/odd/none parity are supported.
1 parent dc1198b commit ce7d337

File tree

8 files changed

+650
-0
lines changed

8 files changed

+650
-0
lines changed

cores/rp2040/Arduino.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ void analogWriteResolution(int res);
9797

9898
#include "SerialUART.h"
9999
#include "RP2040Support.h"
100+
#include "SerialPIO.h"
100101
#include "Bootsel.h"
101102

102103
// Template which will evaluate at *compile time* to a single 32b number

cores/rp2040/SerialPIO.cpp

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
/*
2+
Serial-over-PIO for the Raspberry Pi Pico RP2040
3+
4+
Copyright (c) 2021 Earle F. Philhower, III <[email protected]>
5+
6+
This library is free software; you can redistribute it and/or
7+
modify it under the terms of the GNU Lesser General Public
8+
License as published by the Free Software Foundation; either
9+
version 2.1 of the License, or (at your option) any later version.
10+
11+
This library is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
Lesser General Public License for more details.
15+
16+
You should have received a copy of the GNU Lesser General Public
17+
License along with this library; if not, write to the Free Software
18+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19+
*/
20+
21+
#include "SerialPIO.h"
22+
#include "CoreMutex.h"
23+
#include <hardware/gpio.h>
24+
#include <map>
25+
#include "pio_uart.pio.h"
26+
27+
28+
// ------------------------------------------------------------------------
29+
// -- Generates a unique program for differing bit lengths
30+
static std::map<int, PIOProgram*> _txMap;
31+
static std::map<int, PIOProgram*> _rxMap;
32+
33+
// Duplicate a program and replace the first insn with a "set x, repl"
34+
static pio_program_t *pio_make_uart_prog(int repl, const pio_program_t *pg) {
35+
pio_program_t *p = new pio_program_t;
36+
p->length = pg->length;
37+
p->origin = pg->origin;
38+
uint16_t *insn = (uint16_t *)malloc(p->length * 2);
39+
memcpy(insn, pg->instructions, p->length * 2);
40+
insn[0] = pio_encode_set(pio_x, repl);
41+
p->instructions = insn;
42+
return p;
43+
}
44+
45+
static PIOProgram *_getTxProgram(int bits) {
46+
auto f = _txMap.find(bits);
47+
if (f == _txMap.end()) {
48+
pio_program_t * p = pio_make_uart_prog(bits, &pio_tx_program);
49+
_txMap.insert({bits, new PIOProgram(p)});
50+
f = _txMap.find(bits);
51+
}
52+
return f->second;
53+
}
54+
55+
static PIOProgram *_getRxProgram(int bits) {
56+
auto f = _rxMap.find(bits);
57+
if (f == _rxMap.end()) {
58+
pio_program_t * p = pio_make_uart_prog(bits, &pio_rx_program);
59+
_rxMap.insert({bits, new PIOProgram(p)});
60+
f = _rxMap.find(bits);
61+
}
62+
return f->second;
63+
}
64+
// ------------------------------------------------------------------------
65+
66+
// TODO - this works, but there must be a faster/better way...
67+
static int _parity(int bits, int data) {
68+
int p = 0;
69+
for (int b = 0; b < bits; b++) {
70+
p ^= (data & (1 << b)) ? 1 : 0;
71+
}
72+
return p;
73+
}
74+
75+
// We need to cache generated SerialPIOs so we can add data to them from
76+
// the shared handler
77+
static SerialPIO *_pioSP[2][4];
78+
static void __not_in_flash_func(_fifoIRQ)() {
79+
for (int p = 0; p < 2; p++) {
80+
for (int sm = 0; sm < 4; sm++) {
81+
SerialPIO *s = _pioSP[p][sm];
82+
if (s) {
83+
s->_handleIRQ();
84+
pio_interrupt_clear((p == 0) ? pio0 : pio1, sm);
85+
}
86+
}
87+
}
88+
}
89+
90+
void __not_in_flash_func(SerialPIO::_handleIRQ)() {
91+
if (_rx == NOPIN) {
92+
return;
93+
}
94+
while ((!pio_sm_is_rx_fifo_empty(_rxPIO, _rxSM)) && ((_writer + 1) % sizeof(_queue) != _reader)) {
95+
uint32_t decode = _rxPIO->rxf[_rxSM];
96+
decode >>= 32 - _rxBits;
97+
uint32_t val = 0;
98+
for (int b = 0; b < _bits + 1; b++) {
99+
val |= (decode & (1 << (b * 2))) ? 1 << b : 0;
100+
}
101+
if (_parity == UART_PARITY_EVEN) {
102+
int p = ::_parity(_bits, val);
103+
int r = (val & (1 << _bits)) ? 1 : 0;
104+
if (p != r) {
105+
// TODO - parity error
106+
continue;
107+
}
108+
} else if (_parity == UART_PARITY_ODD) {
109+
int p = ::_parity(_bits, val);
110+
int r = (val & (1 << _bits)) ? 1 : 0;
111+
if (p == r) {
112+
// TODO - parity error
113+
continue;
114+
}
115+
}
116+
117+
_queue[_writer] = val & ((1 << _bits) - 1);
118+
asm volatile("" ::: "memory"); // Ensure the queue is written before the written count advances
119+
_writer = (_writer + 1) % sizeof(_queue);
120+
}
121+
}
122+
123+
SerialPIO::SerialPIO(pin_size_t tx, pin_size_t rx) {
124+
_tx = tx;
125+
_rx = rx;
126+
mutex_init(&_mutex);
127+
}
128+
129+
void SerialPIO::begin(unsigned long baud, uint16_t config) {
130+
_baud = baud;
131+
switch (config & SERIAL_PARITY_MASK) {
132+
case SERIAL_PARITY_EVEN:
133+
_parity = UART_PARITY_EVEN;
134+
break;
135+
case SERIAL_PARITY_ODD:
136+
_parity = UART_PARITY_ODD;
137+
break;
138+
default:
139+
_parity = UART_PARITY_NONE;
140+
break;
141+
}
142+
switch (config & SERIAL_STOP_BIT_MASK) {
143+
case SERIAL_STOP_BIT_1:
144+
_stop = 1;
145+
break;
146+
default:
147+
_stop = 2;
148+
break;
149+
}
150+
switch (config & SERIAL_DATA_MASK) {
151+
case SERIAL_DATA_5:
152+
_bits = 5;
153+
break;
154+
case SERIAL_DATA_6:
155+
_bits = 6;
156+
break;
157+
case SERIAL_DATA_7:
158+
_bits = 7;
159+
break;
160+
default:
161+
_bits = 8;
162+
break;
163+
}
164+
165+
if ((_tx == NOPIN) && (_rx == NOPIN)) {
166+
DEBUGCORE("ERROR: No pins specified for SerialPIO\n");
167+
return;
168+
}
169+
170+
if (_tx != NOPIN) {
171+
_txBits = _bits + _stop + (_parity != UART_PARITY_NONE ? 1 : 0) + 1/*start bit*/;
172+
_txPgm = _getTxProgram(_txBits);
173+
int off;
174+
if (!_txPgm->prepare(&_txPIO, &_txSM, &off)) {
175+
DEBUGCORE("ERROR: Unable to allocate PIO TX UART, out of PIO resources\n");
176+
// ERROR, no free slots
177+
return;
178+
}
179+
180+
digitalWrite(_tx, HIGH);
181+
pinMode(_tx, OUTPUT);
182+
183+
pio_tx_program_init(_txPIO, _txSM, off, _tx);
184+
pio_sm_clear_fifos(_txPIO, _txSM); // Remove any existing data
185+
186+
// Put the divider into ISR w/o using up program space
187+
pio_sm_put_blocking(_txPIO, _txSM, clock_get_hz(clk_sys) / _baud - 2);
188+
pio_sm_exec(_txPIO, _txSM, pio_encode_pull(false, false));
189+
pio_sm_exec(_txPIO, _txSM, pio_encode_mov(pio_isr, pio_osr));
190+
191+
// Start running!
192+
pio_sm_set_enabled(_txPIO, _txSM, true);
193+
}
194+
if (_rx != NOPIN) {
195+
_writer = 0;
196+
_reader = 0;
197+
198+
_rxBits = 2 * (_bits + _stop + (_parity != UART_PARITY_NONE ? 1 : 0) + 1);
199+
_rxPgm = _getRxProgram(_rxBits);
200+
int off;
201+
if (!_rxPgm->prepare(&_rxPIO, &_rxSM, &off)) {
202+
DEBUGCORE("ERROR: Unable to allocate PIO RX UART, out of PIO resources\n");
203+
return;
204+
}
205+
// Stash away the created RX port for the IRQ handler
206+
_pioSP[pio_get_index(_rxPIO)][_rxSM] = this;
207+
208+
pinMode(_rx, INPUT);
209+
pio_rx_program_init(_rxPIO, _rxSM, off, _rx);
210+
pio_sm_clear_fifos(_rxPIO, _rxSM); // Remove any existing data
211+
212+
// Put phase divider into OSR w/o using add'l program memory
213+
pio_sm_put_blocking(_rxPIO, _rxSM, clock_get_hz(clk_sys) / (_baud * 2) - 2);
214+
pio_sm_exec(_rxPIO, _rxSM, pio_encode_pull(false, false));
215+
216+
// Enable interrupts on rxfifo
217+
switch (_rxSM) {
218+
case 0: pio_set_irq0_source_enabled(_rxPIO, pis_sm0_rx_fifo_not_empty, true); break;
219+
case 1: pio_set_irq0_source_enabled(_rxPIO, pis_sm1_rx_fifo_not_empty, true); break;
220+
case 2: pio_set_irq0_source_enabled(_rxPIO, pis_sm2_rx_fifo_not_empty, true); break;
221+
case 3: pio_set_irq0_source_enabled(_rxPIO, pis_sm3_rx_fifo_not_empty, true); break;
222+
}
223+
irq_set_exclusive_handler(PIO0_IRQ_0, _fifoIRQ);
224+
irq_set_enabled(PIO0_IRQ_0, true);
225+
226+
pio_sm_set_enabled(_rxPIO, _rxSM, true);
227+
}
228+
229+
_running = true;
230+
}
231+
232+
void SerialPIO::end() {
233+
if (!_running) {
234+
return;
235+
}
236+
// TODO: Deallocate PIO resources, stop them
237+
_pioSP[pio_get_index(_rxPIO)][_rxSM] = nullptr;
238+
_running = false;
239+
}
240+
241+
int SerialPIO::peek() {
242+
CoreMutex m(&_mutex);
243+
if (!_running || !m || (_rx == NOPIN)) {
244+
return -1;
245+
}
246+
// If there's something in the FIFO now, just peek at it
247+
uint32_t start = millis();
248+
uint32_t now = millis();
249+
while ((now - start) < _timeout) {
250+
if (_writer != _reader) {
251+
return _queue[_reader];
252+
}
253+
delay(1);
254+
now = millis();
255+
}
256+
return -1; // Nothing available before timeout
257+
}
258+
259+
int SerialPIO::read() {
260+
CoreMutex m(&_mutex);
261+
if (!_running || !m || (_rx == NOPIN)) {
262+
return -1;
263+
}
264+
uint32_t start = millis();
265+
uint32_t now = millis();
266+
while ((now - start) < _timeout) {
267+
if (_writer != _reader) {
268+
auto ret = _queue[_reader];
269+
_reader = (_reader + 1) % sizeof(_queue);
270+
return ret;
271+
}
272+
delay(1);
273+
now = millis();
274+
}
275+
return -1; // Timeout
276+
}
277+
278+
int SerialPIO::available() {
279+
CoreMutex m(&_mutex);
280+
if (!_running || !m || (_rx == NOPIN)) {
281+
return 0;
282+
}
283+
return (_writer - _reader) % sizeof(_queue);
284+
}
285+
286+
int SerialPIO::availableForWrite() {
287+
CoreMutex m(&_mutex);
288+
if (!_running || !m || (_tx == NOPIN)) {
289+
return 0;
290+
}
291+
return 8 - pio_sm_get_tx_fifo_level(_txPIO, _txSM);
292+
}
293+
294+
void SerialPIO::flush() {
295+
CoreMutex m(&_mutex);
296+
if (!_running || !m || (_tx == NOPIN)) {
297+
return;
298+
}
299+
while (!pio_sm_is_tx_fifo_empty(_txPIO, _txSM)) {
300+
delay(1); // Wait for all FIFO to be read
301+
}
302+
// Could have 1 byte being transmitted, so wait for bit times
303+
delay((1000 * (_txBits + 1)) / _baud);
304+
}
305+
306+
size_t SerialPIO::write(uint8_t c) {
307+
CoreMutex m(&_mutex);
308+
if (!_running || !m || (_tx == NOPIN)) {
309+
return 0;
310+
}
311+
312+
uint32_t val = c;
313+
if (_parity == UART_PARITY_NONE) {
314+
val |= 7 << _bits; // Set 2 stop bits, the HW will only transmit the required number
315+
} else if (_parity == UART_PARITY_EVEN) {
316+
val |= ::_parity(_bits, c) << _bits;
317+
val |= 7 << (_bits + 1);
318+
} else {
319+
val |= (1 ^ ::_parity(_bits, c)) << _bits;
320+
val |= 7 << (_bits + 1);
321+
}
322+
val <<= 1; // Start bit = low
323+
324+
pio_sm_put_blocking(_txPIO, _txSM, val);
325+
326+
return 1;
327+
}
328+
329+
SerialPIO::operator bool() {
330+
return _running;
331+
}

0 commit comments

Comments
 (0)