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.

MSP430 used for Frequency Measuring, Timer A



I've been attempting to create a frequency measurement device with the MSP430. I'm attempting to use Timer A in capture mode, capturing two consecutive rising edges and subtracting their "captured" count values to determine the period of an incoming square wave. This code seems to get correct periods for signals above 200hz, but when attempting to measure below 200hz, the count value is not stable and seems to fluctuate wildly.

I've posted my code below. I'm also outputting the value of the count to an LCD screen I have an have included the LCD code as well.

Any help in the right direction would be greatly appreciated.

//Libraries To Include
#include<msp430G2553.h>
#include <stdio.h>
#include <stdint.h>

/*
 * EE449 Lux-Meter
 * Andrew Vocaire-Tramposch & Sukjinder Singh
 * Wilson - Section 04
 * November 5th 2013
 */

#define LCM_DIR P2DIR  // Port 2 OUTPUT DIRECTION
#define LCM_OUT P2OUT  // Port 2 OUTPUT BITS

                        // MSP       LCM
#define LCM_PIN_EN BIT0 // P2.0       6
#define LCM_PIN_RW BIT1 // P2.1       5
#define LCM_PIN_RS BIT2 // P2.2       4

#define LCM_PIN_D4 BIT4 // P2.4       11
#define LCM_PIN_D5 BIT5 // P2.5       12
#define LCM_PIN_D6 BIT6 // P2.6       13
#define LCM_PIN_D7 BIT7 // P2.7       14

#define LCM_PIN_MASK  ((LCM_PIN_RS | LCM_PIN_RW | LCM_PIN_EN | LCM_PIN_D7 | LCM_PIN_D6 | LCM_PIN_D5 | LCM_PIN_D4))

#define TRUE 1
#define FALSE 0

//Variables for more readable code
/*volatile static unsigned int i;
volatile static unsigned long freq;
volatile static unsigned long FootCandles;
volatile static unsigned long counter;
volatile static unsigned long prevccr;
volatile static unsigned long ccrvalue;
*/

unsigned int buffer[100];
unsigned int i=0;
unsigned int Current_Edge_Time;
unsigned int Previous_Edge_Time;

void set_clk_1Mhz(void)
{
    //1Mhz
      if (CALBC1_1MHZ==0xFF)                    // If calibration constant erased
      {
        while(1);                               // do not load, trap CPU!!
      }
      DCOCTL = 0;                               // Select lowest DCOx and MODx settings
      BCSCTL1 = CALBC1_1MHZ;                    // Set range
      DCOCTL = CALDCO_1MHZ;
}

void set_clk_8Mhz(void)
{
    //8Mhz
      if (CALBC1_8MHZ==0xFF)                    // If calibration constant erased
      {
        while(1);                               // do not load, trap CPU!!
      }
      DCOCTL = 0;                               // Select lowest DCOx and MODx settings
      BCSCTL1 = CALBC1_8MHZ;                    // Set range
      DCOCTL = CALDCO_8MHZ;
}

void set_clk_16Mhz(void)
{
     //16Mhz
      if (CALBC1_16MHZ==0xFF)                   // If calibration constant erased
      {
        while(1);                               // do not load, trap CPU!!
      }
      DCOCTL = 0;                               // Select lowest DCOx and MODx settings
      BCSCTL1 = CALBC1_16MHZ;                   // Set range
      DCOCTL = CALDCO_16MHZ;                    // Set DCO step + modulation*/
}

//Function to read in data to the LCM
void PulseLcm()
{
    //
    // pull EN bit low
    //
    LCM_OUT &= ~LCM_PIN_EN;
    __delay_cycles(20000);

    //
    // pull EN bit high
    //
    LCM_OUT |= LCM_PIN_EN;
    __delay_cycles(20000);

    //
    // pull EN bit low again
    //
    LCM_OUT &= (~LCM_PIN_EN);
    __delay_cycles(20000);
}

