Simple Single Chip Frequency Counter

A few years ago, there was a discussion on the compiler's forum (www.protonbasic.com) concerning a simple frequency counter, and if one was capable of being written for the Positron8 compiler, so I set about creating a very simple program and a simulation for it so it could be seen working, and it could also be seen how straightforward and clear it was to create in the compiler. The code listing for the simple Frequency Counter working on a PIC18F26K22 device is below.

The frequency is measured with a very straightforward method of a time window counting pulses within it. The CCP2 peripheral is used as a window of one second, and within that window, the pulses arriving on the pin used for the INT0 peripheral are counted. That's it! It does not get much simpler, and because the Positron8 compiler also has 24-bit variables (
Long Types), the frequencies that can be measured are from a few Hz up to the MHz range without using too much code space and time with its Dword (32-bit ) variables.

'
' /\\\\\\\\\
' /\\\///////\\\
' \/\\\ \/\\\ /\\\ /\\\
' \/\\\\\\\\\\\/ /\\\\\ /\\\\\\\\\\ /\\\\\\\\ /\\\\\\\\\\\ /\\\\\\\\\\\ /\\\\\\\\\
' \/\\\//////\\\ /\\\///\\\ \/\\\////// /\\\/////\\\ \////\\\//// \////\\\//// \////////\\\
' \/\\\ \//\\\ /\\\ \//\\\ \/\\\\\\\\\\ /\\\\\\\\\\\ \/\\\ \/\\\ /\\\\\\\\\\
' \/\\\ \//\\\ \//\\\ /\\\ \////////\\\ \//\\/////// \/\\\ /\\ \/\\\ /\\ /\\\/////\\\
' \/\\\ \//\\\ \///\\\\\/ /\\\\\\\\\\ \//\\\\\\\\\\ \//\\\\\ \//\\\\\ \//\\\\\\\\/\\
' \/// \/// \///// \////////// \////////// \///// \///// \////////\//
' Let's find out together what makes a PIC Tick!
'
' Measure a frequency using INT0 and a Special Event Compare interrupt
' The INT0 pin is PORTB.0, and this is where the pulses are entered to be counted
'
' Written by Les Johnson for the Positron8 BASIC compiler.
'

Device = 18F26K22 ' Tell the compiler what device to compile for
Declare Xtal = 64 ' Tell the compiler what frequency the device is opertating at
On_Hardware_Interrupt GoTo ISR_Handler ' Point the interrupt to its handler routine
'
' Setup USART1
'

Declare Hserial_Baud = 9600 ' Set the Baud rate for USART1
Declare HRSOut1_Pin = PORTC.6 ' Set the TX pin to use for USART1
'
' Create variables for the program
'

Dim tFreq_Ready As Bit ' Set true when the pulse count window has reached 1 second
Dim bMilliCounter As Byte Access ' Counts the amount of interrupts that have occured
Dim INT0_lCount As Long Access ' Temporary variable to count the pulses that occur on the INT0 pin
Dim lFrequency As Long Access ' Holds the frequency of the pulses on the INT0 pin


Dim wCCPR2 As CCPR2L.Word ' Combine CCPR2L\H into a 16-bit SFR
Dim wTimer3 As TMR3L.Word ' Combine TMR3L\H into a 16-bit SFR


'------------------------------------------------------------------------------
' Interrupt defines
'

$define IntGlobal_Enable() INTCONbits_GIE = 1 ' Enable global interrupts
$define IntGlobal_Disable() INTCONbits_GIE = 0 ' Disable global interrupts
$define IntPeriph_Enable() INTCONbits_GIEL = 1 ' Enable peripheral interrupts
$define IntPeriph_Disable() INTCONbits_GIEL = 0 ' Disable peripheral interrupts

'------------------------------------------------------------------------------
' INT0 peripheral defines

'
$define INT0_Enable() INTCONbits_INT0IE = 1 ' Enable an INT0 Interrupt
$define INT0_Disable() INTCONbits_INT0IE = 0 ' Disable an INT0 Interrupt
$define INT0_Flag() INTCONbits_INT0IF ' The INT0 interrupt flag
$define INT0_FlagClear() INTCONbits_INT0IF = 0 ' Clear the INT0 interrupt flag
$define INT0_RisingEdgeSet() INTCON2bits_INTEDG0 = 1 ' Set INT0 for a rising edge
$define INT0_FallingEdgeSet() INTCON2bits_INTEDG0 = 0 ' Set INT0 for a falling edge

