Problem with K60 UART Transmit using DMA

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

Problem with K60 UART Transmit using DMA

2,434 Views
RWey
Contributor I

Hello,

For performance reasons, I had to employ DMA for my UART data transmission.  Following several examples I found, I feel confident I configured the DMA correctly, but I noticed that initiation of a transfer would miss the 2nd byte.  This was confirmed by examining the DMA register setup just before initiation and looking at the Tx line using a logic analyzer.  The 2nd byte was simply skipped.

Then, for the heck of it, I enabled the UART Tx FIFO and it suddenly started working correctly.  Now, while this solved the problem for UART0 and UART1, it's still an issue with UARTs 2..5 as these peripherals don't support RxTx FIFOs.

Below are two pieces of relavant code involving the Tx DMA setup.  The first sets the DMA registers that never change.  The second conditions the registers on each data transmission.  As a note, "config" and "cPort" are containing structures I use to hold all necessary variables involving a UART configuration and access, respectively, and aren't worth going into detail here.

   // Configure the Tx DMA registers that never change.
   tcdIdx = TxDmaTcdIndex( config->port );

   DMAMUX0_CHCFG( tcdIdx ) = 0;
   DMAMUX0_CHCFG( tcdIdx ) = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE( tcdIdx + 2 );
   DMA_DADDR( tcdIdx ) = (U32)&cPort->pUart->D; // Destination is the UART D register.
   DMA_DOFF( tcdIdx ) = 0;       // UART D register destination requires no offset after a transfer.
   DMA_SOFF( tcdIdx ) = 1;      // Source Tx buffer increments by 1 after each transfer.
   DMA_NBYTES_MLNO( tcdIdx ) = 1;    // One byte per major request.
   DMA_ATTR( tcdIdx ) = 0;       // 8-bit transfer size.
   DMA_DLAST_SGA( tcdIdx ) = 0;     // No mod to the destination after major transfer.

  // Tell the UART Tx to generate DMA requests.
   cPort->pUart->C5 |= UART_C5_TDMAS_MASK;
   cPort->pUart->C2 |= UART_C2_TIE_MASK;

... ( in my "Transmit" routine )

  memcpy( cPort->txBuf, src, count );

  U32 tcdIdx = TxDmaTcdIndex( port );

  // Configure the necessary DMA registers.
  DMA_SADDR( tcdIdx ) = (U32)cPort->txBuf;
  DMA_CITER_ELINKNO( tcdIdx ) = count;
  DMA_BITER_ELINKNO( tcdIdx ) = count;
  DMA_SLAST( tcdIdx ) = -count;

  // Mark the Tx as busy.  The DMA Tx IRQ will set it to FALSE.
  cPort->txBusy = TRUE;

  // Enable the DMA request signal.
  DMA_SERQ = tcdIdx;

  // Kickstart the DMA transfer.
  DMA_CSR( tcdIdx ) |= DMA_CSR_START_MASK;

The above code works fine so long as the UART Tx FIFO is enabled and set to a depth greater than 1 ( not shown ).  Disable the FIFO and the transfer always skips the 2nd byte.  The not-so-obvious problem, again, is that UARTs 2..5 have no FIFO support.

0 Kudos
6 Replies

1,023 Views
mjbcswitzerland
Specialist V

HI Robert

What I miss is the configuration of DMA_CSR - I would expect the DREQ bit to be set so that the ERQ bit is automatically cleared when the major itteration is completed.

Also I wonder whether the problem may be in the interrupt routine(s) - there are UART and DMA interrupts involved (may be not UART if not configured and no reception at the same time) where something might be getting overwritten (eg. a UART interrupt causing a write to the UART data register to take place).

How do you handle the queue when another 'message' is put to the output when the first buffer transmission hasn't completed yet? Could there be another write taking place that results in a problem?

I have used DMA for Tx and Rx on many KL and K parts, some with RX and TX DMA at the same time in MHz bit speed range on up to 6 parallel UARTs. Some products have been on the market for up to 3 years now without know issues. I never had to enable UART FIFO, so this must be working around an effect in your case but is certainly not needed for a general solution.

If you post a project that can be run on a standard board I may be able to tell you what is taking place (best a complete project - minimum a binary file that runs and a MAP file to locate the relevant assembler code as above).

Complete industrial proven UART/DMA code can be taken from the uTasker project http://www.utasker.com/forum/index.php?topic=1721.0 if you don't manage to solve the issue - it also simulates the Kinetis UART and DMA in Visual Studio which aids in testing and debugging.

Regards

Mark

http://www.utasker.com/kinetis.html

0 Kudos

1,023 Views
RWey
Contributor I

Mark,

