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

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)
{

P2OUT &= ~LCD_CE;
do {
do {
P1OUT &= ~LCD_CLK;
P1OUT |= LCD_DATA;
} else {
P1OUT &= ~(LCD_CLK | LCD_DATA);
}
P1OUT |= LCD_CLK;
if(!type) P2OUT &= ~LCD_DC;
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);
}

```
##### Share on other sites

Good project oPossum. What do you mean by gate time. Does it mean sample time? 1sec for

Good font too!

##### Share on other sites

So can I use this to check how fast a given bus is going? Mainly, can I tie this to a i2c sdc line and check if it's anywhere close to 100khz or 400khz?

##### Share on other sites

Forgive me if I seem dense, but why do you have the LCD reset pin tied to the D/C pin via resistor?

##### Share on other sites

Look at the code and you will understand.

Pulling D/C low for a "long" time will slowly discharge 100n capacitor causing 5110 to reset.

Very clever way to save a pin a still have a way to do a hardware reset. :thumbup:

##### 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

##### 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.

##### Share on other sites

One of the obvious problems is the solder mask under the battery - it shouldn't be there. It is correct in Eagle, so something went wrong somewhere.

##### Share on other sites

Thanks!

What is that steel thing in the middle, bottom left. Is that a stand?

##### Share on other sites

Looks like a pin clasp from a button machine type button.

##### Share on other sites

It can be used as an electronic name badge.

Frugal genius.

##### 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
}

```

##### 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.

##### Share on other sites

Thanks Slo,

Could you post a short video/picture of the LCD running your project.

## Join the conversation

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

×   Pasted as rich text.   Paste as plain text instead

Only 75 emoji are allowed.

×   Your previous content has been restored.   Clear editor

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

×
×
• Blog

• #### Activity

×
• Create New...