UART0 to UART1 DMA transfer

cancel
Showing results for 
Search instead for 
Did you mean: 

UART0 to UART1 DMA transfer

2,370 Views
Safwat
Contributor III

Hi,

I am working on a KL15 device and one part of the end application requires the MCU to pass half/full duplex data between uart0 and uart1. The speed can go upto 500kbps. So I am trying to setup two DMA channels to do this. I have reused code from FRDM sample code package... specifically from the low power uart demo.

Here is how my dma channels and UARTS are setup:

1. DMA channel 0 is set up to transfer data from UART0_D to UART1_D.Cycle steal is on and triggering is done by UART0 byte receive event (UART0_C4->RDMAE bit is set)

2. DMA channel 1 is set up to transfer data from UART1_D to UART0_D. Cycle steal is on and trigger is done by UART1 byte receive event (UART1_C5-> RDMAS bit and UART1_C2 -> RIE bit are set)

3. Both DMA channels are initially loaded with a BCR value of 0F_FFFFh to get lowest interrupts.

4. Both DMA transfer complete interrupts writes a 1 to the DONE bit and then reloads the BCR with 0F_FFFFh.

I can get data from UART1 to UART0 without any problem or corruption. But I cannot get it the other way around. Data that streamed into UART0 does not get streamed out at UART1. If I configure the DMA0 to have UART0_D address for both source and destination, I get echos in my terminal as expected.

I cannot pinpoint the reason why the DMA isn't working when destination register is UART1. Both of the UART are at same baud rate(currently 9600) and same data format (8N1).

I am not sure if this is a normal use case for transferring data between uarts. I was thinking if its a good idea to use a internal buffer between the two uarts. Please advice if there are better or proper ways to do this.

thanks to whoever reads and helps :smileyhappy:

Labels (1)
Tags (2)
0 Kudos
15 Replies

1,088 Views
nitinharish
Contributor V

Can you folks, please post the final working code ?

It will be helpful to the community and especially me who is trying to do this same thing.

Thanks and Regards

Nitin

0 Kudos

1,088 Views
Safwat
Contributor III

Okay I have noticed something new. To debug the issue, I ripped out everything (The RTOS) from firmware and just kept the initialization listed above and a main function with a while loop. In the while loop I poll the UART1 TDRE flag to see if it ever negates. This way I would know that the DMA IS actually putting data in the UART1_D register.

Almost magically, the DMA starts working with this change. If I remove the checking of TDRE flag, it stops working. So it seems if I read the TDRE flag, the DMA is able to work. The confusing part is that I did not set up the DMA with the TDRE flag of UART1. It is triggered from the RDRF flag of UART0. Trying to find why the UART1 TDRE needs to be read for the DMA to work.

0 Kudos

1,088 Views
martynhunt
NXP Employee
NXP Employee

Safwat, thanks for all the information. So I have a few more questions to help me understand your setup.


Are you attempting this in full or half duplex mode? Do you also have UART0 transmitting, and UART1 receiving during this operation? (Are only UART0 RX and UART1 TX connected?)


Also, how are you checking the TDRE flag (i.e. printf(), out_char())? What other flags are you seeing set at the same time? When you perform this operation in interrupt or polling mode (not DMA) what flags are you seeing when data is written to UART1_D?

Thanks,

Martyn

0 Kudos

1,088 Views
Safwat
Contributor III

The intended end application will have full duplex operation. Meaning it will have the following operations working in parallel

[data in]--> UART0 --> DMA0 --> UART1 --> [data out] ....... This is NOT working properly

[data in]--> UART1 --> DMA1 --> UART0 --> [data out] ....... This is working properly

For debugging the issue I only set up the first direction of data flow utilizing DMA0. As I have mentioned before, In the main while loop I can just do a dummy read of UART1_S1 and the data will stream out of UART1_D. This is what I initially had in the main while loop

while(1)

  {

  if( UART1_S1 & UART_S1_TDRE_MASK)

     uart0_putchar('E'); // Just send out something to the terminal

  }

where uart0_putchar is a polled send function for the uart0. I have later changed the code to:

uint8_t Dummy;

while(1)

  {

    Dummy = UART1_S1;

  }

which also works. I am not sure about the actual value of UART1_S1. I will check and post again. Would be great if you could create a simple setup between UART0 and UART1 and test if there is really any issue.

Best regards. Safwat.

0 Kudos

1,088 Views
martynhunt
NXP Employee
NXP Employee

Safwat,

I have your setup working on my dev. board. (UART0-UART1 w/ polling UART1_S1). I will look into why the polling is necessary, and if there is a way around it.

