Bruce
This is how to do free-running DMA on UART 0 on the KL26.
I show the code that I use and then the code under its bonnet which is performed (just showing the DMA part).
- UART errors need to be cleared otherwise further reception is blocked - this means that you should handle error interrupts and clear the cause so that it can continue. Actually I never experienced it erroring when using Rx DMA but it is a valid point and worth testing with intentionally bad reception.
- DMA overflow doesn't need to be detected and BCR does not roll-over - it just stops, therefore BCR is not used.
1a. Configuration of UART in free-running DMA mode (compatible with UART or LPUART and processors with eDMA or the simpler DMA controller in the KL26):
TTYTABLE InterfaceParameters;
InterfaceParameters.Channel = 0;
InterfaceParameters.ucSpeed = SERIAL_BAUD_115200;
InterfaceParameters.Config = (CHAR_8 | NO_PARITY | ONE_STOP | CHAR_MODE);
InterfaceParameters.Rx_tx_sizes.RxQueueSize = 1024;
InterfaceParameters.Rx_tx_sizes.TxQueueSize = 512;
InterfaceParameters.ucDMAConfig = (UART_TX_DMA | UART_RX_DMA | UART_RX_MODULO);
InterfaceParameters.Config |= UART_IDLE_LINE_INTERRUPT;
fnOpen(TYPE_TTY, FOR_I_O, &InterfaceParameters)) != NO_ID_ALLOCATED) {
fnDriver(newSerialID, (TX_ON | RX_ON), 0);
2a. Each time the amount of received received data is to be checked (polled) or after the system has been woken by an idle line (event driven polled) the following code is used:
unsigned char ucInputBuffer[RX_BUFFER_SIZE];
if ((Length = fnRead(SerialPortID, ucInputMessage, RX_BUFFER_SIZE)) != 0) {
}
This is essentially what is shown in the FreeRTOS configuration (with the UART configuration assigned to a different task).
==========================================================================================
And this is now the lower layer details of the API that could be implemented with lower level code or HAL:
In the following code "uart_reg" is a pointer to the corresponding UART register block. And "ptrDMA" is a pointer to the DMA controller register block (channel 2 used in this case)
1b. Your circular buffer must be a modulo size and it must be modulo aligned - eg. above it needs to be located at an address in SRAM that is divisible by 1024.
uTasker queue driver uses
ptTTYQue->tty_queue.QUEbuffer = (unsigned char *)TTY_DRV_MALLO_ALIGN(queue_size, queue_size)
to get aligned memory from the heap but you can also do something like:
#define RX_BUFFER_SIZE 1024
unsigned char buffer[RX_BUFFER_SIZE + (RX_BUFFER_SIZE -1 )];
unsigned char *ptrBuffer = buffer;
while ((((unsigned long)ptrBuffer%RX_BUFFER_SIZE) != 0) {
ptrBuffer++;
}
to get a pointer to an aligned area (or use the linker script or other non-portable methods to reserve aligned space for use).
2b. To enable idle line interrupt
uart_reg->UART_C1 |= UART_C1_ILT; // idle line starts after the stop bit
uart_reg->UART_C2 |= UART_C2_ILIE; // enable idle line interrupt
3b. To enable UART Rx DMA
uart_reg->UART_C5 |= UART_C5_RDMAS; // use DMA rather than interrupts for reception
4b. Prepare Rx DMA (using DMA channel 2 in this case)
ptrDMA->DMA_DSR_BCR = DMA_DSR_BCR_DONE; // clear the DONE flag and clear errors etc.
ptrDMA->DMA_DCR = (DMA_DCR_DSIZE_8 | DMA_DCR_SSIZE_8 | DMA_DCR_DMOD_OFF | DMA_DCR_SMOD_OFF); // transfer size bytes
ptrDMA->DMA_DCR |= (DMA_DCR_DINC); // transfers with increment only on destination
ptrDMA->DMA_SAR = (unsigned long)(void *)&(uart_reg->UART_D); // set source buffer
ptrDMA->DMA_DAR = (unsigned long)ptrBuffer; // set destination buffer
ulDMA_progress[channel] = (unsigned long)ptrBuffer; // initial DMA pointer
ptrDMA->DMA_DSR_BCR = 0xffff0; // set maximum transfer count (this needs to be regularly retriggered for infinite operation)
ptrDMA->DMA_DCR |= DMA_DCR_DMOD_1K; // the modulo setting (manually set to match RX_BUFFER_SIZE in this case)
POWER_UP_ATOMIC(6, DMAMUX0); // enable DMA multiplexer 0
*(unsigned char *)(DMAMUX0_BLOCK + 2) = (unsigned char)(DMA_UART0_RX_CHANNEL | DMAMUX_CHCFG_ENBL); // connect UART 0 trigger to DMA channel 2
ptrDMA->DMA_DCR |= (DMA_DCR_CS | DMA_DCR_EADREQ | DMA_DCR_ERQ); // enable peripheral request - single cycle for each request (asynchronous requests enabled in stop mode) and start
5b. Start UART Rx operation
uart_reg->UART_C2 |= (UART_C2_RE | UART_C2_RIE); // enable UART receiver and reception interrupt (or DMA)
6b. Each time the amount of received received data is to be checked (polled) or after the system has been woken by an idle line (event driven polled) the following code is used (in a multi-tasking/interrupt driven environment it may be necessary to protect this from task switching if something else can access the same circular buffer at the same time
unsigned long ulDMA_rx = ptrDMA->DMA_DAR; // snap-shot of DMA reception progress
if (ulDMA_progress[channel] <= ulDMA_rx) {
tty_queue->chars += (QUEUE_TRANSFER)(ulDMA_rx - ulDMA_progress[channel]); // add the extra number of characters received by DMA since last check
}
else {
tty_queue->chars += (QUEUE_TRANSFER)(RxModulo[channel] - (ulDMA_progress[channel] - ulDMA_rx)); // add the extra number of characters received by DMA since last check
}
ulDMA_progress[channel] = ulDMA_rx; // remember this check state for future comparisons
ptrDMA->DMA_DSR_BCR |= (0xffff0); // re-trigger maximum DMA transfer at each check
This means that it is the DMA pointer that is used to monitor reception progress - the DMA counter cannot be used with this DMA controller (although is the one used when working with eDMA based types).
There is one variable maintained to remember the pointer value the last time that it was checked [ulDMA_progress[channel]] and note that the DMA reception length is always (re)set to its max value again each time the polling/checking is performed due to the fact that DMA controller doesn't have a "real" free-runnning mode (as the eDMA does) and it will otherwise count down to zero and stop at some point.
Each time 'additional' data is detected the circular buffer's character counter is incremented by the amount.
This is then decremented when the user actually "consumes" the data.
What the above code doesn't show is the circular buffer operation itself (apart from tty_queue->chars and ptTTYQue->tty_queue.QUEbuffer) since the uTasker project uses a common queue driver to do this that is shared with SW, OS, UART, USB, Ethernet, I2C etc. for efficiency and reliability (proven in products since 2004) but any circular buffer code can be used.
It should be easy to map the shown values int other HAL library register configurations and - as long as this thread doesn't get lost, as the first on explaining it seems to have, can be used as reference for anyone who is required to develop the functionality. My preference is of course that professionals use the uTasker project directly and save development time redoing the work because it then has a proven base (the mode is used in many industrial Kinetis-based products since 2012) and is operational on any parts without having to redevelop each time the target is moved: As well as IDE and part independence it allows simulation of the complete operation for simplified maintenance and code reviews. As demonstrated by examples I posted it does also give smaller project code size with greater functionality, even if its breadth of support may mislead into initially thinking the opposite.
Regards
Mark