//Function to send a byte to the LCM through 4-bit mode
void SendByte(char ByteToSend, int IsData)
{
    //
    // clear out all pins
    //
    LCM_OUT &= ~(LCM_PIN_MASK);
    //
    // set High Nibble (HN) -
    // usefulness of the identity mapping
    // apparent here. We can set the
    // DB7 - DB4 just by setting P1.7 - P1.4
    // using a simple assignment
    //
    LCM_OUT |= (ByteToSend & 0xF0);

    if (IsData == TRUE)
    {
        LCM_OUT |= LCM_PIN_RS;
    }
    else
    {
        LCM_OUT &= ~LCM_PIN_RS;
    }

    //
    // we've set up the input voltages to the LCM.
    // Now tell it to read them.
    //
    PulseLcm();
     //
    // set Low Nibble (LN) -
    // usefulness of the identity mapping
    // apparent here. We can set the
    // DB7 - DB4 just by setting P1.7 - P1.4
    // using a simple assignment
    //
    LCM_OUT &= ~(LCM_PIN_MASK);
    LCM_OUT |= ((ByteToSend & 0x0F) << 4);

    if (IsData == TRUE)
    {
        LCM_OUT |= LCM_PIN_RS;
    }
    else
    {
        LCM_OUT &= ~LCM_PIN_RS;
    }

    //
    // we've set up the input voltages to the LCM.
    // Now tell it to read them.
    //
    PulseLcm();
}

//The initialization function for the LCM
void InitializeLCM(void)
{
    //Power on
    //Initialize direction of Port1 pins, and clear the output on Port1
    LCM_DIR = LCM_PIN_MASK;
    LCM_OUT = ~LCM_PIN_MASK;

    //delay 30ms
    __delay_cycles(400000);

    //Function Set
    LCM_OUT = 0x20;
    PulseLcm();

    LCM_OUT = 0x20;
    PulseLcm();

    LCM_OUT = 0xC0;
    PulseLcm();

    //Delay > 39us
    __delay_cycles(10000);

    //Display Set
    LCM_OUT = 0x00;
    PulseLcm();

    LCM_OUT = 0xF0;
    PulseLcm();

    //Delay > 39us

    //Display Clear
    LCM_OUT = 0x00;
    PulseLcm();

    LCM_OUT = 0x10;
    PulseLcm();

    //Delay 1.53ms
    __delay_cycles(30000);

    //Entry Mode Set

    LCM_OUT = 0x00;
    PulseLcm();

    LCM_OUT = 0x60;
    PulseLcm();

    //Initialization Complete
}

void PrintStr(char *Text)
{
    char *c;
    c = Text;

    while ((c != 0) && (*c != 0))
    {
        SendByte(*c, TRUE);
        c++;
    }
}

void UpdateLCM(void)
{
    char buf1[9]; //buffers for sprintf string creation function

    SendByte(0x80, FALSE);
    sprintf(buf1, "FQ: %d Hz", buffer[i-1]);
    PrintStr(buf1);
}

void main(void)
{
  WDTCTL = WDTPW | WDTHOLD;                 // Stop watchdog timer

  set_clk_1Mhz();                           // set SMCLK to 1Mhz

  P2SEL = 0x00; // Clear the P2SEL pins 6 and 7 to make these pins GPIO pins.
  P2SEL2 = 0x00;

  InitializeLCM();                          // Initalize the LCM for outputing

// initialize long counter
  //counter = 0;

  // initialize GPIO (Optimalized)
  P1DIR = BIT0 + BIT6;   // Set P1.0 out,1.1 input dir
  P1OUT &= ~BIT0 + ~BIT6; // LEDs off
  P1DIR &= ~BIT2;                     //Port 1.2/TACCR1 - Input
  P1SEL |= BIT2;                      // Select Timer Capture option

  // Initialize Timer_A0
  // TACTL register
  // BIT 9-8 - 00 - Source: SMCLK (TASSEL_2)
  // BIT 7-6 - 00 - Input divider: /1
  // BIT 5-4 - 10 - Mode: Continuous (MC_2)
  // BIT 1   -  1 - Enable TAIV interrupt (TAIE)
  TACTL = TASSEL_2 + TACLR;  // SMCLK

  // Initialize compare block 0
  // CAP = 1 - Use capture mode
  // SCS = 0 - Asynchronous capture
  // CM1 - Capture on rising edge
  // CCIE - External input
  TACCTL1 = CM_1 + CCIS_0 + SCS + CAP + CCIE;

  Current_Edge_Time = 0x00;
  Previous_Edge_Time = 0x00;
  i=0;

  TACTL |= MC_2;

  _enable_interrupts(); // Enable interrupts

  while(1)
  {
	 UpdateLCM();
  }
}

