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.

MSP430FR5994: DMA not triggering with ADC

Part Number: MSP430FR5994

Hi,

I've been having issues with DMA and the ADC with this uC. Specifically with the DMA triggers. I've integrated background sampling of analog channels to an existing project using a timer to trigger the ADC and DMA to move the samples. I finally got it working, but encountered many issues, so I decided to try a minimal example and found similar issues. Maybe I'm not understanding the datasheet, but this fairly simple example fails to trigger:

#include <io430.h>

void ConfigADC( void )
{
	ADC12CTL0 &= ~ADC12ENC;
	ADC12CTL0 &= ~ADC12ON;
	ADC12CTL0 = ADC12SHT0_2 | ADC12MSC_0;
	ADC12CTL1 |=	ADC12SHS_0			// Sample-and-hold source = SC bit
					| ADC12SHP			// SAMPCON signal is sourced from the sampling timer.
	                                | !ADC12ISSH		        // sample-input signal not inverted
	                                | ADC12DIV_0		        // ADC12 clock divider /1
	                                | ADC12SSEL_2		// ADC12 clock source = MCLK
	                                | ADC12CONSEQ_0;	// Sequence-of-Channels Mode
	ADC12CTL2 = ADC12RES__12BIT;
	ADC12CTL3 = ADC12CSTARTADD_0;
	ADC12MCTL0 = ADC12VRSEL_0 | ADC12INCH_0;
	ADC12IER0 = ADC12IER1 = ADC12IER2 = 0;
	ADC12IFGR0 = ADC12IFGR1 = ADC12IFGR2 = 0;
	ADC12CTL0 |= ADC12ON;
}

unsigned int Convert( void )
{
	ADC12IFGR0 = 0;
	ADC12CTL0 |= ADC12ON;
	ADC12CTL0 &= ~ADC12ENC;
	ADC12CTL0 |= ADC12ENC | ADC12SC;
	while ( !ADC12IFGR0 ) {}
	unsigned int adcResult = ADC12MEM0;
	ADC12CTL0 &= ~ADC12ENC;
	ADC12CTL0 &= ~ADC12ON;
	return adcResult;
}

unsigned int TestVar1, TestVar2;

void ConfigDMA()
{
	DMA0CTL &= ~DMAEN__ENABLE;
	DMACTL0 = DMA0TSEL__ADC12IFG;
	DMA0CTL =	DMADT_4			 |
	            DMADSTINCR_0	 |
	            DMASRCINCR_0	 |
	            DMADSTBYTE__WORD |
	            DMASRCBYTE__WORD |
	            DMALEVEL__EDGE   |
	            DMAIE__ENABLE;
	__data16_write_addr((unsigned short)&DMA0SA, (unsigned long) &TestVar1);
	__data16_write_addr((unsigned short)&DMA0DA, (unsigned long) &TestVar2);
	DMA0SZ = 1;
	DMA0CTL |= DMAEN__ENABLE;
}

#pragma vector = DMA_VECTOR
__interrupt void DMA_ISR( void )
{
	static int int_count = 0;
	switch ( __even_in_range( DMAIV, 0x10 ) ) {
	case DMAIV__DMA0IFG:
			int_count++;
		break;
	}
}

void main( void )
{
	WDTCTL = (WDTPW | WDTHOLD);
	__enable_interrupt();

	ConfigADC();
	Convert(); //this breaks the DMA trigger 

	ConfigDMA();

	Convert();
	Convert();
	Convert();
	Convert();
	__no_operation();
}

I've found that doing an ADC conversion before setting up the DMA breaks it, meaning that it'll never trigger with the ADC12IFGR0. Even if I reconfigure the DMA and the ADC all over again (clearing every register before). When I run the above example with a breakpoint at the __no_operation(), I see int_count == 0 , which means the interrupt never occurred, also the DMA IFG is not set, which means that DMA never triggered. The ADC works just fines because I get to the __no_operation() at the end (if the ADC flag had not been raised, the uC would have been stuck in the while loop).

Commenting the line where Convert() is called before ConfigDMA() works as expected,  int_count == 4 at the end of execution.

Is there an issue with the code?

Thanks in advance,

