Timer32

This module implements a 32-bit usec timer using TMR0. It requires that the TMR0 prescaler be set for the timer to count directly in 1usec intervals, so it works for clock settings of 4, 8, 16, 32, and 64MHz.

Sample Code (single-shot mode)

include "timer32.bas"

dim elap_time as longword

timer32.init()

timer32.start()
<code to time here>
elap_time = timer32.read()

Sample Code (continuous-run mode)

include "timer32.bas"

dim start_time, elap_time as longword

timer32.init()
timer32.start()

start_time = timer32.read()
<code to time here>
elap_time = timer32.read() - start_time

start_time = timer32.read()
<more code to time here>
elap_time = timer32.read() - start_time

Sample Code (run code for a time period)

include "timer32.bas"

dim elap_time as longword
dim LED as PORTB.0

timer32.init()

// toggle LED for 100ms
low(LED)
timer32.start()
repeat
    toggle(LED)
    elap_time = timer32.read()
until (elap_time >= 100000)      // 100ms = 100000us

timer32 Module

{
*****************************************************************************
*  Name    : timer32.bas                                                    *
*  Author  : Jerry Messina                                                  *
*  Date    : 05/30/17                                                       *
*  Version : 1.0                                                            *
*  Notes   : 1.0 Initial release                                            *
*****************************************************************************
}
module timer32

//
// 32-bit usec timer using TMR0 counts 4294967296us (4294secs, or 71min) with 1us resolution 
// set TMR0 to count the instruction clock (FOSC/4)
// if the clock is a power of 2 frequency between 4MHz and 64MHz (ie 4/8/16/32, or 64MHz) then
// prescaler can be set to 1,2,4,8,16 (_clock/4) to give 1us counts
// when TMR0 overflows incr usecs_hw (high word of the 32bit usec counter) and clear T0IF
// TMR0 continues to count, so you have 65535us (~65ms) to service the interrupt
//
// usage:
//      timer32.init(event_handler)
//      timer32.start()
//
// continuous mode (difference between two reads, timer continues running):
//      dim start_time, elap_time as longword
//      start_time = timer32.read()
//        <code to time here>
//      elap_time = timer32.read() - start_time
//        <more code to time here>
//      elap_time = timer32.read() - start_time
//
// single-shot mode (restart timer):
//      timer32.start()
//        <code to time here>
//      elap_time = timer32.read()
//
// even though the timer is counting usecs, there's about 30 cycles of overhead in continuous mode
// so that will limit the accuracy compared to single-shot mode
//
#if not(_clock in (4,8,16,32,64)) then
  #error "this module requires a clock of 4MHz-64MHz in binary steps"
#endif

// select interrupt priority for TMR0
// 1 = ipLow, 2 = ipHigh (default)
#option TIMER32_PRIORITY = 2
const INTR_PRIORITY = TIMER32_PRIORITY

// TMR0 interrupt register bits
dim
    TMR0IF as INTCON.bits(2),
    TMR0IE as INTCON.bits(5),
    TMR0IP as INTCON2.bits(2)

// event called by the isr when TMR0 overflows every 65536us
type event_t = event()
dim OnEvent as event_t

// timer overflow count
// 32-bit usec timer upper word is incremented by overflow isr, lower word is TMR0 count value
dim usecs_hw as word

// macros to write/read tmr0 in 16-bit mode
public macro write_tmr0(wval)
    TMR0H = byte(wval >> 8)
    TMR0L = byte(wval)
end macro

public macro read_tmr0(wresult)
    wresult.byte0 = TMR0L
    wresult.byte1 = TMR0H
end macro

// T0CON register bits
const
    T0PS0 = 0,
    T0PS1 = 1,
    T0PS2 = 2,
    PSA = 3,
    T0SE = 4,
    T0CS = 5,
    T08BIT = 6,
    TMR0ON = 7

// tmr0 overflow interrupt
interrupt tmr0_isr(INTR_PRIORITY)
    TMR0IF = 0                      // clear timer IF
    usecs_hw = usecs_hw + 1         // incr counter upper word
    OnEvent()                       // call user event (if set)
end interrupt

// stop timer
public sub stop()
    T0CON.bits(TMR0ON) = 0
    TMR0IE = 0
    TMR0IF = 0
end sub

// start timer
public sub start()
    // stop the timer (in case it's running)
    stop()
    // init
    usecs_hw = 0
    write_tmr0(0)
    TMR0IE = 1
    enable(tmr0_isr)
    // and start timer
    T0CON.bits(TMR0ON) = 1
end sub

// init TMR0 to count 1usec increments
public sub init(event_handle as event_t = 0)
    // make sure timer isn't running
    stop()
    // setup TMR0
    T0CON = 0                   // clear all t0con bits, setting defaults to 0
    T0CON.bits(T08BIT) = 0      // 0 = Timer0 is configured as a 16-bit timer/counter
    T0CON.bits(T0CS) = 0        // 0 = Internal instruction cycle clock (CLKOUT)
    T0CON.bits(PSA) = 0         // 0 = Timer0 prescaler is assigned
    select (_clock)
        case 4                  // 1:1 (no prescaler)
            T0CON.bits(PSA) = 1 // 1 = Timer0 prescaler is not assigned
        case 8                  // 1:2 prescaler
            T0CON.bits(T0PS2) = 0   
            T0CON.bits(T0PS1) = 0
            T0CON.bits(T0PS0) = 0
        case 16                  // 1:4 prescaler
            T0CON.bits(T0PS2) = 0
            T0CON.bits(T0PS1) = 0
            T0CON.bits(T0PS0) = 1
        case 32                  // 1:8 prescaler
            T0CON.bits(T0PS2) = 0
            T0CON.bits(T0PS1) = 1
            T0CON.bits(T0PS0) = 0
        case 64                 // 1:16 prescaler
            T0CON.bits(T0PS2) = 0
            T0CON.bits(T0PS1) = 1
            T0CON.bits(T0PS0) = 1
    end select        

    // set intr priority
  #if (TIMER32_PRIORITY = 2) then
    TMR0IP = 1                  // high priority
  #else
    TMR0IP = 0                  // low priority
  #endif

    // set event (optional)
    OnEvent = event_handle
