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.

ADC10 with Timer A

Other Parts Discussed in Thread: MSP430G2553

Hi all. I'm having trouble with the ADC10 in my msp430g2553. I want to have 2 signals as outputs in P1.1 and P1.2, and want to measure an external voltage using P1.5. I want to do all this at a rate of about 3Hz, which I am achieving. I have pasted lots of external codes that work separately into one, but they don't work altogether. Here is my code, if anyone could tell me what is wrong it would be greatly appreciated. There might be several mistakes as I am just learning how to program in C. 

#include <msp430g2553.h>

#define LED0 BIT0
#define LED1 BIT6

unsigned int esp;
unsigned int value=0;

void ConfigureAdc(void)
{
/* Configure ADC Channel */
ADC10CTL1 = INCH_5 + ADC10SSEL_3; // Channel 5, Elijo SMCLK
ADC10CTL0 = SREF_1 + ADC10SHT_3 + ADC10ON + ADC10IE; //Vcc & Vss as reference
ADC10AE0 |= BIT5; //P1.5 ADC option
}

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

//Calibrate DCO for 1MHz operation
// if (CALBC1_1MHZ ==0xFF || CALDCO_1MHZ == 0xFF)
// {
// while(1); // If cal constants erased, trap CPU!!
// }

// BCSCTL1 = CALBC1_1MHZ;
// DCOCTL = CALDCO_1MHZ;

BCSCTL3 |= LFXT1S_2; // LFXT1 = VLO
IFG1 &= ~OFIFG; // Clear OSCFault flag
//_BIS_SR(SCG1 + SCG0); // Stop DCO
BCSCTL2 |= SELM_3 + SELS; // SMCLK = MCLK = VLO = 12kHz


P1SEL |= 0x16; // P1.1 - P1.2, P1.4 option select
P1SEL2 |= 0x10; // P1.4 option select
P1DIR |= 0x17; // P1.0 - P1.2, P1.4 outputs
P1OUT = 0;

TACCTL0 = OUTMOD_4 + CCIE; // CCR0 toggle, interrupt enabled
TACCTL1 = OUTMOD_4 + CCIE; // CCR1 toggle, interrupt enabled
TACCTL2 = OUTMOD_4 + CCIE; // CCR2 toggle, interrupt enabled
TACTL = TASSEL_2 + MC_2 + TAIE; // SMCLK, Contmode, int enabled

_BIS_SR(LPM0_bits + GIE); // Enter LPM0 w/ interrupt

//WDTCTL = WDTPW + WDTHOLD; // Stop WDT
//BCSCTL1 = CALBC1_1MHZ; // Set range
//DCOCTL = CALDCO_1MHZ;
//BCSCTL2 &= ~(DIVS_3); // SMCLK = DCO = 1MHz
P1DIR |= LED0 + LED1;
P1SEL |= BIT5; //ADC Input pin P1.5
//P1OUT &= ~(LED0 + LED1);

ConfigureAdc();
__enable_interrupt(); // Enable interrupts.

while(1)
{
__delay_cycles(1000); // Wait for ADC Ref to settle
ADC10CTL0 |= ENC + ADC10SC; // Sampling and conversion start
__bis_SR_register(CPUOFF + GIE); // LPM0 with interrupts enabled
value = ADC10MEM;
if (value>200)
{
P1OUT &= ~(LED0 + LED1);
P1OUT |= LED0;
}
else
{
P1OUT &= ~(LED0 + LED1);
P1OUT |= LED1;
}
}
}

// Timer A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A0 (void)
{
if(TACCTL0 & CCI) // If output currently high
{
if (esp == 0){
TACCR0 += 2250;
//esp += 1;
}
else{
TACCR0 += 2000; // 50% high
}
}
else
{
TACCR0 += 1750; // 50% low
}
}

// Timer A1 Interrupt Vector (TA0IV) handler
#pragma vector=TIMER0_A1_VECTOR
__interrupt void Timer_A1(void)
{
switch( TA0IV )
{
case 2: if(TACCTL1 & CCI) // If output currently high
{
TACCR1 += 500; // 12.5% high
}
else
{
TACCR1 += 3500; // 87.5% low
}
break;
case 4: if(TACCTL2 & CCI) // If output currently high
{
TACCR2 += 1000; // 25% high
}
else
{
TACCR2 += 3000; // 75% low
}
break;
//case 10: P1OUT ^= 0x01; // Timer overflow
//break;
default: break;
}
}

// ADC10 interrupt service routine
#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR (void)
{
__bic_SR_register_on_exit(CPUOFF); // Return to active mode
}

