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.

MSP430F5438 using ADC12 + TimerB0 Trigger + DMA

Other Parts Discussed in Thread: MSP430F5438

Hi, 

I'm developing a small program to test the ADC12 + DMA + TimerB Trigger on an MSP430F5438 (Olimex MSP430-H5438).

I wrote a piece of code to configure ADC12 to sample 4 channels (in Sequence Of Channels mode) using TimerB0 as trigger.

Everything works fine without DMA, I see the ADC interrupt fired up when the sequence of channels has finished, but when I add the DMA code, the DMA interrupt is never fired and I can't understand why.

The code I'm actually using is the following:

#ifdef __MSP430F5438__
#include <msp430f5438.h>
#endif
#include "dcolib.h"

unsigned int adcReadings[64];
unsigned int adcReadingsCounter;
unsigned int tmp;

unsigned int ADCMemBuf[4];

#define USE_DMA

void main(void)
{
    // Stop watchdog timer to prevent time out reset
    WDTCTL = WDTPW + WDTHOLD;

    // Port Initialization (Olimex MSP430-H5438)
    PASEL = PBSEL = PCSEL = PDSEL = PESEL = PFSEL = 0x0000; // All GPIO
    PADIR = PBDIR = PCDIR = PDDIR = PEDIR = PFDIR = 0xFFFF; // All OUTPUT
    PAOUT = PBOUT = PCOUT = PDOUT = PEOUT = PFOUT = 0x0000; // All 0

    // DCO Calibration (MCLK = 16MHz, SMCLK = 8MHz)
    if ( calibrate_dco() != DCO_NO_ERROR )
    WDTCTL = 0;

    adcReadingsCounter = 0;

    P6SEL |= (BIT2 | BIT3 | BIT4 | BIT5); // ADC Peripheral Function
    P6DIR &= ~(BIT2 | BIT3 | BIT4 | BIT5); // ADC Input Direction

    P4SEL |= BIT1; // TimerB0.1OUT Peripheral Function
    P4DIR |= BIT1; // TimerB0.1OUT Output Direction

    ADC12CTL0 = ADC12MSC | ADC12SHT0_2 | ADC12ON | ADC12REFON | ADC12REF2_5V;
    ADC12CTL1 = ADC12SHP | ADC12SHS_3 | ADC12SSEL_3 | ADC12DIV_1 | ADC12CONSEQ_1;
    ADC12CTL2 = ADC12RES_2 | ADC12TCOFF;

    ADC12MCTL0 = ADC12INCH_2 | ADC12SREF_1;
    ADC12MCTL1 = ADC12INCH_3 | ADC12SREF_1;
    ADC12MCTL2 = ADC12INCH_4 | ADC12SREF_1;
    ADC12MCTL3 = ADC12INCH_5 | ADC12SREF_1 | ADC12EOS;

#ifdef USE_DMA
    // Configure DMA0 (ADC12IFG trigger)
    DMACTL0 = DMA0TSEL_24; // ADC12IFG trigger
    DMACTL4 = DMARMWDIS; // Read-modify-write disable
    DMA0CTL &= ~DMAIFG;
    __data16_write_addr((unsigned short)&DMA0SA, (unsigned long)&ADC12MEM0); // Source single address
    __data16_write_addr((unsigned short)&DMA0DA, (unsigned long)&ADCMemBuf); // Destination array address
    DMA0SZ = 4; // 4 ADC conversions
    DMA0CTL = DMADT_4 | DMADSTINCR_3 | DMASRCINCR_3 | DMAEN | DMAIE; // Rpt, inc dest, inc src, enable int after seq of convs
#else
    ADC12IE = BIT3;
#endif

    // Wait Refon
    msDelay(20);

    // Enable conversion
    ADC12CTL0 |= ADC12ENC;

    // TB0CCR0, TB0CCR1, OUTMOD_4 to get a rising edge every 1ms on TB0.1
    TB0CCR0 = (SMCLK_TICKS_PER_MS >> 1); // Total Sampling Time
    TB0CCR1 = (SMCLK_TICKS_PER_MS >> 2); // Sampling Period (TB0.1)
    TB0CCTL1 = OUTMOD_4; // Trigger ADC conversion mode
    TB0CTL = TBSSEL_2 | MC_1 | TBCLR; // SMCLK, Up mode

    // Enable GIE
    __bis_SR_register(GIE);

    // Do nothing
    while(1);
}


