Receiving UART via DMA not working properly

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Receiving UART via DMA not working properly

Jump to solution
5,317 Views
WilliamW
Contributor III

This is my first time trying to receive data on the Kinetis via DMA.  I have successfully done this on the ColdFire processors and have successfully done a transfer on the Kinetis but I don't seem to be able to complete the receive.  I am working on a prototype board using a MK64FX512VLQ12 in case that makes any difference.

I tried creating a new project and using the FSL code to perform the transfer.  But that would get me at most the first byte of the 8-byte packet I was trying to receive.  I would do this by calling UART_ReceiveEDMA, stepping over the line in the debugger and then checking the output packet (as a quick and dirty test).  

Next I tried taking apart the DMA0_IRQHandler command and getting the DMA0_IRQHandler to report when the characters were transferred.  That yielded the same result.

I imagine that there must be something that I need to be setting in the code to get it to work correctly but I have been unable to figure out what that is.  I've include the deconstructed version where I've taken apart the DMA0_IRQHandler to demonstrate.  Any help would be appreciated.

0 Kudos
1 Solution
5,265 Views
WilliamW
Contributor III

I'm pretty sure I found the problem.  I'm now able to get full packets, after several builds.  I realized that I had not been so careful on checking my UART errors but rather was concentrating on the DMA error bits.  I found that I had an Over Run error flag set.  Once I cleared it just before enabling RTS I started getting full packets.  I still have plenty of work to do to get it working the way I need it too but I think that I have gotten past this problem.  Thanks so much for your help.

View solution in original post

0 Kudos
10 Replies
5,311 Views
mjbcswitzerland
Specialist V

Hi

See these videos:
https://www.youtube.com/watch?v=dNZvvouiqis
https://www.youtube.com/watch?v=GaoWE-tMRq4&list=PLWKlVb_MqDQFZAulrUywU30v869JBYi9Q&index=11

Works on all Kinetis parts (wth eDMA as in the K64 or simple DMA as in the KL parts) and supports free-running DMA UART reception (and DMA transmission) or idle line terminated and is fully simulated in Visual Studio for learning, debugging or code reviews.

Code is available for all needs:
- Open source on GITHUB (search uTasker)
- Professionally supported with more functionality via the uTasker web site

Otherwise search the forum since there are many discussion about UART Rx DMA where the SDK examples show the principle but are not usable unless you already know that all reception is of a fixed length.
- if you only receive one byte it will be because the DMA transfer is set up for only one byte, or because there is a mismatch in the Baud rate resulting in a framing error after one byte reception - in which case this error needs be be cleared before the UART will be capable of receiving and thing else.

Regards

Mark
uTasker project developer for Kinetis and i.MX RT]
Contact me by personal message or on the uTasker web site to discuss professional training or product development requirements

 

0 Kudos
5,297 Views
WilliamW
Contributor III

Thanks for your quick response.  I watched the videos but I don't think that I'm ready to try to add another level of complexity to try to resolve my DMA problem.  The device that is transmitting data is already being used by at least two other systems so I don't believe it likely that the transmission is badly formed.  I am also not getting errors during the transmission, it just seems to stop transferring after the first byte.  

 

 

 

0 Kudos
5,295 Views
mjbcswitzerland
Specialist V

Hi

1. Check the number of bytes that are programmed to be received via DMA. If it is only one is would be normal to receive only one byte and a second transfer would need to be programmed.

2. Check the DMA error register after the single byte has been received to see whether it indicates a problem.

Regards

Mark
uTasker project developer for Kinetis and i.MX RT]
Contact me by personal message or on the uTasker web site to discuss professional training or product development requirements

 

0 Kudos
5,292 Views
WilliamW
Contributor III

Here is my code.  Note that the setting the start is because setting the ERQ doesn't seem to get it going.  Also I enable RTS from the UART to get the start of a new 8-byte packet. I keep checking the DMA error register and never find any errors.

memset( g_ReceivePacket, 0, 8 );


