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.

MSP430FR2311: Reading multiple ADC channels

Part Number: MSP430FR2311

Ok, so I thought this would be easy, but apparently I'm doing something wrong and it isn't quite coming to me what the root cause of the issue is.  I started with the Out_of_box example.  I can easily read just one channel.  So then I tried to set it up to read a sequence of channels P1.1 and then P1.0.  My understanding is that you pick your highest channel and the sequencer just counts down through the channels, so I should get two readings.  I don't have the logic yet to parse out the readings which is fine for now.  I can deal with that.  It seems to not work because I would expect to get alternating ADC reading values which isn't the case.  There must be some detail I am missing here.  Note:  The extra stuff in the ADC interrupt routine is just leftovers from the out of box example where they were writing the adc result to FRAM.

/*
 * main.c
 */
int main(void) {
    //Stop WDT
    WDT_A_hold(WDT_A_BASE);
    initClockTo16MHz();
    initUART();

/* Initialize peripherals */
    initGPIO();
    initPWM();
    initI2C();

    //Toggle LED1 during start-up
    GPIO_setOutputHighOnPin(GPIO_PORT_P1, GPIO_PIN0);
    __delay_cycles(10000);
    GPIO_setOutputLowOnPin(GPIO_PORT_P1, GPIO_PIN0);

    EUSCI_A_UART_enable(EUSCI_A0_BASE);

    initSleepTimer(TIMER_RATE);  // 128Hz

    while(1){
    	//lightSample();
    	n0Sample();

        uintoa( buf, ADC_Conversion_Result );
        transmitString( buf );

        __bis_SR_register(LPM3_bits + GIE);

	}
}


void initSleepTimer(uint16_t period){
    //Start timer
    Timer_B_clearTimerInterrupt(TIMER_B0_BASE);

    Timer_B_initUpModeParam param = {0};
    param.clockSource = TIMER_B_CLOCKSOURCE_ACLK;
    param.clockSourceDivider = TIMER_B_CLOCKSOURCE_DIVIDER_1;
    param.timerPeriod = period;
    param.timerInterruptEnable_TBIE = TIMER_B_TBIE_INTERRUPT_DISABLE;
    param.captureCompareInterruptEnable_CCR0_CCIE = TIMER_B_CAPTURECOMPARE_INTERRUPT_ENABLE;
    param.timerClear = TIMER_B_DO_CLEAR;
    param.startTimer = true;
    Timer_B_initUpMode(TIMER_B0_BASE, &param);
}

void enable_ADC10(uint8_t adc_channel){
	//Initialize the ADC Module
	/*
	 * Base Address for the ADC Module
	 * Use internal ADC bit as sample/hold signal to start conversion
	 * USE MODOSC 5MHZ Digital Oscillator as clock source
	 * Use default clock divider of 1
	 */
	ADC_init(ADC_BASE, ADC_SAMPLEHOLDSOURCE_SC, ADC_CLOCKSOURCE_ADCOSC, ADC_CLOCKDIVIDER_1);

	ADC_setDataReadBackFormat(ADC_BASE, ADC_UNSIGNED_BINARY);  //AP

	ADC_enable(ADC_BASE);
	/*
	 * Base Address for the ADC Module
	 * Sample/hold for 16 clock cycles
	 * Do not enable Multiple Sampling
	 */
	ADC_setupSamplingTimer(ADC_BASE, ADC_CYCLEHOLD_16_CYCLES, ADC_MULTIPLESAMPLESENABLE);
	//ADC_setupSamplingTimer(ADC_BASE, ADC_CYCLEHOLD_16_CYCLES, ADC_MULTIPLESAMPLESDISABLE);

	ADC_configureMemory(ADC_BASE, adc_channel, ADC_VREFPOS_AVCC, ADC_VREFNEG_AVSS);  // channel mis-map for A1 in driverlib?

	ADC_clearInterrupt(ADC_BASE, ADC_COMPLETED_INTERRUPT);

	//Enable the Memory Buffer Interrupt
	ADC_enableInterrupt(ADC_BASE, ADC_COMPLETED_INTERRUPT);
}

void disable_ADC10(void){
	ADC_disableConversions(ADC_BASE,0);
	ADC_disable(ADC_BASE);
}


void n0Sample(void)
{
    enable_ADC10(ADC_INPUT_VEREF_N);

    __delay_cycles(15);

    //Enable and Start the conversion
    //in Single-Channel, Single Conversion Mode
    //ADC_startConversion(ADC_BASE, ADC_SINGLECHANNEL);
	
	ADC_startConversion(ADC_BASE, ADC_SEQOFCHANNELS);  // Changing this to single sequence did not work with ADC_SEQOFCHANNELS

    //LPM3, ADC conversion complete will force exit
    __bis_SR_register(LPM3_bits + GIE);

    disable_ADC10();
}

