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: OK what have I got wrong in using ADC12 trigger by timer B

Part Number: MSP430F5529

OK quick code fragment. I expect ADC12 to be busy on entering the TIMER B0 interrupt - but it isn't; so my guess is I've missed enabling something. So is it anything obvious? I'll go through the documentation carefully this weekend.

int main(void)
{
  // Stop WDT
  HWREGW( WDT_A_BASE | WDTCTL ) = WDTPW|WDTHOLD;

  // stop interupts whilst we configure
  __disable_interrupt();
    // first disable ADC12
  HWREGW( ADC12_A_BASE | ADC12CTL0 )&= ~ADC12ENC;
  HWREGW( ADC12_A_BASE | ADC12CTL0 )&= ~ADC12ON;
  // 4 ticks sample and hold.
  // 0x80 = 1 - sample on SHi signal
  // 0x40 - 2.5V ADC12_A ref gen volt
  // 0x20 - Ref on
  // 0x10 =0 - ADC12_A is off
  // 0x08 =0 - overflow int disable
  // 0x04 =0 - conversion time int disable
  // 0x02 - enable conversion ????
  // 0x01 - Start conversiom
  HWREGW( ADC12_A_BASE | ADC12CTL0 ) =
    ADC12SHT0_0 | ADC12MSC | ADC12REF2_5V | ADC12REFON ;
  // ADC12CSTARTADDx = 0 (write starting at ADC12MEM0)
  // ADC12SHP =0
  // ADC12SHSx = 2 - Timer B0
  // ADC12ISSH = 0 Not inverted
  // ADC12DIVx = 0 (divide by 1)
  // ADC12SSELx = 3 (SMCLK)  
  HWREGW( ADC12_A_BASE | ADC12CTL1 ) =
    ADC12SHS_2 | ADC12SSEL_3 ;
  // ADC12PDIV =0 (predivide 1)
  // ADC12TCOFF =0 (oddly means temp sensor on)
  // ADC12RES=2 (12bit)
  // ADC12DF=0 (unsigned)
  // ADC12SR=0 (200ksps sample rate)
  // ADC12REFOUT=1 = Ref Output on
  // ADC12REFBURST=1 only on during sample and convert
  HWREGW( ADC12_A_BASE | ADC12CTL2 ) =
    ADC12RES_2 | ADC12REFOUT | ADC12REFBURST;
  // ADC12SREF2 = 1 Vref- user the external reference
  // ADC12SFREG01 = 11 or 10 - user Vref+
  HWREGB( ADC12_A_BASE | ADC12MCTL0 ) = ADC12SREF_7 | ADC12INCH_0;
  HWREGB( ADC12_A_BASE | ADC12MCTL1 ) = ADC12SREF_7 | ADC12INCH_10;
  HWREGB( ADC12_A_BASE | ADC12MCTL2 ) = ADC12EOS;
  // enable ADC12
  HWREGW( ADC12_A_BASE | ADC12CTL0 )|= ADC12ON;
  HWREGW( ADC12_A_BASE | ADC12CTL0 )|= ADC12ENC;
    
  __enable_interrupt();


  // now all set for now just loop, with LED flashing
  while(1) {
  }
  

}
// TIMER B CC
__attribute__ ((interrupt(TIMER0_B0_VECTOR)))
void TIMER0_B0_ISR (void)
{

  sprintf(print,"%i\0", HWREGW( ADC12_A_BASE | ADC12CTL1 )&ADC12BUSY);    
}

The print buffer, is processed by a USCI_A1 interrupt and sent to the UART.

On the UART I just get "0" output - which given that its the first instruction in Timer B interrupter routine, and that is meant to trigger the ADC12, seems wrong.