DMA0->TCD[0].SADDR = (uint32_t)UART_GetDataRegisterAddress(UART3);
DMA0->TCD[0].DADDR = (uint32_t)(&g_ReceivePacket[0]);
DMA0->TCD[0].ATTR = DMA_ATTR_SSIZE(0) | DMA_ATTR_DSIZE(0); // 0,0=8-bit
DMA0->TCD[0].SOFF = 0;
DMA0->TCD[0].DOFF = 1;
DMA0->TCD[0].NBYTES_MLNO = 1;
DMA0->TCD[0].CITER_ELINKNO = 8;
DMA0->TCD[0].BITER_ELINKNO = 8;
DMA0->TCD[0].DLAST_SGA = -8;

/* Enable auto disable request feature */
DMA0->TCD[0].CSR |= DMA_CSR_DREQ_MASK;
/* Enable major interrupt */
DMA0->TCD[0].CSR |= DMA_CSR_INTMAJOR_MASK;

DMA0->SERQ = DMA_SERQ_SERQ(0);

UART3->C5 |= (uint8_t)UART_C5_RDMAS_MASK;

GPIO_PinWrite( GPIOC, 18, 0); // RTS_

UART3->C2 |= (uint8_t)UART_C2_RIE_MASK;

DMA0->TCD[0].CSR |= DMA_CSR_START_MASK;
DMA0->SERQ = DMA_SERQ_SERQ(0);

0 Kudos
5,286 Views
mjbcswitzerland
Specialist V

Hi

As  reference, here is how the receiver and DMA is set up in the uTasker project's UART driver, after which "buffer_length" of bytes will be received to a buffer starting at "buffer_address" which take any UART channel and DMA channel as input:

 

uart_reg->UART_C5 |= UART_C5_RDMAS;                          // use DMA rather than interrupts for reception
fnConfigDMA_buffer(UART_DMA_RX_CHANNEL[channel],
                  (DMAMUX0_CHCFG_SOURCE_UART0_RX + (2 * channel)),
                  buffer_length,
                  uart_data_reg,
                  (void *)buffer_address,
                  (DMA_DIRECTION_INPUT | DMA_BYTES), 0, 0);
fnDMA_BufferReset(UART_DMA_RX_CHANNEL[channel], DMA_BUFFER_START); // enable DMA operation
uart_reg->UART_C2 |= (UART_C2_RE | UART_C2_RIE);             // enable UART receiver and reception interrupt (or DMA)

 

 

Expanded lower level details are (assuming UART 3 using DMA channel 0 as you do and a buffer of 8 bytes) whereby this free runs (repeatedly fills the buffer without interrupt):

 

UART3_C5 |= UART_C5_RDMAS;                        // use DMA rather than interrupts for reception
DMA0_TCD_SOFF = 0;                                // source not incremented
DMA0_TCD_DOFF = 1;                                // destination increment (buffer)
DMA0_TCD_DLASTSGA = (-(signed long)(8));          // when the buffer has been filled set the destination back to the start of it
DMA0_TCD_SLAST = 0;                               // no source displacement on receive buffer completion
DMA0_TCD_ATTR = (DMA_TCD_ATTR_DSIZE_8 | DMA_TCD_ATTR_SSIZE_8); // transfer sizes bytes
DMA0_TCD_SADDR = (unsigned long)UART3_D_ADD;      // source buffer
DMA0_TCD_CSR = 0;                                 // free-running mode without any interrupt
DMA0_TCD_DADDR = (unsigned long)ptrBufDest;       // destination
DMA0_TCD_NBYTES_ML = 1;                           // each request starts a single transfer of this size (minor byte transfer count)
DMA0_TCD_BITER_ELINK = DMA0_TCD_CITER_ELINK = (signed short)(8); // the number of service requests to be performed each buffer cycle
POWER_UP_ATOMIC(6, DMAMUX0);                      // enable DMA multiplexer 0
DMAMUX0_CHCFG0 = (DMA_MUX_REGISTER)(DMAMUX0_CHCFG_SOURCE_UART3_RX | DMAMUX_CHCFG_ENBL); // connect and enable trigger source to DMA channel
ATOMIC_PERIPHERAL_BIT_REF_SET(DMA_ERQ, 0);        // enable the DMA 0 channel's operation
UART3_C2 |= (UART_C2_RE | UART_C2_RIE);           // enable UART receiver and reception interrupt (or DMA)

 

 

