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.

MSP430F5529: Convert and store 1k samples from ADC

Part Number: MSP430F5529


I am trying to capture 1k samples from an analog accelerometer using ADC and store them in an array for further processing and sending through mqtt afterwards. I am a beginner in this and tried a lot of ways including using timer but wasn't successful. So I finally came up with the following code that works, but I would like to know if this is a good approach.

The code is as follows: I have setup the ADC using ACLK clock

static void setup_ADC(void){
    ADC12CTL0 = ADC12SHT02 + ADC12ON;         // Sampling time, ADC12 on
    ADC12CTL1 = ADC12SHP + ADC12SSEL_1;       // Use sampling timer; Pulse Sample Mode; ADC12SHT02 decides the interval of the sampling timer
                                              // ACLK (32.768 kHz) selected as the clock source
    ADC12IE = 0x01;                           // Enable interrupt
    ADC12CTL0 |= ADC12ENC;
    P6SEL |= 0x01;                            // P6.0 ADC option select
    }

The infinite while loop in main has an mqtt function that should run after 1000 samples have been stored.

while(1){
        /* Signal processing and communication only to be done after collecting 1K samples */
        if (SamplesDone){
            /* Send the values through mqtt */
            mqtt();
            samples_count = 0;
        }

        ADC12CTL0 |= ADC12SC;                   // Start sampling/conversion
        __bis_SR_register(LPM0_bits + GIE);     // LPM0, ADC12_ISR will force exit
        __no_operation();                       // For debugger
    }

In the ISR, I simply move the data into the array that can store upto 1000 elements

#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = ADC12_VECTOR
__interrupt void ADC12_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(ADC12_VECTOR))) ADC12_ISR (void)
#else
#error Compiler not supported!
#endif
{
    switch(__even_in_range(ADC12IV,34))
    {
    case  0: break;                           // Vector  0:  No interrupt
    case  2: break;                           // Vector  2:  ADC overflow
    case  4: break;                           // Vector  4:  ADC timing overflow
    case  6:                                  // Vector  6:  ADC12IFG0
        adc_value = ADC12MEM0 & 0x0FFF; // keep only low 12 bits; Move results, IFG is cleared
        adc_values[samples_count] = adc_value;
        samples_count += 1;
        /* Set the flag when 1000 samples are collected */
        if (samples_count == 999) {
            SamplesDone = 1;
        }
        __bic_SR_register_on_exit(LPM0_bits);   // Exit active CPU
    case  8: break;                           // Vector  8:  ADC12IFG1
    case 10: break;                           // Vector 10:  ADC12IFG2
    case 12: break;                           // Vector 12:  ADC12IFG3
    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;
    }
}

