Frequency counter using 7-segment

Thread Starter

jj_alukkas

Joined Jan 8, 2009
753
Im trying to build a frequency counter using a pic 16F628 and the Capture pin from the CCP module on port RB3 and 2 multiplexed 7 segment displays. Did most of the coding, but I dont have any idea what values I can expect from CCPR1H and CCPR1 registers, so donno what to do with them. I think it should be 0-255 After checking a couple of existing projects using capture pin, I made it up to add together and get divided by 100.. Still It doesnt show anything on my display. I need a 30-200Hz counter, so totally confused if my routine is not initialized properly or if I mixed something up. The displays are working fine and expect to show values on 2 digits as 03-20 for 30-200hz range. Where could I have gone wrong? Please Help. Coded in Hi Tech C, no errors.

Rich (BB code):
#include <HTC.H>
#define _XTAL_FREQ 4000000                    //Internal OSC Freq

#define SEGMENT_TRIS     TRISB                //Display Segments Connected Ports
#define SEGMENT_PORT    PORTB

#define DIGIT_TRIS    TRISA                    //Display Digit select data ports
#define DIGIT_PORT    PORTA

__CONFIG(INTIO & WDTDIS & PWRTDIS & BORDIS & LVPDIS );  //Config Bits

unsigned char digits[2]={0,0};                //Defining the Digit Array
//unsigned char kontrol, control;
float counter;

void Wait(unsigned char delay)                //Delay Loops
{
    for(;delay;delay--)
        __delay_us(100);
}

void SevenSegment(unsigned char num)        //Defining the segment pattern for each digit 0-9
{
   switch(num)
   {
      case 0:
        SEGMENT_PORT= 0B01000000;            // Defined in the order xGFEDCBA segments
         break;

      case 1:
        SEGMENT_PORT= 0b11111001;
         break;

      case 2:
        SEGMENT_PORT= 0B00100100;
         break;    

      case 3:
        SEGMENT_PORT= 0B00110000;
         break;

      case 4:
        SEGMENT_PORT= 0B10011001;
         break;

      case 5:
        SEGMENT_PORT= 0B00010010;
         break;

      case 6:
        SEGMENT_PORT= 0B00000010;
         break;

      case 7:
        SEGMENT_PORT= 0B11111000;
         break;

      case 8:
        SEGMENT_PORT= 0B00000000;
         break;

      case 9:
        SEGMENT_PORT= 0B00010000;
         break;
   }
}

void DisplayInit()
{
    
    SEGMENT_TRIS=0b00001000;            //Config segment ports as outputs
    DIGIT_TRIS=0b00100000;                //Config Digit Ports as Outputs

    DIGIT_PORT=0B00000011;                //Digit select port state
    
    //Setup Timer0 for the Digit Scanning Operation
    PS0=1;                                //Prescaler is divide by 64
    PS1=0;
    PS2=1;

    PSA=0;                                //Timer Clock Source is from Prescaler

    T0CS=0;                                //Prescaler gets clock from Internal OSC
    
    T0IE=1;                                //Enable TIMER0 Interrupt
    PEIE=1;                                //Enable Peripheral Interrupt
    GIE=1;                                //Enable INTs globally

    TMR0=1;                                //Staring timer0    
}    

void Print(unsigned short num)            // This function splits a number to Ones and Tens for display on each digit
{
    
    unsigned char i=0;
    unsigned char j;
    if(num>99) return;
    while(num)
    {
        digits=num%10;                //Splitting Numbers and Storing in Arrays
        i++;

        num=num/10;
    }
    for(j=i;j<2;j++) digits[j]=0;
}    

void SevenSegISR()                        // Display Scanner which update the multiplexed Displays    
{
    
    TMR0=150;
    static unsigned char i;
    if(i==1)
        i=0;                            //Checking end of Display Scan
    else
        i++;                            //Incrementing to next digit for refresh
    
    DIGIT_PORT=((DIGIT_PORT & 0b00111111)|(1<<i)); //Display Output on the Digits
    SevenSegment(digits);            //Write the digit in the ith display.
}        

void interrupt ISR()                    //Interrupt Service Routine for Display
{
    if(T0IE && T0IF)                    //Check if TMR0 has Overflowed
    {
        SevenSegISR();                    //Call the SevenSegISR
        T0IF=0;                            //Clear Flag for Timer0
    }
    
}            

void main()