Thanks,

Martyn

0 Kudos

1,088 Views
martynhunt
NXP Employee
NXP Employee

So it looks like a possible work around is to reverse the DMA request, so that the UART1 TX channel drives the request.

DMAMUX0_CHCFG0 = 5;   //Select UART1 TX

UART1_C4 |= UART_C4_TDMAS_MASK; //Turn on TX DMA request

UART1_C2 |= UART_C2_TIE_MASK; //Enable TX interrupts

This way UART1 is waiting for UART0. However, you would have to handle turning the TX on and off. It will transmit 0x00 if the UART0_D is empty.

0 Kudos

1,088 Views
Safwat
Contributor III

Hi Martyn, I was thinking of using two linked DMA channels for achieving the transfer from UART0 to UART1. After each cycle steal operation (Minor loop) from UART0_D to some RAM buffer, the DMA0 channel will link/trigger another DMA channel to perform a transfer from that buffer to UART1_D. This way, the CPU does not need to monitor anything, given that this method functions correctly.

One question, what should the "source slot number" be for the DMA channel which is linked? I could not find this information in the RM.

0 Kudos

1,088 Views
martynhunt
NXP Employee
NXP Employee

Hi Safwat,

For the "source slot number" you would not have to assign any number. Linking DMA0 to DMAx (where x is any other channel number) would drive the DMA request for every cycle-steal or BCR = 0, depending on how you set up the DMA linking. I had this setup working in CS mode with a BCR of 1 for the second DMA channel, and reading the UART1_S1 in the second DMA channel ISR. I will look into it further to see if there is a pure DMA method of running this operation.

p.s. I have tested UART1 to UART0 on my boards as well, and it also works for me.

Best regards,

Martyn

0 Kudos

1,088 Views
martynhunt
NXP Employee
NXP Employee

Hi Safwat,

I have good news. While it turns out that the S1 register has to be read to transmit on UART1, there is a DMA method to move data from UART0 to UART1. You need two linked DMA channels. The first channel (DMA0) should be enabled as such:

  DMA_SAR0 = DMA0_SOURCE_ADDR;   //Set source address to UART1_S1, to read S1 register after every UART0 DMA request

  DMA_DSR_BCR0 = DMA_DSR_BCR_BCR(TRANSFER_LENGTH);   //Set BCR to know how many bytes to transfer

  DMA_DCR0 &= ~(DMA_DCR_SSIZE_MASK | DMA_DCR_DSIZE_MASK);    //Clear source size and dstination size fields

  /* Set DMA as follows:

         Source size is 8-bit size

         Destination size is 8-bit size

         Cycle steal mode

         External requests are enabled

Interrupt on completion of transfer

         Link DMA0 to DMA1, performing DMA requests every cycle-steal

  */

  DMA_DCR0 |= (DMA_DCR_SSIZE(1)

               | DMA_DCR_DSIZE(1)

               | DMA_DCR_CS_MASK

               | DMA_DCR_ERQ_MASK

               | DMA_DCR_EINT_MASK

               | DMA_DCR_LINKCC(0x2)

               | DMA_DCR_LCH1(0x01));

  DMA_DAR0 = DMA0_DESTINATION];  //Set destination address to a memory location

  DMAMUX0_CHCFG0 = 2;   //Select UART0 RX as channel source

  DMAMUX0_CHCFG0 |= DMAMUX_CHCFG_ENBL_MASK;   //Enable the DMA MUX channel

The second channel should be set up as follows:

DMA_SAR1 = DMA1_SOURCE_ADDR;    //Set source address to UART0_D register

DMA_DSR_BCR1 = DMA_DSR_BCR_BCR(TRANSFER_LENGTH);   //Set BCR to know how many bytes to transfer

DMA_DCR1 &= ~(DMA_DCR_SSIZE_MASK | DMA_DCR_DSIZE_MASK);    //Clear source size and dstination size fields

/* Set DMA as follows:

Source size is 8-bit size

Destination size is 8-bit size

Cycle steal mode

Interrupt on completion of transfer

*/

DMA_DCR1 |= (DMA_DCR_SSIZE(1)

| DMA_DCR_DSIZE(1)

| DMA_DCR_CS_MASK

| DMA_DCR_EINT_MASK);

DMA_DAR1 = DMA1_DESTINATION; //Set destination to UART1_D register

DMAMUX0_CHCFG1 |= DMAMUX_CHCFG_ENBL_MASK;

Your DMA interrupt handlers need only clear the "Done" bit and reset the BCR values.