void initGPIO(void){

    //Set Px.x to output direction
    //P1DIR |= 0xFF;  // outputs
    //P1OUT = 0x00;
    P1DIR |= 0x00;  // inputs
   //P2DIR |= 0xFF;
    P2DIR |= 0b00000011;
    P2OUT = 0x00;

     // Select P2.0 as PWM Timer output function
    GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P2,GPIO_PIN0,GPIO_PRIMARY_MODULE_FUNCTION);

    // AP Configure GPIO
    P1SEL1 &= ~(BIT6 | BIT7);                 // USCI_A0 UART operation
    P1SEL0 |= BIT6 | BIT7;

    GPIO_setAsOutputPin(GPIO_PORT_P1, GPIO_PIN0);

    // I2C pins
    P1SEL0 |= BIT2 | BIT3;
    P1SEL1 &= ~(BIT2 | BIT3);

    //ADC Pins
    P1SEL0 |= BIT0 | BIT1;
    P1SEL1 |= BIT0 | BIT1;
    /*
     * Disable the GPIO power-on default high-impedance mode to activate
     * previously configured port settings
     */
    PMM_unlockLPM5();
}

// ADC interrupt service routine
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=ADC_VECTOR
__interrupt void ADC_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(ADC_VECTOR))) ADC_ISR (void)
#else
#error Compiler not supported!
#endif
{
    //Get previous write protection setting
    uint8_t state = HWREG8(SYS_BASE + OFS_SYSCFG0_L);
#ifdef DFWP
    uint8_t wp = DFWP | PFWP;
#else
    uint8_t wp = PFWP;
#endif

#ifdef FRWPPW
    HWREG16(SYS_BASE + OFS_SYSCFG0) = FWPW | (state & ~wp);
#else
    HWREG8(SYS_BASE + OFS_SYSCFG0_L) &= ~wp;
#endif

    ADC_Conversion_Result = ADCMEM0; 
    //Restore previous write protection setting
#ifdef FRWPPW
    HWREG16(SYS_BASE + OFS_SYSCFG0) = FWPW | state;
#else
    HWREG8(SYS_BASE + OFS_SYSCFG0_L) = state;
#endif

	__bic_SR_register_on_exit(LPM3_bits);              // Sleep Timer Exits LPM3
}






  • > enable_ADC10(ADC_INPUT_VEREF_N);
    As you observed the other day, this is ADCINCH_0. Did you mean:
    > enable_ADC10(ADC_INPUT_VEREF_P);
    (Personally, I would just plug ADCINCH_1 in there, since it's evident how the abstraction works.)
  • That was a mistake as a I was trying a different channel just to see if there was a different behavior. I tried that and it still did not work.  I just get bogus values almost as if the mux has tried two channels together or floating.  Completely different values then if I just configure it for just one channel only.  Also changing the number of sample and hold clock cycles completely changes the behavior.

    I even tried running two different sampling routines in series in the main(). n0sample and n1sample. one was configured for A1 and the other was configured for A0. It still just gave me the output of just one channel.

  • > n0sample and n1sample. one was configured for A1 and the other was configured for A0.
    Were they both single-channel? If so, maybe (some of) the trouble is elsewhere.

    > changing the number of sample and hold clock cycles completely changes the behavior.
    One of the headaches with the FR2 ADC is that there is only one MEM register and no DMA. This means that for burst sampling you need to grab sample N before sample (N+1) overwrites it, which requires some strategy.

    The ADC oscillator runs at about 5MHz, so with SHT=16 clocks, you have (16+14+1)/5MHz = ~6us to grab a given sample. That's 6*16=96 MCLKs, but you should probably de-rate by half for FRAM wait states, so figure 50 clocks. You probably lose 20 clocks just in ISR overhead, so based on your observation I suspect the clutter in your ISR is pushing you over. Suggestions:
    1) Clean out any unneeded code in the ISR.
    2) Consider extending the SHT setting by at least enough to fetch the value(s). You'll probably have to measure/experiment to figure out what this value is. Add lots of padding since (I suppose) this will be going into a busier program.
    3) Capture the samples into an array, not a single word. As I recall, in the FR2 the ADC keeps running while you're at a breakpoint, so debugging is tricky.
    4) Consider setting MSC=0 (ADC_MULTIPLESAMPLESDISABLE) and sending multiple SC triggers (ADC_startConversion) or using a timer trigger (SHS>0). At that point you could probably just use CONSEQ=3, since the CPU is pacing the ADC.
    5) In extremum, use a very slow ADC clock. ACLK is plenty slow enough to avoid the race, but that's 150x slowdown in your data input.
  • I missed this the first time:

    > __bis_SR_register(LPM3_bits + GIE);
    Per Data Sheet (SLASE58C) Table 5-2, wakeup from LPM3 takes 10us, so you've already lost the race. Consider using LPM0 -- it's not a long time.
  • It appears I got something working by calling a single reading to each channel in main() and printing it right after the function call.

    I'm having issues reading the on board temperature sensor as I seem to get readings that vary wildly and seem to correlate with other input channel voltages. I know I have the right channel because if I switch from 12 to 13 I get a nice stable voltage which would be the onboard reference voltage. I enabled the on board reference and the temperature sensor with the appropriate functions and before I enabled the temperature sensor, I was getting 0V on channel 12.

    The datasheet for this part is copy/paste disaster. In some spots it says the internal reference is 1.5V and then in other spots it says it is 1.2V. Which isn't helping me sort things out.

    www.ti.com/.../msp430fr2311.pdf

  • I haven't tried the temperature sensor on the FR2311, but (like most msp430s) it requires an extra long S/H time. The UG says 30us, which is about 150 ADCOSC clocks. You also have to apply the calibration out of the TLV [see also example msp430fr231x_adc10_16.c].
  • Yep, that was the issue. I just changed the sample and hold clocks and it seems to work ok.

    Thanks

**Attention** This is a public forum