4
\$\begingroup\$

I'm trying to build a simple guitar tuner using MSP430G2452. The guitar's signal will come through its pickups, via an amplifier and 1kHz filter that I built using LM358.

== Context ==

Now, my concept of pitch detection is loosely based on zero crossings. I'm counting the number of peaks that I get by keeping track of 3 samples at a time. If the middle sample is greater than the two on the sides, it's a peak. This way, I count the occurrence of the peaks and find the pitch (which can either be the fundamental or one of the harmonics).

I've tested this using recorded guitar notes and Python on my PC, and it seems to work in theory.

== Now, my problem ==

I want the ADC to be triggered at my desired rate of say, 2kHz. For this, I'm using a timer interrupt enables and starts the ADC conversion. The ADC interrupt processes my sample and sets the timer again. This cyclic process is supposed to give me a sample every 1/2000 seconds.

However, I've noticed that the timer doesn't work as expected for very low counts.

Let's say I have the timer clock at 12kHz (VLOCLK) and the count as 6 to get an interrupt every 1/2000 sec. I set an LED to toggle in the timer interrupt to be able to view it. Naturally, the frequency is so fast that the LED should appear to be completely glowing. But instead, the LED blinks once at 4 or 5 seconds. It seems as if the timer is counting all the way up to the highest value, and coming back to the 6, and only then an interrupt is generated.

This only happens if the value of the counter is too low. I have a rough code below. This is a slightly older version that the current that I have. But it should give a good idea of what I'm trying to achieve. (In the code below, the timer has been set interrupt once per second, but doesn't work quite well for the low intervals I want)

#include <msp430g2452.h>

#define TIMER_COUNT 12000
volatile unsigned int value = 0;

void configClocks();
void configTimer();
void configADC();

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

    P1DIR = 0xFF;
    P1DIR &= ~BIT5;
    P1SEL |= BIT5;

    configClocks();
    configTimer();
    configADC();

    TACCR0 = TIMER_COUNT;
    TACTL |= MC_2;

    __enable_interrupt();

    while (1)
    {
    }
}

void configClocks()
{
    //Set basic clock
    DCOCTL = CALDCO_1MHZ; //DCO 1mhz
    BCSCTL1 = CALBC1_1MHZ; //Aclk 1mhz
    BCSCTL2 = SELM_0 + DIVM_0 + SELS + DIVS_0; //Master 1 Mhz, SMCLK = VLO = 12
}

void configTimer()
{
    TACTL = TASSEL_2 + ID_0 + MC_0; // SMLCK, No div, Stopped
    TACCTL0 = CCIE;
}

void configADC()
{
    ADC10CTL1 = INCH_5 + ADC10SSEL_1;
    ADC10CTL0 = SREF_0 + ADC10SHT_3 + ADC10ON + ADC10IE;
    ADC10AE0 |= BIT5;
}

// Timer A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A (void)
{
    P1OUT ^= 0x01;
    ADC10CTL0 |= ENC + ADC10SC;
}

//ADC10 interrupt routine
#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR (void)
{
    value = ADC10MEM;
    P1OUT ^= BIT6;
    TACCR0 += TIMER_COUNT; //Set timer count
    //__bic_SR_register_on_exit(CPUOFF);        // Return to active mode
}

== Another approach I'm thinking ==

There could be something I'm overlooking, or my idea itself might be fundamentally flawed. The other plan I thought was to have everything inside a while loop. Something like below:

while (1)
{
    //delay for 1/2000 time
    //enable ADC
    //SLEEP (will be woken up by ADC interrupt
    value = ADC10MEM
    //do something meaningful with the value
}

I have the try to above one. But I've put that in just in case one of you can capture any flaws in it, or point me to a better way of doing it.

\$\endgroup\$
1
\$\begingroup\$

The MSP430G2452 has three Timer A bugs in both revisions, any of which might be affecting you. They can be read in the Errata for the chip. I believe maybe Bug TA12:

Interrupt is lost (slow ACLK):
Timer_A counter is running with slow clock (external TACLK or ACLK)compared to MCLK. The compare mode is selected for the capture/compare channel and the CCRx register is incremented by one with the occurring compare interrupt (if TAR = CCRx). Due to the fast MCLK the CCRx register increment (CCRx = CCRx+1) happens before the Timer_A counter has incremented again. Therefore the next compare interrupt should happen at once with the next Timer_A counter increment (if TAR = CCRx + 1). This interrupt gets lost.

Workaround: Switch capture/compare mode to capture mode before the CCRx register increment. Switch back to compare mode afterwards.

Even if this is not the case, you can easily fix this. You are using the default 1MHz Master clock. You can change this to 2 MHz or 4 MHz with a simple code change, at which point the same divider would produce a VLO of 24KHz or 48Khz. This would allow you to set a timer count a bit higher, again either 12 or 24, avoiding the lower count issue. Of course, testing is needed to be sure.

Your alternative plan also works. If you are not too concerned with low power, simple delays would work. Of course, as this is running on the Master clock of 1Mhz instead, you will need to calculate an apropos delay, and realize that there will be some slight drift.

\$\endgroup\$
1
\$\begingroup\$

It looks like you're putting the timer in Continuous mode (MC_2), in which case the timer will count up to 65535 before it returns to 0. It will interrupt whenever it hits 6 (or 12000 or whatever the value of TA0CCR0 is), but it will not reset the counter to 0 in that case -- which means the first interrupt may happen in 6 ticks, but the period of the interrupt will be 65536.

I think you want Up mode (MC_1), which will reset the counter when it reaches the target value.

You may also want to clear the timer (TACLR) as you turn it on, just so you know the timer is starting from zero.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.