Using Timer1 to Measure Frequency

Coding and general discussion relating to the compiler

Moderators: David Barker, Jerry Messina

Post Reply
Jon Chandler
Registered User
Registered User
Posts: 185
Joined: Mon Mar 10, 2008 8:20 am
Location: Seattle, WA USA
Contact:

Using Timer1 to Measure Frequency

Post by Jon Chandler » Wed Jul 26, 2023 1:23 am

I'm working on a tachometer/frequency meter, but I'm rather lost.

I'm following a Microchip document including a section "Using Timer 1 to Measure Frequency. It uses the method of triggering the gate with the input signal to measure the number of clock pulses that occur between input pulses.

I probably don't have the interrupts set up properly - I never get the gate interrupt that triggers reading the timer count that can then be converted to a frequency. There are many registers to set up but I believe I've translated from the 'bare metal" section of the above document, which was done with a different chip than the 18F25k22 that I'm using.

This is my translation of the different registers.

Code: Select all


    /* Timer controlled by gate function */
    T1GCONbits.GE = 1;

* T1GCON.7 = 1          *

    /* Timer gate toggle mode enabled */
    T1GCONbits.GTM = 1;
    
* T1GCON.5 = 1           *
   
    /* Timer gate active high */
    T1GCONbits.GPOL = 1;
    
* T1GCON.6 = 1           *
    
    /* Timer acquistion is ready */
    T1GCONbits.GGO_nDONE = 1;
    
* T1GCON.3 =1           *   
    
    /* Timer gate single pulse mode enabled */
    T1GCONbits.T1GSPM = 1;  

* T1GCON.4 = 1           *

    /* Source Clock FOSC/4 */
    T1CLKbits.CS = 0x1;
    
* T1CON.7 = 0           *
* T1CON.6 = 0           *
    
    /* Clearing gate IF flag before enabling the interrupt */
    PIR5bits.TMR1GIF = 0;
    
* PIR3.0 = 0           *    
    
    /* Enabling TMR1 gate interrupt */
    PIE5bits.TMR1GIE = 1;
    
* PIE3.0 = 1           *    
    
    /* CLK Prescaler 1:8 */
    T1CONbits.CKPS = 0x3;

* T1CON.5 = 1           *
* T1CON.4 = 1           *

    /* TMR1 enabled */
    T1CONbits.ON = 1;  
    
* T1CON.0 = 1           *       
}

-----------------------------------
Enable interurupts

    /* Enable the Global Interrupts */
    INTCONbits.GIE = 1;

* INTCON.7 = 1           *

    /* Enable the Peripheral Interrupts */
    INTCONbits.PEIE = 1; 
    
* INTCON.6 = 1           *    

--------------------------------
When the timer finishes measuring the frequency of the external signal, an interrupt will occur in the interrupt manager. It will check for the source of the interrupt and, if it is from TMR1 gate, will call a handler function. The following function is used:

/* Interrupt handler function */
static void __interrupt() INTERRUPT_InterruptManager(void)
{
    // interrupt handler
    if(INTCONbits.PEIE == 1)
    {
        if(PIE5bits.TMR1GIE == 1 && PIR5bits.TMR1GIF == 1)
        {
            TMR1_GATE_ISR();
        } 
        else
        {
            //Unhandled Interrupt
        }
    }      
    else
    {
        //Unhandled Interrupt
    }
}

*******
INTERRUPT OnTimer()
    IF INTCON.6 = 1 then 
        if PIE3.0 = 1 AND PIR3.0 = 1 THEN
            TMR1_GATE_ISR
        end if 
    END IF   
end interrupt
*******


The handler needs to clear the Interrupt flag, read the counted value and reset it afterward, and re-enable the timer gate control for a new acquisition. The following function is used:

COPY/* TMR1 gate ISR function */
static void TMR1_GATE_ISR(void)
{
    volatile uint16_t value = 0;

    /* Clearing gate IF flag */
    PIR5bits.TMR1GIF = 0; 

    /* Read TMR1 value */
    value = TMR1_readTimer();

    /* Reset the counted value */
    TMR1_writeTimer(0);

    /* Prepare for next read */
    T1GCONbits.GGO_nDONE = 1;
}

