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.

MSP430G2553 ADC using SMCLK

This is the first time I have programmed a microcontroller. My current program uses SMCLK and Timer_A interrupt. The ADC example below is based on a program I found on this forum, and it was originally written to use ACLK. I wish to execute using SMCLK so that I can incorporate ADC into my existing program.

I set ADC10SSEL_3 in the ADC10CTL1 register, and set TASSEL_2 in the TACTL register. But the ADC10 ISR does not execute. What's missing?

Thanks

#include <msp430.h>


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

  ADC10CTL1 = INCH_3 + ADC10SSEL_3 + SHS_1; // P1.3, SMCLK, TA1 trigger sample start
  ADC10AE0 = 0x08;                          // P1.3 ADC10 option select

  P2DIR |= 0x08;                            // Set P1.0 to output direction

  TACCTL0 = CCIE;                           // Enable interrupt
  TACCR0 = 32-1;                            // PWM Period
  TACCTL1 = OUTMOD_3;                       // TACCR1 set/reset
  TACCR1 = 2;                               // TACCR1 PWM Duty Cycle
  TACTL = TASSEL_2 + MC_1;                  // SMCLK, up mode


  __bis_SR_register(GIE);                   // enable interrupts

}

// ADC10 interrupt service routine
#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void)
{
  ADC10CTL0 &= ~ENC;                        // ADC10 disabled
  ADC10CTL0 = 0;                            // ADC10, Vref disabled completely

  if (ADC10MEM > 600)
    P2OUT &= ~0x08;
  else
    P2OUT |= 0x08;

}