//Timer_A1 TACCR0 Interrupt Vector Handler Routine
#pragma vector = TIMER0_A1_VECTOR
__interrupt void TA1_ISR(void)
{
  switch (__even_in_range(TAIV, 10))        // Efficient switch-implementation
    {
      case 0: break;
      case 2:                                 // CCR1 capture interrupt
        P1OUT ^= BIT0;                        // P1.0 toggle every rising input edge

        Current_Edge_Time = TACCR1;
        if(Current_Edge_Time < Previous_Edge_Time)
        {
            buffer[i++] = ((0xFFFF - Previous_Edge_Time) + Current_Edge_Time);
        }
        else
        {
            buffer[i++] = Current_Edge_Time - Previous_Edge_Time;
        }
        Previous_Edge_Time = Current_Edge_Time;

        if (i>100)
        {
          i=0;
          __no_operation();                       // PLACE BREAKPOINT HERE
        }
        break;
      case 4: break;                          // CCR2 interrupt
      case 6: break;                          // Reserved
      case 8: break;                          // Reserved
      case 10:P1OUT ^= BIT6; break;           // Timer Overflow
   }
}

  • My freq meter is working from 1 Hz til 50 MHz, with only one measurement range...

    http://forum.43oh.com/topic/3317-msp430f550x-based-frequency-meter

    If it is based on DCO, and not exetrnal XTAL, used for MCLK / timer reference, result will be floating with too big error.

  • I like the idea behind your implementation but the C source code that another user posted doesnt seem to be for continuous measurement of a changing frequency. If you could provide a little more insight into the code that would be fantastic.

  • Hello,

    I have a project last year to measure motorcycle RPM, which is also a low frequency signal.

    At first attempt, I use the same method as you, but then I realized that high frequency measurement and low frequency measurement should be done in a different way.

    200 Hz would be 5 mS in time, and you are using 1uS timer (no frequency divider as I see). In my opinion, att some point the timer TCCR value will be overflowed, e.g. the 1st edge is captured at TCCR = 63000, and the second edge is captured at TCCR = 2465 (since 16 timer buffer is overflowed because 5 mS time is equal to count 5000 times).

    My method is to use a global variable called "tick" and set Timer as Compare Match Mode with Interrupt. Lets say I made the timer to do 10 uS Compare Match, at each interrupt the tick value will be incremented. At the detection of the second edge, the tick value is processed as frequency (like f = 1 / tick_in_10uS) ), then reset the tick value to zero.

    Of course my method require the edge to be detected at another External Interrupt pin, but this method is proven good to measure low frequency signals, at least for me, it proves right even to measure 1 Hz frequency.

  • Is your "200Hz" threshold based on your signal generator or your LCD?

    I ask because your LCD says "Hz" but actually displays microseconds, which is (proportional to) the inverse of Hz. Thus smaller actual Hz implies a larger value to display.

    > char buf1[9]; //buffers for sprintf string creation function

    > sprintf(buf1, "FQ: %d Hz", buffer[i-1]);

    This will overflow buf1[] for any value greater than 9(usec). The larger the value, the higher the probability that it will cause visible damage. At 4 digits (1/200Hz = 5000usec), it's quite possible your MCU is resetting over and over. I suggest you use snprintf() instead.

    That said, I agree with the other posters that for a human-directed display (your LCD only updates about once/sec) computing edge deltas is somewhat roundabout; it would by contrast be appropriate if you were trying to detect transient events. For this application it would probably be better to just count edges over a fixed timespan. This could be done with the capture unit, but could also be done with a simple pin interrupt.
  • It is very desirable to know the theory. To do this, you can read a book "Fundamentals of the Electronic Counters".

**Attention** This is a public forum