Timer16

Many of the PIC18 timers have the capability of 16-bit operation and are comprised of two 8-bit registers: TMRxH and TMRxL.

Since it takes multiple instructions to access the two 8-bit registers, you must take care when reading/writing to the timers that the contents are valid and that a rollover hasn't occurred while operating on them. You can set the timer for '16-bit' read/write mode via a bit in the TxCON register (note that the bit name/location can change depending on the device and timer, so check the datasheet)

In 16-bit mode the registers must be accessed in a particular order: to read you must access TMRxL then TMRxH, and to write you use the reverse order TMRxH then TMRxL. The module below contains macro routines to take care of this for you.

timer16.bas module

//
// 16-bit timer macros
// these are used to enforce reading/writing to the timer 0/1/3/5/7 registers
// in the correct order when they are used in 16-bit mode
//
module timer16

// not all devices support TMR3, TMR5, and/or TMR7, so make support for them
// conditional. set the option true to enable them (disabled by default)
#option TIMER16_TMR3 = false
#option TIMER16_TMR5 = false
#option TIMER16_TMR7 = false

// macro CheckParam constants
// these are from system.bas, but redefined here since they may not be visible
// to the module that includes this file
public const
    cpConst              = $01,
    cpVariable           = $02,
    cpArray              = $04,
    cpSize01             = $08,
    cpSize08             = $10,
    cpSize16             = $20,
    cpSize32             = $40,

    cpInteger            = $0100,
    cpReal               = $0200,
    cpString             = $0400,
    cpChar               = $0800,
    cpBoolean            = $1000,

    etError              = 0,
    etWarning            = 1,
    etMessage            = 2,
    etHint               = 3

//
// the PIC18 timers are typically set for 8-bit read/write mode by default.
// to enable 16-bit mode you have to set the appropriate bit in the TxCON
// register. the exact bit depends on the device and the timer, so check
// the datasheet. some of the bits to look for are:
//     T0CON.T08BIT (bit 6)
//     T1CON.RD16 (usually bit 7)
//     TxCON.TxRD16 (usually bit 1)
//

//
//-----------------------------------------------------------------------------
// write_tmr16()
// write timer specified by 'tmr' with the value 'wval'
// in 16-bit mode, TMRxH isn't actually updated until TMRxL is written
//-----------------------------------------------------------------------------
//
public macro write_tmr16(tmr, wval)
    if (tmr = 0) then
        TMR0H = byte(wval >> 8)
        TMR0L = byte(wval)
    elseif (tmr = 1) then
        TMR1H = byte(wval >> 8)
        TMR1L = byte(wval)
  #if (TIMER16_TMR3)
    elseif (tmr = 3) then
        TMR3H = byte(wval >> 8)
        TMR3L = byte(wval)
  #endif
  #if (TIMER16_TMR5)
    elseif (tmr = 5) then
        TMR5H = byte(wval >> 8)
        TMR5L = byte(wval)
  #endif
  #if (TIMER16_TMR7)
    elseif (tmr = 7) then
        TMR7H = byte(wval >> 8)
        TMR7L = byte(wval)
  #endif
    else
        CheckParam(etError, "unsupported TMR select")
    endif
end macro

//
//-----------------------------------------------------------------------------
// read_tmr16()
// reads timer specified by 'tmr', stores it in 'wresult'
// in 16-bit mode, reading TMRxL latches TMRxH
//-----------------------------------------------------------------------------
//
public macro read_tmr16(tmr, wresult)
    if (tmr = 0) then
        wresult.byte0 = TMR0L
        wresult.byte1 = TMR0H
    elseif (tmr = 1) then
        wresult.byte0 = TMR1L
        wresult.byte1 = TMR1H
  #if (TIMER16_TMR3)
    elseif (tmr = 3) then
        wresult.byte0 = TMR3L
        wresult.byte1 = TMR3H
  #endif
  #if (TIMER16_TMR5)
    elseif (tmr = 5) then
        wresult.byte0 = TMR5L
        wresult.byte1 = TMR5H
  #endif
  #if (TIMER16_TMR7)
    elseif (tmr = 7) then
        wresult.byte0 = TMR7L
        wresult.byte1 = TMR7H
  #endif
    else
        CheckParam(etError, "unsupported TMR select")
    endif