// Timer A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A(void)
{
  ADC10CTL0 = SREF_1 + ADC10SHT_2 + REFON + ADC10ON + ADC10IE;
  ADC10CTL0 |= (ENC);                         // ADC10 enable set
}

  • Russell,

    from the first sight I notice that your program simply ends after enabling the interrupts in your main. You should add a

      ...
    
      __bis_SR_register( GIE );                   // enable interrupts
    
      while( 1 )
      {
      }
    }

    to it so the program can run in an infinite loop, interrupted by your interrupts.

    Dennis

  • It will also help to ask your ADC to generate an interrupt by setting the ‘ADC10IE’ bit.
  • He does it in the Timer ISR:

    // Timer A0 interrupt service routine
    #pragma vector=TIMER0_A0_VECTOR
    __interrupt void Timer_A(void)
    {
      ADC10CTL0 = SREF_1 + ADC10SHT_2 + REFON + ADC10ON + ADC10IE;
      ADC10CTL0 |= (ENC);                         // ADC10 enable set
    }

    Anyway - I don't really get what you want to do. You want the timer to trigger an ADC sample, but you use the timer's CCR0 interrupt to configure the ADC. So maybe you could explain what you intend to do.

    Dennis

  • I want to trigger an ADC sample from the timer interrupt with SMCLK. The original program used ACLK, i.e., (TACTL = TASSEL_1 + MC_1). I changed the input channel and threshold level and was able to verify correct operation, but I am unable to change the clock source to SMCLK and get the ADC sampling to execute. Ultimately, I want to use this knowledge to incorporate an ADC into my existing program that uses SMCLK. Perhaps there's a better way to do this?

  • You are using SMCLK for the timer and the ADC. Maybe it is because you have such small timer values:

    TACCR0 = 32-1;                            // PWM Period
    TACCR1 = 2;                               // TACCR1 PWM Duty Cycle
    

    You are running the timer, the ADC and the CPU with the same clock and stepping into and out of the ISR is (if I remember correctly) 11 MCLK cycles. So there is not much left until the next interrupt occurs. The ADC needs 13 clock cycles. This could be a reason - the ACLK normally runs at much slower frequencies. You now have about 1MHz. Try using some dividers for the clock to see if it then works.


    Dennis

  • Eventually this will run on a 20ms interrupt, so I changed TACCR0 to 20000. Still no luck.
  • There is no reason why you can't have the ADC module use ACLK for it's reference while the rest of your system runs from SMCLK. The processor architecture is designed to allow CPU and modules all run from independent clocks. Your timer can run from SMCLK, generate an interrupt to CPU (also SMCLK), and the CPU start and ADC conversion cycle with the ADC using ACLK. When the conversion is done, the ADC will signal an interrupt to the CPU, and the CPU (using SMCLK) will service the ISR.

    Using SMCLK will use more power than using ACLK, and you haven't reduced any complexity in your system. So I don't see the gain here.
  • I found a way to read the ADC value in the Timer A interrupt. The pertinent code snippets are below.

    #define P1_3_VOLTAGE_LVL      BIT3
    #define P2_3_LED              BIT3
    
    
    void main(void)
    {
       // Enable peripheral module on P1.3
       P1SEL |= P1_3_VOLTAGE_LVL;
    
       // Configure the ADC to use Vref/Vss, sample & hold 8 x ADC10CLKs,
       //  use internal 2.5V Vref, and enable ADC
       ADC10CTL0 = SREF_1 | ADC10SHT_2 | REF2_5V | REFON | ADC10ON;
    
       // Configure the ADC to use Channel 3 (P1.3), Sample & Hold Source ADC, SMCLK
       ADC10CTL1 = INCH_3 | SHS_0 | ADC10SSEL_3;
    
    // Set output direction on LED P2DIR |= P2_3_LED; // Timer A Control = Timer A Source Select 2 (SMCLK) and Mode Control 1 (up to CCR0) TACTL = TASSEL_2 | MC_1; //use CPU clk as source, don't divide, Count to TACCR0 repeatedly // Timer A Capture/Compare 0 = 20ms TACCR0 = 20000; // Timer A Capture/Compare Interrupt Enable TACCTL0 = CCIE; // Set Timer A Counter Interrupt Enable TACTL |= TAIE; // Globally enable interrupts _BIS_SR(GIE);
    // Only need System Clock Generator 1 _bis_SR_register(SCG0+OSCOFF+CPUOFF); } #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer_A (void) { // Turn the ADC on, enable converter, start conversion, and wait for it to complete ADC10CTL0 |= (ADC10ON | ENC | ADC10SC); while(ADC10CTL1 & ADC10BUSY); // Disable the ADC ADC10CTL0 &= (~ADC10ON & ~ENC & ~ADC10SC); // Read the sampled value voltage = ADC10MEM;


    if(voltage > 700)

             P2OUT &= ~P2_3_LED;

          else

             P2OUT |= P2_3_LED;

       // Clear the interrupt vector
       TA0IV = TA0IV_NONE;
    }

  • Russel,

    good to hear that it works now, but the way you are doing it isn't a good one in general. At least if your program will grow and therefore has to do more tasks. Never use a while()-loop inside an interrupt - try to avoid it in all cases...during the initialization before the main program starts this might be OK. So I would recommend to configure the ADC at startup and then only set the ADC10SC bit in the timer-interrupt. No need to set and reset ENC since you are not changing the configuration of the ADC. And no need to disable the ADC everytime, unless you want to save as much power as possible. But doing so isn't a problem, of course. Then use the ADC's interrupt and wait for it to fire and fetch the ADC result in it. The small portion of code to set/reset the output pin can be done in the interrupt, too.

    And here is a critical thing in your code:

    // Set Timer A Counter Interrupt Enable
    TACTL |= TAIE;

    The TAIE interrupt is located in

    #pragma vector = TIMER0_A1_VECTOR

    so you do not have an interrupt vector for it. This can cause your program to crash.

    What I'm also wondering about is that you do not set any ADC10AE bit for your input - you should not get any correct value since P1.3 isn't configured to be an analog input. Furthermore this statement

    // Enable peripheral module on P1.3
    P1SEL |= P1_3_VOLTAGE_LVL;

    is no valid configuration of P1.3 - when you set P1SEL in combination with P1DIR then you can select the ADC10CLK output function. Or setting P1SEL in combination with P1SEL2 and P1DIR will select the CAOUT function. But only setting P1SEL is nothing:

    For using P1.3 as an analog input, the PSEL bits are don't care.

    Dennis

  • Dennis, these are good suggestions; thanks for the general review. Particularly I was not aware of TAIE located in the A1 vector (oops!) But I cannot explain why not setting ADC10AE did not cause me any grief. I tested the sampled ADC value using 2.5V reference and a 1.8V input (1.8V = 736 ADC clicks.) The ADC was usually within +/-3 values from expected. Nonetheless I took your advice to set ADC10AE appropriately.

    Thanks

**Attention** This is a public forum