Thanks a lot in advance. 

  • I don't see anything obvious (doesn't mean there isn't anything)
    Only two minor things.

    First, the settlign delay for the referecne isn't needed on every loop. Only once after enabling the reference. But you don't use any reference (VCC doesn't need to settle), so if the delay is meant to slow down the loop, then the comment is misleading.

    The other thing is the following construct:

    Diego Kaulen said:
    ADC10CTL0 |= ENC + ADC10SC; // Sampling and conversion start
    __bis_SR_register(CPUOFF + GIE); // LPM0 with interrupts enabled

    If, for some reason, an interrupt interrupts main right after starting the conversion, then it may be that the conversion is already done when the ISR exits. Then the ADC10 ISR is executed and exits LPM before you have entered it at all. The very next instruciton will then enter LPM but since the ADC10 ISR was already there, there won't be any more ADC10 interrupt and main will never wake up.
    Especially since you are running the CPU on VLO while the ADC converts with ADC10OSC speed, this is a likely situation. You must _disable_interrupt(); and also NOP(); right before starting the conversion to avoid this deadlock.

    About the timer ISRs, both of your 'PWM's have a cycle time of 4000 timer ticks (you say, you want only two signals, even though you actually program 3). in this case, you can setup the timer to do the PWM output in hardware CCR0 controls the cycle length (3999) of the tiemr (running in up mode) and CCR1/2 control the duty cycle of their two outputs, using the set/reset or reset/set output mode. No need for any software intervention once this has been set.

  • Thanks a lot for your answer. The problem is that, in the end, I want to have 6 different outputs (I'm still figuring out how to do that), so the hardware solution you propose will not work. 

    I finally configured the ADC10 sampling in the CCR2 register, and it works now. The problem I have now is, how can I watch the value of the ADC10 WHILE it´s working. I can watch it in debug mode with interruptions points, but I want to leave it running and see how the ADC is reading the value. Is there any way to do this? If the data were exported to a .txt file would be fine, or if I could make a plot of the values. 

    Thanks a lot again. 

  • Diego Kaulen said:
    The problem is that, in the end, I want to have 6 different outputs (I'm still figuring out how to do that), so the hardware solution you propose will not work. 

    There are MSPs with a TimerB that has 7 CCR registers, giving you up to 6 synchronous PWM outputs with different DC.
    Anotehr way is to do it with one high-frequency interrupt. YOu need to figure out the largest common divider in the timings. E.g. 100 tiemr ticks. Then you tprogram teh timer in up mode and set the cycle time to 100 timer ticks. Inside the ISR, you count up a variable for each of the outputs you want and when you reach the threshold, you raise the associated port pin, then continue to count to the second trigger point, reset the port pin and reset the variable.
    This way, you can do any number of timings with one CCR interrupt. Also, you remove the overhead of multiple ISR calls when more than one output needs a change. Just keep your CPU fast enough to do the job (worst case for all outputs) within the 100 timer ticks (or whatever the largest common divider is)

    Diego Kaulen said:
    how can I watch the value of the ADC10 WHILE it´s working. [...]I want to leave it running and see how the ADC is reading the value.

    Well, you cannot see the value of ADC10 while it is working or see how the ADC is reading ht evalue. You'd need a raster force microscope - and a lightning-fast one too :)

    I guess you mean you want to see the results in realtime.
    That's difficult.

    If you had an MSP with more I/O pins, you could jsut output the value on 10 I/O pins as digital value.
    You can send it through UARt or SPI and look at them using a logic analyzer. But since both can only send 8 bit at a time, this means either only sending the upper eight bits of the result or dividing the value into two transmissions (requiring additional interrupt work, but that's possibly something you'll need later anyway)

    The MSP itself cannot export to a .txt file. The G2553 is definitely too small to access an SD card and implement the necessary file system. You'll need an MSP with at least 2k of ram for this (without file system, 1k would be enough). So to store the values, you can send hem serially to a PC which is logging the data. See above.

    When I did the first steps with the 5438 and didn't have code for the serial port at hand, I configured a timer to output a 16 bit value: The timer was running in cont mode and CCR0 was outputting the value through its duty cycle. Later, I refined this by using a variable timing, sending the low byte and the high byte in ms as low and high times of the output signal. This allowed me to use a simple external timer/counter to show me the values.
    Of course I switched to UART debug output as soon as I got the serial code working.
    I never used a debugger for MSP debugging. really, never! (my first projects required hard realtime and a debugger, as you encountered yourself, is affecting the system, so it loses any realtime capability)

  • Thank you VERY much. Your answers have been very useful. 

**Attention** This is a public forum