end macro

//
//-----------------------------------------------------------------------------
// incr_tmr16()
// add a word value 'wval' to a running timer specified by 'tmr'
//
// since the timer has to be read LB,HB and written HB,LB we can't do the addition
// directly on the timer regs as the math HAS to be done as a word operation
// to account for any carry from the low byte, which means doing the LB first.
// as is, the code isn't much better than just doing
//      read_tmr16(0, w)
//      w = w + 1234
//      write_tmr16(0, w)
//          
// if you knew for a fact that the addition would never generate a carry then
// this could be shortened to something like:
//      PRODL = TMR0L        // read TMRL to latch TMRH in 16-bit mode
//      TMR0H = TMR0H + byte(wval >> 8)
//      TMR0L = PRODL + byte(wval)
//
// note: to be cycle accurate you'll have to adjust 'wval' to account for the 
// number of instruction cycles it takes for the code to read and update the 
// timer registers. the exact adjustment depends on 'wval' (const or variable)
// and which bank it's in. it's typ 12 instruction cycles, but it may vary
//-----------------------------------------------------------------------------
//
dim PROD as PRODL.asword        // define 16-bit PROD register
public macro incr_tmr16(tmr, wval)
    if (tmr = 0) then
        PRODL = TMR0L           // read TMRL (latches TMRH in 16-bit mode)
        PRODH = TMR0H
        PROD = PROD + wval      // add wval to current timer
        TMR0H = PRODH           // write timer in proper order (HB,LB)
        TMR0L = PRODL
    elseif (tmr = 1) then
        PRODL = TMR1L           // read TMRL (latches TMRH in 16-bit mode)
        PRODH = TMR1H
        PROD = PROD + wval      // add wval to current timer
        TMR1H = PRODH           // write timer in proper order (HB,LB)
        TMR1L = PRODL
  #if (TIMER16_TMR3)
    elseif (tmr = 3) then
        PRODL = TMR3L           // read TMRL (latches TMRH in 16-bit mode)
        PRODH = TMR3H
        PROD = PROD + wval      // add wval to current timer
        TMR3H = PRODH           // write timer in proper order (HB,LB)
        TMR3L = PRODL
  #endif
  #if (TIMER16_TMR5)
    elseif (tmr = 5) then
        PRODL = TMR5L           // read TMRL (latches TMRH in 16-bit mode)
        PRODH = TMR5H
        PROD = PROD + wval      // add wval to current timer
        TMR5H = PRODH           // write timer in proper order (HB,LB)
        TMR5L = PRODL
  #endif
  #if (TIMER16_TMR7)
    elseif (tmr = 7) then
        PRODL = TMR7L           // read TMRL (latches TMRH in 16-bit mode)
        PRODH = TMR7H
        PROD = PROD + wval      // add wval to current timer
        TMR7H = PRODH           // write timer in proper order (HB,LB)
        TMR7L = PRODL
  #endif
    else
        CheckParam(etError, "unsupported TMR select")
    endif
end macro

end module

Example usage:

device = 18F4450       // supports tmr 0, 1
'device = 18F4520       // supports tmr 0, 1, 3
'device = 18F26K22      // supports tmr 0, 1, 3, 5
'device = 18F87K22      // supports tmr 0, 1, 3, 5, 7

// examples to enable support for addtl timers
#if (_device = 18F4520)
  #option TIMER16_TMR3 = true
#endif
#if (_device = 18F26K22)
  #option TIMER16_TMR3 = true
  #option TIMER16_TMR5 = true
#endif
#if (_device = 18F87K22)
  #option TIMER16_TMR3 = true
  #option TIMER16_TMR5 = true
  #option TIMER16_TMR7 = true
#endif
include "timer16.bas"

dim w as word

// load tmr0 with a const value
write_tmr16(0, $1234)

// load tmr0 with a word value
w = $1234
write_tmr16(0, w)

// read tmr1, add an offset, and re-write it
read_tmr16(1, w)
w = w + $1234
write_tmr16(1, w)

// same as above, but slightly faster
incr_tmr16(1, $1234)