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.

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

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);
}
- I don’t have experience of programming the Atmel directly, which is why I bottled this and went to the PIC where I do ↩︎
One thought on “measuring frequency with a 16F628A PIC”