Skip to content

Commit ae2757f

Browse files
committed
pbio/drv/uart_ev3: Use EDMA for TX.
Appears to solve stability issues. Sensors no longer lock up. Not implemented for RX since there isn't much of an advantage to receiving single bytes asynchronously, due to the way the ring buffers are currently implemented. It could work well with async reads with known buffer sizes.
1 parent 3b9ae1a commit ae2757f

File tree

3 files changed

+160
-86
lines changed

3 files changed

+160
-86
lines changed

lib/pbio/drv/uart/uart_ev3.c

Lines changed: 121 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@
2323
#include "./uart_ev3.h"
2424
#include "./uart_ev3_pru.h"
2525

26-
#include <tiam1808/uart.h>
27-
#include <tiam1808/psc.h>
28-
#include <tiam1808/hw/soc_AM1808.h>
29-
#include <tiam1808/hw/hw_types.h>
30-
#include <tiam1808/hw/hw_syscfg0_AM1808.h>
26+
#include <tiam1808/armv5/am1808/edma_event.h>
3127
#include <tiam1808/armv5/am1808/interrupt.h>
28+
#include <tiam1808/edma.h>
29+
#include <tiam1808/hw/hw_edma3cc.h>
30+
#include <tiam1808/hw/hw_syscfg0_AM1808.h>
31+
#include <tiam1808/hw/hw_types.h>
32+
#include <tiam1808/hw/soc_AM1808.h>
33+
#include <tiam1808/psc.h>
34+
#include <tiam1808/uart.h>
3235