Federico

  • Hello Federico,

    Thanks for your posting.

    In your Convert() function you have the code unsigned int adcResult = ADC12MEM0; which access the ADC12MEM0 memory location. This will reset the ADC12IFGR0 flag.

    Few changes I will recommend to try:

    1.  __data16_write_addr((unsigned short)&DMA0SA, (unsigned long) &TestVar1); The source address should be your ADC memory location ADC12MEM0.

    2. unsigned int adcResult = ADC12MEM0; The adcResult should equal to TestVar2 which is your destination address of the DMA.

    3. Configure the DMA before doing the ADC conversion.

    Thanks,

    Yiding

  • If I replace the final line of ConfigDMA (the DMAEN setting) with:

    > DMA0CTL |= DMALEVEL__LEVEL; // Level sensitive for a moment
    > DMA0CTL |= DMAEN__ENABLE;
    > DMA0CTL &= ~DMALEVEL__LEVEL; // OK, that moment's over.

    i.e. set LEVEL=1 just before and =0 just after, I get an immediate DMA transfer/ISR -- even though the level (IFG) is 0 -- and then the rest of the program proceeds normally, using LEVEL=0 (EDGE). (int_count==1+4)

    From the outside, it seems as though the first ADC IFG sent the DMA request and the DMA unit latched the "it's high" indicator. But since the ADC DMA request wasn't enabled, the DMA unit didn't notice the IFG level going low again. Since an(other) EDGE can't happen until the level goes low [ref sec 11.2.3], the DMA is (as described) stuck. Setting LEVEL=1, however, matches with the DMA's stored state, so it runs then stops after the first transfer since it then observes that the level is no longer high [ref Fig 11-3]. At that point, it is un-stuck.

    If this is what is happening, I suppose the designers didn't intend it. I also don't see an obvious workaround except for the (rather clumsy) one above. The cost of that workaround is an extraneous DMA transfer, which may or may not be tolerable. At least it doesn't do any harm if there is no state stuck in the DMA unit.

    [Disclaimer: SLAU367O sec 11.2.3.2 does say "For proper operation, level-sensitive triggers can only be used when external trigger DMAE0 is selected as the trigger.", so strictly speaking my experiment is outside the spec. In context, though, it does seem interesting.]
  • Hi Yiding, thanks for taking the time to answer.

    I'm afraid I've already tried what you suggest. Actually, that's were I started at, when I was trying to use a timer, the ADC and DMA to periodically sample without CPU intervention.The thing is that what you propose in "3. Configure the DMA before doing the ADC conversion." is not a solution, because I need to, every now and then, stop the background sampling and use de ADC to do a few manual conversions on other channel. After these conversions, when I reconfigure the DMA to resume background sampling, it no longer works.

  • Hi Bruce, thanks for your answer.

    That's an interesting result you got there. I suspected this was the case, but couldn't figure out a way to test it. I found a similar workaround were I would do:

    DMACTL0 |= DMAEN_ENABLE;
    DMACTL0 = 0;
    DMACTL0 = DMA0TSEL__ADC12IFG;

    This is also out of spec, because the trigger setting should not be modified with DMA enabled, as it can (and it does) generate random triggers. So the cost of this workaround is the same as yours, a spurious DMA transfer. My interpretation was that this spurious transfer would reset the trigger circuit, effectively becoming un-stuck.

    Is there a way to elevate this issue to Texas Instruments support? Maybe they could verify if this is a problem with the DMA controller and include it in the errata of the uC.

  • The things that I still wonder (I may/may not get back to this soon):

    1) Why is the DMA latching (or at least seems to be) Any state from a non-trigger channel? If (as it seems) that's what's happening, that's the root of it all. Maybe there's a good reason, but I can't think of one.
    2) Does this happen with other DMA "clients"? I've certainly written programs (including the FR5 series) which use SPI and flip between DMA and PIO. This is common e.g. when working with an SD card.

    I have no particular "in" with the TI people. I think they watch this forum, though.
  • Hello Federico,

    I am aware of the behavior that Bruce mentioned here now and I will look into it for more details.

    But I think your situation is different from the behavior Bruce described.

    First, like you said you want to use DMA and every now and than to do manual ADC conversion, to do this I believe you will need two different ADC conversion functions. Please let me explain why:

    If we use DMA, after the 1st ADC conversion complete the ADCIFG will be set and immediately DMA will be triggered and access the ADCMEM and the ADCIFG will be cleared. So you cannot poll the ADCIFG to start your next ADC conversion because the ADCIFG was cleared by DMA. If you just check the ADCIFG your 2nd conversion will never start.

    Here is the code I modified for your DMA ADC conversion:

    unsigned int Convert( void )
    {
        isADCDone = false;
        ADC12IFGR0 = 0;
        ADC12CTL0 |= ADC12ON;
        ADC12CTL0 &= ~ADC12ENC;
        ADC12CTL0 |= ADC12ENC | ADC12SC;
    
        __disable_interrupt();
        while(isADCDone == false)
        {
            __bis_SR_register(LPM0_bits | GIE);
        }
    
        unsigned int adcResult = TestVar2;
        ADC12CTL0 &= ~ADC12ENC;
        ADC12CTL0 &= ~ADC12ON;
        return adcResult;
    }
    
    void ConfigDMA()
    {
        DMA0CTL &= ~DMAEN__ENABLE;
        DMACTL0 = DMA0TSEL__ADC12IFG;
        DMA0CTL =   DMADT_4          |
                    DMADSTINCR_0     |
                    DMASRCINCR_0     |
                    DMADSTBYTE__WORD |
                    DMASRCBYTE__WORD |
                    DMALEVEL__EDGE   |
                    DMAIE__ENABLE;
        __data16_write_addr((unsigned short)&DMA0SA, (unsigned long) &ADC12MEM0);
        __data16_write_addr((unsigned short)&DMA0DA, (unsigned long) &TestVar2);
        DMA0SZ = 1;
        DMA0CTL |= DMAEN__ENABLE;
    }
    
    #pragma vector = DMA_VECTOR
    __interrupt void DMA_ISR( void )
    {
        static int int_count = 0;
        switch ( __even_in_range( DMAIV, 0x10 ) ) {
        case DMAIV__DMA0IFG:
                int_count++;
                isADCDone = true;
                LPM0_EXIT;
            break;
        }
    }

    Second, if you just want to do manual ADC conversion, in your unsigned int Convert( void ) function can stay the same but you will need to disable the DMA and change isADCDone flag in your ADC ISR because your are not use DMA.

    Please let me know if this make sense.

    Thanks,

    Yiding

  • Hello Bruce,

    I am aware of the behavior that you mentioned here now and I will look into it for more details.

    Thanks,
    Yiding

**Attention** This is a public forum