Any ideas anyone? I'll keep digging.

  • Did you configure the output module of the timer? The trigger is output TB0.0.

    The pin doesn't need to be enabled but the output module does.

  • 1) I don't see where you set up the timer. The notion behind SHS is that you have the timer (CCR0 in this case) generate a signal where a rising edge triggers the ADC -- you effectively generate the SHI signal seen in User Guide (SLAU208Q) Fig 28-5. For CCR0, you would use OUTMOD=4 (at twice the speed, i.e. half the CCR0 value). 

    2) 

    >  HWREGW( ADC12_A_BASE | ADC12CTL1 ) = ADC12SHS_2 | ADC12SSEL_3 ;

    You're configuring 2 channels (MCTL0/1) and MSC=1, but setting (by omission) CONSEQ=0, so you'll only get one channel. You probably want CONSEQ=1. You also probably want SHP=1:

    >  HWREGW( ADC12_A_BASE | ADC12CTL1 ) = ADC12SHS_2 | ADC12SSEL_3  | ADC12SHP | ADC12CONSEQ_1;

    3) With CONSEQ=1, you need to toggle ADC12ENC (=0 then =1) after each batch [Ref UG Sec 28.2.7.2]. This is typically done inside the ADC ISR (the timer ISR is almost certainly too early).

    4) 

    > HWREGW( ADC12_A_BASE | ADC12CTL0 )|= ADC12ENC;

    ADC12CTL0 is an absolute address, not an offset. This form works by accident since ADC12_A_BASE is a multiple of 256 (0xX00). Preferred form is something like:

    > ADC12CTL0 |= ADC12ENC;

  •   // Now set up Timer B
      // first disable before programming
      HWREGW( TIMER_B0_BASE | TBxCTL )&=0xffcf;
      // all Timer B latch updates not tied together (But 
      // 16 bit
      // SMCLK is the clock
      // divide by 1
      // keep disabled
      // Time clear - here we clear just be sure, but will do again when start
      // TBIFG interrupt disabled (we just do CC0)
      // - this done when we emable at the bottom 
      //  HWREGW( TIMER_B0_BASE | TBxCTL )=0x30;
      // and set TBIDEX to divide by 1 as well
      HWREGW( TIMER_B0_BASE | TBxEX0 )= TAIDEX_0;
      // write CCR0 to CL0 immediatly
      HWREGW( TIMER_B0_BASE | TBxCCTL0 ) &= ~(CLLD1|CLLD0);  
      // CCR0 sets period
      // and CCR0 is the interupt
      HWREGW( TIMER_B0_BASE | TBxCCTL0 )= CCIE;
      //  HWREGW( TIMER_B0_BASE | TBxCCR0 )= Period-1;
      HWREGW( TIMER_B0_BASE | TBxCCR0 )= 12000-1; // harmonic 1-8 + bridge
      // now CC5-6 the PWM compare states
      // latch when TB0R counts to zero
      // (implicit) Compare mode
      // Reset/Set on PWM
      HWREGW( TIMER_B0_BASE | TBxCCTL5 )= CLLD_1 | OUTMOD_7 ;
      HWREGW( TIMER_B0_BASE | TBxCCR5 )= 0;
      // Set/Reset - quick hack of push/pull - needs more though and a gap
      HWREGW( TIMER_B0_BASE | TBxCCTL6 )= CLLD_1 | OUTMOD_3 ;
      HWREGW( TIMER_B0_BASE | TBxCCR6 )= 0;
      // and enable output pins TB0.5 - TB0.6 - 5 and 6 used as on ext pins
      HWREGB( P3_BASE + PxDIR ) |= BIT5 ;
      HWREGB( P3_BASE + PxDIR ) |= BIT6 ;
      // and switch to PWM usage
      HWREGB( P3_BASE + PxSEL ) |= BIT5 ;
      HWREGB( P3_BASE + PxSEL ) |= BIT6 ;
      // Now all set enable and clear
      HWREGW( TIMER_B0_BASE | TBxCTL )=
        TBCLGRP_0 | CNTL__16 | TBSSEL__SMCLK | MC__UP | TBCLR ;
    
    Thanks Bruce and David - haven't got time right now to check your thoughts, but sound good.

    Can post my timer B configuration quickly though. This has been tested as working.

    My HWREGW macro write a unint16_t to the memory address, I usually set up the offsets in a .h file. I prefer this method because comping the c, already does the OR and gets a memory address - and gives code to write a variable of the right length to memory. I never liked the Texas Instrument method, where symbols get resolved in the linker ...

  • My HWREGW macro write a unint16_t to the memory address, I usually set up the offsets in a .h file. I prefer this method because comping the c, already does the OR and gets a memory address - and gives code to write a variable of the right length to memory. I never liked the Texas Instrument method, where symbols get resolved in the linker ...

    Doah - and now I'm going to look silly!

    Checked my code this morning - and I hadn't redefined the ADC12* variables, e.g. ADC12CTL0 - so it was using the value in the linker!

    So this morning - changed the variable names to ADC12ACTL0 etc; so if not found in the header - it would error.

    Reflashed - and bingo, I'm now getting the busy flag!

    So was my coding style that caused the problem. How silly. Have to see if I can back out the using of the linking files, so names I don't want to be resolved, are found.

    Thanks though both for your help, will go through the comments carefully anyway.

  • There are TI supplied headers that have offsets. For example OFS_ADC12CTL0 in msp430f5xx_6xxgeneric.h

    It is still a silly way to do things just to resolve addresses, mostly, before the linking stage.

  • 3) With CONSEQ=1, you need to toggle ADC12ENC (=0 then =1) after each batch [Ref UG Sec 28.2.7.2]. This is typically done inside the ADC ISR (the timer ISR is almost certainly too early).

    think you meant 

      HWREGW( ADC12_A_BASE | OFS_ADC12CTL0 ) |= ADC12SC;
      HWREGW( ADC12_A_BASE | OFS_ADC12CTL0 ) &= ~ADC12SC;
    

    At least that gets the busy flag going as I expect it - now to read that values!

    Oh yes I changed to doing the trigger going on ADC12SC - rather than timerB CCR0 - setting CCR0 messed up my other code, and also I need to to happen every Timer B interrupt and not every second one!

    Ah I see now - doing timer B triggers you toggle ADC12ENC, but with ADC12SC triggers, you toggle ADC12SC.

    Am also hoping not to need the ADC12 ISR.

    Whats great about TI documentation, is I think *everything* is described in the user manual, with almost no omissions. Whats bad is that makes a 1000 page document, that is *very* dense, with the devil in the detail!

  • Only oddity now is that the temperature ref ADCINCH_10 is always giving a maximum reading - I've set ADC12TCOFF to 0, think I've enabled the ref voltage - which from Fig 18-1 seems to be where Ref_x the drive voltage for the thermistor comes from.

      HWREGW( ADC12_A_BASE | OFS_ADC12CTL0 ) =
        ADC12SHT1_0 | ADC12SHT0_0 | ADC12MSC | ADC12REF2_5V | ADC12REFON ;
      HWREGW( ADC12_A_BASE | OFS_ADC12CTL1 ) =
        ADC12CSTARTADD_0 | ADC12SHS_0 | ADC12SHP | ADC12DIV_0 | ADC12SSEL_3 | ADC12CONSEQ_1 ;
      HWREGW( ADC12_A_BASE | OFS_ADC12CTL2 ) =
        ADC12RES_2 | ADC12REFOUT | ADC12REFBURST;
      HWREGB( ADC12_A_BASE | OFS_ADC12MCTL0 ) = ADC12SREF_7 | ADC12INCH_0;
      HWREGB( ADC12_A_BASE | OFS_ADC12MCTL1 ) = ADC12SREF_7 | ADC12INCH_10;
      HWREGB( ADC12_A_BASE | OFS_ADC12MCTL2 ) = ADC12EOS;
      HWREGW( ADC12_A_BASE | OFS_ADC12CTL0 )|= ADC12ON;
      HWREGW( ADC12_A_BASE | OFS_ADC12CTL0 )|= ADC12ENC;
    

  • ADC12SREF_7 selects an external reference (both + and -) for the ADC. Is that really what you want?

  • Yes think that was the problem, changing to ADS12SREF_1 and I get "random numbers" on the ADC - now to increase samples and see how it changes the stability.

    Think the crux was fig 28-1 of slau208q, which shows more clearly that bits ADC12SREF0/1setting to 01, selects the path from AVCC via the Reference - didn't find that described in the text anywhere!

    Thanks both - helps me find the way round the UG.

  • Table 28-8 clearly shows the effects of ADC12SREF. Assuming you understand the difference between VREF and VeREF anyway.

    When using the internal temperature sensor you want to use the internal reference. This lets you use the TLV calibration data to correct the result.

  • Assuming you understand the difference between VREF and VeREF anyway.

    Yes that was my problem, only saw the difference in fig 18-1; oh yes also change to programming the REFerence via REFCTL0.

**Attention** This is a public forum