'------------------------------------------------------------------------------
' Timer3 peripheral defines

'
$define Timer3_FlagClear() PIR2bits_TMR3IF = 0 ' Clear the Timer3 interrupt flag
$define Timer3_IntEnable() PIE2bits_TMR3IE = 1 ' Enable a Timer3 Interrupt
$define Timer3_IntDisable() PIE2bits_TMR3IE = 0 ' Disable a Timer3 Interrupt
'------------------------------------------------------------------------------
' CCP2 peripheral defines

'
$define CCP2_IntEnable() PIE2bits_CCP2IE = 1 ' Enable a CCP2 Interrupt
$define CCP2_IntDisable() PIE2bits_CCP2IE = 0 ' Disable a CCP2 Interrupt
$define CCP2_Flag() PIR2bits_CCP2IF ' The CCP2 interrupt flag
$define CCP2_FlagClear() PIR2bits_CCP2IF = 0 ' Clear the CCP2 interrupt flag

$ifndef True
$define True 1
$endif
$ifndef False

$define False 0
$endif

'----------------------------------------------------------------------------------
' The main program starts here
' Displays the frequency on a serial terminal operating at 9600 Baud
'

Main:
Setup() ' Setup the program and peripherals

Do ' Create a loop
If tFreq_Ready = True Then ' Has a frequency been measured?
tFreq_Ready =
False ' Yes. So reset the flag
HRSOutLn "Frequency = ", Dec lFrequency, " Hz" ' Transmit the frequency on INT0 to the serial terminal
EndIf
Loop ' Do it forever

'----------------------------------------------------------------------------------
' Setup the variables and peripherals
' Input : None
' Output : None
' Notes : None
'

Proc Setup()
tFreq_Ready =
False ' Reset the measurement window flag
bMilliCounter = 0
' Clear the millisecond counter
INT0_lCount = 0
' Clear the INT0 pulse counter
lFrequency = 0
' Clear the Frequency value

CCP2_Init() ' Setup CCP2 for special event interrupt
TMR3_Init() ' Setup Timer3 to operate as the special event interrupt timer
INT0_Init() ' Setup the INT0 peripheral

IntPeriph_Enable() ' Enable Peripheral Interrupts
IntGlobal_Enable() ' Enable Global Interrupts
EndProc

'----------------------------------------------------------------------------------
' Setup the CCP2 peripheral for a special event interrupt
' Input : None
' Output : None
' Notes : None
'

Proc CCP2_Init()
CCP2CON = $0B
' CCP2M Special Event Trigger. DC2B 0. P2M0 single
CCPTMRS0bits_C2TSEL0 = 1
' \ Select Timer3 for the capture timer
CCPTMRS0bits_C2TSEL1 = 0
' /
CCP2_FlagClear() ' Clear the CCP2 interrupt flag
CCP2_IntEnable() ' Enable the CCP2 interrupt
'
' Calculate the value to place into the CCPR2L\H SFRs to achieve a certain interrupt rate (in us)
'

$define cPrescalerValue 8 ' Alter this to match the prescaler used for Timer3
$define cMicroSeconds 20000 ' Interrupt rate (in uS)
$define cCCPR_Value $eval (cMicroSeconds / cPrescalerValue) * (_xtal / 4)
$if cCCPR_Value > 65535
$SendError "Value too large for interrupt duration"
$elseif cCCPR_Value = 0
$SendError "Value too small for interrupt duration"
$endif
wCCPR2 =
cCCPR_Value ' Load CCPR2L\H with the value to trigger an interrupt at a certain duration
EndProc

'----------------------------------------------------------------------------------
' Setup Timer3 to act with the CCP2 peripheral
' Input : None
' Output : None
' Notes : Set for a 1:8 prescaler
'

Proc TMR3_Init()
T3GCON = $00
wTimer3 = 0
' Clear TMR2L/H
Timer3_FlagClear() ' Clear the Timer3 interrupt flag
T3CON = $37
' T3CKPS 1:8
' T3OSCEN disabled
' nT3SYNC do not synchronize
' TMR3CS FOSC/4
' TMR3ON enabled
' T3RD16 enabled
EndProc

'----------------------------------------------------------------------------------
' Setup the INT0 peripheral
' Input : None
' Output : None
' Notes : None
'

Proc INT0_Init()
INT0_FlagClear() ' Clear the INT0 interrupt flag
INT0_RisingEdgeSet() ' Set for a Rising Edge (INT0)
INT0_Enable() ' Enable an INT0 Interrupt (Frequency)
EndProc

