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.

MSP432 ADC14 Interrupt not always triggering

I'm modifying the msp432p401_adc14_5.c program to take 4 readings and then average the 4 readings. I'm not always getting the interrupt to trigger but if I enter the debugger and pause that seems to get the triggers to happen yet my breakpoints don't always work. I'm far from a great programmer so hopefully someone can take a look at what I've done and see where I'm not doing things correctly.

/* --COPYRIGHT--,BSD_EX
 * Copyright (c) 2013, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *******************************************************************************
 *
 *                       MSP432 CODE EXAMPLE DISCLAIMER
 *
 * MSP432 code examples are self-contained low-level programs that typically
 * demonstrate a single peripheral function or device feature in a highly
 * concise manner. For this the code may rely on the device's power-on default
 * register values and settings such as the clock configuration and care must
 * be taken when combining code from several examples to avoid potential side
 * effects. Also see www.ti.com/.../mspdriverlib for an API functional
 * library & https://dev.ti.com/pinmux/ for a GUI approach to peripheral configuration.
 *
 * --/COPYRIGHT--*/
//******************************************************************************
//******************************************************************************
//  MSP432P401 Demo - ADC14, Sample A1, AVcc Ref, Set P1.0 if A1 > 0.5*AVcc
//
//   Description: A single sample is made on A1 with reference to AVcc.
//   Software sets ADC14SC to start sample and conversion - ADC14SC
//   automatically cleared at EOC. ADC14 internal oscillator times sample (16x)
//   and conversion. In Mainloop MSP432 waits in LPM0 to save power until ADC14
//   conversion complete, ADC14_ISR will force exit from LPM0 in Mainloop on
//   reti. If A0 > 0.5*AVcc, P1.0 set, else reset. The full, correct handling of
//   and ADC14 interrupt is shown as well.
//
//
//                MSP432p401rpz
//             -----------------
//         /|\|              XIN|-
//          | |                 |
//          --|RST          XOUT|-
//            |                 |
//        >---|P5.4/A1      P1.0|-->LED
//
//   Dung Dang
//   Texas Instruments Inc.
//   November 2013
//   Built with Code Composer Studio V6.0
//******************************************************************************
#include "msp432.h"
#define  NUM_SAMPLES 4

short samples[NUM_SAMPLES];
int sample_index=0;
int voltage_ave=0;

int main(void) {
	volatile unsigned int i;
    WDTCTL = WDTPW | WDTHOLD;                 // Stop WDT

  // Initialize the shared reference module
  // By default, REFMSTR=1 => REFCTL is used to configure the internal reference
  while(REFCTL0 & REFGENBUSY);              // If ref generator busy, WAIT
  REFCTL0 |= REFVSEL_0 + REFON;             // Enable internal 1.2V reference

    // GPIO Setup
    P1OUT &= ~BIT0;                           // Clear LED to start
    P1DIR |= BIT0;                            // Set P1.0/LED to output
    P5SEL1 |= BIT4;                           // Configure P5.4 for ADC
    P5SEL0 |= BIT4;

    __enable_interrupt();
    NVIC_ISER0 = 1 << ((INT_ADC14 - 16) & 31);         // Enable ADC interrupt in NVIC module

      while(!(REFCTL0 & REFGENRDY));            // Wait for reference generator
                                            // to settle

    // Configure ADC14
    ADC14CTL0 = ADC14SHT0_2 | ADC14SHP | ADC14ON;          // Sampling time, S&H=16, ADC14 on
    ADC14CTL1 = ADC14RES_2;                   // Use sampling timer, 12-bit conversion results

    //ADC14MCTL0 |= ADC14INCH_1;                // A1 ADC input select; Vref=AVCC
    ADC14MCTL0 =ADC14VRSEL_1 + ADC14INCH_1;      // A1 ADC input select; Vref=1.2V
    ADC14IER0 |= ADC14IE0;                    // Enable ADC conv complete interrupt

    SCB_SCR &= ~SCB_SCR_SLEEPONEXIT;           // Wake up on exit from ISR
   

    while (1)
    {
      //for (i = 20000; i > 0; i--);            // Delay
       ADC14CTL0 |= ADC14ENC | ADC14SC;        // Start sampling/conversion
//      while(!(ADC14IFGR0 & BIT0));         // Wait for conversion to complete
      if (sample_index == 0) {
    	  voltage_ave = 0;
		  for (i=0; i<4; i++) {
			voltage_ave = voltage_ave + samples[i];
		  }
		  voltage_ave = voltage_ave >> 2; // divide by 4 by right shifting 2
		  for (i=0; i<4; i++) {
		  			samples[i] = 0;
		  		  }
		  P1OUT ^= BIT0;	// Toggle LED0 after every average
      }
      __sleep();
//      __bis_SR_register(LPM0_bits | GIE);     // LPM0, ADC14_ISR will force exit
      __no_operation();                       // For debugger
    }
}