The change to stop after a buffer is full and generate an interrupt is simply

DMA0_TCD_CSR = (DMA_TCD_CSR_INTMAJOR | DMA_TCD_CSR_DREQ); // interrupt when the transmit/receive buffer is full

instead of

DMA0_TCD_CSR = 0;

The same code works also on i.MX RT processors (although the UART part uses LPUART registers instead)

Looking at you code I think there are the following potential problems:
1. You are starting the process with a software trigger whereby it should be started by a UART reception.
2. I don't see you using the DMAMUX to connect your UART Rx reception to the DMA unit to trigger a single transfer on each character reception

 

Regards

Mark
uTasker project developer for Kinetis and i.MX RT]
Contact me by personal message or on the uTasker web site to discuss professional training or product development requirements

 

0 Kudos
5,280 Views
WilliamW
Contributor III

Thanks for the example code.  It was helpful to see another version of the same code that I was working on.  I implemented my code to look like your example code.  This time I'm including the entire function for triggering a receive packet (modified to match your code).  If I call this repeatedly you can see the values that I get from the printf at the end of the function.  Specifically nothing (all 0's) where I'm expecting to get something like 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34.  The last two values of the 8-byte packet are a sequence number and CRC8 which are essentially random until I start getting successive packets and start checking the CRC8.  Thanks in advance for any more help you can provide.

void CoreUART_TriggerReceivePacket( void )
{
memset( g_ReceivePacket, 0, 8 );

UART3->C5 |= (uint8_t)UART_C5_RDMAS_MASK;
DMA0->TCD[0].SOFF = 0;
DMA0->TCD[0].DOFF = 1;
DMA0->TCD[0].DLAST_SGA = -(unsigned long)8;
DMA0->TCD[0].SLAST = 0;
DMA0->TCD[0].ATTR = DMA_ATTR_SSIZE(0) | DMA_ATTR_DSIZE(0); // 0,0=8-bit
DMA0->TCD[0].SADDR = (uint32_t)UART_GetDataRegisterAddress(UART3);
DMA0->TCD[0].CSR = 0;
DMA0->TCD[0].DADDR = (uint32_t)(&g_ReceivePacket[0]);
DMA0->TCD[0].NBYTES_MLNO = 1;
DMA0->TCD[0].CITER_ELINKNO = (unsigned short)8;
DMA0->TCD[0].BITER_ELINKNO = (unsigned short)8;
// POWER_UP_ATOMIC(6, DMAMUX0); // Don't know what this is supposed to do.
DMAMUX->CHCFG[1] = (uint8_t)((DMAMUX->CHCFG[1] & ~DMAMUX_CHCFG_SOURCE_MASK) | DMAMUX_CHCFG_SOURCE(kDmaRequestMux0UART3Tx));
DMAMUX->CHCFG[1] |= DMAMUX_CHCFG_ENBL_MASK;

DMA0->SERQ = DMA_SERQ_SERQ(0);

UART3->C2 |= (uint8_t)UART_C2_RIE_MASK | UART_C2_RE_MASK;


GPIO_PinWrite( GPIOC, 18, 0); // RTS_


Timer_Wait( 1 ); // Wait's 1-2ms. Should produce at least one packet

GPIO_PinWrite( GPIOC, 18, 1); // RTS_

printf( "0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X\n",
g_ReceivePacket[0], g_ReceivePacket[1], g_ReceivePacket[2], g_ReceivePacket[3],
g_ReceivePacket[4], g_ReceivePacket[5], g_ReceivePacket[6], g_ReceivePacket[7] );
}