******************
SUB TMR1_GATE_ISR()
        PIR3.0 = 0
        VALUE.H = TMR1H
        VALUE.L = TMR1L
        
        TMR1H = 0
        TMR1L = 0
        T1GCON.3 = 1
*************        
        
static uint16_t TMR1_readTimer(void)
{
    /* Return TMR1 value */
    return ((uint16_t)TMR1H << 8) | TMR1L; 
}

static void TMR1_writeTimer(uint16_t timerValue)
{
    /* Write TMR1H value */
    TMR1H = timerValue >> 8;
    
    /* Write TMR1L value */
    TMR1L = timerValue;
}


Here's my Swordfish code. My input is PortB.5. LED3 should flash on an interrupt but never does. LED4 mirrors the status of PortB.5 to show there is an intput (which is a square wave) and it is flashing as expected.

Code: Select all

Device = 18f25k22
Clock = 20
        
Config   'for K-series device
    FOSC = HSHP ,'HS oscillator (high power > 16 MHz)
    PLLCFG = Off ,'Oscillator used directly
    PRICLKEN = Off ,'Primary clock can be disabled by software
    FCMEN = Off ,'Fail-Safe Clock Monitor disabled
    IESO = Off ,'Oscillator Switchover mode disabled
    'PWRTEN = Off ,'Power up timer disabled
    PWRTEN = on ,'Power up timer enabled

    BOREN = Off ,'Brown-out Reset disabled in hardware and software
    'BOREN = on ,'Brown-out Reset enabled 
    BORV = 285 ,'VBOR set to 2.85 V nominal
    WDTEN = Off ,'Watch dog timer is always disabled. SWDTEN has no effect.
    WDTPS = 256 ,'1:256
    PBADEN = Off ,'PORTB<5:0> pins are configured as digital I/O on Reset
    HFOFST = Off ,'HFINTOSC output and ready status are delayed by the oscillator stable status
    MCLRE = EXTMCLR ,'MCLR pin enabled, RE3 input pin disabled
    STVREN = On ,'Stack full/underflow will cause Reset
    LVP = On ,'Single-Supply ICSP enabled if MCLRE is also 1
    'LVP = Off ,'Single-Supply ICSP disabled
    XINST = Off ,'Instruction set extension and Indexed Addressing mode disabled (Legacy mode)
    Debug = Off'Disabled

    Include "utils.bas"
    Include "usart.bas" 
    Include "convert.bas"
    Include "math.bas"

    Const LEDon = 0
    Const LEDoff = 1
    Const Pressed = 0
    Const NotPressed = 1
    
    Dim LED1 As PORTA.5 
    Dim LED2 As PORTB.3
    Dim LED3 As PORTC.0 
    Dim LED4 As PORTA.3
    
  '  Dim S1 As PORTB.5 
    Dim S2 As PORTB.4
    Dim TMR1Gate As PORTB.5
    
    Dim Counter As Word
    
    

    Sub TMR1_GATE_ISR()
        PIR3.0 = 0
        Counter.Byte1 = TMR1H
        Counter.byte0 = TMR1L
        
        TMR1H = 0
        TMR1L = 0
        T1GCON.3 = 1
        USART.write ("Counter = ", DecToStr(Counter), 13, 10)
        Toggle (LED3)
    End Sub

    Interrupt OnTimer()
        If INTCON.6 = 1 Then 
            If PIE3.0 = 1 And PIR3.0 = 1 Then
                TMR1_GATE_ISR
            End If 
        End If   
    End Interrupt

    
 
    SetAllDigital
    
    T1CON.0 = 1 
    USART.SetBaudrate(br115200)

    Input(TMR1Gate)  
    Output(LED1)
    Output(LED2)
    Output(LED3)
    Output(LED4)
    
    LED1 = LEDoff
    LED2 = LEDoff
    LED3 = LEDoff
    LED4 = LEDoff
    
    LED1 = LEDon
        USART.write ("yeah, what's it to you?", 13, 10)
    TMR1 = 0        
