About Me

Sunday, September 26

PIC FREQUENCY COUNTER USING 16F877A


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 frequency1Hz
Max frequency~50MHz (limited by input pin characteristics).
Input signal levelTTL



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

   }
}