#ifdef USE_DMA
#pragma vector=DMA_VECTOR
__interrupt void DMA0_ISR (void)
{
    switch(__even_in_range(DMAIV,16))
    {
        case 0: break; // No interrupt
        case 2: // DMA0IFG
            // ADC10 conversions completed

            // Need to set again the ADC12ENC
            ADC12CTL0 |= ADC12ENC;
            // Toggle port (to debug on oscilloscope)
            P8OUT ^= BIT4;
        break;
        case 4: break; // DMA1IFG
        case 6: break; // DMA2IFG
        case 8: break; // Reserved
        case 10: break; // Reserved
        case 12: break; // Reserved
        case 14: break; // Reserved
        case 16: break; // Reserved
        default: break;
    }
}
#else
#pragma vector=ADC12_VECTOR
__interrupt void ADC12_ISR (void)
{
    switch(__even_in_range(ADC12IV,12))
    {
        case 0: break; // Vector 0: No interrupt
        case 2: break; // Vector 2: ADC overflow
        case 4: break; // Vector 4: ADC timing overflow
        case 6: break; // Vector 6: ADC12IFG0
        case 8: break; // Vector 8: ADC12IFG1
        case 10: break; // Vector 10: ADC12IFG2
        case 12: // Vector 12: ADC12IFG3
            tmp = ADC12MEM0;
            tmp = ADC12MEM1;
            tmp = ADC12MEM2;
            tmp = ADC12MEM3;
            adcReadingsCounter++;
            // Need to set again the ADC12ENC
            ADC12CTL0 |= ADC12ENC;
            if(adcReadingsCounter == 20)
            {
                adcReadingsCounter = 0;
                // Toggle port (to debug on oscilloscope)
                P8OUT ^= BIT4;
            }
        break;
        case 14: break; // Vector 14: ADC12IFG4
        case 16: break; // Vector 16: ADC12IFG5
        case 18: break; // Vector 18: ADC12IFG6
        case 20: break; // Vector 20: ADC12IFG7
        case 22: break; // Vector 22: ADC12IFG8
        case 24: break; // Vector 24: ADC12IFG9
        case 26: break; // Vector 26: ADC12IFG10
        case 28: break; // Vector 28: ADC12IFG11
        case 30: break; // Vector 30: ADC12IFG12
        case 32: break; // Vector 32: ADC12IFG13
        case 34: break; // Vector 34: ADC12IFG14
        default: break;
    }
}
#endif

Thanks for any help and suggestions.

Regards,
Samuele. 

  • Solved this issue myself. 

    I was using the wrong DMADT value, because when ADC is in CONSEQ_1 the DMA gets triggered on the end of the sequence conversion so I have to use DMADT_1 or DMADT_5. 

  • Samuele Forconi said:
    Solved this issue myself. 

    Congratulations :) (no irony - solving a riddle by yourself is the most satisfying way to solve it)

    Samuele Forconi said:
    I was using the wrong DMADT value, because when ADC is in CONSEQ_1 the DMA gets triggered on the end of the sequence conversion so I have to use DMADT_1 or DMADT_5. 

    Indeed. However, the DMA should have fired once per completed ADC sequence, moving the first word after the first sequence, the second after the second sequence etc.
    With DMADT_5, i twil indeed move all results in a burst when a sequence completes. And again when the sequence completes next time.

  • Yes, with DMADT_5 I'll move all the data at the end of the sequence conversion.

    In my application I'm sampling a periodic signal at a certain frequency and my goal would be to let the ADC/DMA sample and store the data in memory until the end of period is reached (or a certain number of samples have been taken). I'm still trying to understand which should be the most efficient solution to perform this task. If you have just faced this problem before, your suggestions are well appreciated. 

    Regards,

    Samuele.

  • In my own projects, I didn't have very tight constraints for the ADC timing. I let it run continuously and pick the last conversion result when I need it. i don't care whether it is already a few 100 µs old when I read it in my 1ms interrupt. So I never needed a DMA or even an ADC interrupt.

    What solution works best for you depends on your requirements. If you have a high CPU clock and a low samplign frequency, you may jsu ttrigger an ISR when the ADC is finished with the sequence, Or trigger an interrupt after each conversion (which wastes some CPU cycles but distributes the CPU load evenly). DMA is only required if after the sequence is complete, the next sequence may start (and overwrite the old results) before your ISR could finish the calculations. And it is convenient if you want to fill a buffer with subsequent results and keep the CPU in sleep mode until the buffer is filled.

**Attention** This is a public forum