PIC18F4550 Timers

The 18F4550 has four timers named TMR0, TMR1, TMR2, and TMR3. It would be more precise to call them counters. Calling them timers has more to do with the intended purpose rather than what they actually are. Timers 0, 1, and 3 are 16 bit counters and timer 2 is an 8 bit counter. A counter can be used as a timer if it’s known what the period of the input clock is. For example, if the input clock has a 1 ms period and the counter is set to count to 1000 then 1 second will be timed. When these timers complete a count operation they trigger a bit flag to indicate the count was reached. All four of these timers can be configured to count the internal clock. The period of the internal clock varies with the design but is easy to obtain. With a known period and a known count you can code the processor to measure a fixed time using these timers.

Timer Resolution
The 18F4550 has a USB capability that depends on a 48 MHz clock (Fosc = 48MHz). So a typical design is configured to operate at that frequency. The processor uses 4 clock pulses to complete an instruction cycle. It fetches an instruction from memory. Then it uses this instruction to put the processor in the proper state. Then it executes the state, and finally makes the processor ready for another fetch. The frequency the timers see is called Fcy, the frequency of the instruction cycle, which is 12 MHz (Fcy = Fosc/4). That makes the period, Tcy, of the instruction cycle 83.3 ns (1/Fcy). So, each of these timers can produce a measured time by counting instruction cycles. The resolution is limited by the period of the instruction cycle. Each timer can only produce a time interval that is some integer multiple of Tcy.

Fosc = 48 MHz
Fcy = Fosc/4 = 12 MHz
Tcy = 1/Fcy = 83.3 ns

The timers each have an additional feature called a prescaler. The prescaler can be set to divide Fcy by some integer or equivalently multiply Tcy. With the use of the prescaler the timer’s resolution, Trz, can be changed to a larger value. TMR0’s prescaler can be set as high as 256. This changes the resolution of the timer by increasing Trz by a factor of 256 so instead of counting a pulse every 83.3 ns it counts a pulse every 21.3 us. With this setting, the timer will only be accurate to 21.3 us but can now time a period 256 times longer. This enables TMRO to time a period of 1.4 seconds – the largest time period of the 4 timers. See the table below for available prescaler values, Trz, and Tmax.

Timer Resolution = Trz = N * Tcy, where N is the value of the Prescaler

Timer Range
Each timer slips two instruction cycles when first started. Counting just once yields the minimum time of 3 * Tcy or 0.250 us (the 2 cycle time slip is typically lost in rounding errors). The maximum count of a 16 bit counter is 65,536 so that yields a maximum time of 5.46 ms for TMR0, TMR1, and TMR3. An 8 bit counter can count to 256 so the maximum time for TMR2 is 21.3 us. The greater Trz is, the greater Tmax will be.

Timer's Maximum Time

TMR2 is not only unique as the only 8-bit timer in the device but it’s also the only timer with a post-scale capability. “Post-scaling” is similar to prescaling only it’s being applied after the 8-bit count yet before the bit flag is tripped. TMR2 can have a post-scale value from 1 to 16 inclusive. Divide the desired time by the selected postscale value to determine the settings needed for TMR2.

TMR0 has a control bit allowing it to be operated as an 8-bit counter instead of a 16-bit. If you only need a brief time interval then this configuration bit will spare you the extra step of calculating the high-byte needed for the 16-bit mode. TMR1 and 3 are 16 bit counters only.

Each of these timers can only count up. When they reach their full count they roll over to zero just like the odometer does on a car. When rolling over to zero the bit flag is tripped. Checking for this flag is how you know the timer is done. It’s the equivalent of the dinging bell on an egg timer and like the dinging bell you have to be listening for it to know that it’s gone off.

Timer Register's Terminal Count

Example
To configure a timer to time 1 ms do the following:

Step1) Divide 1 ms by Trz (Prescaler =1 in this example: Trz = Tcy)
1ms / Trz = 1E-3 / 83.333E-9 = 12,000

