Timers Tutorial PDF
Timers Tutorial PDF
In the sections dealing with toggling a LED, it is assumed to be connected to PORTB, bit 0 of your chosen AVR (pin 1 of DIP AVRMEGA16). To start off, we will deal with basic timer functionality and move on from there.
Extremely simple. I'm going to assume you are familiar with the basics of setting up AVR ports as well as bit manipulation (if you're uncertain about the latter, refer to this excellent tutorial). With that in mind, I'll add in the LED-related code and add in the IF statement: Code:
#include <avr/io.h> int main (void) { DDRB |= (1 << 0); // Set LED as output // TODO: Set up timer
for (;;) { // TODO: Check timer value in if statement, true when count matches 1/20 of a second if () { PORTB ^= (1 << 0); // Toggle the LED } } } // TODO: Reset timer value
Now we need to start dealing with the timer. We want to do nothing more than start it at 1MHz, then check its value later on to see how much time has elapsed. We need to deviate for a second and learn a little about how the timer works in its most basic mode.
The AVR timer circuits come in two different widths, 8 and 16 bit. While the capabilities of the two timer types differ, at the most basic level (simple counting), the only difference is the maximum amount of time the timer can count to before overflowing and resetting back to zero. Those familiar with C will know that an unsigned eight bit value can store a value from 0 to (2^8 - 1), or 255, before running out of bits to use and becoming zero again. Similarly, an unsigned 16 bit value may store a value from 0 to (2^16 - 1), or 65535 before doing the same. As the name suggests, an 8 bit timer stores its value as an eight bit value in its count register, while the 16 bit timer stores its current count value in a pair of eight bit registers. Each advancement of the counter register for any AVR timer indicates that one timer period has elapsed.
Our project needs a fairly long delay, of 1/20 of a second. That's quite short to us humans, but to a microcontroller capable of millions of instructions per second it's a long time indeed! Our timer will be running at the same clock speed as the AVR core to start with, so we know that the frequency is 1MHz. One Megahertz is 1/1000000 of a second, so for each clock of the timer only one millionth of a second has elapsed! Our target is 1/20 of a second, so let's calculate the number of timer periods needed to reach this delay: Target Timer Count = (1 / Target Frequency) / (1 / Timer Clock Frequency) = (1 / 20) / (1 / 1000000) = .05 / 0.000001 = 50000 So running at 1MHz, our timer needs to count to 50000 before 1/20th of a second has elapsed. That's a very large value - too large for an 8 bit value! We'll need to use the 16 bit timer 1 instead. Firstly, we need to start the timer at the top of our main routine, so that it will start counting. To do this, we need to supply the timer with a clock; as soon as it is clocked it will begin counting in parallel with the AVR's CPU core (this is called asynchronous operation). To supply a clock of Fcpu to the timer 1 circuits we need to set the CS10 bit (which selects a Fcpu prescale of 1 - more on that later) in the TCCR1B, the Timer 1 Control Register B.
Code:
#include <avr/io.h> int main (void) { DDRB |= (1 << 0); // Set LED as output TCCR1B |= (1 << CS10); // Set up timer for (;;) { // TODO: Check timer value in if statement, true when count matches 1/20 of a second if () { PORTB ^= (1 << 0); // Toggle the LED // TODO: Reset timer value } } }
Now, with only one line of code, we've started the asynchronous timer 1 counting at 1MHz - the same speed as our AVR. It will now happily continue counting independently of our AVR. However, at the moment it isn't very useful, we still need to do something with it! We want to check the timer's counter value to see if it reaches 1/20 of a second, or a value of 50000 at 1MHz as we previously calculated. The current timer value for timer 1 is available in the special 16-bit register, TCNT1. In actual fact, the value is in two 8-bit pair registers TCNT1H (for the high byte) and TCNT1L (for the low byte), however the C library implementation we're using helpfully hides this fact from us. Let's now add in our check to our code - it's as simple as testing the value of TCNT1 and comparing against out wanted value, 50000. To prevent against missed compares (where the timer updates twice between checks so our code never sees the correct value), we use the equal to or more than operator, ">=". Code:
#include <avr/io.h> int main (void) { DDRB |= (1 << 0); // Set LED as output TCCR1B |= (1 << CS10); // Set up timer for (;;) { // Check timer value in if statement, true when count matches 1/20 of a second if (TCNT1 >= 50000) { PORTB ^= (1 << 0); // Toggle the LED // TODO: Reset timer value } } }
Great! We've only got one more line of code to write, to reset the timer value. We already know the current value is accessed via the TCNT1 register for Timer 1, and since this is a read/write register, we can just write the value 0 to it once our required value is reached to rest it.
Code:
#include <avr/io.h> int main (void) { DDRB |= (1 << 0); // Set LED as output TCCR1B |= (1 << CS10); // Set up timer for (;;) { // Check timer value in if statement, true when count matches 1/20 of a second if (TCNT1 >= 50000) { PORTB ^= (1 << 0); // Toggle the LED } } } TCNT1 = 0; // Reset timer value
And there we have it! We've just created a very basic program that will toggle our LED every 1/20 of a second at a 1MHz clock. Testing it out on physical hardware should show the LED being dimmer than normal (due to it being pulsed quickly). Good eyesight might reveal the LED's very fast flickering. Next, we'll learn about the prescaler so we can try to slow things down a bit.
For a 1MHz clock, we can construct a table of resolutions using the avaliable prescaler values and a Fcpu of 1MHz. Prescaler Value 1 8 64 256 1024 Resolution @ 1MHz 1us 8us 64us 256us 1024us
If you recall our equation for calculating the timer value for a particular delay in part 1 of this tutorial, you'll remember it is the following: Target Timer Count = (1 / Target Frequency) / (1 / Timer Clock Frequency) However, as we've just altered the prescaler term, the latter half is now different. Substituting in our new resolution equation from above we get: Target Timer Count = (1 / Target Frequency) / (Prescale / Input Frequency)
Now, we want to see if there is a prescaler value which will give an *exact* delay of 1Hz. One Hertz is equal to one cycle per second, so we want our compare value to be one second long, or 1000000uS. Let's divide that by each of our resolutions and put the results in a different table:
The results are interesting. Of the available prescaler values, we can immediately discount 256 and 1024 - they do not evenly divide into our wanted delay period. They are of course usable, but due to the rounding of the timer count value the resultant delay will be slightly over or under our needed delay. That leaves us with three possible prescales; 1, 8 and 64. Our next task is to remove the values that aren't possible. On an 8-bit timer, that means discounting values of more than 255, as the value won't fit into the timer's 8-bit count register. For our 16-bit timer, we have a larger range of 0 to 65535. Only one of our prescaler values satisfies this requirement - a prescale of 64 - as the other two possibilities require a timer count value of more bits than our timer is capable of storing.
Let's go back to our original timer program and modify it to compare against our new value of 15625, which we've found to be 1 second at a prescale of 64 and a Fcpu of 1MHz: Code:
#include <avr/io.h> int main (void) { DDRB |= (1 << 0); // Set LED as output // TODO: Set up timer at Fcpu/64 for (;;) { // Check timer value in if statement, true when count matches 1 second if (TCNT1 >= 15625) { PORTB ^= (1 << 0); // Toggle the LED } } } TCNT1 = 0; // Reset timer value
Note I've removed the timer setup line, as it is no longer valid. We want to set up our timer to run at Fcpu/64 now. To do this, we need to look at the datasheet of the MEGA16 to see which bits need to be set in which control registers. Checking indicates that we need to set both the CS10 and CS11 prescaler bits in TCCR1B, so let's add that to our program: Code:
#include <avr/io.h> int main (void) { DDRB |= (1 << 0); // Set LED as output TCCR1B |= ((1 << CS10) | (1 << CS11)); // Set up timer at Fcpu/64 for (;;) { // Check timer value in if statement, true when count matches 1 second if (TCNT1 >= 15625) { PORTB ^= (1 << 0); // Toggle the LED } } } TCNT1 = 0; // Reset timer value
Compile it, and we're done! Remembering that our timer runs as soon as it gets a clock source, our program will now work, flashing the LED at a frequency of 1Hz.
We need some code to create an initialize a new counter variable to 0, then increment it when the counter reaches one second as our psudocode states. We also need to add in a test to see if our new variable reaches the value of 60, indicating that one minute has elapsed.
Code:
#include <avr/io.h> int main (void) { // TODO: Initialize a new counter variable to zero DDRB |= (1 << 0); // Set LED as output TCCR1B |= ((1 << CS10) | (1 << CS11)); // Set up timer at Fcpu/64 for (;;) { // Check timer value in if statement, true when count matches 1 second if (TCNT1 >= 15625) { TCNT1 = 0; // Reset timer value // TODO: Increment counter variable // TODO: Check here to see if new counter variable has reached 60 if () { // TODO: Reset counter variable PORTB ^= (1 << 0); // Toggle the LED } } } }
Now that we have our new program's structure, replacing the TODOs becomes very simple. We want a target count of 60, which is well within the range of an unsigned integer variable, so we'll make our counter variable of type unsigned integer. The rest of the code is extreamly simple, so I'll add it all in at once: Code:
#include <avr/io.h> int main (void) { unsigned char ElapsedSeconds = 0; // Make a new counter variable and initialize to zero DDRB |= (1 << 0); // Set LED as output TCCR1B |= ((1 << CS10) | (1 << CS11)); // Set up timer at Fcpu/64 for (;;) { // Check timer value in if statement, true when count matches 1 second if (TCNT1 >= 15625) { TCNT1 = 0; // Reset timer value ElapsedSeconds++; if (ElapsedSeconds == 60) // Check if one minute has elapsed { ElapsedSeconds = 0; // Reset counter variable PORTB ^= (1 << 0); // Toggle the LED } } } }
Compile and run, and the LED should toggle once per minute. By extending this technique, we can produce delays of an arbitrary duration. One point of interest is to note that any timing errors compound - so if the timer input frequency is 1.1MHz rather than 1.0MHz our one minute timer will be sixty times that small error out in duration. For this reason it is important to ensure that the timer's clock is as accurate as possible, to reduce long-term errors as much as possible.
} }
Now, we need to flesh out the skeleton code we have. First up we need to configure our timer for CTC mode. As you might be able to guess, we want to configure our timer, thus the bits we want will be located in the timer's Control registers. The table to look for is the one titled "Waveform Generation Mode Bit Description", and is located in timer Control register descriptions for each timer. This table indicates all the possible timer modes, the bits required to set the timer to use those modes, and the conditions each mode reacts to. You should note that our previous examples have ignored this table altogether, allowing it to use its default value of all mode bits set to zero. Looking at the table we can see that this setup corresponds to the "Normal" timer mode. We want to use the CTC mode of the timer, so let's look for a combination of control bits that will give us this mode. Interestingly, it seems that two different combinations in Timer 1 of the MEGA16 will give us the same CTC behavior we desire. Looking to the right of the table, we can see that the "Top" value (that is, the maximum timer value for the mode, which corresponds to the compare value in CTC mode) uses different registers for each. Both modes behave in the same manner for our purposes and differ only by the register used to store the compare value, so we'll go with the first. The table says that for this mode, only bit WGM12 needs to be set. It also says that the register used for the compare value is named OCR1A. Looking at the timer control registers (TCCR1A and TCCR1B) you should notice that the WGM1x bits - used to configure the timer's mode - are spread out over both registers. This is a small pain as you need to find out which bits are in which register, but once found setting up the timer becomes very easy. In fact, as we only have one bit to set - WGM12 - our task is even easier. The MEGA16's datasheet says that WGM12 is located in the TCCR1B register, so we need to set that. Code:
#include <avr/io.h> int main (void) { DDRB |= (1 << 0); // Set LED as output TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode // TODO: Set compare value for a compare rate of 1Hz TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64 for (;;) { if () // TODO: Check CTC flag { PORTB ^= (1 << 0); // Toggle the LED } } } // TODO: Clear CTC flag
The second task for this experiment is to set the compare value - the value that will reset the timer and set the CTC flag when reached by the timer. We know from the datasheet that the register for
this is OCR1A for the MEGA16 in the first CTC timer mode, so all we need is a compare value. From our previous experiment we calculated that 1Hz at 1MHz with a prescaler of 64 needs a compare value of 15625, so let's go with that. Code:
#include <avr/io.h> int main (void) { DDRB |= (1 << 0); // Set LED as output TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode OCR1A = 15625; // Set CTC compare value to 1Hz at 1MHz AVR clock, with a prescaler of 64 TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64 for (;;) { if () // TODO: Check CTC flag { PORTB ^= (1 << 0); // Toggle the LED } } } // TODO: Clear CTC flag
There, almost done already! Last thing we need is a way of checking to see if the compare has occurred, and a way to clear the flag once its been set. The place to look for the compare flags is in the timer's Interrupt Flag register - an odd place it seems, but the reason will become clear in the next section dealing with timer interrupts. The MEGA16's Timer 1 interrupt flags are located in the combined register TIFR, and the flag we are interested in is the "Output Compare A Match" flag, OCF1A. Note the "A" on the end; Timer 1 on the MEGA16 has two CTC channels (named channel A and channel B), which can work independently. We're only using channel A for this experiment. Checking for a CTC event involves checking the OCF1A flag in this register. That's easy - but what about clearing it? The datasheet includes an interesting note on the subject: Quote: ...OCF1A can be cleared by writing a logic 1 to its bit location Very strange indeed! In order to clear the CTC flag, we actually need to set it - even though it's already set. Due to some magic circuitry inside the AVR, writing a 1 to the flag when its set will actually cause it to clear itself. This is an interesting behavior, and is the same across all the interrupt bits. Despite that, we can now add in our last lines of code to get a working example: Code:
#include <avr/io.h> int main (void) { DDRB |= (1 << 0); // Set LED as output TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode OCR1A = 15625; // Set CTC compare value to 1Hz at 1MHz AVR clock, with a prescaler of 64
TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64 for (;;) { if (TIFR & (1 << OCF1A)) { PORTB ^= (1 << 0); // Toggle the LED } } } TIFR = (1 << OCF1A); // Clear the CTC flag (writing logic one to the set flag clears it)
Note that when clearing the OCF1A flag, we assign its value to the TIFR register. This is because it will assign a logic one to the flag's position (clearing it) but also because as writing zeros to the other flags won't affect them, we can go ahead and use the smaller (codesize-wise) direct assignment, rather than ORing the register to form a read/modify/write sequence. This is also beneficial because it prevents the compiler from writing logic one to the other flags if they were already set via the read/modify/write, which would clear unwanted flags. And there we have it, a working 1Hz LED flasher using the CTC timer mode!
Set timer compare value to one second WHILE forever END WHILE
We can start off this by working with our skeleton main code, used in previous examples. I'll skip the details on the parts already discussed in previous sections. Code:
#include <avr/io.h> int main (void) { DDRB |= (1 << 0); // Set LED as output TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode // TODO: Enable CTC interrupt // TODO: Enable global interrupts OCR1A = 15625; // Set CTC compare value to 1Hz at 1MHz AVR clock, with a prescaler of 64
TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64 for (;;) { } } // TODO: Add compare ISR here
Note how it's a modified version of the non-interrupt driven CTC example covered in the last section. All we need to do is tell the timer to run the compare ISR we define when it counts up to our compare value, rather then us polling the compare match flag in our main routine loop. We'll start with creating the ISR first, as that's quite simple. In AVR-GCC - specifically, the avr-libc Standard C Library that comes with it - the header file for dealing with interrupts is called (unsurprisingly) "interrupt.h" and is located in the "avr" subdirectory. We need to include this at the top of our program underneath our include to the IO header file. The top of our code should look like this: Code:
#include <avr/io.h> #include <avr/interrupt.h> int main (void) { ...
This gives us access to the API for dealing with interrupts. We want to create an ISR for the Timer 1 Compare Match event. The syntax for defining an ISR body in AVRGCC is: Code:
ISR(VectorName_vect) { // Code to execute on ISR fire here }
Where "VectorName" is the name of the ISR vector which our defined ISR handles. The place to go to find this name is the "Interrupt" section of the datasheet, which lists the symbolic names for all the ISR vectors that the chosen AVR supports. When writing the vector name into GCC, replace all spaces with underscores, and append "_vect" to the end of the vector's name. Like in part four we are still dealing with Channel A Compare of Timer 1, so we want the vector named "TIMER1 COMPA". In GCC this is called "TIMER1_COMPA_vect", after performing the transformations outlined in the last paragraph. Once the ISR is defined, we can go ahead and write out it's body, adding the LED toggling code. Code:
#include <avr/io.h> #include <avr/interrupt.h> int main (void) { DDRB |= (1 << 0); // Set LED as output TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode // TODO: Enable CTC interrupt // TODO: Enable global interrupts OCR1A = 15625; // Set CTC compare value to 1Hz at 1MHz AVR clock, with a prescaler of 64 TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64 for (;;) { } } ISR(TIMER1_COMPA_vect) { PORTB ^= (1 << 0); // Toggle the LED }
Notice how we don't clear the CTC event flag like in part four - this is automatically cleared by the AVR hardware once the ISR fires. Neat, isn't it! Running the code so far won't yield any results. This is because although we have our ISR all ready to handle the CTC event, we haven't enabled it! We need to do two things; enable the "TIMER1 COMPA" interrupt specifically, and turn on interrupt handling on our AVR. The way to turn on our specific interrupt is to look into the second interrupt-related register for our timer, TIMSK. This is the Timer Interrupt Mask register, which turns on and off ISRs to handle specific timer events. Note that on the MEGA16 this single register contains the enable bits for all the timer interrupts for all the available timers. We're only interested in the Timer 1 Compare A Match interrupt enable bit, which we can see listed as being called OCIE1A (Output Compare
Interrupt Enable, channel A). By setting that bit we instruct the timer to execute our ISR upon compare match with our specified compare value. Let's put that line into our program's code and see how it all looks. Code:
#include <avr/io.h> #include <avr/interrupt.h> int main (void) { DDRB |= (1 << 0); // Set LED as output TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode TIMSK |= (1 << OCIE1A); // Enable CTC interrupt // TODO: Enable global interrupts OCR1A = 15625; // Set CTC compare value to 1Hz at 1MHz AVR clock, with a prescaler of 64
TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64 for (;;) { } ISR(TIMER1_COMPA_vect) { PORTB ^= (1 << 0); // Toggle the LED }
Only one more thing to do - enable global interrupts. The AVR microcontrollers have a single control bit which turns on and off interrupt handling functionality. This is used in pieces of code where interrupt handling is not desired, or to disable interrupts while an ISR is already being executed. The latter is done automatically for us, so all we need to do is turn on the bit at the start of our code, and our compare interrupt will start to work. The command to do this is called "sei" in the avr-libC library that ships with WinAVR, and is named to correspond with the assembly instruction which does the same for AVRs (the SEI instruction). That's irrelevant however, as we just need to call the command in our code. Code:
#include <avr/io.h> #include <avr/interrupt.h> int main (void) { DDRB |= (1 << 0); // Set LED as output TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode TIMSK |= (1 << OCIE1A); // Enable CTC interrupt sei(); // Enable global interrupts OCR1A = 15625; // Set CTC compare value to 1Hz at 1MHz AVR clock, with a prescaler of 64
TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64 for (;;) { } }
And our example is finished! Running this will give a nice 1Hz LED flashing, using the timer's event interrupts. The nice thing is that the timer operation is now completely handled for us in hardware - once set up, we just need to react to the events we've configured. Notice that our main loop is now empty; you may fill that will other code which executed when the timer is counting up to the compare value, or even put in sleep commands to save power between compares.
Now we have a problem. All the previous chapters have assumed the LED is attached to PORTB, bit 0 - but we'll have to move it for this chapter. As stated above the alternative functions cannot be moved to another pin, so we must move moses...I mean, our LED, to the pin with the required alternative function. Timer 1 Channel A's Compare Output is located on PD5, so move the LED there for the rest of this example. Now, let's psudocode: Code:
Set up LED hardware Set up timer in CTC mode Enable timer 1 Compare Output channel A in toggle mode Set timer compare value to one second WHILE forever END WHILE
Amazing how simple it is, isn't it! Well, we can already fill in almost all of this: Code:
#include <avr/io.h> #include <avr/interrupt.h> int main (void) { DDRB |= (1 << 0); // Set LED as output TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode // TODO: Enable timer 1 Compare Output channel A in toggle mode OCR1A = 15625; // Set CTC compare value to 1Hz at 1MHz AVR clock, with a prescaler of 64
TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64 for (;;) { } }
All we need is to configure the timer so that it'll toggle our channel A output each time the timer value is equal to our compare value. The datasheet has several descriptions for the functionality of the COM1Ax and COM1Bx bits, so we need to find the table corresponding to the mode we're using the timer in. CTC mode isn't listed - instead the appropriate table is listed as "Compare Output mode, Non PWM". PWM stands for "Pulse Width Modulation", and will be covered later on in this tutorial. For now, it is sufficient to know that the CTC mode is not a form of PWM and thus the non-PWM bit description table is the one we're looking for. To make the channel A Compare Output pin toggle on each compare, the datasheet says we need to set bit COM1A0 in TCCR1A. That's out missing line - let's add it in! Code:
#include <avr/io.h> #include <avr/interrupt.h> int main (void) { DDRB |= (1 << 0); // Set LED as output
TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode TCCR1A |= (1 << COM1A0); // Enable timer 1 Compare Output channel A in toggle mode OCR1A = 15625; // Set CTC compare value to 1Hz at 1MHz AVR clock, with a prescaler of 64
TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64 for (;;) { } }
Simple, isn't it! We've now created the simplest (code-wise) LED flasher possible using pure hardware functionality. Running this will cause the LED to flash at 1Hz, without any code other than the timer initialization!