Values that I get from the printf

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

0 Kudos
5,275 Views
mjbcswitzerland
Specialist V

Hi

1.

POWER_UP_ATOMIC(6, DMAMUX0); // Don't know what this is supposed to do.

is equivalent to

SIM_SCGC6 |= (SIM_SCGC6_DMAMUX0);
to set the DMA mux's clock gate but using a bit banding method as explained in https://www.youtube.com/watch?v=FZnkZ1h_EAQ

2.

The DMA configuration should be called only once and not repeatedly. It will run forever if DMA0->TCD[0].CSR = 0;

and you should see the rx data fill the buffer and then further data overwrite the buffer. This will happen whether the processor is running or not.

Regards

Mark
uTasker project developer for Kinetis and i.MX RT]
Contact me by personal message or on the uTasker web site to discuss professional training or product development requirements

 

0 Kudos
5,271 Views
WilliamW
Contributor III

Thanks for the pointer to the video.  I can see how bit banding can be useful but it doesn't really seem necessary here.  Here is the rest of the setup code for the DMA in case you see something amiss.  I plan on calling the setup code from the function I sent last time only a single time once I get things working even once.  If I use the ERQ to get try to get things started nothings happens.  If I use the START then at least I get one byte.  In my case I plan to process a packet each time I get an interrupt and either restart it or leave it free running.  But as I've said, I can't even get a single full packet.  Well, I did get one once and it seemed to be working but the next time I restarted the program I didn't work and hasn't worked since.  I imaging that there must be something small that I am setting or not setting that must be causing the problem but I just can't figure out what it is.  Do you have anything else that you could suggest that I could check?  Your continued help is much appreciated.

CLOCK_EnableClock(kCLOCK_Dmamux0);

CLOCK_EnableClock(kCLOCK_Dma0);

/* clear all the enabled request, status to make sure EDMA status is in normal condition */
DMA0->ERQ = 0U;
DMA0->INT = 0xFFFFFFFFU;
DMA0->ERR = 0xFFFFFFFFU;
DMA0->CR = DMA_CR_HOE_MASK;

UART_Init(UART3, &CoreUART_UART_config, CLOCK_GetFreq(UART3_CLK_SRC));

DMAMUX->CHCFG[0] = (uint8_t)((DMAMUX->CHCFG[0] & ~DMAMUX_CHCFG_SOURCE_MASK) | DMAMUX_CHCFG_SOURCE(kDmaRequestMux0UART3Rx));

DMAMUX->CHCFG[0] |= DMAMUX_CHCFG_ENBL_MASK;

 

0 Kudos
5,225 Views
mjbcswitzerland
Specialist V

Hi

>>I can see how bit banding can be useful but it doesn't really seem necessary here.

That is correct, it won't change the overall behavior but does give slightly faster and smaller code.

In the same way you use
DMA0->SERQ = DMA_SERQ_SERQ(0);
instead of
DMA0->ERQ |= DMA_ERQ_ERQ(0);
(since it is slightly faster and smaller code) it doesn't change overall behavior.

However if one has macros available that increase efficiency and reduce code size [ATOMIC_PERIPHERAL_BIT_REF_SET(DMA_ERQ, 0); does the same but can be used with any register via bit-banding], like POWER_UP_ATOMIC(6, DMAMUX0);,
one tends to use them pretty much all the time and overall has faster, smaller code.

Regards

Mark

 

0 Kudos
5,266 Views
WilliamW
Contributor III

I'm pretty sure I found the problem.  I'm now able to get full packets, after several builds.  I realized that I had not been so careful on checking my UART errors but rather was concentrating on the DMA error bits.  I found that I had an Over Run error flag set.  Once I cleared it just before enabling RTS I started getting full packets.  I still have plenty of work to do to get it working the way I need it too but I think that I have gotten past this problem.  Thanks so much for your help.

0 Kudos