This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

DMA Ping Ponging on F5438A



Team,

My customer was trying to set up a SW-less DMA ping pong to capture a continuous stream of ADC samples. He wants to avoid SW for this in case the CPU is servicing an interrupt, the buffer switches too late, and he misses a sample. Also would like to avoid nested interrupts.

My thought process was that he could use DMA1 to pull an address for DMA0 to write to, since DMA0 can trigger DMA1. I initialized two addresses in RAM for DMA1 to pull from. After DMA0 completes fills a buffer using ADC12, it could trigger DMA1 to get a new destination address. After DMA0 fills that second buffer, DMA1 would trigger again and reload the first buffer address into DMA0.

I wrote this code (attached) to test this on the F5438A. It will "ping" to the second buffer by loading a new buffer address to DMA0, but not "pong" back to the first address afterwards. DMA1SZ (initially set to 2) stays at 1 and will not load the second address back into DMA0.

The part that is weird is that this ping ponging will work if DMAIE is enabled. Even if the IRQ does nothing, it allows the ping ponging to work. Any thoughts on how to get this to work without the interrupt?

I know the DMA had an errata in the non-A version where it would not allow the DMA to write to the DMA, but it looks to not be an issue on the A-version.

Thank you! I hope this description makes sense. Please call me if needed.

Regards,

Nishant

//******************************************************************************
//  MSP430F543xA Demo - DMA0, Single transfer using ADC12 triggered by TimerB
//
//  Description: This software uses TBCCR1 as a sample and convert input into
//  the A0 of ADC12. ADC12IFG is used to trigger a DMA transfer and DMA
//  interrupt triggers when DMA transfer is done. TB1 is set as an output and
//  P1.0 is toggled when DMA ISR is serviced.
//  ACLK = REFO = 32kHz, MCLK = SMCLK = default DCO 1048576Hz
//
//                 MSP430F5438A
//             -----------------
//         /|\|              XIN|-
//          | |                 | 32kHz
//          --|RST          XOUT|-
//            |                 |
//            |             P1.0|-->LED
//            |             P4.1|-->TB1 output
//            |                 |
//            |             P6.0|<--A0
//
//  M. Morales
//  Texas Instruments Inc.
//  June 2009
//  Built with CCE v3.1 Build 3.2.3.6.4 & IAR Embedded Workbench Version: 4.11B
//******************************************************************************

#include "msp430x54xA.h"

//unsigned int DMA_DST = 0x3000;

void main(void)
{
  WDTCTL = WDTPW+WDTHOLD;                   // Hold WDT

  P1OUT &= ~BIT0;                           // P1.0 clear
  P1DIR |= BIT0;                            // P1.0 output
  P4DIR |= BIT1;                            // P4.1 output
  P4SEL |= BIT1;                            // P4.1 select

  //Setup Timer B0
  TBCCR0 = 0xFFFE;
  TBCCR1 = 0x8000;
  TBCCTL1 = OUTMOD_3;                       // CCR1 set/reset mode
  TBCTL = TBSSEL_2+MC_1+TBCLR;              // SMCLK, Up-Mode

  // Setup ADC12
  ADC12CTL0 = ADC12SHT0_15+ADC12MSC+ADC12ON;// Sampling time, MSC, ADC12 on
  ADC12CTL1 = ADC12SHS_3+ADC12CONSEQ_2;     // Use sampling timer; ADC12MEM0
                                            // Sample-and-hold source = CCI0B =
                                            // TBCCR1 output
                                            // Repeated-single-channel
  ADC12MCTL0 = ADC12SREF_0+ADC12INCH_7;     // V+=AVcc V-=AVss, A7 channel
  ADC12CTL0 |= ADC12ENC;

  // Setup DMA0
  __data16_write_addr((unsigned long) 0x4000,(unsigned long) 0x3014);
                                              // ... buffer #2 stored at 0x4000
  __data16_write_addr((unsigned long) 0x4002,(unsigned long) 0x3000);
                                              // ... original buffer #1 stored at 0x4002

  DMACTL0 = DMA0TSEL_24;                    // ADC12IFGx triggered
  DMACTL4 = DMARMWDIS;                      // Read-modify-write disable
  DMA0CTL &= ~DMAIFG;
  DMA0CTL = DMADT_4+DMAEN+DMADSTINCR_3/*+DMAIE*/; // Rpt single tranfer, inc dst, interrupt?
  DMA0SZ = 10;                               // DMA0 size = 10
  __data16_write_addr((unsigned short) &DMA0SA,(unsigned long) &ADC12MEM0);
                                            // ... from ADC12MEM0
  __data16_write_addr((unsigned short) &DMA0DA,(unsigned long) 0x3000);
                                            // ... to destination in RAM, buffer #1

  DMACTL0 |= DMA1TSEL_30;                    // ADC12IFGx triggered
  DMACTL4 = DMARMWDIS;                      // Read-modify-write disable
//  DMA0CTL &= ~DMAIFG;
  DMA1CTL = DMADT_4+DMAEN+DMASRCINCR_3; // Rpt single tranfer, inc src
  DMA1SZ = 2;                               // DMA1 size = 2
  __data16_write_addr((unsigned short) &DMA1SA,(unsigned long) 0x4000);
                                              // ... start at 0x4000 and increment to 0x4002
  __data16_write_addr((unsigned short) &DMA1DA,(unsigned long) &DMA0DA);
                                              // ... to destination DMA0DA



  __bis_SR_register(LPM0_bits + GIE);       // LPM0 w/ interrupts
  __no_operation();                         // used for debugging
}

//------------------------------------------------------------------------------
// DMA Interrupt Service Routine
//------------------------------------------------------------------------------
#pragma vector=DMA_VECTOR
__interrupt void DMA_ISR(void)
{
  switch(__even_in_range(DMAIV,16))
  {
    case 0: break;
    case 2: break;                               // DMA0IFG = DMA Channel 0
    case 4:                           // DMA1IFG = DMA Channel 1
    P1OUT ^= BIT0;                        // Toggle P1.0
    break;
    case 6: break;                          // DMA2IFG = DMA Channel 2
    case 8: break;                          // DMA3IFG = DMA Channel 3
    case 10: break;                         // DMA4IFG = DMA Channel 4
    case 12: break;                         // DMA5IFG = DMA Channel 5
    case 14: break;                         // DMA6IFG = DMA Channel 6
    case 16: break;                         // DMA7IFG = DMA Channel 7
    default: break;
  }
}

  • Nishant Garg said:
    DMA1 would trigger again and reload the first buffer address into DMA0.

    The DMA unit cannot write to its own registers. This is a documented erratum, as you already discovered.
    I'm not sure whether it applies to all MSPs with DMA, or whether it only affects the registers of the currently executing DMA channel or only happens under certain circumstances. Maybe the description of the erratum is a bit too generic here or this issue hasn't been searched thoroughly enough to find the specific circumstances.

    I'm surprised that you got as far as you did.

    However, why not solving the problem differently:

    Use DMA0 to copy from ADC to buffer 1

    Triger DMA1 by DMA0 and jsu tcopy the data from buffer 1 to buffer two. At the end of DMA1, trigger an itnerrutp and the software can work with the data newly arrived in buffer2. It will always be in buffer2 while buffer 1 fills. It even makes the software part a bit simpler as it doesn't have to use two buffers alternating. Th edata to work on is always in buffer2.

**Attention** This is a public forum