// ADC14 interrupt service routine

 void ADC14IsrHandler(void) {

      samples[sample_index] = ADC14MEM0 ;
      sample_index = (sample_index + 1) % NUM_SAMPLES ;  // modulus 4 (wrap around)
}

  • George,

    The delay function that you commented out from the original code might be necessary for the functionality you desire. If you refer to the MSP432 Reference Manual Section 20.2.12 ADC14 Interrupts (attached below), you will see that there are flags that tell you whether an ADC14MEMx overflow or conversion time overflow has occurred. I would create while loops within your main function to determine if one of these is occurring, which would explain some of the missing triggers.

    www.ti.com/.../slau356a.pdf

    As far as your missing breakpoints, again the statement above could be the culprit, but using breakpoints with ISRs is generally not a great idea. The functionality of these breakpoints will be hit or miss, even if the code works properly in Release Mode.

    Hope this helps,
    Michael Arriete
  • Hi Michael,

    Thanks for looking at this. I did play around with using that delay loop and it didn't seem to make a difference. I wasn't always trying to trigger inside the ISR but in the while loop.

    I didn't really like using the delay loop (why does TI example code seem to use these instead of something better?) so I looked into the TRM and tried to add some code but wasn't too successful at that either.

    Here's what my while loop looks like now:

        while (1)
        {
          for (i = 20000; i > 0; i--);            // Delay
    //       ADC14CTL0 |= ADC14ENC | ADC14SC;        // Start sampling/conversion
    //      while(!(ADC14IFGR0 & BIT0));         // Wait for conversion to complete
          if (sample_index == 0) {
        	  voltage_ave = 0;
      		  for (i=0; i<4; i++) {
      			 voltage_ave = voltage_ave + samples[i];
      		  }
      		  voltage_ave = voltage_ave >> 2; // divide by 4 by right shifting 2
      		  for (i=0; i<4; i++) {
      		  		samples[i] = 0;
      		  }
    		  P1OUT ^= BIT0;	// Toggle LED0 after every average
          }
          __sleep();
    //      __bis_SR_register(LPM0_bits | GIE);     // LPM0, ADC14_ISR will force exit
          __no_operation();                       // For debugger
        }

    Is "while(!(ADC14IFGR0 & BIT0)); // Wait for conversion to complete" the "proper" way to wait for a conversion sample to complete?

  • George,

    I made the following changes to your while loop and all seems to be working now. Let me know if this is the case for you as well.

        while (1)
        {
    //      for (i = 20000; i > 0; i--);            // Delay
        	while(!(ADC14IFGR1 & BIT6));
           ADC14CTL0 |= ADC14ENC | ADC14SC;        // Start sampling/conversion
    //      while(!(ADC14IFGR0 & BIT0));         // Wait for conversion to complete
          if (sample_index == 0) {
              voltage_ave = 0;
              for (i=0; i<4; i++) {
                voltage_ave = voltage_ave + samples[i];
              }
              voltage_ave = voltage_ave >> 2; // divide by 4 by right shifting 2
              for (i=0; i<4; i++) {
                        samples[i] = 0;
                      }
              P1OUT ^= BIT0;    // Toggle LED0 after every average
          }
    //      __sleep();
    //      __bis_SR_register(LPM0_bits | GIE);     // LPM0, ADC14_ISR will force exit
          __no_operation();                       // For debugger
        }
    }
    
    // ADC14 interrupt service routine
    
     void ADC14IsrHandler(void) {
    
          samples[sample_index] = ADC14MEM0 ;
          sample_index = (sample_index + 1) % NUM_SAMPLES ;  // modulus 4 (wrap around)
    
     }
    

    Also, a better way to check if your code is working properly using breakpoints in conjunction with ISRs is to set a breakpoint where desired, view the breakpoint window when in debug mode, right click on the new breakpoint and enter Breakpoint Properties. Here you can change the type of breakpoint from Remain Halted to Update View and select Expressions to be updated. This will allow you to see Expressions changing while running the code without having to halt the processor.  On the Expressions window toolbar make sure Continuous Refresh is selected for this to work.

    Best Regards,

    Michael Arriete

  • Great tip on the debugging session! I've always wondered how to do that and now I'll have to go try it out.

    I have 2 questions for you though on your changes:

    • You're using 
      while(!(ADC14IFGR1 & BIT6));

      but in my code I've initialized the interrupt as

      ADC14IER0 |= ADC14IE0;

      So I would expect to use ADC14IFGR0, not ADC14IFGR1?

    • Low power isn't my top priority but I noticed that you commented out the sleep statement. Was this also causing a problem and if so why?

  • George,

    I apologize! I forgot to uncomment the sleep() line. Once I did, I realized that the code was still not working, but have made further changes and all is working again. I was able to get the averaging function to work without the use of a delay or while loop. See code below:

        while (1)
        {
    //      for (i = 20000; i > 0; i--);            // Delay
    //       ADC14CTL0 |= ADC14ENC | ADC14SC;        // Start sampling/conversion
    //      while(!(ADC14IFGR0 & BIT0));         // Wait for conversion to complete
          if (sample_index == 0) {
              voltage_ave = 0;
              for (i=0; i<4; i++) {
                voltage_ave = voltage_ave + samples[i];
              }
              voltage_ave = voltage_ave >> 2; // divide by 4 by right shifting 2
              for (i=0; i<4; i++) {
                        samples[i] = 0;
                      }
              P1OUT ^= BIT0;    // Toggle LED0 after every average
          }
          ADC14CTL0 |= ADC14ENC | ADC14SC;        // Start sampling/conversion
          __sleep();
    //      __bis_SR_register(LPM0_bits | GIE);     // LPM0, ADC14_ISR will force exit
          __no_operation();                       // For debugger
          __no_operation();
        }
    }

    My assumption as to why it wasn't working before with sleep is that the while loop,

    while(!(ADC14IFGR0 & BIT0));

    is waiting for the conversion to be completed before continuing on, but that finished conversion is actually what is supposed to wake us up from LPM0. From what I was seeing, if the conversion has already completed before entering LPM0 the code ends up getting stuck in sleep mode. In the newer version, the start of the conversion was moved to the line before entering LPM0.

    Regards,

    Michael Arriete

  • Thanks again, I owe you a beer for your help! The reason why I was taking 4 readings and getting the average is because the project requires some accuracy in the readings. Most of the readings are almost dead on (I'm using a power supply and supplying 250mV to P5.4) but I'm seeing some outliers that can be extremely off. As an example I'm seeing ~845(845 / 2^12 * 1.2V) but then I see an occasional ~650 and also some high values ~1300. Can you think of why this might be?

    In the final application I won't have to continually take measurements but as part of the program I'll need to sample this voltage before I take another reading.

    Just to clean things up here is the full code that I'm running now with your latest changes:

    #include "msp432.h"
    #define  NUM_SAMPLES 4
    
    short samples[NUM_SAMPLES];
    int sample_index=0;
    int voltage_ave=0;
    
    int main(void) {
    	volatile unsigned int i;
        WDTCTL = WDTPW | WDTHOLD;                 		// Stop WDT
    
        // Initialize the shared reference module
        // By default, REFMSTR=1 => REFCTL is used to configure the internal reference
        while(REFCTL0 & REFGENBUSY);              		// If ref generator busy, WAIT
        REFCTL0 |= REFVSEL_0 + REFON;             		// Enable internal 1.2V reference
    
        // GPIO Setup
        P1OUT &= ~BIT0;                           		// Clear LED to start
        P1DIR |= BIT0;                            		// Set P1.0/LED to output
        P5SEL1 |= BIT4;                           		// Configure P5.4 for ADC
        P5SEL0 |= BIT4;
    
        __enable_interrupt();
        NVIC_ISER0 = 1 << ((INT_ADC14 - 16) & 31);      // Enable ADC interrupt in NVIC module
    
        while(!(REFCTL0 & REFGENRDY));	            	// Wait for reference generator
                                                		// to settle
    
        // Configure ADC14
        ADC14CTL0 = ADC14SHT0_2 | ADC14SHP | ADC14ON;	// Sampling time, S&H=16, ADC14 on
        ADC14CTL1 = ADC14RES_2;                   		// Use sampling timer, 12-bit conversion results
    
        //ADC14MCTL0 |= ADC14INCH_1;                	// A1 ADC input select; Vref=AVCC
        ADC14MCTL0 =ADC14VRSEL_1 + ADC14INCH_1;      	// A1 ADC input select; Vref=1.2V
        ADC14IER0 |= ADC14IE0;                    		// Enable ADC conv complete interrupt
    
        SCB_SCR &= ~SCB_SCR_SLEEPONEXIT;           		// Wake up on exit from ISR
       
        while (1)
        {
    //      while(!(ADC14IFGR1 & BIT6));
    //    while(!(ADC14IFGR0 & BIT0));         // Wait for conversion to complete
          if (sample_index == 0) {
        	  voltage_ave = 0;
      		  for (i=0; i<4; i++) {
      			voltage_ave = voltage_ave + samples[i];
      		  }
      		  voltage_ave = voltage_ave >> 2; // divide by 4 by right shifting 2
      		  for (i=0; i<4; i++) {
      		  	samples[i] = 0;
      		  }
    		  P1OUT ^= BIT0;	// Toggle LED0 after every average
          }
          ADC14CTL0 |= ADC14ENC | ADC14SC;        // Start sampling/conversion
          __sleep();
          __no_operation();                       // For debugger
        }
    }
    
    // ADC14 interrupt service routine
    
    void ADC14IsrHandler(void) {
            samples[sample_index] = ADC14MEM0 ;
            sample_index = (sample_index + 1) % NUM_SAMPLES ;  // modulus 4 (wrap around)
            __no_operation();                       // For debugger
    }
    

  • George,

    Before diving into what might be happening incorrectly in the code, I would suggest hooking the output pin of the power supply (while connected to the MSP) to an oscilloscope and measuring the voltage of the pin. Make sure that the voltage never reaches the peaks and valleys you stated of 0.38mV and 0.19mV, respectively.

    Regards,
    Michael Arriete
  • Michael,

    Good thought. I haven't had any issues with my Agilent power supply in the past so didn't think this could be related to the actual measurements. I definitely don't have the cleanest of hook ups but the output of the supply is very good UNTIL it is connected to the LaunchPad board. I'm seeing some ringing when I measure the voltage at P5.4 which looks like it's about 300kHz for some reason.

    That certainly explains the variance in the readings and I'm surprised I'm not seeing a wider range of read values. I'm not seeing the ringing on the LaunchPad power rails so maybe this is something with the ADC?

  • George,

    You may benefit from adding a bypass capacitor connected to the power rail and ground, if you haven't added one already. This could help reduce the noise you're seeing.

    Regards,
    Michael Arriete
  • 
    

    Michael,

    I'm not seeing those spikes on the power rails on the board and looking at the LaunchPad schematic I see they do have bypass caps (as expected) on both AVcc and Vcc as well as a 10uF so that all looks OK. The power supply is not showing any current draw so I'm wondering if this is linked to the timing that's being used to take the measurements. That's something I'll have to set aside for now but I'd be curious to see what others have found in the past.

    I'm looking at the code now and wondering why in the example code they have the enable bit set inside the while loop:

    ADC14CTL0 |= ADC14ENC | ADC14SC;        // Start sampling/conversion

    Seems to me that you should set this in your original configuration and the while loop should only start a new conversion. Which brings me to my next question which I hope you can help me sort out. All the example code assumes that you just want to blindly start conversions and let them run forever which I think is far from a realistic situation. I've messed with the ADC14CTL0 settings and have yet to come up with a way to only run the conversions when I want. In my case I'd like to take 4 (or 8) measurements and be done. What's the best way to start a conversion, record the value (as is done in the ISR now), and start a new conversion. I've read through the TRM and am a little confused on if the ISR is triggered only when the conversion is done or if there should be some sort of check to make sure the conversion is done before you read it.

    I'm thinking this should be fairly straight forward but stepping through the code and watching the registers I'm left scratching my head and re-reading what I've read before.

  • I need to do a little more digging but since I am using the 1.2V reference for my ADC measurements I thought I'd take a look at that. If I'm correct, the DC-DC output of the MSP432 is the one which generates this rail and it doesn't look so clean:

  • George,

    Sorry for the late reply! If you take a look at the software flow charts starting at Section 20.2.8.1 within the MSP432 User's Guide (provided below), you can see that the behavior of ADC14ENC within Single-Channel Single-Conversion Mode can be somewhat unpredictable. In all other modes, however, it is required to toggle ADC14ENC back to 1 when a conversion is completed because the peripheral will clear this bit on its own. My guess is that it's done in this case as a safety precaution for the bit's unpredictable behavior.

    www.ti.com/.../slau356a.pdf

    I had the same thought about whether or not it is safe to assume that the ISR only triggers when the conversion is complete, so in the code you provided I did write in a check for the conversion complete flag before reading MEM0, just in case.

    As for the noise you're experiencing on the 1.2V reference, I am looking into this further to see if I can replicate the signal. I'll get back to you as soon as I have made some progress.

    Thanks for your patience,
    Michael Arriete
  • I've looked at those charts and I'm sorry but for me they are confusing! I was doing some additional reading and at some point I may want to switch to using the Driver Library. Unless I'm mistaken it seems that it handles single channel repeat differently than the "standard" code. You can feed it start and ending memory address location you want the reading to go into but with the standard code when you use this mode it writes to the same memory address and overwrites. The driver library implementation seems ideal since you could tell it to go take 1-32 measurements and it will handle everything for you.

    If you find something on the DC-DC I should probably open a different thread for that since I'm already stomping a bit on this thread as it is!

  • George,

    In order to accurately reproduce the issue, can you tell me where exactly you are probing on the MSP432 Launchpad for the screenshot above? I would like to see the ringing for myself and figure out if there is some type of solution.

    Also, did changing the code to DriverLib work out for you?

    Regards,
    Michael Arriete
  • Hi Michael,

    Good Morning, I hope you had a good weekend. I just tried to reproduce that scope plot and I'm not seeing those spikes. I usually try to run tests a few times before I post so I'm not sure what's different but for now let's chalk it up to operator error. If I see it again I'll open a new thread.

    I did not create a new project with the DriverLib API. I guess I got a little stubborn trying to figure out what was wrong with my current setup before learning a new API. I do like that the DriverLib is located in ROM which saves program space but it looks different enough that I'll pass for today but add it onto my list of things to do.

    I created a GitHub project for code which you can take a look at as it still is having odd behaviors with the interrupts not triggering as expected. Somewhere lurking in the code is probably something dumb that I've done but it is still not jumping out to my eyes.

    Regards,

    George

**Attention** This is a public forum