'----------------------------------------------------------------------------------
' Interrupt handler
' Counts pulses from INT0 in a 1 second window
' Input : None
' Output : tFreq_Ready is true if the 1 second time window has occured (must be reset in the main program)
' : lFrequency holds the frequency for the signal on INT0 (PORTB.0)
' Notes : The exact timing window is achieved with a special event interrupt based upon the CCP2 peripheral
'

ISR_Handler:
Context Save ' Save SFRs and system variables
'
' INT0 pin pulse counter
'

If INT0_Flag() = True Then ' Was it an INT0 that triggered the interrupt?
Inc INT0_lCount ' Yes. So increment the pulse counter for INT0
INT0_FlagClear() ' Clear the INT0 interrupt flag
EndIf
'
' Millisecond timer for the pulse counting window
'

If CCP2_Flag() = True Then ' Was it a Compare Special Event on CCP2 that triggered the interrupt?
Inc bMilliCounter ' Yes. So increment the millisecond counter
If bMilliCounter >= 50 Then ' Have we reached a 1 second window?
tFreq_Ready =
True ' Yes. So signal the window is complete (must be reset in the main program)
lFrequency = INT0_lCount
' Get a frequency from the amount of pulses on INT0
INT0_lCount = 0
' Clear the INT0 counter
bMilliCounter = 0
' Reset the millisecond counter
EndIf
CCP2_FlagClear() ' Clear the CCP2 interrupt flag
EndIf
Context Restore ' Restore SFRs and system variables

'----------------------------------------------------------------------------------
' Setup the fuses to operate the PIC18F26K22 device at 64MHz using an external 16MHz crystal
'

Config_Start
PLLCFG = On
' Oscillator multiplied by 4
FOSC = HSHP
' HS oscillator (high power > 16 MHz)
Debug = Off
' Background debugger disabled. RB6 and RB7 configured as general purpose I/O pins
WDTEN = Off
' WDT disabled (control is placed on SWDTEN bit)
XINST = Off
' Disabled
FCMEN = On
' Fail-Safe Clock Monitor enabled
IESO = Off
' Oscillator Switchover mode disabled
PWRTEN = On
' PWRT enabled
BOREN = On
' Brown-out Reset enabled and controlled by software (SBOREN is enabled)
BORV = 190
' VBOR set to 1.9 V nominal
WDTPS = 128
' Watchdog ratio 1:128
MCLRE = EXTMCLR
' MCLR pin enabled, RE3 input pin disabled
HFOFST = Off
' The system clock is held off until the HF-INTOSC is stable
PRICLKEN = On
' Primary clock enabled
PBADEN = Off
' PORTB<4:0> pins are configured as digital I/O on Reset
CCP2MX = PORTC1
' CCP2 input/output is multiplexed with RC1
CCP3MX = PORTB5
' P3A/CCP3 input/output is multiplexed with RB5
T3CMX = PORTC0
' T3CKI is on RC0
P2BMX = PORTB5
' P2B is on RB5
STVREN = On
' Stack full/underflow will cause Reset
LVP = Off
' Single-Supply ICSP disabled
Cp0 = Off
' Block 0 (000800-001FFF) not code-protected
CP1 = Off
' Block 1 (002000-003FFF) not code-protected
CPB = Off
' Boot block (000000-0007FF) not code-protected
CPD = Off
' Data EEPROM not code-protected
WRT0 = Off
' Block 0 (000800-001FFF) not write-protected
WRT1 = Off
' Block 1 (002000-003FFF) not write-protected
WRTB = Off
' Boot block (000000-0007FF) not write-protected
WRTC = Off
' Configuration registers (300000-3000FF) not write-protected
WRTD = Off
' Data EEPROM not write-protected
EBTR0 = Off
' Block 0 (000800-001FFF) not protected from table reads executed in other blocks
EBTR1 = Off
' Block 1 (002000-003FFF) not protected from table reads executed in other blocks
EBTRB = Off
' Boot block (000000-0007FF) not protected from table reads executed in other blocks
Config_End

The code listing above can be seen running within a simulator in the image below:

The source code and the Proteus Simulator project can be downloaded from here: PIC18F26K22_Frequency_Counter.zip, and if advice is required to get the program operating, or make changes to it, please visit the compiler's forum at www.protoncompiler.com, because we are always happy to help.