3336
/**
3437
* This file contains two almost entirely separate implementations of UART
@@ -126,56 +129,10 @@ pbio_error_t pbdrv_uart_read(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uin
126129
PBIO_OS_ASYNC_END(PBIO_SUCCESS);
127130
}
128131

129-
/**
130-
* Helper function to write one byte in the write protothread.
131-
*
132-
* @param [in] uart The UART device.
133-
* @param [in] byte The byte to write.
134-
* @return True if the byte was written, false otherwise.
135-
*/
136-
static bool pbdrv_uart_try_to_write_byte(pbdrv_uart_dev_t *uart, uint8_t byte) {
137-
const pbdrv_uart_ev3_platform_data_t *pdata = uart->pdata;
138-
139-
// Attempt to write one byte with the hardware UART.
140-
if (pdata->uart_kind == EV3_UART_HW) {
141-
return UARTCharPutNonBlocking(pdata->base_address, byte);
142-
}
143-
144-
// Otherwise, attempt to write one byte with the PRU UART.
145-
// We could be sending more than one byte at a time, but we keep it
146-
// consistent with the hardware UART limitations for simplicity, since
147-
// EV3 sensors typically don't send much data anyway.
148-
return pbdrv_uart_ev3_pru_write_bytes(pdata->peripheral_id, &byte, 1);
149-
}
132+
static pbio_error_t pbdrv_uart_write_pru(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) {
150133

151-
/**
152-
* Helper function to check if the UART is ready to write.
153-
*
154-
* @param [in] uart The UART device.
155-
* @return True if the UART is ready to write, false otherwise.
156-
*/
157-
static bool pbdrv_uart_can_write(pbdrv_uart_dev_t *uart) {
158134
const pbdrv_uart_ev3_platform_data_t *pdata = uart->pdata;
159135

160-
// Check UART_LSR for THR_EMPTY
161-
if (pdata->uart_kind == EV3_UART_HW) {
162-
// Always call back after this. This creates a (yielding) poll loop for
163-
// writing. This works around the unreliable TX_EMPTY interrupt when
164-
// reading and writing simultaneously: Reading the status during an RX
165-
// interrupt clears the TX_EMPTY interrupt, which is not re-triggered.
166-
// Since UART writes on EV3 are limited to small messages like sensor
167-
// mode changes, this is acceptable.
168-
pbio_os_request_poll();
169-
return UARTSpaceAvail(pdata->base_address);
170-
}
171-
172-
// For PRU UART, we rely on the flag set by its IRQ handler.
173-
// So no need to poll here.
174-
return pbdrv_uart_ev3_pru_can_write(pdata->peripheral_id);
175-
}
176-
177-
pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) {
178-
179136
PBIO_OS_ASYNC_BEGIN(state);
180137

181138
// Can only write one thing at once.
@@ -195,13 +152,19 @@ pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, ui
195152
PBIO_OS_AWAIT_UNTIL(state, ({
196153
// Try to write one byte if any are remaining.
197154
if (uart->write_pos < uart->write_length) {
198-
if (pbdrv_uart_try_to_write_byte(uart, uart->write_buf[uart->write_pos])) {
155+
// Attempt to write one byte with the PRU UART. We could be sending
156+
// more than one byte at a time, but we keep it consistent with the
157+
// current PRU API. EV3 sensors don't send much data anyway.
158+
if (pbdrv_uart_ev3_pru_write_bytes(pdata->peripheral_id, &uart->write_buf[uart->write_pos], 1)) {
199159
uart->write_pos++;
200160
}
201161
}
162+
// Completion on transmission of whole message and finishing writing, or timeout.
163+
bool complete = pbdrv_uart_ev3_pru_can_write(pdata->peripheral_id) && uart->write_pos == uart->write_length;
164+
bool expired = timeout && pbio_os_timer_is_expired(&uart->write_timer);
202165

203-
// Completion on transmission of whole message and finishing writing or timeout.
204-
(pbdrv_uart_can_write(uart) && uart->write_pos == uart->write_length) || (timeout && pbio_os_timer_is_expired(&uart->write_timer));
166+
// Await until complete or timed out.
167+
complete || expired;
205168
}));
206169

207170
uart->write_buf = NULL;
@@ -213,6 +176,76 @@ pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, ui
213176
PBIO_OS_ASYNC_END(PBIO_SUCCESS);
214177
}
215178

179+
pbio_error_t pbdrv_uart_write_hw(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) {
180+
181+
const pbdrv_uart_ev3_platform_data_t *pdata = uart->pdata;
182+
183+
PBIO_OS_ASYNC_BEGIN(state);
184+
185+
// Can only write one thing at once.
186+
if (uart->write_buf) {
187+
return PBIO_ERROR_BUSY;
188+
}
189+
190+
uart->write_buf = msg;
191+
192+
// Write length and pos properties not used in this implementation.
193+
uart->write_length = 0;
194+
uart->write_pos = 0;
195+
196+
if (timeout) {
197+
pbio_os_timer_set(&uart->write_timer, timeout);
198+
}
199+
200+
volatile EDMA3CCPaRAMEntry paramSet = {
201+
.srcAddr = (uint32_t)uart->write_buf,
202+
// UART transmit register address.
203+
.destAddr = pdata->base_address + UART_THR,
204+
// Number of bytes in an array.
205+
.aCnt = 1,
206+
// Number of such arrays to be transferred.
207+
.bCnt = length,
208+
// Number of frames of aCnt*bBcnt bytes to be transferred.
209+
.cCnt = 1,
210+
// The src index should increment for every byte being transferred.
211+
.srcBIdx = 1,
212+
// The dst index should not increment since it is a h/w register.
213+
.destBIdx = 0,
214+
// Transfer mode.
215+
.srcCIdx = 0,
216+
.destCIdx = 0,
217+
.linkAddr = 0xFFFF,
218+
.bCntReload = 0,
219+
.opt = EDMA3CC_OPT_DAM | ((pdata->sys_int_uart_tx_int_id << EDMA3CC_OPT_TCC_SHIFT) & EDMA3CC_OPT_TCC) | (1 << EDMA3CC_OPT_TCINTEN_SHIFT),
220+
};
221+
222+
// Save configuration and start transfer.
223+
EDMA3SetPaRAM(SOC_EDMA30CC_0_REGS, pdata->sys_int_uart_tx_int_id, (EDMA3CCPaRAMEntry *)&paramSet);
224+
EDMA3EnableTransfer(SOC_EDMA30CC_0_REGS, pdata->sys_int_uart_tx_int_id, EDMA3_TRIG_MODE_EVENT);
225+
UARTDMAEnable(pdata->base_address, UART_RX_TRIG_LEVEL_1 | UART_DMAMODE | UART_FIFO_MODE);
226+
227+
// Await until all bytes are written or timeout reached.
228+
PBIO_OS_AWAIT_UNTIL(state, !uart->write_buf || (timeout && pbio_os_timer_is_expired(&uart->write_timer)));
229+
230+
uart->write_buf = NULL;
231+
232+
if (timeout && pbio_os_timer_is_expired(&uart->write_timer)) {
233+
return PBIO_ERROR_TIMEDOUT;
234+
}
235+
236+
PBIO_OS_ASYNC_END(PBIO_SUCCESS);
237+
}
238+
239+
pbio_error_t pbdrv_uart_write(pbio_os_state_t *state, pbdrv_uart_dev_t *uart, uint8_t *msg, uint8_t length, uint32_t timeout) {
240+
const pbdrv_uart_ev3_platform_data_t *pdata = uart->pdata;
241+
242+
if (pdata->uart_kind == EV3_UART_HW) {
243+
return pbdrv_uart_write_hw(state, uart, msg, length, timeout);
244+
} else {
245+
return pbdrv_uart_write_pru(state, uart, msg, length, timeout);
246+
}
247+
}
248+
216249
void pbdrv_uart_set_baud_rate(pbdrv_uart_dev_t *uart, uint32_t baud) {
217250
if (uart->pdata->uart_kind == EV3_UART_HW) {
218251
UARTConfigSetExpClk(uart->pdata->base_address, SOC_UART_0_MODULE_FREQ, baud, UART_WORDL_8BITS, UART_OVER_SAMP_RATE_16);
@@ -238,13 +271,18 @@ void pbdrv_uart_flush(pbdrv_uart_dev_t *uart) {
238271
}
239272
}
240273

274+
/**
275+
* Handles RX interrupts for the hardware UART.
276+
*
277+
* @param [in] uart The UART device.
278+
*/
241279
void pbdrv_uart_ev3_hw_handle_irq(pbdrv_uart_dev_t *uart) {
242280

243281
/* This determines the cause of UART0 interrupt.*/
244282
unsigned int int_id = UARTIntStatus(uart->pdata->base_address);
245283

246284
/* Clears the system interrupt status of UART in AINTC. */
247-
IntSystemStatusClear(uart->pdata->sys_int_uart_int_id);
285+
IntSystemStatusClear(uart->pdata->sys_int_uart_rx_int_id);
248286

249287
/* Check if the cause is receiver data condition.*/
250288
if (UART_INTID_RX_DATA == (int_id & UART_INTID_RX_DATA) || (UART_INTID_CTI == (int_id & UART_INTID_CTI))) {
@@ -272,7 +310,8 @@ void pbdrv_uart_ev3_hw_handle_irq(pbdrv_uart_dev_t *uart) {
272310

273311
void pbdrv_uart_ev3_pru_handle_irq(pbdrv_uart_dev_t *uart) {
274312

275-
IntSystemStatusClear(uart->pdata->sys_int_uart_int_id);
313+
// rx and tx have the same interrupt ID.
314+
IntSystemStatusClear(uart->pdata->sys_int_uart_rx_int_id);
276315

277316
// This calls what is mostly equivalent the original LEGO/ev3dev IRQ
278317
// handler. This is using its own ring buffer that we pull data from below.
@@ -295,34 +334,41 @@ void pbdrv_uart_ev3_handle_irq(uint8_t id) {
295334
}
296335
}
297336

337+
void pbdrv_uart_ev3_handle_tx_complete(uint8_t id) {
338+
pbdrv_uart_dev_t *uart = &uart_devs[id];
339+
UARTDMADisable(uart->pdata->base_address, (UART_RX_TRIG_LEVEL_1 | UART_FIFO_MODE));
340+
uart->write_buf = NULL;
341+
pbio_os_request_poll();
342+
}
343+
298344
static void pbdrv_uart_init_hw(pbdrv_uart_dev_t *uart) {
299345
const pbdrv_uart_ev3_platform_data_t *pdata = uart->pdata;
300346

301-
/* Enabling the PSC for given UART.*/
347+
// Enabling the PSC for given UART.
302348
PSCModuleControl(SOC_PSC_1_REGS, pdata->peripheral_id, PSC_POWERDOMAIN_ALWAYS_ON, PSC_MDCTL_NEXT_ENABLE);
303349

304-
/* Enabling the transmitter and receiver*/
350+
// Enabling the transmitter and receiver.
305351
UARTEnable(pdata->base_address);
306352

307-
/* Configuring the UART clock and baud parameters*/
353+
// Configuring the UART clock and baud parameters.
308354
pbdrv_uart_set_baud_rate(uart, BAUD_115200);
309355

310-
/* Enabling the FIFO and flushing the Tx and Rx FIFOs.*/
356+
// Enabling the FIFO and flushing the Tx and Rx FIFOs.
311357
UARTFIFOEnable(pdata->base_address);
312358

313-
/* Setting the UART Receiver Trigger Level*/
359+
// Setting the UART Receiver Trigger Level.
314360
UARTFIFOLevelSet(pdata->base_address, UART_RX_TRIG_LEVEL_1);
315361

316-
/* Registers the UARTIsr in the Interrupt Vector Table of AINTC. */
317-
IntRegister(pdata->sys_int_uart_int_id, pdata->isr_handler);
318-
319-
/* Map the channel number 2 of AINTC to UART system interrupt. */
320-
IntChannelSet(pdata->sys_int_uart_int_id, 2);
321-
322-
IntSystemEnable(pdata->sys_int_uart_int_id);
323-
324-
/* Enable the Interrupts in UART.*/
362+
// Registers the UARTIsr for reading in the Interrupt Vector Table of AINTC.
363+
// Map the channel number 2 of AINTC to UART system interrupt.
364+
// Interrupts for reading stay enabled, unlike writing.
365+
IntRegister(pdata->sys_int_uart_rx_int_id, pdata->isr_handler);
366+
IntChannelSet(pdata->sys_int_uart_rx_int_id, 2);
367+
IntSystemEnable(pdata->sys_int_uart_rx_int_id);
325368
UARTIntEnable(pdata->base_address, UART_INT_LINE_STAT | UART_INT_RXDATA_CTI);
369+
370+
// Request DMA Channel for UART Transmit at event queue 0.
371+
EDMA3RequestChannel(SOC_EDMA30CC_0_REGS, EDMA3_CHANNEL_TYPE_DMA, pdata->sys_int_uart_tx_int_id, pdata->sys_int_uart_tx_int_id, 0);
326372
}
327373

328374

@@ -333,12 +379,11 @@ void pbdrv_uart_init_pru(pbdrv_uart_dev_t *uart) {
333379
pbdrv_uart_set_baud_rate(uart, BAUD_115200);
334380

335381
// Registers the UARTIsr in the Interrupt Vector Table of AINTC.
336-
IntRegister(pdata->sys_int_uart_int_id, pdata->isr_handler);
337-
338382
// Map the channel number 2 of AINTC to UART system interrupt.
339-
IntChannelSet(pdata->sys_int_uart_int_id, 2);
340-
341-
IntSystemEnable(pdata->sys_int_uart_int_id);
383+
// One handler for both RX and TX interrupts with same ID.
384+
IntRegister(pdata->sys_int_uart_tx_int_id, pdata->isr_handler);
385+
IntChannelSet(pdata->sys_int_uart_tx_int_id, 2);
386+
IntSystemEnable(pdata->sys_int_uart_tx_int_id);
342387
}
343388

344389
void pbdrv_uart_init(void) {

lib/pbio/drv/uart/uart_ev3.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ typedef struct {
2727
uint32_t base_address;
2828
/** Peripheral ID (PSC id in case of hardware, PRU UART line ID in case of PRU) */
2929
uint32_t peripheral_id;
30-
/** The UART interrupt number. */
31-
uint32_t sys_int_uart_int_id;
30+
/** The UART TX interrupt number. */
31+
uint32_t sys_int_uart_tx_int_id;
32+
/** The UART RX interrupt number. */
33+
uint32_t sys_int_uart_rx_int_id;
3234
/** Interrupt handler for this peripheral. */
3335
void (*isr_handler)(void);
3436
} pbdrv_uart_ev3_platform_data_t;
@@ -49,4 +51,14 @@ extern const pbdrv_uart_ev3_platform_data_t
4951
*/
5052
void pbdrv_uart_ev3_handle_irq(uint8_t id);
5153

54+
/**
55+
* Callback to be called by the hardware UART TX complete handler.
56+
*
57+
* The driver will forward this call to the relevant hardware or PRU UART
58+
* TX complete handler, if applicable.
59+
*
60+
* @param id [in] The UART instance ID.
61+
*/
62+
void pbdrv_uart_ev3_handle_tx_complete(uint8_t id);
63+
5264
#endif // _INTERNAL_PBDRV_UART_EV3_H_

lib/pbio/platform/ev3/platform.c

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -209,36 +209,41 @@ const pbdrv_uart_ev3_platform_data_t
209209
.uart_kind = EV3_UART_HW,
210210
.base_address = SOC_UART_1_REGS,
211211
.peripheral_id = HW_PSC_UART1,
212-
.sys_int_uart_int_id = SYS_INT_UARTINT1,
212+
.sys_int_uart_tx_int_id = EDMA3_CHA_UART1_TX,
213+
.sys_int_uart_rx_int_id = SYS_INT_UARTINT1,
213214
.isr_handler = pbdrv_uart_ev3_handle_irq_uart1,
214215
},
215216
[UART0] = {
216217
.uart_kind = EV3_UART_HW,
217218
.base_address = SOC_UART_0_REGS,
218219
.peripheral_id = HW_PSC_UART0,
219-
.sys_int_uart_int_id = SYS_INT_UARTINT0,
220+
.sys_int_uart_tx_int_id = EDMA3_CHA_UART0_TX,
221+
.sys_int_uart_rx_int_id = SYS_INT_UARTINT0,
220222
.isr_handler = pbdrv_uart_ev3_handle_irq_uart0,
221223
},
222224
[PRU0_LINE1] = {
223225
.uart_kind = EV3_UART_PRU,
224226
.base_address = 0, // Not used.
225227
.peripheral_id = 1, // Soft UART line 1.
226-
.sys_int_uart_int_id = SYS_INT_EVTOUT1,
228+
.sys_int_uart_tx_int_id = SYS_INT_EVTOUT1, // One common IRQ handler
229+
.sys_int_uart_rx_int_id = SYS_INT_EVTOUT1,
227230
.isr_handler = pbdrv_uart_ev3_handle_irq_pru0_line1,
228231
},
229232
[PRU0_LINE0] = {
230233
.uart_kind = EV3_UART_PRU,
231234
.base_address = 0, // Not used.
232235
.peripheral_id = 0, // Soft UART line 0.
233-
.sys_int_uart_int_id = SYS_INT_EVTOUT0,
236+
.sys_int_uart_tx_int_id = SYS_INT_EVTOUT0, // One common IRQ handler
237+
.sys_int_uart_rx_int_id = SYS_INT_EVTOUT0,
234238
.isr_handler = pbdrv_uart_ev3_handle_irq_pru0_line0,
235239
},
236240
[UART2] = {
237241
// TODO: Add CTS/RTS pins.
238242
.uart_kind = EV3_UART_HW,
239243
.base_address = SOC_UART_2_REGS,
240244
.peripheral_id = HW_PSC_UART2,
241-
.sys_int_uart_int_id = SYS_INT_UARTINT2,
245+
.sys_int_uart_tx_int_id = EDMA3_CHA_UART2_TX,
246+
.sys_int_uart_rx_int_id = SYS_INT_UARTINT2,
242247
.isr_handler = pbdrv_uart_ev3_handle_irq_uart2,
243248
},
244249
};
@@ -408,10 +413,22 @@ unsigned int EDMAVersionGet(void) {
408413
* @param status [in] Status of the transfer. Currently only EDMA3_XFER_COMPLETE.
409414
*/
410415
static void Edma3CompleteCallback(unsigned int tccNum, unsigned int status) {
411-
if (tccNum == EDMA3_CHA_SPI1_TX) {
412-
pbdrv_display_ev3_spi1_tx_complete(status);
416+
switch (tccNum) {
417+
case EDMA3_CHA_SPI1_TX:
418+
pbdrv_display_ev3_spi1_tx_complete(status);
419+
return;
420+
case EDMA3_CHA_UART0_TX:
421+
pbdrv_uart_ev3_handle_tx_complete(UART0);
422+
return;
423+
case EDMA3_CHA_UART1_TX:
424+
pbdrv_uart_ev3_handle_tx_complete(UART1);
425+
return;
426+
case EDMA3_CHA_UART2_TX:
427+
pbdrv_uart_ev3_handle_tx_complete(UART2);
428+
return;
429+
default:
430+
return;
413431
}
414-
// Add other callbacks here as needed.
415432
}
416433

417434
/**

0 commit comments

Comments
 (0)