Step 2) Calculate the integer to put into the 16 bit timer register
A 16 bit register can count up to 65,536. So it’s necessary to start the counter at some pre-determined count that when subtracted from 65,536 equals 12,000. Remember – these counters only count up and they only trip the done flag when rolling over from full count to zero. So the starting count needs to be 65,536 – 12,000 = 53,536. It is necessary to load this number into the timer’s register as the timer is started.

Step 3) Convert the integer into 2 separate bytes
The PIC18’s are 8 bit micro-controllers. This means that they can only move data around 1 byte at a time. To load the start count into the timer it is necessary to convert the number to hexadecimal and identify the high byte and low byte separately. To convert this decimal number to hex perform successive divisions by 16 keeping track of the remainder for each calculation.

53536/16 = 3346, remainder = 0, 0x0
3346/16 = 209, remainder = 2, 0x2
209/16 = 13, remainder = 1, 0x1
13/16 = 0, remainder = 13, 0xD

So 0xD120 needs to be the starting count with TMR_high_byte = 0xD1 and TMR_low_byte = 0x20.

Register Count of 12000

Step 4) Configure all the timer options and turn the timer on

Timer Configuration
In addition to setting the high and low counter bytes, it’s also necessary to set the configuration register for the timer and then monitor the interrupt flag. The configuration byte has several options to set but the main thing is it turns the timer on and off. Here’s the C-code to do this:

TMR0

// Setting Timer 0 configuration register (T0CON) one bit at a time
T0CONbits.TMR0ON = 1; // 0 = turn timer off, 1 = turn timer on
T0CONbits.T08BIT = 0; // 0 = 16 bit mode, 1 = 8 bit mode
T0CONbits.T0CS = 0; // Clock Source: 0 = Fcy, 1 = pulses on T0CKI pin
T0CONbits.T0SE = 0; // Select Edge: 0 = low to high, 1 = high to low
T0CONbits.PSA = 1; // 1 = bypass the prescaler. 0 = prescaler per the T0PS2:0 bits
T0CONbits.T0PS2 = 0; // Prescaler Selection: 000 = 2, 001 = 4,
T0CONbits.T0PS1 = 0; // 010 = 8, 011 = 16, 100 = 32,
T0CONbits.T0PS0 = 0; // 101 = 64, 110= 128, 111 = 256
// Alternatively the TOCON register could be set as a single byte:
// ON 8B Src Edg PSA PS2 PS1 PS0
T0CON = 0x88; // 1 0 0 0 1 0 0 0 : on, 16 bit, Fcy, no Prescale
TMR0H = 0xD1; // TMROH must be written first
TMR0L = 0x20; // count up from D120h

INTCONbits.TMR0IF = 0; // reset the interrupt flag

while(!INTCONbits.TMR0IF) { // wait for the interrupt flag = 1 then break the loop
// turn something on or off - or just wait
};

T0CONbits.TMR0ON = 0; // 0 = turn timer off, 1 = turn timer on


TMR1

// Setting Timer 1 configuration register (T1CON) one bit at a time
T1CONbits.RD16 = 1; // 0 = R/W as 8 bits, 1 = R/W as 16 bits
//TMR1_OSC = T1CONbits.T1RUN = ; // read only: 1 = TMR1 OSC in use
T1CONbits.T1CKPS1 = 0; // 2 bit Prescale value: 00: PS = 1,
T1CONbits.T1CKPS0 = 0; // 01: PS = 2, 10: PS = 4, 11: PS = 8
T1CONbits.T1OSCEN = 0; // Timer 1 Osc Enable: 0 = off, 1 = on
T1CONbits.T1SYNC = 0; // Timer 1 Osc Sync: 0 = on, 1 = off
T1CONbits.TMR1CS = 0; // Clock Source: 0 = Fcy, 1 = TMR1_OSC
T1CONbits.TMR1ON = 1; // 0 = turn timer off, 1 = turn timer on
// Alternatively the T1CON register could be set as a single byte:
// RD16 CHK PS1 PS0 OSC SYNC SRC ON
T1CON = 0x81; // 1 0 0 0 0 0 0 1
TMR1H = 0xD1; // TMR1H must be written first
TMR1L = 0x20; // count up from D120h