end sub

// read 32-bit timer
// don't stop the timer... just read it until there are two successive 
// equal readings of the high word to ensure there isn't a false value 
// because of the interrupt
public function read() as longword
    repeat
        result.word1 = usecs_hw         // read high word
        read_tmr0(result.word0)         // get the low word value from the tmr
    until (result.word1 = usecs_hw)     // make sure we weren't interrupted
end function

// module init
OnEvent = 0

end module

Test program

device = 18F25K22
clock = 64 				// 4, 8, 16, 32, or 64

config
    FOSC = INTIO7,
    PLLCFG = OFF,
    PRICLKEN = OFF,
    FCMEN = OFF,
    IESO = OFF

// timer32 defaults to high-priority (2)
'#option TIMER32_PRIORITY = 1
include "timer32.bas"

include "usart.bas"
include "convert.bas"

// define an event that's called when the timer overflows (every 65536us)
dim LED as PORTB.0
event event_handler()
    toggle(LED)
end event

dim start_time as longword,
    elap_time as longword

dim w as word

// set 18F25K22 osc 
public sub set_clock()
    // select primary osc (depends on OSC config bits)
  #if (_clock = 4)
    OSCCON = $50            // primary osc, 4MHz int osc
  #elseif (_clock = 8) or (_clock = 32)
    OSCCON = $60            // primary osc, 8MHz int osc
  #elseif (_clock = 16) or (_clock = 64)
    OSCCON = $70            // primary osc, 16MHz int osc
  #else
    #error "unsupported _clock freq setting"
  #endif    
  #if (_clock = 32) or (_clock = 64)
    OSCTUNE.bits(6) = 1     // PLL enabled (x4)
  #else
    OSCTUNE.bits(6) = 0     // pll off
  #endif
    delayms(10)         // wait for clock to stabilize
end sub


main:
set_clock()
usart.setbaudrate(br115200)

usart.write("clock = ", dectostr(_clock), "MHz", #13, #10)

// setup timer (no event)
timer32.init()

// measure overhead
// minimal overhead using start()/read()
timer32.start()
elap_time = timer32.read()
usart.write("overhead start-read = ", dectostr(elap_time), "us", #13, #10)

// there's about 32 instruction cycles of overhead on reading the timer twice,
// so at 64MHz that's about 2usecs
start_time = timer32.read()
elap_time = timer32.read() - start_time
usart.write("overhead read-read  = ", dectostr(elap_time), "us", #13, #10)
usart.write(#13, #10)

// example 1: run code for a time period
// toggle LED for 100ms
low(LED)
timer32.start()
repeat
    toggle(LED)
    elap_time = timer32.read()
until (elap_time >= 100000)      // 100ms = 100000us
LED = 0
usart.write("LED toggled for ", dectostr(elap_time), "us", #13, #10)
usart.write(#13, #10)


// setup timer to use event 
timer32.init(event_handler)

// example 2: measure delayus calls
w = 1
while (w <= 10000)
    timer32.start()
    delayus(w)
    elap_time = timer32.read()
    usart.write(dectostr(w), "us start-read = ", dectostr(elap_time), "us", #13, #10)

    start_time = timer32.read()
    delayus(w)
    elap_time = timer32.read() - start_time
    usart.write(dectostr(w), "us read-read  = ", dectostr(elap_time), "us", #13, #10)

    w = w * 10
end while
usart.write(#13, #10)

// example 3: measure delayms calls
w = 1
while (w <= 10000)
    timer32.start()
    delayms(w)
    elap_time = timer32.read()
    usart.write(dectostr(w), "ms start-read = ", dectostr(elap_time), "us", #13, #10)

    start_time = timer32.read()
    delayms(w)
    elap_time = timer32.read() - start_time
    usart.write(dectostr(w), "ms read-read  = ", dectostr(elap_time), "us", #13, #10)

    w = w * 10
end while
usart.write(#13, #10)

Sample test output

clock = 64MHz
overhead start-read = 0us
overhead read-read  = 2us

LED toggled for 100002us

1us start-read = 1us
1us read-read  = 3us
10us start-read = 10us
10us read-read  = 12us
100us start-read = 100us
100us read-read  = 102us
1000us start-read = 1000us
1000us read-read  = 1002us
10000us start-read = 10000us
10000us read-read  = 10002us

1ms start-read = 1001us
1ms read-read  = 1003us
10ms start-read = 9997us
10ms read-read  = 9998us
100ms start-read = 99965us
100ms read-read  = 99969us
1000ms start-read = 999655us
1000ms read-read  = 999657us
10000ms start-read = 9996545us
10000ms read-read  = 9996549us



clock = 8MHz
overhead start-read = 4us
overhead read-read  = 13us

LED toggled for 100006us

1us start-read = 17us
1us read-read  = 25us
10us start-read = 16us
10us read-read  = 25us
100us start-read = 105us
100us read-read  = 114us
1000us start-read = 1005us
1000us read-read  = 1013us
10000us start-read = 10005us
10000us read-read  = 10013us

1ms start-read = 1011us
1ms read-read  = 1020us
10ms start-read = 10011us
10ms read-read  = 10020us
100ms start-read = 100027us
100ms read-read  = 100051us
1000ms start-read = 1000244us
1000ms read-read  = 1000252us
10000ms start-read = 10002367us
10000ms read-read  = 10002392us