Jump to content
43oh

Frequency Counter using Launchpad & Nokia 5110 LCD


Recommended Posts

This is a simple frequency counter with a range of 1 Hz to 16 MHz. It uses the 32 kHz watch crystal so the accuracy is good enough for many applications. Gate time is automatically set to 250 ms or 1 second.

 

There is no signal conditioning (front end), so it is limited to 3V logic levels signals only! It is intended to be used with digital circuits like another MSP430 or 74HCxx circuits. It is not a substitute for a proper commercial frequency counter.

 

How a frequency counter works: Fundamentals of Electronic Counters by Agilent

 

Wiring

post-2341-13513554163_thumb.png

 

 

 

main.c

#include "msp430g2553.h"
#include "lcd.h"

static const unsigned long dv[] = {             // Base 10 digit weights
      10000000,                                 // 8 digit maximum count
       1000000,                                 //
        100000,                                 //
         10000,                                 //
          1000,                                 //
           100,                                 //
            10,                                 //
             1,                                 //
             0                                  //
};

static void print_freq(unsigned long n)
{
    const unsigned long *dp = dv;
    unsigned x = 0;
    unsigned c;
    unsigned long d;
    
    while(n < *dp) {                            // Skip leading zeros
        if(*dp == 100000 || *dp == 100) x += 2; // Space between 3 digit groups
        ++dp;                                   //
        lcd_pd10(10, x, 2);                     // Print space
        x += 10;                                //
    }                                           //
    if(n) {                                     // Non-zero
        do {                                    //
            d = *dp++;                          // Get digit value
            c = 0;                              //
            while(n >= d) ++c, n -= d;          // Divide
            if(d == 100000 || d == 100) x += 2; // Space between 3 digit groups
            lcd_pd10(c, x, 2);                  // Print digit
            x += 10;                            //
        } while(!(d & 1));                      // Until all digits done
    } else                                      //
        lcd_pd10(0, x - 10, 2);                 // Print zero
}

static unsigned clock_input = 0;

void set_input(void)
{
    const unsigned char z = 0;
    lcd_pos(0, 4);
    lcd_send(&z, 84, lcd_data_repeat);
    
    switch(clock_input) {
        default:
            clock_input = 0;
        case 0:
            TACTL = TASSEL_2;
            lcd_print("Internal 16MHz", 0, 4);
            break;
        case 1:
            TACTL = TASSEL_0;
            lcd_print("Clock In P1.0", 3, 4);
            break;
#if 0           
        case 2:                                 // This should always show 32768    
            TACTL = TASSEL_1;                   //  Something is very wrong if it doesn't
            lcd_print("Internal 32kHz", 0, 4);
            break;
#endif          
    }
}

void set_gate(unsigned long f)
{
    if(WDTCTL & WDTIS0) {                       // 250 ms gate currently in use
        if(f < 800000) {                        // Switch to 1 s gate if frequncy is below 800 kHz
            lcd_print("1 Second Gate", 3, 5);
            WDTCTL = WDTPW | WDTTMSEL | WDTSSEL;
        }
    } else {                                    // 1 s gate currently in use
        if(f > 900000) {                        // Switch to 250 ms gate if frequency above 900 kHz
            lcd_print(" 250 ms Gate ", 3, 5);
            WDTCTL = WDTPW | WDTTMSEL | WDTSSEL | WDTIS0;
        }
    }
}