PIR1bits.TMR1IF = 0; // reset the interrupt flag

while(!PIR1bits.TMR1IF) { // wait for the interrupt flag = 1 then break the loop
// turn something on or off - or just wait
};

T1CONbits.TMR1ON = 0; // 0 = turn timer off, 1 = turn timer on


TMR3

// Setting Timer 3 configuration register (T3CON) one bit at a time
T3CONbits.RD16 = 1; // 0 = R/W as 8 bits, 1 = R/W as 16 bits
T3CONbits.T3CCP2 = 0; // used with both CCP modules
T3CONbits.T3CKPS1 = 0; // 2 bit Prescale value: 00: PS = 1,
T3CONbits.T3CKPS0 = 0; // 01: PS = 2, 10: PS = 4, 11: PS = 8
T3CONbits.T3CCP1 = 0; // used with CCP2 module
T3CONbits.T3SYNC = 0; // Timer 1 Osc Sync: 0 = on, 1 = off
T3CONbits.TMR3CS = 0; // Clock Source: 0 = Fcy, 1 = Ext Clk
T3CONbits.TMR3ON = 1; // 0 = turn timer off, 1 = turn timer on
// Alternatively the T3CON register could be set as a single byte:
// RD16 CCP PS1 PS0 CCP1 SYNC SRC ON
T3CON = 0x81; // 1 0 0 0 0 0 0 1
TMR3H = 0xD1; // TMR3H must be written first
TMR3L = 0x20; // count up from D120h

PIR2bits.TMR3IF = 0; // reset the interrupt flag

while(!PIR2bits.TMR3IF) { // wait for the interrupt flag = 1 then break the loop
// turn something on or off - or just wait
};

T3CONbits.TMR3ON = 0; // 0 = turn timer off, 1 = turn timer on


TMR2
TMR2 Postscaler
In order to get a 1 ms time using TMR2 it will be necessary to use both the prescaler and the postscaler.
Perform the following steps:
Step1) Divide 1 ms by a postscale of 16 to get 62.5 us – this is what TMR2 will time
Step2) Multiply 83.333 ns (Tcy) by a prescale of 4 to get Trz = 0.333 us
Step3) Divide 62.5 us by 0.333 us to get the needed count of 187.5 – round up to 188
Step4) Subtract 188 from 256 to get 68 the starting count

// Setting Timer 2 configuration register (T2CON) one bit at a time
T2CONbits.T2OUTPS3 = 1; // 4 bit Postscale value: 0000 = 1, 0001 = 2,
T2CONbits.T2OUTPS2 = 1; // 0010 = 3, 0011 = 4, 0100 = 5, 0101 = 6, 0110 = 7,
T2CONbits.T2OUTPS1 = 1; // 0111 = 8, 1000 = 9, 1001 = 10, 1010 = 11, 1011 = 12,
T2CONbits.T2OUTPS0 = 1; // 1100 = 13, 1101 = 14, 1110 = 15, 1111= 16
T2CONbits.TMR2ON = 1; // 0 = turn timer off, 1 = turn timer on
T2CONbits.T2CKPS1 = 0; // 2 bit Prescale value: 00: PS = 1,
T2CONbits.T2CKPS0 = 1; // 01: PS = 4, 1x: PS = 16
// Alternatively the T2CON register could be set as a single byte:
// NA OPS3 OPS3 OPS3 OPS3 ON PS1 PS0
T2CON = 0x7D; // 0 1 1 1 1 1 0 1
TMR2 = 68; // count up from 68, 0x44

PIR1bits.TMR2IF = 0; // reset the interrupt flag

while(!PIR1bits.TMR2IF) { // wait for the interrupt flag = 1 then break the loop
// turn something on or off - or just wait
};

T2CONbits.TMR2ON = 0; // 0 = turn timer off, 1 = turn timer on