This PIC frequency counter circuit uses a multiplexed seven segment display and uses timer 1 to count edges of the input signal.
It uses the simpler method of direct frequency measurement which is easy to do but means that the number of digits displayed depends on the input frequency.
Note: If you want to display all digits all the time there is a technique calledreciprocal counting - but this requires floating point (maybe fixed point) routines and would be difficult to implement with this hardware since it needs constant time routines to count accurately.
Specification of the frequency counter circuit
Min frequency | 1Hz |
Max frequency | ~50MHz (limited by input pin characteristics). |
Input signal level | TTL |
The major difference in this project is that the display must be continuously refreshed so that your eye is fooled into thinking that display as is not flickering (persistence of vision).
For this frequency counter circuit project the display is refreshed every millisecond which is excessive - but does work. This refresh rate was chosen due to the timing period of the gate loop (999us) and allows easier constant time operation. Your eye only needs a refresh rate of about 50Hz or 20ms.
source code for pic 16f877a
//////////////////////////////////////////////////////////////////////
//
// File: Frequency-counter-4MHz-7seg-TMR1.c
//
// Author: rohith t
//
// Description:
//
// Measure frequency using TMR1 up to about 50MHz (depends on PIC chip input).
//
// Displays result on 8 x 7segment display.
//
// Compiler : mikroC, mikroElektronika C compiler
// for Microchip PIC microcontrollers
// Version: 5.0.0.3
//
// Note Testing:
//
// Tested on 16F877A
//
// Note measurement accuracy:
//
// The basic accuracy of the unit is set by the crystal oscillator accuracy.
//
// Requirements:
//
// Xtal clock : 4MHz
// (Use any type but better (smaller) ppm specification = better accuracy).
// (for different xtal frequency - recode frequency measurement part & tune).
//
// Target : 16F877A
//
// Accuracy: uses ONLY the main crystal clock as a reference
// - so the counter accuracy depends on the crystal.
//
// Note : re-tune gate_loop when ever code is added/removed.
//
// Check: By using simulator timer see code in gate.c (gate_loop)
// and here.
//
// Version:
// 1.01 16/Feb/06 - Change compiler to 5.0.0.3
// Changed to using RE0 and RE1 as 4017 controls.
// (Frees up analogue ports for other projects was A0,1).
// - more efficient so need to re-code time critical parts.
// 1.00 - Initial release.
//
//
//////////////////////////////////////////////////////////////////////
#include "bit.h"
#include "delay.h"
#include "gate.h"
#include "bcd.h"
#include "sevensegment.h"
//#include "usart_support.h"
//////////////////////////////////////////////////////////////////////
// Globals
//////////////////////////////////////////////////////////////////////
// globals for time critical code - to force compiler to keep.
volatile unsigned short sdmy=0; // for code critical section sdmy must not be optimised out
volatile unsigned int udmy=0; // for code critical section sdmy must not be optimised out
volatile unsigned int st_TMR1O; // updated in gate.c
// controls on this code comment out the following to stop using LCD
//#define USE_MY_LCD
//#define USE_LCD
//#define USE_USART
// This macro saves typing a lot of code.
#define REFRESH_7SEG display_str_8seg7(&op[2],dpIdx)
////////////////////////////////////////////////////////////////////////
void init_ports(void) {
ADCON1 = 0x06; // all digital outputs
PORTA = 0;
TRISA = 0xFF; // inputs
PORTB = 0;
TRISB = 0; // out
PORTC = 0x01; // b0 = 1 for 7seg
TRISC = 0x81; // B7 = i/p for USART, RC0 T1CKI-Timer1 i/p.
PORTD = 0;
TRISD = 0; // out
PORTE = 0;
TRISE = 0; // out
}
////////////////////////////////////////////////////////////////////////
// Start here
//
void main() org 440 {
char op[12] ; // Display string for seven segment display
unsigned short dpIdx=20 ; // Decimal point index into seven segment.
unsigned int i ; // loop counter
unsigned short st_TMR1L ; // Store Timer 1 low byte (after calced).
unsigned short st_TMR1H ; // store Timer 1 high byte time.
unsigned short refresh ; // refresh display interval count (during calcs).
unsigned long df = 0 ; // dispFlay frequency value to use.
unsigned short blinkc=0 ; // blink counter
#ifdef USE_LCD
char lcdop[4];
#endif
#ifdef USE_MY_LCD
char lcdop[4];
#endif
init_ports();
init_str_8seg7();
#ifdef USE_USART
USART_Init(2400);
#endif // USE_USART
// Timer 1
// prescale = 0, oscillator off, timer on, external clock, unsynchronized.
T1CON = (1<<TMR1ON) | (1<<TMR1CS) | (1<<T1SYNC);
// Enable Timer 1 interrupt flag so that the flag is updated
// interrupt not used just its output.
PIE1 = (1<<TMR1IE) ; // TMR1 overflow flag is enabled and can be polled.
// Note: actual interrupts not enabled.
#ifdef USE_MY_LCD
_LCD_init();
_LCD_Cmd(LCD_CLEAR);
_LCD_Cmd(LCD_CURSOR_OFF);
#endif
#ifdef USE_LCD
LCD_Config(&PORTB,2,1,0,7,6,5,4); // RS,E,W,D7..4
LCD_Cmd(LCD_CLEAR);
LCD_Cmd(LCD_CURSOR_OFF);
#endif
for(;;) {
// blink the led on port C.
if (blinkc>0) { // processor alive indicator
setBit(PORTC,2);
} else {
resBit(PORTC,2);
}
blinkc=~blinkc;
////////////////////////////
// Calculate frequency from timer 1
//
if (!(st_TMR1L==0 && st_TMR1H==0 && st_TMR1O==0) ) {
// Fails in 5.0.0.3
// df =( ((unsigned long) st_TMR1L) ) + \
// ( ((unsigned long) st_TMR1H)<<8 ) + \
// ( ((unsigned long) st_TMR1O)<16);
df = (unsigned long) st_TMR1L + (unsigned long) st_TMR1H * 256 + 65536 * (unsigned long) st_TMR1O;
} else {
df = 0;
}
REFRESH_7SEG;
// convert long to the display string.
ulong2bcd_p1(df); // conversion 1st part.
REFRESH_7SEG;
for (i=0;i<7;i++) {
ulong2bcd_process(void); // conversion 2nd part.
REFRESH_7SEG;
}
process_ulong2str(op);
REFRESH_7SEG;
// Initialize ready for next gate time
st_TMR1L = 0;
st_TMR1H = 0;
st_TMR1O = 0;
//////////////////////////
// GATE and REFRESH
//
// TUNE THIS GATE TIME to 1 second.
//
// 1. Tune the gate_loop first in gate.c
// 2. Then tune here (the overall gate time).
//
// 1.Break on
// gate_loop(1000,op);
// 2. reset the stop watch
// 3. step through until landing on statement:
// TMR1L_st = TMR1L;
//
// Check simluation stopwatch result is 1 second.
//
TMR1H = 0; // clear timer1 high count - the timer1 hardware.
TMR1L = 0; // clear timer1 low count - the timer1 hardware.
PIR1 &= ~(1<<TMR1IF); ; // clear Timer1 overflow bit.
//////////////////////////
// Start of gate time
//
gate_loop(1000,op); // op is the seven segment display string
delay_500_us();
delay_100_us();
delay_100_us();
delay_10_us();
delay_10_us();
delay_10_us();
asm {
CLRW
CLRW
CLRW
CLRW
CLRW
}
st_TMR1L = TMR1L; // get counter values before it updates
st_TMR1H = TMR1H;
/// End of gate time
////////////////////
// test for overflow after tune delay
if ( PIR1 & (1<<TMR1IF) ) {
PIR1 &= ~(1<<TMR1IF); // clear the bit.
st_TMR1O += 1; // increase high count.
}
#ifdef USE_MY_LCD
// lots of refresh needed to keep approx 1ms 7seg refresh rate
_LCD_Print(2,1,"FREQ:"); REFRESH_7SEG;
_LCD_Print(2,6,op); REFRESH_7SEG;
_LCD_Print(1,1,"H"); REFRESH_7SEG;
ByteToStr(st_TMR1H,lcdop); REFRESH_7SEG;
_LCD_Print(1,2, lcdop); REFRESH_7SEG;
_LCD_Print(1,9,"L"); REFRESH_7SEG;
ByteToStr(st_TMR1L,lcdop); REFRESH_7SEG;
_LCD_Print(1, 10, lcdop); REFRESH_7SEG;
#endif
#ifdef USE_LCD
// lots of refresh needed to keep approx 1ms 7seg refresh rate
LCD_Out(2,1,"FREQ:"); REFRESH_7SEG;
LCD_Out(2,6,op); REFRESH_7SEG;
LCD_Out(1,1,"H"); REFRESH_7SEG;
ByteToStr(st_TMR1H,lcdop); REFRESH_7SEG;
LCD_Out(1,2, lcdop); REFRESH_7SEG;
LCD_Out(1,9,"L"); REFRESH_7SEG;
ByteToStr(st_TMR1L,lcdop); REFRESH_7SEG;
LCD_Out(1, 10, lcdop); REFRESH_7SEG;
#endif
#ifdef USE_USART
// test shift left in 5.0.0.3
// st_TMR1O=5;
// st_TMR1H=20;
// df = 70 + 21 * 256 + 65536 * st_TMR1O; // OK
// df = 70 + 21 * 256 + st_TMR1O<<16; // fails
// USART_Print("LONG:");
// LongToStr(df,bcd_ascii); // check that df is accurate
// USART_Println(bcd_ascii);
USART_Print("FREQ:");
USART_Println(op);
USART_Print("H:");
ByteToStr(st_TMR1H,op);
USART_Print(op);
USART_Print(" L:");
ByteToStr(st_TMR1L,op);
USART_Print(op);
USART_Print(" O:");
WordToStr(st_TMR1O,op);
USART_Println(op);
#endif // USE_USART
}
}