Previous Tutorial | Tutorial 11 | Next Tutorial | |||||
Timer Preloading | Generate Delay With Timer (100% Accurate) | |||||||
Intermediate level ★★☆☆☆ |
In this tutorial, we’ll discuss how to generate accurate time intervals with timer modules using the timer preloading technique. You’ll learn both how to measure and eliminate error in timer output intervals. It’s going to be an easy yet interesting read so let’s get started!
[toc]
Components Needed for this tutorial
Quantity | Component Name |
1 | PIC16F877A |
1 | Breadboard |
1 | Jumper Wires Pack |
1 | 330Ω Resistors |
1 | LED |
1 | 4MHz Crystal OSCillator |
1 | LM7805 Voltage Regulator |
1 | 9v Battery or any Power Supply Source |
1 | PICkit3 (PIC Programmer) |
The Source Of Error
Solving any kind of problems always starts by defining the problem itself. Figuring out what’s wrong or what’s the source of these tiny glitches is the first step to eliminate them all. That’s why we should point out the real reason behind the erroneous output of the timer1 module in the last tutorial (Tutorial 9 – LAB7). The step at which we were calculating the required number of timer overflows in order to get 1-second time interval as an output.
For the 16-Bit Timer1 module, with a system being clocked at a rate of 4MHz, with a prescaler ratio of 1:1, with an initial value of 0 loaded in TMR1 register, and the output time interval is set to be 1-second. Then the number of overflows (X) needed to complete 1-second is given by the following general equation.
By solving for X, we need 15.26 overflows in order to get a time interval of 1 second. In the previous tutorial, we’ve neglected the 0.26 part. That’s obviously the main source of the error!
Well, the Toverflow is = 0.06553
Hence the output time interval, Tout = 15 * 0.06553 = 0.982 second
It should be 1 second, not 0.982 of course. But 15.26 * 0.06553 = 1 second
That’s why we should have taken that 0.26 fraction part into account! But how? How can the number of interrupt overflows be a floating-point number? Does it make sense to have a 4.7, 10.2, or 12.8 number of overflows?
Actually, it does make sense! yet it’s absolutely doable. But it may not be a very good idea to follow as long as we can avoid getting a floating number in the first place!
In this tutorial, I will explain both methods that I use and refer to as “Timer preloading”. Here is a brief description for both:
The first method
I always prefer and recommend this way and find it much easier to explain and implement. In this method, we avoid getting a floating number as a value for the number of overflows (X). This is technically achieved by adjusting the Toverflow for the timer1 module by preloading a calculated value into the TMR1 register at the beginning of each round.
The second method
Which I do not recommend as much. Anyway, in this method we firstly deal with the fraction part of X. Say it was (13.8), In this case, we’ll preload a value to get a (0.8 Toverflow). Then we’ll perform 13 complete overflows starting from (0 to 65535).
What we’ll be doing next?
Well, we’ll discuss the first method as it has proven to be the easier to grasp in most cases. In the next section, I will explain the basic idea in detail. The following couple of sections are going to be a step-by-step implementation, and a practical LAB respectively. Then, the tutorial is done and you can just jump to the next one. However, the final section in this tutorial will be a full demonstration of the second method of preloading for those who are interested (It’s optional).
Timer Preloading Technique
The basic idea of this technique is to avoid getting a floating number as a value for the number of overflows (X). How can we achieve this?
Let’s consider the previous case encountered in LAB7. In which we had a 16-Bit timer module (timer1), a system clocked at a rate of 4MHz, a prescaler of 1:1, and an initial value of 0. To get the Toverflow for this timer module in such settings, just use the general equation. By substituting for the number of overflows(X) by 1, you’ll get the Toverflow
Note that: For (X=1) ==> Tout = Toverflow
The result will be Toverflow = 0.06553 second
And that crazy number is actually the source of pain. Why? well, if that number is the time it takes our timer1 module to reach overflow state. Then how many overflow interrupts do we need to complete a 1-second time interval?
Obviously, it’s 1 / 0.06553
which will result in 15.26 overflow
That’s why Toverflow must be changed!
Well, it takes the timer1 module about (0.06553 seconds) to count from (0 to 65535). What if we could force it to overflow in less time? well, it can be a good solution. Especially if we chose a period of (0.05 second) which is less than 0.06553 and more importantly won’t generate a floating number when we divide 1second/0.05second = 20. Which means that we’ll exactly need clean 20 overflows to get our desired 1-second output interval. There won’t be any remainder fractions and so.
At this point, we all should be questioning the same thing. How can we force the timer1 module to overflow in just 0.05 second instead of 0.06553?
Well, if counting from (0 to 65535) takes 0.06553 seconds. Then if it started counting from a specific value in between, then it can theoretically overflow nearly at any instance of time we wish!
By using the general equation, setting (Tout = 1, X = 20, Fosc = 4MHz, PS = 1), and solving for TMR1. We’ll get the initial value to be preloaded in timer1 module. This will result in
TMR1 = 15535
And that is the value we should preload to the TMR1 register in order to make (Toverflow = 0.05sec). Which means that a Tout of 1-second will need clear 20 overflows!
That’s the basic idea behind the timer preloading technique. We’re changing the time till overflow Toverflow by loading a specific value in the TMR1 register at the beginning of counting, instead of starting at zero as usual!
Implementing Timer Preloading
Implementing the timer preloading technique in C-Code should be an easy task obviously. As long as you’ve correctly performed the calculations involved. Which means you’ve got a value to load to TMR1 and a number for the total interrupts that counts for the desired Tout. And here is the systematic step-by-step procedure for the timer preloading.
step1 – Configure The Timer Module
Check the Timer1 logic diagram in the datasheet to make sure that you’re doing this right. Don’t forget to preload the value which we’ve calculated previously. Let’s consider the same settings for LAB7, thus the loading value will be (15535) and the counter X will go up to (20) as we’ve calculated previously. The C-Code for this step will be as follows
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// -- [[ Configure Timer1 To Operate In Timer Mode ]] -- // Preload The Value Which We've Calculated To The TMR1 16-Bit Register! TMR1 = 15535; // Choose the local clock source (timer mode) TMR1CS = 0; // Choose the desired prescaler ratio (1:1) T1CKPS0 = 0; T1CKPS1 = 0; // Event1 = (e.g. LED ON, Read sensor, etc) ... // Switch ON Timer1 Module! TMR1ON = 1; |
step2- Write The ISR
At each overflow, you should preload the specific value in TMR1 register. And the if-statement should check for the new value of X which is 20 instead of 15 (as in LAB7)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void interrupt ISR () { // Check The Flag Bit if (TMR1IF) { C++; if(C==20) // Note that we're counting up to 20 timer overflows! { // Event2 = (e.g. Toggle LED, Stop perupheral, Take ADC reading, etc) ... // Clear The Global Counter C = 0; } // Preload The Value To TMR1 Register Every Overflow Interrupt TMR1 = 15535; TMR1IF = 0; // Clear The Flag Bit } } |
And that’s it! Implementing the timer preloading technique in this method should take no longer than 5-minutes to perform the previous couple of steps!
Generate 1sec Delay With Timer Preloading – LAB
Lab Name | Generate 1-sec Delay With Timer Preloading |
Lab Number | 8 |
Lab Level | Intermediate |
Lab Objectives | Learn how to use the timer preloading technique to generate 100% accurate time intervals with Timer1 Module. And double-check the results. |
1. Coding
Open the MPLAB IDE and create a new project name it “Timer Preloading”. If you have some issues doing so, you can always refer to the previous tutorial using the link below.
Set the configuration bits to match the generic setting which we’ve stated earlier. And if you also find troubles creating this file, you can always refer to the previous tutorial using the link below.
Now, open the main.c file and let’s start developing the firmware for our project.
Our first task is to configure the RB0 to be an output pin (for LED) and set it to be OFF (initially)
1 2 |
TRISB0 = 0; RB0 = 0; |
Now, we’ll configure the Timer1 module to operate in timer mode. And don’t forget to preload the 15535 value which we’ve previously calculated.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// -- [[ Configure Timer1 To Operate In Timer Mode ]] -- // Preload The Value Which We've Calculated To The TMR1 16-Bit Register! TMR1 = 15535; // Choose the local clock source (timer mode) TMR1CS = 0; // Choose the desired prescaler ratio (1:1) T1CKPS0 = 0; T1CKPS1 = 0; // Event1 = LED ON RB0 = 1; // Switch ON Timer1 Module! TMR1ON = 1; |
Now, it’s time to configure the timer overflow interrupt and write the ISR handler for it. The configuration step is the same as we’ve been doing for a while.
1 2 3 4 5 |
// -- [[ Interrupts Configurations ]] -- TMR1IE = 1; // Timer1 Interrupt Enable Bit TMR1IF = 0; // Clear The Interrupt Flag Bit PEIE = 1; // Peripherals Interrupts Enable Bit GIE = 1; // Global Interrupts Enable Bit |
Then, we’ll handle the Timer1 overflow interrupt by writing the ISR (interrupt service routine)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void interrupt ISR () { // Check The Flag Bit if (TMR1IF) { C++; if(C==20) // Note that we're counting up to 20 timer overflows! { // Event2 = Toggle LED RB0 = ~RB0; // Clear The Global Counter C = 0; } // Preload The Value To TMR1 Register Every Overflow Interrupt TMR1 = 15535; TMR1IF = 0; // Clear The Flag Bit } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
#include "config.h" #include <stdint.h> uint8_t C = 0; // Global Counter Variable void main() { // -- [[ IO Configurations ]] -- TRISB0 = 0; RB0 = 0; // -- [[ Configure Timer1 To Operate In Timer Mode ]] -- // Preload The Value Which We've Calculated To The TMR1 16-Bit Register! TMR1 = 15535; // Choose the local clock source (timer mode) TMR1CS = 0; // Choose the desired prescaler ratio (1:1) T1CKPS0 = 0; T1CKPS1 = 0; // Event1 = LED ON RB0 = 1; // Switch ON Timer1 Module! TMR1ON = 1; // -- [[ Interrupts Configurations ]] -- TMR1IE = 1; // Timer1 Interrupt Enable Bit TMR1IF = 0; // Clear The Interrupt Flag Bit PEIE = 1; // Peripherals Interrupts Enable Bit GIE = 1; // Global Interrupts Enable Bit while(1) { // Stay IDLE ,, Timer Interrupt Will Handle Everything For Us ! } } // Interrupt Service Routine - ISR void interrupt ISR () { // Check The Flag Bit if (TMR1IF) { C++; if(C==20) // Note that we're counting up to 20 timer overflows! { // Event2 = Toggle LED RB0 = ~RB0; // Clear The Global Counter C = 0; } // Preload The Value To TMR1 Register Every Overflow Interrupt TMR1 = 15535; TMR1IF = 0; // Clear The Flag Bit } } |
And that’s it! Implementing the timer preloading technique in this method should take no longer than several minutes to accomplish and develop the necessary firmware. Now, cross your fingers and Hit that compile button!
It should work flawlessly for you. And it’s the time to have your own hands-on experience and test some different values and settings! And I’ll be here to help you if it gets dodgy.
2. Simulation
The simulation results for this LAB should be a pure square wave with a 1-sec pulse width as shown below
Compare that output diagram with the one we got in the previous LAB7 and notice the difference
Without Timer Preloading
The Output Time Interval Tout = 0.98 second |
With Timer Preloading
The Output Time Interval Tout = 1 second (Exactly)! |
Second Method Of Timer Preloading
For those who are interested in the second way of implementing the timer preloading technique. This should be a bonus (optional) section of this tutorial. Anyway, let’s recap the main problem that we’re trying to solve via time preloading. Which is most of the time we get rational numbers as a value for X which is the number of overflows required to complete the desired time interval (Tout).
Let’s consider the following case, we’ve got a system running @ 4MHz, with 16-Bit timer, 1:1 Prescaler, and we need to get a time interval of (Tout = 2 seconds). Assuming that the timer starts counting at zero (TMR1 = 0), then the general equation will give the following results.
X = (1 / 0.06553) = 30.52 overflows
As you’ve noticed, X is not an integer! We also can not neglect the fraction part (0.52 overflow) it’s not an option. But the idea of the method we’re talking about right now is to preload the timer module in order to perform a (0.52 of the full Toverflow period). Which is 0.52 * 0.06553 = 0.034 second. And here is the step-by-step procedure in order to calculate and implement this method of preloading.
Step1 – Calculate the initial value to be loaded
The time interval value for (0.52 of Toverflow) is = 0.034 second. So let’s substitute for Tout by 0.034, X = 1, and solve for the initial value TMR1
Thus, the value which we’ll be loading to Timer1 is => (TMR1 = 34002)
Step2 – Configure The Timer1 Module
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// -- [[ Configure Timer1 To Operate In Timer Mode ]] -- // Load The Value Which We've Calculated To The TMR1 Register TMR1 = 34002; // Choose the local clock source (timer mode) TMR1CS = 0; // Choose the desired prescaler ratio (1:1) T1CKPS0 = 0; T1CKPS1 = 0; // Event1 = LED ON RB0 = 1; // Switch ON Timer1 Module! TMR1ON = 1; |
Step3 – Write The ISR Handler
Just pay attention to where we’re loading the specific value to the TMR1 register. It’s done only once before the first overflow, and the Timer1 will be counting from (0 to 65535) for all of the consecutive 30-overflows till the end of the pre-calculated Tout time interval. Which means that we load it once only when (C == 31)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void interrupt ISR () { // Check The Flag Bit if (TMR1IF) { C++; if(C==31) // Note That We're Taking The First Overflow Into Account! { // Event2 = Toggle LED RB0 = ~RB0; // Clear The Global Counter C = 0; } // Load The Value Which We've Calculated To The TMR1 Register TMR1 = 34002; TMR1IF = 0; // Clear The Flag Bit } } |
And That’s it! Yea it’s actually more efficient way as it seems to be. However, from my own perspective, I can see that it’s a little bit trickier and not a beginner-friendly way to do this job. Anyway, Here is the full code listing for the time preloading implementation (method-2). Cross your fingers! Hit the compile button! Then, it’s the time to play with it and do some testing on your own.
The Full Code Listing For LAB-8 Using The 2nd Method of Timer Preloading
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
#include "config.h" #include <stdint.h> uint8_t C = 0; // Global Counter Variable void main() { // -- [[ IO Configurations ]] -- TRISB0 = 0; RB0 = 0; // -- [[ Configure Timer1 To Operate In Timer Mode ]] -- // Load The Value Which We've Calculated To The TMR1 Register TMR1 = 34002; // Choose the local clock source (timer mode) TMR1CS = 0; // Choose the desired prescaler ratio (1:1) T1CKPS0 = 0; T1CKPS1 = 0; // Event1 = LED ON RB0 = 1; // Switch ON Timer1 Module! TMR1ON = 1; // -- [[ Interrupts Configurations ]] -- TMR1IE = 1; // Timer1 Interrupt Enable Bit TMR1IF = 0; // Clear The Interrupt Flag Bit PEIE = 1; // Peripherals Interrupts Enable Bit GIE = 1; // Global Interrupts Enable Bit while(1) { // Stay IDLE ,, Timer Interrupt Will Handle Everything For Us ! } } // Interrupt Service Routine - ISR void interrupt ISR () { // Check The Flag Bit if (TMR1IF) { C++; if(C==31) // Note That We're Taking The First Overflow Into Account! { // Event2 = Toggle LED RB0 = ~RB0; // Clear The Global Counter C = 0; } // Load The Value Which We've Calculated To The TMR1 Register TMR1 = 34002; TMR1IF = 0; // Clear The Flag Bit } } |
That’s it actually. Try calculating and generating a 1-second time interval. The value to preload should be nearly 34002 and X=15.26 Hence, the C counter should be counting up to 16. Double-Check these results yourself and try other values if you wish. And I’ll be here for help!
Conclusion
1
Use oscilloscope and logic analyzer to double check your output validity. Get familiar with your tools to find out if everything is running OK or there is a problem. You should also be able to figure out when and where changes have to be made.
2
The timer preloading technique that we’ve discussed in this tutorial will help you generate accurate time intervals with timer modules without any glitches. However, if improperly implemented, it’ll do more of a headache than good. So just be careful and take the time to calculate the required stuff.
If you find this useful, then share it with your network and let me know that you’re willing to see more of my tutorials. See You In The Next Tutorials… Good Luck ^^
Previous Tutorial | Tutorial 11 | Next Tutorial |
Just want to say thank you. I’m a hardware engineer trying to break into the fw side of things and this is by far the best site I’ve come across.
You’re welcome. I’m so glad to hear this ^^
Dear Brother Khaled
I have built this code using your instruction, the generated time interval does not give 1s delay. Could you please take a close look at the code and let me know what do you think.
Thanks very much in advance
Adam
// Configuration Bit Settings
#pragma config FOSC = HS
#pragma config WDTE = OFF
#pragma config PWRTE = OFF
#pragma config BOREN = OFF
#pragma config LVP = OFF
#pragma config CPD = OFF
#pragma config WRT = OFF
#pragma config CP = OFF
// Variable Declaration
#include
#define _XTAL_FREQ 4000000
void main(void)
{
ADCON1=7; // To Disable All Internal Analogue Channel
CMCON=7; // To Disable The Intenal Comparator
TRISBbits.TRISB1=0;
PORTBbits.RB1=0;
// Timer TMR0 SetUp
PIR1bits.TMR1IF=0; // Clear Interrupt Flag bit
TMR1=15535; // Load Count Value Into TMR-1
T1CONbits.TMR1CS=0; // Assign the Internal Clock Source
T1CONbits.T1CKPS1=0; // Select the Prescaler Rate 1:1
T1CONbits.T1CKPS0=0;
PIE1bits.TMR1IE=1; // Enable Interrupt Upon Overflow
INTCONbits.GIE=1; // Enable Global Interrupt
INTCONbits.PEIE=1; //Enable Peripheral Interrupt
T1CONbits.TMR1ON=1; //Enable Peripheral Interrupt
// Main Program
while(1)
{
if (!PIR1bits.TMR1IF)
{
PORTBbits.RB1=~PORTBbits.RB1;
PIR1bits.TMR1IF=0; //Clear Interrupt Flag of TMR0
TMR1=0x0BDC;
}
}
}
// End of Program
Hmmm. I’m just wondering why did you enable the TMR1 Interrupt signal despite the fact that you’re not using it at all. You’re polling the TMR1IF flag bit. It’s OK to do so, functionally, but it’s not the way to handle interrupts generally.
Counting from 15535 up to 65535 won’t be done in 1 second as you hope for.
Provided that your Fosc=4MHz & TMR1 Prescaler is 1:1, So the TMR1 will overflow in 0.05 second.
Therefore, you’ll have to create a software counter “Variable” and count 20 timer overflow event. Only then you can toggle your IO pin and expect it to be 1 second time interval.
I hope this helps and please copy the code in the tutorial and give it a try.
Good luck,
Regards,
Thanks very much for the hint…The fragment of counter code has now been added in order to count the number of overflow events. The generated waveform (using Proteus Software) gives a precise 1 second delay, but the shape of waveform is like the capacitor behavior [during charge & discharge states]. The waveform actually comes with ramps and decays, I expected to be a square wave. Probably this is something to do with the software itself.
I would appreciate hearing your input on this matter
Best Wishes,
Adam
No problem my friend, you’re always welcome ^^
Well, set the oscilloscope’s coupling to “DC”, instead of “AC”. Then, tell me if it looks like what you want or not
Great tutorial and now I understand everything behind the scenes in the Timer Modules manner.But I wonder why we declared again the TMR1=15535 in the ISR part and if part of the code while we have already declared it at the beginning of the code globally?
No No .. TMR1 is not a variable at all, it’s the Timer1 16-bit register
After Enabling the Timer1 module to count “By setting the TMR1ON bit”
TMR1ON = 1;
Then, the TMR1 starts incrementing automatically each cycle from Fosc/4 clock input
The line of TMR1=15535 is preloading the Timer1 module’s register
to start counting not from zero but from the value 15535
and every time it reaches the maximum value 2^16-1 = 65535, then an interrupt is fired and in the ISR we preload it again with TMR1=15535
I hope this clarifies the technique a little bit more ^^
I see.Then, can we say it would be enough to preload a value to timer1 which is TMR1=15535 only in the ISR(Interrupt Service Routine) handler part of the code?I mean in the if sections of the code?
O.o That’s Ture! I think I’ve cleared the idea but implemented it wrong in the code!
It should be edited now. Thanks a lot, Yaşar <3
Should not we write in the second method if (C==1) instead of if(C==31)?
Why to do so?
Preloading a value of 34002 will make the Timer1 overflow once each 0.0315 second
so the number of interrupts needed to count a 1 second time interval is 1/0.0315 = 31 (nearly)
I am not a C programmer but have worked with Basic and machine language
as a hobby. So that is my prospective here.
In tutorial 11, lab 8 looking at the coding for the interrupt ISR second timing
method, shouldn’t TMR1 = 34002 be under the third set of braces?
To me, this would preload the timer only when C is 31. And, since
TMR!F is cleared right after, the preloading would happen before
the next timer overflow.
Thank you for your perspective on this matter.
The timer should be loaded with the value we’ve calculated each time it reaches overflow state. This doesn’t depend on the number of overflow interrupts (C) so it’s placed at the end of ISR.
If you’re in doubt, you can still simulate this and measure the period on a scope.
I realized that we loaded the 34002 value to TMR1 register for every overlow situation.Shouldn’t we write
this value to TMR1 register only for the fraction part of X=0.24(in other words for Tout = 0.034 seconds) ?