tinygo icon indicating copy to clipboard operation
tinygo copied to clipboard

nrf: Add .Bus member to UART type

Open thegrumpylion opened this issue 4 years ago • 7 comments

Tested with examples/echo on pca10056 & nrf52840-mdk.

thegrumpylion avatar Nov 19 '19 15:11 thegrumpylion

Have you tested this with UART1/UARTE1? I think receiving won't work currently because it is missing an interrupt handler for UARTE1.

aykevl avatar Nov 20 '19 17:11 aykevl

No i haven't but actually this is the purpose of this PR, to have the primitives to enable UART1 on nrf52840. This plus UART1 should be one change then. I'll have a go at it.

thegrumpylion avatar Nov 20 '19 17:11 thegrumpylion

This go hairy fairly quickly :/

From looking in device/nrf you can come to the following conclusion:

  • nrf51: UART0 (uart mode only)
  • nrf52: UART0 (uart or uarte mode)
  • nrf52810: UART0 (uarte mode only)
  • nrf52840: UART0 (uart or uarte mode) UART1 (uarte mode only)

Then things can be separated

machine_nrf51.go

type UART struct {
	Buffer *RingBuffer
	Bus    *nrf.UART_Type
}

var UART0 = UART{
	Buffer: NewRingBuffer(),
	Bus:    nrf.UART0,
}

machine_nrf52_common.go

type UART struct {
	Buffer *RingBuffer
	Bus    *nrf.UARTE_Type
}

var UART0 = UART{
	Buffer: NewRingBuffer(),
	Bus:    nrf.UARTE0,
}

machine_nrf52840.go

var UART1 = UART{
	Buffer: NewRingBuffer(),
	Bus:    nrf.UARTE1,
}

But now the handleInterrupt() needs to change in the case of UARTE to account for data being in the dma buffer instead of the RXD register. And the interrupt now fires when the dma buffer is full which will be an issue if the transmission was smaller than buffer len.

One idea could be to have the UARTE drivers implement the buffer functions (Used Put Get) on top of the dma buffer?

Any suggestions?

EDIT: Typos

thegrumpylion avatar Nov 21 '19 14:11 thegrumpylion

I think the easiest way forward would be to use UART on the nrf51 and UARTE on all the nrf52 chips. That means that the implementations on both families are (almost?) entirely separate.

Alternatively, you could do UARTE on the nrf52840 and nrf52810 only, but that is probably more complicated.

EDIT: rereading your comment, looks like you were already planning that. Yes that sounds good.

You could probably use a buffer size of 1 and get almost the same behavior as today, but with more overhead than necessary. Doing the buffer operations directly on the DMA buffer would be ideal, but I don't know whether DMA allows for circular buffers?

aykevl avatar Nov 22 '19 00:11 aykevl

I do not think the ARM DMA allows circular buffers, generally they support double buffering via alternating the address used for DMA operations from what I've seen.

deadprogram avatar Nov 23 '19 10:11 deadprogram

Circular buffering does not seem to be possible (looking at the datasheet), but this might be done manually. For example, with a receive buffer of (say) 16 bytes where bytes 4-10 are filled, the DMA could be configured from byte 11 to the end. When the "buffer full" interrupt is received, the DMA could be readjusted to the start of the buffer, byte 0-4 (or a bigger range if more bytes have been read from the buffer in the meantime).

I do think that just using a single-byte buffer would be easier for now. It completely defeats the purpose of DMA, but at least makes UART1 usable.

And the interrupt now fires when the dma buffer is full which will be an issue if the transmission was smaller than buffer len.

If you hadn't seen, there is RXD.AMOUNT and TXD.AMOUNT which can be used to read the currently received/transmitted number of bytes.

aykevl avatar Nov 23 '19 22:11 aykevl

I was wrong about an interrupt being generated only on buffer full condition. From the datasheet:

For each byte received over the RXD line, an RXDRDY event will be generated. This event is likely to occur before the corresponding data has been transferred to Data RAM.

also

Important: If the ENDRX event has not already been generated when the UARTE receiver has come a stop, which implies that all pending content in the RX FIFO has been moved to the RX buffer, the UARTE will generate the ENDRX event explicitly even though the RX buffer is not full. In this scenario the ENDRX event will be generated before the RXTO event is generated.

Given the above, a nice way to implement a circular buffer on top of DMA using ENDRX interrupt will be as follows (showing only relevant functions and RX only.):

// UART on the NRF52.
type UART struct {
	Buffer *RingBuffer
	Bus    *nrf.UARTE_Type
	Irq    uint8  
	rxptr   volatile.Register8
}

// UART0 is the hardware serial port on the NRF.
var UART0 = UART{
	Buffer: NewRingBuffer(),
	Bus:    nrf.UARTE0,
	Irq:    nrf.IRQ_UARTE0
}

// Configure the UART.
func (uart UART) Configure(config UARTConfig) {
	// Default baud rate to 115200.
	if config.BaudRate == 0 {
		config.BaudRate = 115200
	}

	uart.SetBaudRate(config.BaudRate)

	// Set TX and RX pins from board.
	uart.setPins(UART_TX_PIN, UART_RX_PIN)

	// set DMA buffer to the rxbuffer
	uart.Bus.RXD.PTR = &uart.Buffer.rxbuffer
	// set MAXCNT to the size of the buffer (128)
	uart.Bus.RXD.MAXCNT = bufferSize
	// set local rxptr
	uart.rxptr.Set(0)

	uart.Bus.ENABLE.Set(nrf.UARTE_ENABLE_ENABLE_Enabled)
	uart.Bus.TASKS_STARTTX.Set(1)
	uart.Bus.TASKS_STARTRX.Set(1)
	uart.Bus.INTENSET.Set(nrf.UARTE_INTENSET_ENDRX)

	// Enable RX IRQ.
	arm.SetPriority(uart.Irq, 0xc0) // low priority
	arm.EnableIRQ(uart.Irq)
}

func (uart UART) handleInterrupt() {
	if uart.Bus.EVENTS_ENDRX.Get() != 0 {
		amount := uart.Bus.RXD.AMOUNT
		nextptr := amount + uart.rxptr.Get()
		if uart.Buffer.Used() != bufferSize {
			if nextptr < bufferSize {
				uart.Bus.RXD.PTR = &uart.Buffer.rxbuffer[nextptr]
				uart.Bus.RXD.MAXCNT = bufferSize - nextptr
				uart.rxptr.Set(nextptr)
			} else {
				// wrap to the start of rxbuffer
				uart.Bus.RXD.PTR = &uart.Buffer.rxbuffer
				uart.Bus.RXD.MAXCNT = bufferSize - uart.Buffer.Used()
				uart.rxptr.Set(0)
			}
		} else {
			// buffer is full!
		}
		uart.Buffer.head.Set(uart.Buffer.head.Get() + amount)
		uart.Bus.EVENTS_ENDRX.Set(0x0)
		uart.Bus.TASKS_STARTRX.Set(1)
	}
}

I haven't tested this yet on hardware but putting some numbers on paper looks good.

thegrumpylion avatar Nov 26 '19 16:11 thegrumpylion