void main(void)
{
    unsigned long freq = 12345678L;             // Measured frequency
                                                //
    WDTCTL = WDTPW | WDTHOLD;                   // Disable watchdog reset
                                                //
    DCOCTL = 0;                                 // Run at 16 MHz
    BCSCTL1 = CALBC1_16MHZ;                     //
    DCOCTL  = CALDCO_16MHZ;                     //
    //BCSCTL3 = XCAP_1;                         // 6 pF (default)
    BCSCTL3 = XCAP_2;                           // 10 pF
    //BCSCTL3 = XCAP_3;                         // 12.5 pF
                                                //
    lcd_init();                                 // Initialize LCD                                                     
                                                //
    P1SEL |= BIT0;                              // Use P1.0 as TimerA input
    P1SEL2 &= ~BIT0;                            //
    P1DIR &= ~BIT0;                             // 
    P1OUT &= ~BIT0;                             // Enable pull down resistor to reduce stray counts
    P1REN |= BIT0;                              //
                                                //
    WDTCTL = WDTPW | WDTTMSEL | WDTCNTCL | WDTSSEL | WDTIS0; // Use WDT as interval timer
                                                // Default to 250 ms gate so that initial call to set_gate()
                                                //  will switch to 1 s gate and update the LCD
                                                //
    lcd_clear(0);                               // Clear LCD
    lcd_print("Frequency", 16, 0);              // What it is
    lcd_print("Counter", 22, 1);                //
    print_freq(freq);                           // 8 digit frequency
    set_input();                                // Set input and show on LCD
    set_gate(0);                                // Set gate time and show on LCD
                                                //
    for(; {                                   // for-ever
        freq = 0;                               // Clear frequency
        TACTL |= TACLR;                         // Clear TimerA
                                                //
        IFG1 &= ~WDTIFG;                        // Wait for WDT period to begin
        while(!(IFG1 & WDTIFG));                //
                                                //
        TACTL |= MC_2;                          // Start counting - TimerA continuous mode
                                                //
        IFG1 &= ~WDTIFG;                        //
        while(!(IFG1 & WDTIFG)) {               // While WDT period..
            if(TACTL & TAIFG) {                 // Check for TimerA overflow
                freq += 0x10000L;               // Add 1 to msw of frequency
                TACTL &= ~TAIFG;                // Clear overflow flag
            }                                   //
        }                                       //
                                                //
        TACTL &= ~MC_2;                         // Stop counting - TimerA stop mode
        if(TACTL & TAIFG) freq += 0x10000L;     // Handle TimerA overflow that may have occured between
                                                //  last check of overflow and stopping TimerA 
        freq |= TAR;                            // Merge TimerA count with overflow 
        if(WDTCTL & WDTIS0) freq <<= 2;         // Multiply by 4 if using 250 ms gate
        print_freq(freq);                       // Show on LCD
                                                //
        set_gate(freq);                         // Adjust gate time if necessary
                                                //
        if(!(P1IN & BIT3)) {                    // Check if pushbutton down
            ++clock_input;                      // Switch clock input
            set_input();                        //
        }                                       //
    }                                           //
}

lcd.h

typedef enum {
    lcd_command = 0,        // Array of one or more commands
    lcd_data = 1,           // Array of one or more bytes of data
    lcd_data_repeat = 2     // One byte of data repeated
} lcd_cmd_type;

void lcd_send(const unsigned char *cmd, unsigned len, const lcd_cmd_type type);
void lcd_home(void);
void lcd_pos(unsigned char x, unsigned char y);
void lcd_clear(unsigned char x);
void lcd_init(void);
void lcd_print(char *s, unsigned x, unsigned y);
void lcd_pd10(unsigned n, unsigned x, unsigned y);


lcd.c

#include "msp430g2553.h"
#include "lcd.h"

//static const unsigned TXD = BIT1;
static const unsigned RXD = BIT2;
static const unsigned SWITCH = BIT3;
static const unsigned LCD_CLK = BIT5;
static const unsigned LCD_BACKLIGHT = BIT6;
static const unsigned LCD_DATA = BIT7;

static const unsigned LCD_DC = BIT0;    // PORT2
static const unsigned LCD_CE = BIT1;    // PORT2

void lcd_send(const unsigned char *cmd, unsigned len, const lcd_cmd_type type)
{
    register unsigned mask;
    
    P2OUT &= ~LCD_CE;
    do {
        mask = 0x0080;
        do {
            if(*cmd & mask) {
                P1OUT &= ~LCD_CLK;
                P1OUT |= LCD_DATA;
            } else {
                P1OUT &= ~(LCD_CLK | LCD_DATA);
            }
            P1OUT |= LCD_CLK;
            mask >>= 1;
        } while(!(mask & 1));
        if(!type) P2OUT &= ~LCD_DC;
        if(*cmd & mask) {
            P1OUT &= ~LCD_CLK;
            P1OUT |= LCD_DATA;
        } else {
            P1OUT &= ~(LCD_CLK | LCD_DATA);
        }
        P1OUT |= LCD_CLK;
        P2OUT |= LCD_DC;
        if(!(type & 2)) ++cmd;
    } while(--len);
    P2OUT |= LCD_CE;
}

static const unsigned char home[] = { 0x40, 0x80 };

void lcd_home(void)
{
    lcd_send(home, sizeof(home), lcd_command);
}

void lcd_pos(unsigned char x, unsigned char y)
{
    unsigned char c[2];
    c[0] = 0x80 | x;
    c[1] = 0x40 | y;
    lcd_send(c, sizeof(c), lcd_command);
}

void lcd_clear(unsigned char x)
{
    lcd_home();
    lcd_send(&x, 504, lcd_data_repeat);
    lcd_home();
}

void lcd_init(void)
{
    static const unsigned char init[] = {
        0x20 + 0x01,    // Function set - extended instructions enabled
        //0x80 + 64,    // Set vop (contrast) 0 - 127
        0x80 + 66,      // This is better for fast LCD update
        0x04 + 0x02,    // Temperature control
        0x10 + 0x03,    // Set bias system
        0x20 + 0x00,    // Function set - chip active, horizontal addressing, basic instructions
        0x08 + 0x04     // Display control - normal mode
    };
    
    P1REN = RXD | SWITCH;
    P1DIR = LCD_CLK | LCD_BACKLIGHT | LCD_DATA;
    P1OUT = LCD_CLK | RXD | SWITCH | LCD_BACKLIGHT;
    
    P2REN = 0;
    P2DIR = LCD_DC | LCD_CE;
    P2OUT = LCD_CE;
    
    __delay_cycles(20000);
    P2OUT |= LCD_DC;
    __delay_cycles(20000);

    lcd_send(init, sizeof(init), lcd_command);
}

static const unsigned char font6x8[96][5] = {
    0x00, 0x00, 0x00, 0x00, 0x00, // 20  32   <space>
    0x00, 0x00, 0x5F, 0x00, 0x00, // 21  33   !
    0x00, 0x07, 0x00, 0x07, 0x00, // 22  34   "
    0x14, 0x7F, 0x14, 0x7F, 0x14, // 23  35   #
    0x24, 0x2A, 0x7F, 0x2A, 0x12, // 24  36   $
    0x23, 0x13, 0x08, 0x64, 0x62, // 25  37   %
    0x36, 0x49, 0x56, 0x20, 0x50, // 26  38   &
    0x00, 0x08, 0x07, 0x03, 0x00, // 27  39   '
    0x00, 0x1C, 0x22, 0x41, 0x00, // 28  40   (
    0x00, 0x41, 0x22, 0x1C, 0x00, // 29  41   )
    0x2A, 0x1C, 0x7F, 0x1C, 0x2A, // 2A  42   *
    0x08, 0x08, 0x3E, 0x08, 0x08, // 2B  43   +
    0x00, 0x40, 0x38, 0x18, 0x00, // 2C  44   ,
    0x08, 0x08, 0x08, 0x08, 0x08, // 2D  45   -
    0x00, 0x00, 0x60, 0x60, 0x00, // 2E  46   .
    0x20, 0x10, 0x08, 0x04, 0x02, // 2F  47   /
    0x3E, 0x51, 0x49, 0x45, 0x3E, // 30  48   0
    0x00, 0x42, 0x7F, 0x40, 0x00, // 31  49   1
    0x42, 0x61, 0x51, 0x49, 0x46, // 32  50   2
    0x21, 0x41, 0x49, 0x4D, 0x33, // 33  51   3
    0x18, 0x14, 0x12, 0x7F, 0x10, // 34  52   4
    0x27, 0x45, 0x45, 0x45, 0x39, // 35  53   5
    0x3C, 0x4A, 0x49, 0x49, 0x30, // 36  54   6
    0x41, 0x21, 0x11, 0x09, 0x07, // 37  55   7
    0x36, 0x49, 0x49, 0x49, 0x36, // 38  56   8
    0x06, 0x49, 0x49, 0x29, 0x1E, // 39  57   9
    0x00, 0x00, 0x14, 0x00, 0x00, // 3A  58   :
    0x00, 0x00, 0x40, 0x34, 0x00, // 3B  59   ;
    0x00, 0x08, 0x14, 0x22, 0x41, // 3C  60   <
    0x14, 0x14, 0x14, 0x14, 0x14, // 3D  61   =
    0x00, 0x41, 0x22, 0x14, 0x08, // 3E  62   >
    0x02, 0x01, 0x51, 0x09, 0x06, // 3F  63   ?
    0x3E, 0x41, 0x5D, 0x59, 0x4E, // 40  64   @
    0x7C, 0x12, 0x11, 0x12, 0x7C, // 41  65   A
    0x7F, 0x49, 0x49, 0x49, 0x36, // 42  66   B
    0x3E, 0x41, 0x41, 0x41, 0x22, // 43  67   C
    0x7F, 0x41, 0x41, 0x41, 0x3E, // 44  68   D
    0x7F, 0x49, 0x49, 0x49, 0x41, // 45  69   E
    0x7F, 0x09, 0x09, 0x09, 0x01, // 46  70   F
    0x3E, 0x41, 0x49, 0x49, 0x7A, // 47  71   G
    0x7F, 0x08, 0x08, 0x08, 0x7F, // 48  72   H
    0x00, 0x41, 0x7F, 0x41, 0x00, // 49  73   I
    0x20, 0x40, 0x41, 0x3F, 0x01, // 4A  74   J
    0x7F, 0x08, 0x14, 0x22, 0x41, // 4B  75   K
    0x7F, 0x40, 0x40, 0x40, 0x40, // 4C  76   L
    0x7F, 0x02, 0x1C, 0x02, 0x7F, // 4D  77   M
    0x7F, 0x04, 0x08, 0x10, 0x7F, // 4E  78   N
    0x3E, 0x41, 0x41, 0x41, 0x3E, // 4F  79   O
    0x7F, 0x09, 0x09, 0x09, 0x06, // 50  80   P
    0x3E, 0x41, 0x51, 0x21, 0x5E, // 51  81   Q
    0x7F, 0x09, 0x19, 0x29, 0x46, // 52  82   R
    0x26, 0x49, 0x49, 0x49, 0x32, // 53  83   S
    0x01, 0x01, 0x7F, 0x01, 0x01, // 54  84   T
    0x3F, 0x40, 0x40, 0x40, 0x3F, // 55  85   U
    0x1F, 0x20, 0x40, 0x20, 0x1F, // 56  86   V
    0x3F, 0x40, 0x38, 0x40, 0x3F, // 57  87   W
    0x63, 0x14, 0x08, 0x14, 0x63, // 58  88   X
    0x03, 0x04, 0x78, 0x04, 0x03, // 59  89   Y
    0x61, 0x51, 0x49, 0x45, 0x43, // 5A  90   Z
    0x00, 0x7F, 0x41, 0x41, 0x41, // 5B  91   [
    0x02, 0x04, 0x08, 0x10, 0x20, // 5C  92   '\'
    0x00, 0x41, 0x41, 0x41, 0x7F, // 5D  93   ]
    0x04, 0x02, 0x01, 0x02, 0x04, // 5E  94   ^
    0x80, 0x80, 0x80, 0x80, 0x80, // 5F  95   _
    0x00, 0x03, 0x07, 0x08, 0x00, // 60  96   '
    0x20, 0x54, 0x54, 0x54, 0x78, // 61  97   a
    0x7F, 0x28, 0x44, 0x44, 0x38, // 62  98   b
    0x38, 0x44, 0x44, 0x44, 0x28, // 63  99   c
    0x38, 0x44, 0x44, 0x28, 0x7F, // 64  100  d
    0x38, 0x54, 0x54, 0x54, 0x18, // 65  101  e
    0x00, 0x08, 0x7E, 0x09, 0x02, // 66  102  f
    0x18, 0xA4, 0xA4, 0xA4, 0x7C, // 67  103  g
    0x7F, 0x08, 0x04, 0x04, 0x78, // 68  104  h
    0x00, 0x44, 0x7D, 0x40, 0x00, // 69  105  i
    0x00, 0x20, 0x40, 0x40, 0x3D, // 6A  106  j
    0x00, 0x7F, 0x10, 0x28, 0x44, // 6B  107  k
    0x00, 0x41, 0x7F, 0x40, 0x00, // 6C  108  l
    0x7C, 0x04, 0x78, 0x04, 0x78, // 6D  109  m
    0x7C, 0x08, 0x04, 0x04, 0x78, // 6E  110  n
    0x38, 0x44, 0x44, 0x44, 0x38, // 6F  111  o
    0xFC, 0x18, 0x24, 0x24, 0x18, // 70  112  p
    0x18, 0x24, 0x24, 0x18, 0xFC, // 71  113  q
    0x7C, 0x08, 0x04, 0x04, 0x08, // 72  114  r
    0x48, 0x54, 0x54, 0x54, 0x24, // 73  115  s
    0x04, 0x04, 0x3F, 0x44, 0x24, // 74  116  t
    0x3C, 0x40, 0x40, 0x20, 0x7C, // 75  117  u
    0x1C, 0x20, 0x40, 0x20, 0x1C, // 76  118  v
    0x3C, 0x40, 0x30, 0x40, 0x3C, // 77  119  w
    0x44, 0x28, 0x10, 0x28, 0x44, // 78  120  x
    0x4C, 0x90, 0x90, 0x90, 0x7C, // 79  121  y
    0x44, 0x64, 0x54, 0x4C, 0x44, // 7A  122  z
    0x00, 0x08, 0x36, 0x41, 0x00, // 7B  123  {
    0x00, 0x00, 0x77, 0x00, 0x00, // 7C  124  |
    0x00, 0x41, 0x36, 0x08, 0x00, // 7D  125  }
    0x02, 0x01, 0x02, 0x04, 0x02, // 7E  126  ~
    0x00, 0x06, 0x09, 0x09, 0x06, // 7F  127 degrees
};

void lcd_print(char *s, unsigned x, unsigned y)
{
    lcd_pos(x, y);
    while(*s) {
        lcd_send(&font6x8[*s - 32][0], 5, lcd_data);
        lcd_send(&font6x8[0][0], 1, lcd_data);
        ++s;
    }
}

static const unsigned char num10x16[11][9 * 2] = {      // Numbers for 10x16 cell
    0xF0,0xF8,0x0C,0x04,0x04,0x04,0x0C,0xF8,0xF0, // 0
    0x0F,0x1F,0x30,0x20,0x20,0x20,0x30,0x1F,0x0F,
    0x00,0x00,0x10,0x10,0xFC,0xFC,0x00,0x00,0x00, // 1
    0x00,0x00,0x20,0x20,0x3F,0x3F,0x20,0x20,0x00,
    0x18,0x1C,0x04,0x04,0x04,0x04,0x8C,0xF8,0x70, // 2
    0x20,0x30,0x38,0x2C,0x26,0x23,0x21,0x20,0x20,
    0x18,0x1C,0x04,0x84,0x84,0x84,0xCC,0x78,0x30, // 3
    0x18,0x38,0x20,0x20,0x20,0x20,0x31,0x1F,0x0E,
    0x00,0x80,0x40,0x20,0x10,0x08,0xFC,0xFC,0x00, // 4
    0x03,0x02,0x02,0x02,0x02,0x02,0x3F,0x3F,0x02,
    0x00,0x7C,0x7C,0x44,0x44,0x44,0xC4,0x84,0x04, // 5
    0x18,0x38,0x20,0x20,0x20,0x20,0x30,0x1F,0x0F,
    0xE0,0xF0,0x58,0x4C,0x44,0x44,0xC4,0x84,0x00, // 6
    0x0F,0x1F,0x30,0x20,0x20,0x20,0x30,0x1F,0x0F,
    0x04,0x04,0x04,0x04,0x04,0xC4,0xF4,0x3C,0x0C, // 7
    0x00,0x00,0x30,0x3C,0x0F,0x03,0x00,0x00,0x00,
    0x30,0x78,0xCC,0x84,0x84,0x84,0xCC,0x78,0x30, // 8
    0x0E,0x1F,0x31,0x20,0x20,0x20,0x31,0x1F,0x0E,
    0xF0,0xF8,0x0C,0x04,0x04,0x04,0x0C,0xF8,0xF0, // 9
    0x00,0x21,0x23,0x22,0x22,0x32,0x1A,0x0F,0x07,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // <space>
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};

void lcd_pd10(unsigned n, unsigned x, unsigned y)       // Print 10x16 digit
{
    unsigned char c[2];
    c[0] = 0x80 | x;
    c[1] = 0x40 + y; lcd_send(c, 2, lcd_command);   lcd_send(num10x16[n], 9, lcd_data);
    c[1] = 0x41 + y; lcd_send(c, 2, lcd_command);   lcd_send(num10x16[n] + 9, 9, lcd_data);
}

Link to post
Share on other sites

Sswweeeeeeetttt! Very impressive project there! That a custom PCB there? Got a picture of the whole thing?

 

Thats some very nice coding there. Thanks for all the helpful comments, without them i'd be completely lost. (perhaps I still am).

So, for the p1.0 input, you use that as an external clock and then use a hardware timerA to count based upon that external clock? However many counts timer A accumulates = frequency? Then what you call the "gate time" is how often you update the screen/reset the mcu?

 

Anyhow, very cool application and thanks for sharing :)

