measuring frequency with a 16F628A PIC

After disappointing results measuring frequency with an Arduino due to the Arduino system interrupts stealing clock cycles, I tried a 16F628A PIC. This is a much more primitive chip. In my system it has only one job, to spit out a count of 4MHz main system clock cycles that have passed between the onboard comparator detecting changes due to a 4.8kHz squarewave derived from a crystal oscillator.

That 4.8kHz is derived from a 2.4576MHz oscillator block, which I divided by 512 using a CD4060 to get 4.8kHz in the same way as last time.

histogram of counts using the PIC crystal oscillator

The spread of 6 in 64612 is 92ppm. I had expected even better and don’t understand the asymmetric spread. But it’s a darn sight better than the Arduino, where I was eating a spread of ~ 3745 ppm. That’s not a criticism of the Atmel chip used in the Uno  – in the end if I am going to use the Arduino overlay to run the chip then I am not working on the bare metal1 in the same way as the PIC.

If I look at the stream I can see that the low values come in bursts

64612
64613
64612
64612
64612
64612
64613
64612
64606
64611
64605
64611
64612
64607
64606
64613
64607
64606
64612
64612
64613
64612
64612

Which makes me wonder if the serial port is quite as self-contained as it seems from the datasheet. But the whole thing is on a breadboard

You don’t really get the right to precision timing with this sort of construction

and with that standard of construction maybe 100ppm is good enough. I did have a decoupling cap jumped across the PIC when taking the readings. But the two oscillators could be beating with each other across the power supply, or the great big white wire carrying the comparator in could be picking up a mobile phone somewhere.

I need better short-term precision for a PCSM, building this properly would probably help, as the histogram shows the vast majority of the results are 64612 or 64613

Code – this is messy and the comments are more a stream of consciousness in development. The comments may be inaccurate 😉 Every half second this spits out the number of 1µs clock cycles that have passed since the PIC counted 620 transitions of the 4.8kHz. That should be

(1/4800)*620/2*1000000 µs = 64583 µs

I chose 620 to keep the number of counts within the 16-bit length of TMR1

#define _XTAL_FREQ 4000000


// CONFIG
#pragma config FOSC = XT  // Oscillator Selection bits (INTOSC oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON       // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is MCLR)
#pragma config BOREN = OFF      // Brown-out Detect Enable bit (BOD disabled)
#pragma config LVP = OFF        // Low-Voltage Programming Enable bit (RB4/PGM pin has digital I/O function, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EE Memory Code Protection bit (Data memory code protection off)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)



#define _XTAL_FREQ 4000000


#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <xc.h>


char int_temp; // throwaya reg for ISR
volatile uint16_t int_counts = 0; 
volatile __bit init_flag;

void putch(char data)
{
 while(!TXIF);    // wait until TXREG is available
 TXREG = data;
}
 

/********************** end UART functions **************************/

void __interrupt() ISR(void)
{
  if (PIR1bits.CMIF && PIE1bits.CMIE)
  {
    int_counts++;
    if (int_counts == 1 && init_flag == 0 ) {
        // this is the first time an edge has passed
        TMR1=0;
        TMR1ON=1; // start the system clock counter
    }
    if (int_counts == 621) { // should this be odd due to 1 start
        TMR1ON=0; // stop the counter
        int_counts = 0;
        init_flag=1;
        PIE1bits.CMIE=0; // stop interrupts (there are no others enabled))
    }
    int_temp = CMCON;  // read this, discard, so that
    PIR1bits.CMIF=0;   // this is cleared
  }
}

int main ( void ) {

    
    TRISA=0b11100111; // A2 is Vref out that the comparator uses
    TRISB=0b00000000; // all out for now
    VRCON=0b11001000 ; // enable, to pin A2, Hi range, VCC/2
    CMCON=0b11000101 ; // One comparators C2
    OPTION_REG=0b01110000 ; // tmr0 ext input prescaler off (count 256 cycles, let it overflow) RB pullups on. 
    T1CON=0b00000000 ; // set timer 1 running internal osc 1x prescaled, stopped (clear and enable TMR1ON when ready)

    
    TXSTA=0b00100100; // high speed BRGH=1
    
    RCSTA=0b10000000; // enable port async
    SPBRG=25; // from datasheet for 9.6k
    
 

    PIE1=0;     // disable all stuff here, will enable CMIE separately
    INTCON=0 ; // disallow all interrupts
    INTCONbits.PEIE=1;
    ei(); // enable all interrupts
    
    for ( ;; ) {
       TMR1ON=0; // stop this (should be already). It counts main system clock cycles
        //TMR1=0; // clear this, stopped so non-atomicity is OK
        
        // clear the low freq counter
        int_counts = 0;
        // we are all set now
        int_temp = CMCON;  // read this, discard, so that
        PIR1bits.CMIF=0;   // this is cleared
        init_flag = 0;
        PIE1bits.CMIE=1; // permit the low frequency comparator changes to run the ISR
        // now sit and wait until init_flag is set high in the ISR, result can be read;
        while ( init_flag == 0) {
             //do nothing. This may lock up if LF osc is too low or absent
        }

        printf("%u\r\n",TMR1); // TMR1 should be stopped here %u because TMR1 is unsigned

          __delay_ms(500);  // wait 1 second
	}

    return (EXIT_SUCCESS);
}
  1. I don’t have experience of programming the Atmel directly, which is why I bottled this and went to the PIC where I do ↩︎