I would be very thankful to any suggestions/comments on this approach.

  • Hi Deepak,

    I recommend you look into using DMA to transfer your ADC conversions to RAM. You can use the ADC12IFG to trigger a DMA transfer from ADC12MEM0 to the adc_values array. You can also setup the DMA to interrupt the CPU once it has moved 1000 conversions to RAM and then wake from LPM. Start by reading through the DMA section of the MSP430x5xx and MSP430x6xx Family User's Guide (Rev. P) and let me know if you have any further questions.

    This will save power and CPU cycles during operation.

    Best regards,

    Caleb Overbay

  • Hi Caleb,

    Thank you very much for your response. Based on your response, I tried implementing DMA but it appears I am missing something.

    Here is the DMA setup done inside main:

    /* Configure DMA to transfer A2 and A3 values from the ADC to RAM */
        DMACTL0 = DMA0TSEL_24;                       // ADC12IFGx triggers DMA0
        DMA0SA = ADC12MEM0;                          // Source address
        DMA0DA = &adc_values[0];                     // Destination address
        DMA0SZ = 2;                                  // Transfer 2 16 bit words per trigger
    

    In the ADC12_ISR, I have the following line

    DMA0CTL = DMADT_0 | DMADSTINCR_3 | DMASRCINCR_3 | DMAEN | DMAIE; // Single transfer, increment dest addr, enable DMA & interrupt
    

    Here are the problems that I am facing for which I would highly appreciate your suggestions:

    1. The main problem I have is that I have a function call inside the infinite while loop that sends these values. So I can't have ADC in repeated mode. ADC and DMA have to work in single mode, enabling each other's ISRs. If I understand it correctly, the flow should be like, ADC ISR->ADC_ISR invokes DMA_ISR->Value is copied from ADC12MEM0, thus clearing IFG flag of ADC->DMA_ISR exit->ADC_ISR exit->Function inside infinite while loop of main sends the value

      However, during debugging, DMA doesn't seem to be invoked at all.

    2. "You can also setup the DMA to interrupt the CPU once it has moved 1000 conversions to RAM and then wake from LPM"

      As shown in my code, I am using a counter samples_count to take care of when 1000 samples are collected. Do you mean I need to move this to the DMA ISR (where I am currently doing nothing)? I am taking one sample from ADC and then moving it to the array using DMA. Can I still implement your suggestion in this scenario?

    3. The following line shows warning

      DMA0SA = ADC12MEM0;

      warning #515-D: a value of type "unsigned int" cannot be assigned to an entity of type "__SFR_FARPTR"

      The same is the case for DMA0DA line as well.

  • Hi Deepak,

    First it doesn't look like you've properly setup the source and destination addresses. Please take a look at the MSP430F55xx_dma_04.c example showing how to perform a repeat single transfer from ADC12 using DMA. Link to code examples

    To answer your questions:

    1. The main problem I have is that I have a function call inside the infinite while loop that sends these values. So I can't have ADC in repeated mode. ADC and DMA have to work in single mode, enabling each other's ISRs. If I understand it correctly, the flow should be like, ADC ISR->ADC_ISR invokes DMA_ISR->Value is copied from ADC12MEM0, thus clearing IFG flag of ADC->DMA_ISR exit->ADC_ISR exit->Function inside infinite while loop of main sends the value
      1. You don't send the values until you have 1000 of them, correct? If this is the case you can setup the ADC in repeated mode and the DMA in repeat single channel mode. Then, also set the DMAxSZ register to 1000. You would then get a DMA interrupt when 1000 results have been transferred to memory. In the ISR you can then wake the CPU and send the 1000 values using the function in the main while(1).

    2. "You can also setup the DMA to interrupt the CPU once it has moved 1000 conversions to RAM and then wake from LPM" As shown in my code, I am using a counter samples_count to take care of when 1000 samples are collected. Do you mean I need to move this to the DMA ISR (where I am currently doing nothing)? I am taking one sample from ADC and then moving it to the array using DMA. Can I still implement your suggestion in this scenario?
      1. DMAxSZ keeps track of how many samples have been transferred. By setting it to 1000, after 1000 samples have been transferred a DMA interrupt will fire. Inside the interrupt I recommend disabling the ADC, then upon exit waking the CPU. When the CPU is awake then you can send the 1000 samples into your mqtt function. 

    3. The following line shows warning: DMA0SA = ADC12MEM0;
      1. You are improperly setting the source address and destination address. Please see the example I linked to previously for the proper way to do this.

    Finally, DMA is a fairly complex module but can be powerful when used correctly. It took me a few read-throughs of the DMA section of the User's guide before I fully understood how to use it correctly. I recommend reviewing it a multiple times as well. Keep me updated on your progress and let me know if you have any questions. 

    Best regards, 

    Caleb Overbay

  • Hi Caleb,

    I really appreciate your help! I tried to incorporate the changes you have suggested while referring to the user's guide multiple times.

    Here's how the ADC setup looks like with Repeat-single-channel conversion mode:

    static void setup_ADC(void){
    	ADC12CTL0 = ADC12SHT02 + ADC12MSC+ ADC12ON;					// Sampling time, ADC12 on
    	ADC12CTL1 = ADC12SHP + ADC12SSEL_1 + ADC12CONSEQ_2;			// Use sampling timer; Pulse Sample Mode; ADC12SHT02 decides the interval of the sampling timer
    																// ACLK (32.768 kHz) selected as the clock source // Repeat-single-channel
    	ADC12IE = 0x01;												// Enable interrupt
    	ADC12CTL0 |= ADC12ENC;
    	P6SEL |= 0x01;                            // P6.0 ADC option select
    }

    The DMA setup with Repeated single transfer mode is like this:

    static void setup_DMA(void){
    	/* Configure DMA to transfer values from the ADC to RAM */
    	DMACTL0 = DMA0TSEL_24;                               // ADC12IFGx triggers DMA0
    	DMACTL4 = DMARMWDIS;                                 // Read-modify-write disable
    	DMA0CTL &= ~DMAIFG;
    	DMA0CTL = DMADT_4 | DMADSTINCR_3 | DMAEN | DMAIE;    // Repeated single transfer, increment dest addr, enable DMA & interrupt
    	DMA0SZ = 1000;                                       // Transfer 2 16 bit words per trigger
    	__data16_write_addr((unsigned short) &DMA0SA,(unsigned long) &ADC12MEM0);	        // Source block address
    	__data16_write_addr((unsigned short) &DMA0DA,(unsigned long) &adc_values);		// Destination single address
    }

    Inside the main, they are called in this sequence:

           
    	setup_ADC();
    	setup_DMA();
    
    	while(1){
    		/* Start ADC*/
    		ADC12CTL0 |= ADC12SC;                   // Start sampling/conversion
    
    		__bis_SR_register(LPM0_bits + GIE);     // LPM0, ADC12_ISR will force exit
    		__no_operation();                       // For debugger
    	}

    There is only one ISR that is 

    #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
    #pragma vector=DMA_VECTOR
    __interrupt void DMA_ISR(void)
    #elif defined(__GNUC__)
    void __attribute__ ((interrupt(DMA_VECTOR))) DMA_ISR (void)
    #else
    #error Compiler not supported!
    #endif
    {
    	switch(__even_in_range(DMAIV,16))
    	{
    	case 0: break;
    	case 2:                                 // DMA0IFG = DMA Channel 0
    		__bic_SR_register_on_exit(LPM0_bits);   // Exit active CPU
    	case 4: break;                          // DMA1IFG = DMA Channel 1
    	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;
    	}
    }

    I have the following problems now and would be thankful to have your suggestions/pointers on the same:

    1. The debugger gets stuck at the following line inside main and never goes to the ISR. (There is only DMA_ISR in the code.)

    __bis_SR_register(LPM0_bits + GIE);

    The same happens with MSP430F55xx_dma_04.c example as well.

    2. Since DMAEAN=1 as well as ADC12IFG0 gets set, shouldn't DMA_ISR get called? Do I need to enable/set something else as well?

    3. "Inside the interrupt I recommend disabling the ADC"

    So if I understand correctly, inside the DMA_ISR, I need to do something like clearing ADC12ENC bit.

    Feedback on Documentation:
    In the page 390 of slau208p, the section "11.2.3 Initiating DMA Transfers" says " The DMAxTSEL bits should be modified only when the DMACTLx DMAEN bit is 0." I think it should be DMAxCTL DMAEN instead.

  • Hi Deepak,

    First, it looks like there is a bug in the example I provided you. An ADC conversion is never started before going to LPM. Therefore, no DMA transfer is triggered. You can fix this bug by adding ADC12CTL0 |= ADC12SC; before going to LPM0. Thanks for bringing this to my attention.

    I see you mentioned that the DMA ISR is the only one in your system currently but you still enable ADC interrupts with the line: ADC12IE = 0x01;
    This is causing a jump to an undefined ISR and subsequently trapping the CPU. If you remove this line from your code, you will see the DMA interrupt fire!

    And yes, inside your DMA ISR I recommend, disabling conversions by clearing the ADC12ENC bit. Then before starting a new conversion in main, re-enabling conversions. Other than that your code looks good!

    Best regards,
    Caleb Overbay
  • Hi Caleb,

    Yes, it worked! Thank you very much for that.

    Now I am working on making the best of MSP430 power saving as well as efficiency. However these are the hurdles that I'm currently facing:

    1. I was able to read the values through DMA, however the code seems to be very slow.  I am sharing the recent code to show this problem. The output "Inside main" shows up on the serial monitor like once in a second.

    while(1){
    		CLI_Write((_u8 *)" Inside main \n\r");
    		DMA0SZ = 1000;                                 // Transfer 2 16 bit words per trigger
    		/* Read ADC values*/
    		ADC12CTL0 |= ADC12ENC | ADC12SC;		// Enable and Start sampling/conversion
    		__bis_SR_register(LPM0_bits + GIE);     // LPM0, DMA_ISR will force exit
    		__no_operation();                       // For debugger
    	}

    The ADC and DMA settings being as follows

    static void setup_DMA(void){
    	/* Configure DMA to transfer values from the ADC to RAM */
    	DMA0CTL &= ~DMAEN;/* DMAxTSEL should by modified only when the DMACTLx DMAEN bit is 0 */
    	if(~DMAEN){
    		DMACTL0 = DMA0TSEL_24;                       // ADC12IFGx triggers DMA0
    	}else{
    		CLI_Write((_u8 *)" Cannot modify DMAxTSEL. DMA already enabled \n\r");
    	}
    	DMACTL4 = DMARMWDIS;                      // Read-modify-write disable
    	DMA0CTL &= ~DMAIFG;
    	DMA0CTL = DMADT_4 | DMADSTINCR_3 | DMAEN | DMAIE; // Repeated single transfer, increment dest addr, enable DMA & interrupt
    	__data16_write_addr((unsigned short) &DMA0SA,(unsigned long) &ADC12MEM0);	// Source block address
    	__data16_write_addr((unsigned short) &DMA0DA,(unsigned long) &adc_values);		// Destination single address
    }
    
    static void setup_ADC(void){
    	ADC12CTL0 = ADC12SHT0_0 | ADC12MSC | ADC12ON;					// Sampling time, multiple sample and conversion, ADC12 on
    	ADC12CTL1 = ADC12SHP | ADC12SSEL_1 | ADC12DIV1 | ADC12CONSEQ_2;			// Use sampling timer; Pulse Sample Mode; ADC12SHT02 decides the interval of the sampling timer
    	// ACLK (32.768 kHz) selected as the clock source
    	// Divide the clock by 2. New Clock becomes ACLK/2 around 16kHz
    	// Repeat-single-channel
    	P6SEL |= 0x01;                            // P6.0 ADC option select
    }

    Inside DMA ISR, I'm just enabling the ADC conversion with the following line

    ADC12CTL0 &= ~ADC12ENC; // Disable conversion

    2. In the ADC, I'm currently using ADC12SHT0_0. I understand that when ADC12SHP is set, i.e. Pulse Sample mode,  ADC12SHT0x decides the sampling timer interval. Which ADC12SHT0x should I use so that the sampling interval for SAMPCON would be just enough?

    3. MCLK is disabled in LPM0, but DMA needs 2 MCLK cycles. So apparently it enables it without waking up the CPU and then disables. Doesn't it affect the efficiency? I am looking for optimizing the time my code stays in the Low power mode. If you have some guidelines for the same please let me know.

  • Hi Deepak, 

    I wouldn't say your code is running slow, it just takes a while to sample, convert, then transfer 1000 samples to memory. Especially when your running the ADC at 16kHz as your comments above suggest. If you want your code to run faster you'll need to increase your ADC clock source and decrease the sample and hold time as much as possible. 

    To reduce your sample and hold timing to a minimum, take a look at section 28.2.5.3 of the MSP430F5529 User's Guide. This short section describes how to calculate the minimum sampling time required for your application. 

    Finally, using DMA is the best way to optimize for low power in this type of application. While it takes 2 MCLK cycles to transfer data, the CPU remains in LPM0 during the transfers.

    Best regards, 
    Caleb Overbay

  • Hi Deepak,

    How's progress on this? Do you have anymore unanswered questions?

    Best regards,
    Caleb Overbay
  • Hi Caleb,

    I'm really sorry I thought I have already shared updates with you, but I was wrong. Everything worked great! 

    I would like to thank you once again for all the help you have provided to make the best use of MSP430 in my project.

    Best Regards,

    Deepak Koranga

**Attention** This is a public forum