Link to post
Share on other sites

blu#: Yes, gate time is the sample time. It switches to 1 s at under 800 kHz, and to 250 ms at over 900 kHz. (100 kHz hysteresis).

 

cde: A frequency counter requires a steady continuous signal, so it is not really useful for IIC or SPI because they have clocks that occur in bursts.

 

neutron: It allows the MSP430 to reset the LCD as one of the first tasks in main(). This ensures the LCD will be "in sync" with the MSP430 code and the LCD will operate as expected. The D/C (data/command) line is used because it only has to be low for 1 bit of a command, and is otherwise held high. So a simple R/C filter allows it to do double duty - the brief low pulse when sending a command byte will not reset the LCD, but a longer low period will.

 

username: Yes, it is a custom PCB. There are a few minor things that have to be fixed on the next rev. I will post the CAD files once I verify everything is corrected. There are about 20 simple projects I am working on that will use that board.

 

TimerA counts for 1 second or 250 milliseconds. For the 1 second period, the count is the frequency in Hz. For the 250 ms period, the count is in units of 4 Hz. The LCD will update once every 2 seconds with a 1 second gate time, or 2 times a second with a 250 ms gate time. It may be possible to speed that up by resetting the WDT after the LCD has been updated - I have not tired that.

Link to post
Share on other sites
  • 3 weeks later...

oPossum I don't get it. How is this supposed to work?

Shouldn't main.c have lines like:

 
BCSCTL1 &=~XTS             // LFXTCLK 0:Low Freq. / 1: High Freq. 
BCSCTL3 |= XCAP_3        //  XIN/XOUT Cap : 12.5 pF
BCSCTL3 |= LFXT1S_0;            // LFXT1 = 32768 crystal 

while(IFG1 & OFIFG) 
{ 
  IFG1 &= ~OFIFG;              // Clear OSCFault flag 
  _delay_cycles(100000);       // delay for flag and visibility 
} 


Link to post
Share on other sites
  • 1 month later...

oPossum, thank you for all the great and useful code you are posting.

 

I have "forked" frequency counter and created a Frequency Counter using Launchpad & UART that can more readily be integrated in programs. It works on G2553 and sends measured frequency via UART. Code is available on github, fully working, however there is still a lot room for improvement.

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...