While 1 = 1   

    If PORTB.5 = 1 Then
            LED4 = LEDon
        Else
            LED4 = LEDoff
    End If  
   
   USART.write (DecToStr(TMR1),"        ", DecToStr(PIR3.0),13, 10)
  ' USART.write(DecToStr(PIR3.0), 13, 10)
 
Wend
What am I overlooking? Thanks for the help!

Jon
Jon

Check out the TAP-28 PIC Application board at http://www.clever4hire.com/throwawaypic/

Jerry Messina
Swordfish Developer
Posts: 1473
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: Using Timer1 to Measure Frequency

Post by Jerry Messina » Wed Jul 26, 2023 11:29 am

Try this, Jon:

Code: Select all

Device = 18F25K22
Clock = 32
        
Config   'for K-series device
    FOSC = HSHP ,'HS oscillator (high power > 16 MHz)
    PLLCFG = Off ,'Oscillator used directly
    PRICLKEN = Off ,'Primary clock can be disabled by software
    FCMEN = Off ,'Fail-Safe Clock Monitor disabled
    IESO = Off ,'Oscillator Switchover mode disabled
    'PWRTEN = Off ,'Power up timer disabled
    PWRTEN = on ,'Power up timer enabled

    BOREN = Off ,'Brown-out Reset disabled in hardware and software
    'BOREN = on ,'Brown-out Reset enabled 
    BORV = 285 ,'VBOR set to 2.85 V nominal
    WDTEN = Off ,'Watch dog timer is always disabled. SWDTEN has no effect.
    WDTPS = 256 ,'1:256
    PBADEN = Off ,'PORTB<5:0> pins are configured as digital I/O on Reset
    HFOFST = Off ,'HFINTOSC output and ready status are delayed by the oscillator stable status
    MCLRE = EXTMCLR ,'MCLR pin enabled, RE3 input pin disabled
    STVREN = On ,'Stack full/underflow will cause Reset
    LVP = On ,'Single-Supply ICSP enabled if MCLRE is also 1
    'LVP = Off ,'Single-Supply ICSP disabled
    XINST = Off ,'Instruction set extension and Indexed Addressing mode disabled (Legacy mode)
    DEBUG = Off'Disabled

include "intosc.bas"        // JM - added for clock=32

Include "utils.bas"
Include "usart.bas" 
Include "convert.bas"

Const LEDon = 0
Const LEDoff = 1
Const Pressed = 0
Const NotPressed = 1

Dim LED1 As PORTA.5 
Dim LED2 As PORTB.3
Dim LED3 As PORTC.0 
Dim LED4 As PORTA.3

'Dim S1 As PORTB.5 
Dim S2 As PORTB.4
Dim TMR1Gate As PORTB.5

Dim Counter As Word

sub init_TMR1()
    T1GCON.7 = 1    // Timer controlled by gate function
    T1GCON.6 = 1    // Timer gate active high
    T1GCON.5 = 1    // Timer gate toggle mode enabled
    T1GCON.4 = 1    // Timer gate single pulse mode enabled
    T1GCON.3 = 1    // Timer acquistion is ready
    T1GCON.1 = 0    // Timer Gate Source Select = Gate pin
    T1GCON.0 = 0    // 

    T1CON = 0       // initial PON value
    T1CON.7 = 0     // Source Clock FOSC/4
    T1CON.6 = 0
    T1CON.5 = 1     // CLK Prescaler 1:8
    T1CON.4 = 1
  
    TMR1L = 0
    TMR1H = 0
      
    PIR3.0 = 0      // Clearing gate IF flag before enabling the interrupt
    PIE3.0 = 1      // Enable TMR1 gate interrupt

    T1CON.0 = 1     // turn on TMR1
end sub

// The isr handler needs to clear the Interrupt flag, 
// read the counted value and reset it afterward, 
// and re-enable the timer gate control for a new acquisition
// since this is the only interrupt we don't need to check PIR, etc
Interrupt Tmr1GateIsr()
    PIR3.0 = 0          // clear TMR1 gate IF
    Counter.byte0 = TMR1L   // read low-byte, high-byte
    Counter.byte1 = TMR1H
    
    TMR1H = 0           // clear count...
    TMR1L = 0           // write high-byte, low-byte
    T1GCON.3 = 1        // re-enable gate

    Toggle (LED3)