I hope this works for you. Let me know if you have any questions.

Best regards,

Martyn

1,088 Views
Safwat
Contributor III

Hi Martyn, Sorry I could not reply earlier, was caught up with something.

The solution you have suggested works for me too. Even though its almost a pure DMA operation but it still has the overhead of an interrupt at each byte which is exactly the situation I was trying to avoid in the first place. But your solution still has some benefits as the CPU does not have to poll anything and everything is automated. I have marked it as a helpful answer.

Will you be documenting this in the errata? Or update the manual to indicate the behavior perhaps?

0 Kudos

1,088 Views
martynhunt
NXP Employee
NXP Employee

Safwat,

I'm glad that the solution will work for you. However, the most recent solution does not require an interrupt every byte.

A received character on UART0 trigers a read of UART1_S1. The contents of UART1_S1 is written to a memory location, this DMA transfer triggers a DMA request in the linked channel. The linked channel them reads UART0_D and writes it to UART1_D to transmit data. No CPU involvement using cycle steal.

I have it running with a BCR much greater than 1. The DMA interrupts would only occur after the BCR has been reduced to 0. There should be no CPU involvement until the BCR values have reached 0.

We are still deciding the appropriate way to document this behavior.

Thank you,

Martyn

0 Kudos

1,088 Views
Safwat
Contributor III

Thanks for testing my setup martyn. Good to know that I wasn't doing something wrong.

So if polling is necessary to get this done, it kind of defeats the purpose of using the DMA right? Data transfer is not happening without CPU intervention. Hope you can help me find if this scenario is expected or its a bug of some sort. Does it happen in other parts with similar DMA+UART peripheral?

I have thought of using your workaround method also. But I am trying to find the most efficient way to control turning on and off the TX DMA for UART1. My CPU has to do other things when data transfer is going on and cannot guarantee a constant monitoring loop.

0 Kudos

1,088 Views
martynhunt
NXP Employee
NXP Employee

Hi,

I have some questions, so I can get a better understanding of how you are setting up your DMA channels and UARTs.

Do you mean UART0_C5->RDMAE & UART1_C4->RDMAS? Also, you should not have to enable HW interrupts on your UART1 while using DMA.

How are you configuring your DMA channels? Are you setting DMAMUX0_CHCFG0 to the correct source for transmit and receive?

Best regards,

Martyn

0 Kudos

1,088 Views
Safwat
Contributor III

Just to add, my UARTs work in interrupt mode or polling mode, I can send and receive data through them. Its just that the interrupt overhead is high for handling data streams. All the port clocks are enabled in the MCU init code.

Thanks!

0 Kudos

1,088 Views
Safwat
Contributor III

Hi Martyn, Thanks for the reply.

Yes I do mean UART0_C5->RDMAE & UART1_C4->RDMAS. Mistakenly swapped the register names in the original post.

The DMA_Init routine for UART0 to UART1 (not working data direction) is as below

void UART0to1_DMA_Init(){

// Setting up the NVIC for DMA0 interrupt

  NVIC_ICPR |= (0x01);   // Clear any pending interrupt for DMA0

  NVIC_ISER |= (0x01);  // Enable the interrupt for DMA0 module

  NVIC_IPR0 |= (0x02<<6); // Set the priority to be 2

  /* Enable the clock to DMA MUX and DMA */

  SIM_SCGC6 |= SIM_SCGC6_DMAMUX_MASK;

  SIM_SCGC7 |= SIM_SCGC7_DMA_MASK;

  // Disable the DMA0 channel

  DMAMUX0_CHCFG0 = 0x00;

  // Clear pending errors or the done bit for channel 1

  if (((DMA_DSR_BCR0 & DMA_DSR_BCR_DONE_MASK) == DMA_DSR_BCR_DONE_MASK)

       | ((DMA_DSR_BCR0 & DMA_DSR_BCR_BES_MASK) == DMA_DSR_BCR_BES_MASK)

       | ((DMA_DSR_BCR0 & DMA_DSR_BCR_BED_MASK) == DMA_DSR_BCR_BED_MASK)

       | ((DMA_DSR_BCR0 & DMA_DSR_BCR_CE_MASK) == DMA_DSR_BCR_CE_MASK))

    DMA_DSR_BCR0 |= DMA_DSR_BCR_DONE_MASK;

  // Set Source Address for DMA0

  DMA_SAR0 = 0x4006A007;

  // Set DMA byte counter

  DMA_DSR_BCR0 = DMA_DSR_BCR_BCR(0xFFFFF);

  // Clear the bits config bits

  DMA_DCR0 &= ~(DMA_DCR_SSIZE_MASK| DMA_DCR_DSIZE_MASK);

  // Set the transfer attributes

  DMA_DCR0 |= (DMA_DCR_EINT_MASK

                           | DMA_DCR_SSIZE(1) 

                           | DMA_DCR_DSIZE(1)

                           | DMA_DCR_CS_MASK      

              | DMA_DCR_ERQ_MASK

              );

  // Set Destination Address

  DMA_DAR0 =  0x4006B007;

   

  // Enables the DMA channel and select the DMA Channel Source

  DMAMUX0_CHCFG0 = 0x02;  // Source set to uart0 receive

  DMAMUX0_CHCFG0 |= DMAMUX_CHCFG_ENBL_MASK;

}