{
    CMCON=0x07;
    //unsigned int value;
    float frequency;
    TMR1CS = 0;                //Timer1 uses int clk as osc source
    TMR1IF = 0;                //Reset Timer1 Flag Bit 
    TMR1H  = 0x00;  TMR1L = 0x00;    //Reset Timer1 Registers
    T1CKPS1 = 0;  T1CKPS0 = 0; //1:1 Timer1 Prescalar
    T1SYNC = 0;                //External Clock Input sync off
    TMR1IE = 1;                //Timer1 Interrupt enabled
    TMR1ON = 1;             //Timer1 Enabled and started
    CCP1M3 = 0; CCP1M2 = 1; CCP1M1 = 0; CCP1M0 = 1;    //Capture Mode enabled on rising edge
    CCP1IE = 1;                //Prevent False Interrupts
    GIE = 1;                 //Interrupt Enable 
      PEIE = 1;                //Global Interrupts Enable    
    DisplayInit();             //Initialize the Seven Segment Displays
    while(1)
    {
        
        // if capture interrupt changed, update 'value' from CCP value
      if ( CCP1IF ) 
    {
    counter=(CCPR1H+CCPR1L)/100;
    CCP1IF = 0;
     }
     // if timer interrupt: reset timer and toggle Port B.2
    if ( TMR1IF ) 
    {
    TMR1H = 0x00;  TMR1L = 0x00;
    TMR1IF = 0;
      }
        Print(counter);
        __delay_ms(100);
        
    }
        
}
Chip is a 16F628, RB0-RB2,RB4-RB7 segment drivers... RA0-RA1- digit drivers, RB3 as capture pin, int osc, RB3 driven by optocoupler.. Timer0 for display mux refresh, Timer1 for capture interrupt. 2 led 7 segment displays muxed..

Input fed through this ckt


Sources are a 9-0-9 ac transformer, pc soundcard output using function generator and a 50Khz multimeter square wave output.. none of these could be detected..
 

Attachments

Last edited:

ErnieM

Joined Apr 24, 2011
8,377
You have a lot going on in this "simple" project, and if any piece fails you can get results such as you say.

You need to break the chain. Test each part first, then link together.

- does the 7 seg really respond to BCD data? Try giving it a few test values.

- does the isolator driver really work? You need to make sure something is coming out, if you have a scope you probably did that. If you just have a voltmeter you need to drive it on and off slowly so the voltmeter can tell you if it works.

I'll on my way out now, will check back later after I look at the CCP.

Meanwhile, two words for you: CODE TAGS !! ;)
 

Thread Starter

jj_alukkas

Joined Jan 8, 2009
753
You have a lot going on in this "simple" project, and if any piece fails you can get results such as you say.

You need to break the chain. Test each part first, then link together.

- does the 7 seg really respond to BCD data? Try giving it a few test values.
I actually started coding with the display. If I print 53, display shows 53, but when I went for this ccp section, i didnt know how to 'take' the output without a display.. so had to put everything together and that made me lose it all.. The 7 segment shows just 00 when the ccp codes are used and im getting crazy seeing global interupts everywhere. The timer0 and timer1 along with the GIE and all makes me confused. somewhere it will be 0 and somewhere its 1. I started building step by step, but now its a mess. Could you tell me anyway to check the ccp module alone so that i can test it without the display code?

does the isolator driver really work? You need to make sure something is coming out, if you have a scope you probably did that. If you just have a voltmeter you need to drive it on and off slowly so the voltmeter can tell you if it works.