Sorry ... I forgot to post three lines of code I have in my static setup.  They are as follows and you can see I am in fact properly setting the CSR register:

   // Install the Tx DMA ISR handler and enable DMA interrupts.

   _int_install_isr( INT_DMA0_DMA16 + tcdIdx, DmaTxIrq, (void *)cPort );

   _cortex_int_init( INT_DMA0_DMA16 + tcdIdx, config->irqPriority, TRUE );

   DMA_CSR( tcdIdx ) = DMA_CSR_INTMAJOR_MASK | DMA_CSR_DREQ_MASK;

With respect to concurrent UART ISR routines, I put a test in the normal UART ISR such that if the interrupting channel had DMA enabled, it would increment an error counter.  I put breakpoints on the increments, and it never halted the debugger.  This tells me that my DMA configured channels are in fact properly routing the interrupts to the DMA ISRs, and there's no unexpected standard UART ISR accessing UART registers for these channels.

Unfortunately, my project is far to target-specific to upload it for anyone to try, but thanks for the offer just the same.

I'll take a look at the uTasker code.  I've heard it mentioned in more than a couple threads so it's probably worth consideration.

Thanks.

0 Kudos

1,023 Views
mjbcswitzerland
Specialist V

Roberrt

It should be able to take a driver out of the context of a larger project so that it can be tested. For example, just sending a string to the UART after each reset should allow it to be tested by anyone (if it makes the error each time).

However my feeling is that when the DMA is enabled there must be two transfers taking place instead of one - this would cause a TX overrun (loss of 2nd byte). If the UART has FIFO enabled it wouldn't be lost.

Aftter these 2 initial transfer sthe reste seems Ok if no further losses take place. I don't think that there is a flag in the UART to show when a Tx overrun has taken place, otherwise it could have been checked....

Regards

Mark

0 Kudos

1,023 Views
RWey
Contributor I

I found the problem.  The standard UART interrupt was in fact clearing the C2.TIE bit.  I had a test inside the Tx handling portion of the interrupt that looked like this:

     if( cPort->port == COM2 )

          while( 0 );

The purpose of this test was to trap unwanted Tx interrupts when using DMA.  I finally put a break point on the "if" statement, I saw that "port" was in fact COM2, then I WATCHED THE DEBUGGER BLOW RIGHT OVER THE while( 0 ) loop ...  go figure.

This is only a problem if you have mixed DMA support on a single UART.  That is for example, Tx only has DMA and the Rx is interrupt driven.  In cases like this, the standard UART ISR is active and you must further qualify the inspection of the UART.S1 TDRE and RDRF flags with the TDMAS and RDMAS flags, respectively.  If the respective xDMAS flag is set, you have to bypass any processing in the ISR for that section.

Had my test code ( like above ) been working, I would have found this hours ago.  I don't know if the compiler optimized it out or what, but frankly it doesn't make me happy considering the time I've wasted.

I'll at least be able to go home tonight on a good note.

Thanks for your consideration.  It's appreciated.

- RW

0 Kudos

1,023 Views
mjbcswitzerland
Specialist V

Hi Robert

I am pretty sure that "while (0) {}" will always be optimsed away ("while (1) {}" of course not).

Yes, one has to be careful about the Rx interrupt handling when the Tx is using DMA since you don't want it messing with the Tx part. I use (UARTn_C5 & UART_C5_TDMAS) to check whether the channel is in DMA tx mode or not and then skip some things accordingly.

Recently I have been using DMA UART transmission combined with very low power stop mode operation where there is a little extra complication beause if the low power mode is entered before the last bits of a transmission have completed the UART freezes with these bits still in its shift register. An additional interrupt IS then needed at the transmitter even when in DMA mode to solve this. It is however interesting because an average current consumption of less that 100uA for a K60 can be achieved when the tx / rx UART activity tends to occur in random bursts (up to some Baud limit - see Using Kinetis Low Power Stop Modes with unrestricted UART operation - a report ) and of course Ethernet is not needed at the same time...

Regards

Mark

0 Kudos

1,023 Views
RWey
Contributor I

OK, I have it working.  There were two general problems:  1) This was a polished interrupt-driven comm driver I created a while ago and then I bolted on DMA after the fact, which more or less demanded brain surgery and things got messy.  2) I was flustered by about 2:00pm yesterday and should have just quit and come back later with a clear head ... I know better.

When I first created the driver, I had considered going the DMA route, and in hindsight I wish I had.  In the end, the new DMA has significantly improved its performance and yesterday's headaches are a small price to pay I guess.  I now have an improved model I can draw upon for future comm requirements.

Thanks again for your help and recommendations.

- RW

0 Kudos