End Interrupt

// read 'Counter' value until 2 successive readings match
// indicating that the read was valid and wasn't interrupted
function GetCounter() as word
    dim t_count as word
    
    repeat
        t_count = Counter      // 'Counter' can be changed by the isr
    until (t_count = Counter)  // re-read and compare
    result = t_count
end function

main:
SetAllDigital

USART.SetBaudrate(br115200)

Input(TMR1Gate)  
Output(LED1)
Output(LED2)
Output(LED3)
Output(LED4)

LED1 = LEDoff
LED2 = LEDoff
LED3 = LEDoff
LED4 = LEDoff

LED1 = LEDon
USART.write ("yeah, what's it to you?", 13, 10)

Counter = 0     // JM - init value

init_TMR1()             // init TMR
enable(Tmr1GateIsr)     // enable interrupts

While 1 = 1   
    If PORTB.5 = 1 Then
        LED4 = LEDon
    Else
        LED4 = LEDoff
    End If  
    
    USART.write ("Counter = ", DecToStr(GetCounter()), 13, 10)
Wend
I changed it to use the intosc @ 32MHz (just to match the app note... you can always change it back).
Added TMR1 register init, and a 'GetCounter() function to safely read the count.

I think the big thing was not having an 'enable()' call in there to actually enable the interrupts.
As usual, completely untested code, so...

edit: added setting TMR1L/H = 0
there are routines in the 'timer16.bas' library that will read/write the 16-bit timer values in the proper order, which is important if you use 16-bit mode (TMR16=1)

Jon Chandler
Registered User
Registered User
Posts: 185
Joined: Mon Mar 10, 2008 8:20 am
Location: Seattle, WA USA
Contact:

Re: Using Timer1 to Measure Frequency

Post by Jon Chandler » Wed Jul 26, 2023 7:19 pm

Thanks Jerry, I'll give this a try later today.
Jon

Check out the TAP-28 PIC Application board at http://www.clever4hire.com/throwawaypic/

Jon Chandler
Registered User
Registered User
Posts: 185
Joined: Mon Mar 10, 2008 8:20 am
Location: Seattle, WA USA
Contact:

Re: Using Timer1 to Measure Frequency

Post by Jon Chandler » Thu Jul 27, 2023 7:22 pm

Yay, progress! Once I managed to enable the frequency generator output (details, details!) the LED showing an interrupt occurred starting flashing and I got a counter output.

Thanks for cleaning up my mess of code. On to working out the equation for counts --> frequency.

My goal is to make a frequency meter/tachometer and also a counter - how many pulses in a given gate interval. This goes back to the old days, the first time I was in the electrical shop in the naval shipyard and they were testing a rebuilt motor. That glorious HP counter with nixie tubes, counting up pulses in a one minute gate period to see RPM.

Jon
Jon

Check out the TAP-28 PIC Application board at http://www.clever4hire.com/throwawaypic/

Jon Chandler
Registered User
Registered User
Posts: 185
Joined: Mon Mar 10, 2008 8:20 am
Location: Seattle, WA USA
Contact:

Re: Using Timer1 to Measure Frequency

Post by Jon Chandler » Thu Jul 27, 2023 8:42 pm

Testing at 100, 200 and 400 Hz with the internal 32MHz oscillator yields slightly low values. Interestingly, switching to the 20MHz clock signal, also yields slightly low values, almost identical to the 32MHz values. I am forced to conclude that my signal generator is slightly off :wink:

Using the 20MHz crystal, that gives me a minimum range of just under 10Hz. (Using Fosc/4 and 8:1 prescale). It looks like checking the timer1 rollover flag will let me go down to a very low frequency without the need to set it as an interrupt. On the high end, I can get adequate resolution into the kHz range which is good for what I'm planning, but I could reduce the prescale for higher frequencies.
Jon

Check out the TAP-28 PIC Application board at http://www.clever4hire.com/throwawaypic/

Post Reply