Meanwhile, two words for you: CODE TAGS !!
Isolator section is fine, multimeter shows hi and lows fine for the inputs I give. overall hardware is fine I guess, its just the ccp :(

sorry abt the code tags.. I clicked on quote tags and thought as code tags :p My mistake.
 

ErnieM

Joined Apr 24, 2011
8,377
Tried the code a bit in MPLAB. I needed to change a few things, I believe you have the old compiler. Do download the current version it comes packed along with MPLAB. AFAIK the old one has some bugs. The new one has changed the definitions for __CONFIG:

Rich (BB code):
__CONFIG(FOSC_INTOSCIO & WDTE_OFF & PWRTE_OFF & BOREN_OFF & LVP_OFF ); //Config Bits
I get a compiler bark for "Print(counter);" as counter is a float and Print is looking for an unsigned short. Since your input is an integer from a counter and so is your output (number between 0 and 99 on the display) there is no reason to use floats here, do it all with integers as it will be faster and perhaps more accurate too.

You should be able to simulate this inside MPLAB just fine. You can set a frequency at RB3 using Debugger | Stimulus to set some frequency there.

I did that and saw the ISR gets hung up once your Timer1 tosses an interrupt and the ISR doesn't handle it, so it gets stuck doing the ISR over and over and over.
Your ISR needs to look something like this:
Rich (BB code):
void interrupt ISR() //Interrupt Service Routine for Display
{
//  if(T0IE && T0IF)  //Check if TMR0 has Overflowed
  if(T0IF)            //Check if TMR0 has Overflowed (no need to check T0IE)
  {
    SevenSegISR();    //Call the SevenSegISR
    T0IF=0;           //Clear Flag for Timer0
  }

  if (TMR1IF)
  {
    // do something with this information???
    TMR1IF = 0;
  }
}
Besides just doing raw code dump next time please include a description of the logic behind the code.
 

Thread Starter

jj_alukkas

Joined Jan 8, 2009
753
Thanks for your reply, I have the newer compiler, but once I installed, all my fuses showed up as errors and I thought it was a bug :p lol :p will install it now..
The thing is display uses timer0 and an interrupt for the digit selection scan and has an interrupt ISR() with just

Rich (BB code):
void interrupt ISR()            //Interrupt Service Routine for Display 
{
     if(T0IE && T0IF)                    //Check if TMR0 has Overflowed
     {         SevenSegISR();         //Call the SevenSegISR
               T0IF=0;                    //Clear Flag for Timer0
     }
}
And to run the capture module, Timer1 must be used and I didnt know where to put the interrupt and overflow checking stuffs for T1??

So should I put the second interrupt within the first function or as a second one? Thats where all the mess starts..
I'll try it the way you told.. all interrupts inside the isr()

will check and see.. thanks for the sim tip.. can watch registers there :)
 

Thread Starter

jj_alukkas

Joined Jan 8, 2009
753
Did an update to picc 9.82, updated fuses, some quick check ups and all the corrections, placed timer1 iterrupts and overflow checks inside isr routine, integer value for counter, and checked.. now it started showing some results! for a 50hz input, it shows 88 and when its off, it shows 00. I can work out the math to bring it to range, but can you tell me what will be the range for CCPR1H+CCPR1L and for what input at RB0?? In what form will I get the data? hex? binary? With that, I may be able to get an idea for the math..

Here's the updated code.

Rich (BB code):
#include <HTC.H>
#define _XTAL_FREQ 4000000                    //Internal OSC Freq

#define SEGMENT_TRIS     TRISB                //Display Segments Connected Ports
#define SEGMENT_PORT    PORTB

#define DIGIT_TRIS    TRISA                    //Display Digit select data ports
#define DIGIT_PORT    PORTA

__CONFIG(FOSC_INTOSCIO & WDTE_OFF & PWRTE_OFF & BOREN_OFF & LVP_OFF );  //Config Bits

unsigned char digits[2]={0,0};                //Defining the Digit Array
unsigned short int counter;

void Wait(unsigned char delay)                //Delay Loops
{
    for(;delay;delay--)
        __delay_us(100);
}

void SevenSegment(unsigned char num)        //Defining the segment pattern for each digit 0-9
{
   switch(num)
   {
      case 0:
        SEGMENT_PORT= 0B01000000;            // Defined in the order xGFEDCBA segments
         break;

      case 1:
        SEGMENT_PORT= 0b11111001;
         break;

      case 2:
        SEGMENT_PORT= 0B00100100;
         break;    

      case 3:
        SEGMENT_PORT= 0B00110000;
         break;

      case 4:
        SEGMENT_PORT= 0B10011001;
         break;

      case 5:
        SEGMENT_PORT= 0B00010010;
         break;

      case 6:
        SEGMENT_PORT= 0B00000010;
         break;

      case 7:
        SEGMENT_PORT= 0B11111000;
         break;

      case 8:
        SEGMENT_PORT= 0B00000000;
         break;

      case 9:
        SEGMENT_PORT= 0B00010000;
         break;
   }
}

void DisplayInit()
{
    
    SEGMENT_TRIS=0b00001000;            //Config segment ports as outputs
    DIGIT_TRIS=0b00100000;                //Config Digit Ports as Outputs

    DIGIT_PORT=0B00000011;                //Digit select port state
    
    //Setup Timer0 for the Digit Scanning Operation
    PS0=1;                                //Prescaler is divide by 64
    PS1=0;
    PS2=1;

    PSA=0;                                //Timer Clock Source is from Prescaler

    T0CS=0;                                //Prescaler gets clock from Internal OSC
    
    T0IE=1;                                //Enable TIMER0 Interrupt
    PEIE=1;                                //Enable Peripheral Interrupt
    GIE=1;                                //Enable INTs globally

    TMR0=1;                                //Staring timer0    
}    

