tinygo
tinygo copied to clipboard
nrf: Add .Bus member to UART type
Tested with examples/echo on pca10056 & nrf52840-mdk.
Have you tested this with UART1/UARTE1? I think receiving won't work currently because it is missing an interrupt handler for UARTE1
.
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.
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
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?
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.
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.
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.