The DMA interrupt handler is as below

void DMA0_IRQHandler()

{

  // Clear pending errors or the done bit

   if (((DMA_DSR_BCR0 & DMA_DSR_BCR_DONE_MASK) == DMA_DSR_BCR_DONE_MASK)

        | ((DMA_DSR_BCR0 & DMA_DSR_BCR_BES_MASK) == DMA_DSR_BCR_BES_MASK)

        | ((DMA_DSR_BCR0 & DMA_DSR_BCR_BED_MASK) == DMA_DSR_BCR_BED_MASK)

        | ((DMA_DSR_BCR0 & DMA_DSR_BCR_CE_MASK) == DMA_DSR_BCR_CE_MASK))

   {

   DMA_DSR_BCR0 |= DMA_DSR_BCR_DONE_MASK;

   // Reload the BCR

   DMA_DSR_BCR0 = DMA_DSR_BCR_BCR(0xFFFFF);

   }

}

And finally the UART0 init (skipping the baud rate generation code)

/* Selecting the clock source for the UART0. We are selecting the MCGFLLCLK, 24Mhz */

  SIM_SOPT2 |= (0x1<<16); // Set PLLFLLSEL

  SIM_SOPT2 |= SIM_SOPT2_UART0SRC(0x01); // Set UART Source

  /* Enable the clock to UART0 */

  SIM_SCGC4 |= SIM_SCGC4_UART0_MASK;

  /* Enable the pins of UART output */

  PORTA_PCR1 |= PORT_PCR_MUX(2); // Select the function of PTA1 to be UART0RX

  PORTA_PCR2 |= PORT_PCR_MUX(2); // Select the function of PTA2 to be UART0TX

  UART0_C2 &= ~(UARTLP_C2_RE_MASK | UARTLP_C2_TE_MASK);   // Turn off the receiver and transmitter

/* set the baud rate registers

.

.

.

*/

  UART0_C2 |= (UARTLP_C2_RE_MASK | UARTLP_C2_TE_MASK); /* Enable the transmitter and receiver */

  UART0_C5 |= UARTLP_C5_RDMAE_MASK;   // Turn on DMA request for UART0

and for UART1

/* Enable the clock to UART1 */

  SIM_SCGC4 |= SIM_SCGC4_UART1_MASK;

  /* Enable the pins of UART output */

  PORTE_PCR0 |= PORT_PCR_MUX(3); // Select the function of PTE0 to be UART1TX

  PORTE_PCR1 |= PORT_PCR_MUX(3); // Select the function of PTE1 to be UART1RX

  /* Make sure that the Transmitter and Receiver is disabled */

  UART1_C2 &= ~(UART_C2_RE_MASK | UART_C2_TE_MASK);

  UART1_C1 = 0x0;     // Set default settings

    // Enable the interrupt and DMA request

  UART1_C4 |= UART_C4_RDMAS_MASK; // Turning on receive DMA req

  // Enable the receive interrupt

  UART1_C2 |= UART_C2_RIE_MASK;

  /* Enable receiver and transmitter */

  UART1_C2 |= (UART_C2_TE_MASK | UART_C2_RE_MASK );

as for the requirement to turn on UART1 Receive interrupt, it is mentioned inside the RM for RDMAS:

NOTE: If RIE is cleared, the RDRF DMA and RDRF interrupt request signals are not asserted when the

RDRF flag is set, regardless of the state of RDMAS.

0 If RIE is set and the RDRF flag is set, the RDRF interrupt request signal is asserted to request

interrupt service.

1 If RIE is set and the RDRF flag is set, the RDRF DMA request signal is asserted to request a DMA

transfer.

New Picture (3).bmp

So to pass data from UART1 to UART0 using DMA triggered at UART1 receive (which is working) requires RIE to be set.

0 Kudos