void Print(unsigned short num)            // This function splits a number to Ones and Tens for display on each digit
{
    
    unsigned char i=0;
    unsigned char j;
    if(num>99) return;
    while(num)
    {
        digits=num%10;                //Splitting Numbers and Storing in Arrays
        i++;

        num=num/10;
    }
    for(j=i;j<2;j++) digits[j]=0;
}    

void SevenSegISR()                        // Display Scanner which update the multiplexed Displays    
{
    
    TMR0=150;
    static unsigned char i;
    if(i==1)
        i=0;                            //Checking end of Display Scan
    else
        i++;                            //Incrementing to next digit for refresh
    
    DIGIT_PORT=((DIGIT_PORT & 0b00111111)|(1<<i)); //Display Output on the Digits
    SevenSegment(digits);            //Write the digit in the ith display.
}        

void interrupt ISR()                    //Interrupt Service Routine
{
    if(T0IE && T0IF)                    //Check if TMR0 has Overflowed
    {
        SevenSegISR();                    //Call the SevenSegISR
        T0IF=0;                            //Clear Flag for Timer0
    }
    if ( CCP1IF )         // if capture interrupt changed, update 'value' from CCP value
    {
        counter=((CCPR1H+CCPR1L)/100)+20;
        CCP1IF = 0;
     }
    if ( TMR1IF )         // if timer1 register overflow: reset timer
    {
    TMR1H = 0x00;  TMR1L = 0x00;
    TMR1IF = 0;
      }
}            

void captureInit()
{
    CMCON=0x07;
    TMR1CS = 0;                //Timer1 uses int clk as osc source
    TMR1IF = 0;                //Reset Timer1 Flag Bit 
    TMR1H  = 0x00;  TMR1L = 0x00;    //Reset Timer1 Registers
    T1CKPS1 = 0;  T1CKPS0 = 0; //1:1 Timer1 Prescalar
    //T1SYNC = 0;                //External Clock Input sync off
    TMR1IE = 1;                //Timer1 Interrupt enabled
    TMR1ON = 1;             //Timer1 Enabled and started
    CCP1M3 = 0; CCP1M2 = 1; CCP1M1 = 0; CCP1M0 = 1;    //Capture Mode enabled on rising edge
    CCP1IE = 1;                //Prevent False Interrupts
    GIE = 1;                 //Interrupt Enable 
      PEIE = 1;                //Global Interrupts Enable
}
void main()

{
    DisplayInit();             //Initialize the Seven Segment Displays
    captureInit();
    while(1)
    {
        Print(counter);
        __delay_ms(1000);
        
    }
    
        
}
 

ErnieM

Joined Apr 24, 2011
8,377
I can't tell until you tell me what your logic is, what you are setting and what you expect to happen.

It is nearly impossible to reverse engineer code to get the intent. That's what comments and flow charts are for.

I can tell you that the CCPR1 registers will hold a copy of TMR1 when the interrupt happens. If we assume that TMR1 was cleared last time this happened then CCPR1 holds how many counts occurred since the last time, this is a period measurement. CCPR1 is made of two 8 bit registers, so to copy the value you need to do:

Rich (BB code):
counter=((CCPR1H<<8 + CCPR1L);
That is assuming a unsigned short int (counter) can hold 16 bits. Check that too.

If you divide how many Timer1 clocks occur in 1 second (that should be a constant) by counter then you have the frequency.
 

Thread Starter

jj_alukkas

Joined Jan 8, 2009
753
mm.. looks logical.. i tried a sim watching the registers and with an input 50hz pulse.. The thing runs, for sometime, both the 8bit CCPR1 registers value changes and after some time gives an error core halted as pc register overflowed and was reset to 0x00. And on loading to the pic, I multiplied with 0.1362 which was the value i found when the sim was run with a 50hz and registers changed.. now i get crazy results in multiples of 11 like 22 44 99 :p let me try the shifting method to get the value to counter..

if you dont mind, can you show me an example how to count number of captures between 2 timer1 interrupts? just a rough idea wud be enough, ill check commands..
 

ErnieM

Joined Apr 24, 2011
8,377
if you dont mind, can you show me an example how to count number of captures between 2 timer1 interrupts? just a rough idea wud be enough, ill check commands..
I would do the following in the ISR. Note no need to do anything here with Timer1 overflowing; if it does you have invalid data there but with a 1MHz clock a 30Hz signal the max count would be 10^6/30 = 33,333, and a 16 bit register can hold up to 65,535 without overflowing.

This compiler can handle dividing 10^6 by counter, at least it compiled here.

Rich (BB code):
void interrupt ISR()                    //Interrupt Service Routine
{
    if(T0IF)              //Check if TMR0 has Overflowed
    {
        SevenSegISR();                  //Call the SevenSegISR
        T0IF=0;                         //Clear Flag for Timer0
    }
    if ( CCP1IF )         // if capture interrupt changed, update 'value' from CCP value
    {
        counter=(CCPR1H<<8 + CCPR1L);   // copy how many counts
        TMR1 = 0;                       // reset Timer1 for the next time
        CCP1IF = 0;                     // clear the CCP flag
     }
}
 

Thread Starter

jj_alukkas

Joined Jan 8, 2009
753
Tried operating on this code.. No response for any inputs.. Have doubt.. how will CCPR1H<<8 work on this? cant we do something like x 10^8 or something? because when I make it as (CCPR1H + CCPR1H)/100 the value changes..:confused:
 

Markd77

Joined Sep 7, 2009
2,806
<<8 means left shift 8 times, the same as multiply by 256, but it forces the complier to do it the most efficient way.
If you have 1 in CCPR1H you can't just add it to CCPR1L because it means 256.
I don't know if C handles the case where CCPR1L overflows just after you read CCPR1H so it is probably best to stop the timer to read it if you can.
 

ErnieM

Joined Apr 24, 2011
8,377
I don't know if C handles the case where CCPR1L overflows just after you read CCPR1H so it is probably best to stop the timer to read it if you can.
CCPR1 L & H are not counters, not part of Timer1. They hold the last latched value of Timer1 when the RB3 input transistions. Latched. Not counting. No need to do anything special but read them within the first 65.5 milliseconds of the interupt routine triggered by that same transition. Should you read them after that, then you just get the next reading of the period.

(The other part was quite true and well taken.)
 

Thread Starter

jj_alukkas

Joined Jan 8, 2009
753
That code had problems in interrupts and fetching data.. I found a similar code online designed to something else and modified it for a frequency counter.. now it works just fine and accuracy +/- 1Hz for the internal osc.. it works only till 100hz, have to get it beyond that.. I think I will use this one as it uses fewer lines of code and will have room if I need to add more stuffs..

Rich (BB code):
#include <htc.h>
#define _XTAL_FREQ 4000000        

__CONFIG(FOSC_INTOSCIO & WDTE_OFF & PWRTE_OFF & BOREN_OFF & LVP_OFF );  //Config Bits

unsigned char control;

void interrupt ISR(){
TMR1H=0; TMR1L=0;
GIE=0;

control=1; 

CCP1IF=0;
GIE=1;
}

main(void)
{
unsigned const char number[10]={0x40, 0xf9, 0x24,
          0x30, 0x99,0x12,0x02,0xf8,0x00,0x10};
unsigned char select[2]={1,2};
unsigned int counter,value,remainder1,remainder2;
float frequency;
unsigned char a,i,display[5],data;

TRISA=0x00;
TRISB=0x08;
CMCON=0x07;

control=0;
PORTA=0; PORTB=0;

CCP1IE=1;

CCP1CON=0b00000110; 

T1CON=0b00100001;
          
GIE=1;
PEIE=1;

for(;;){


counter=256*CCPR1H+CCPR1L;

if(control==1)frequency=100000000/counter;
if(control==0)frequency=0;

if(counter<10000)frequency=0; 

control=0;

for(a=0;a<25;a++){ 

    value=(int)frequency;
    display[2]=value/1000;
    remainder1=value-(display[2]*1000);

    display[1]=remainder1/100;
    remainder2=remainder1-(display[1]*100);

    //display[1]=remainder2/10;
    //display[2]=remainder2-display[1]*10;
    
    
    for(i=0;i<2;i++){
        PORTB=0;
        PORTA=0;

        data=number[display[i+1]];
        PORTB=data&0xF8;
        data=data<<0;
        PORTB=PORTB|(data&0x0F);

        PORTA=select;
        __delay_ms(6);
    }
} 
}
}


Here as Markd77 stated, CCPR1H was multiplied by 256 and a variable was placed in interrupt to detect if something was captured.. and checked to print the value.. now it works just fine..
 
Top