TIMERS
Timer 1 etc. TCNT1 overflows. etc... Target timer count, every 50,000 ticks. //TCCR1A and TCCR1B Are different registers that operate on the same timer.
Below is some simple code to flash an LED on PD0. TCCR1B is just the register we use to control one of the 328's timers. CS10 is the bit to set if you don't want to prescale. More on that in a moment. For the time being, it just means that the timer is ticking along at the same speed as the clock.
TCNT1 is the way to check the timer's current value.
The sharper among you may have noticed that going at 16Mhz, counting to 50,000 will take no time at all. As a result, this example is kind of useless as the LED will be flashing so fast it'll just look as though it's always on.
That's where a PRESCALE helps...
`#includeint main (void) { DDRD |= (1 << PD0); TCCR1B |= (1 << CS10); for(;;) { if (TCNT1 >= 49999){ PORTD ^= (1 << PD0); TCNT1 = 0; } } return 0; } `
Prescaling is just a way of trading accuracy for duration. With a prescale, you can slow that 16Mhz tick right down to something useful.
The resolution of a timer is:
Timer Resolution = 1/Input Frequency
So, running at the default speed the default resolution is:
Timer Resolution = 1/16,000,000 (MHz)
And gives you an idea of the fractions of a second the timer can count in.
This can be made useful by bringing it down a gear. For example, one of the 328's available ways of prescaling is to divide this by 1024. So timer resolution becomes:
Timer Resolution = 1/(Input Frequency/Prescale)
Timer Resolution = 1/(16,000,000 / 1024)
Therefore, it can't detect as many fractions of a second, but it does mean TNCT1 won't run over as often.
The target timer count from earlier now makes a little more sense:
Target Count = ( (1/Target Frequency) / (Prescale/Input Frequency) ) - 1
Remember:
Target Frequency is how often we want it to happen.
Input Frequency is how fast our clock is.
So, if we want our LED to flick on/off once a second-ish (1Hz), it would be:
1/1 = 1 (1/Target Frequency) 1024 / 16,000,000 = 0.000064 (Prescale/Input Frequency)
Pulling all this together gives us what we should set TCNT1 to check:
Target Timer Count = (1 / 0.000064) - 1 = 15624.
Now that we've worked all that out, we have to actually tell the timer it's using a prescale of /1024. We do that by setting bits CS12 and CS10. Look for these in the datasheet and hopefully the other modes should make sense.
`#includeint main (void) { DDRD |= (1 << PD0); // Look for CS10, CS11 or CS12 in the datasheet, it'll make sense then. // Table 16-5 TCCR1B |= ((1 << CS12) | (1 << CS10)); for(;;) { if (TCNT1 >= 15624){ PORTD ^= (1 << PD0); TCNT1 = 0; } } return 0; } `
CTC - Clear Timer On Compare.
This is a way of using a flag set by the 328 itself to trigger when something should happen. It uses the same timers we already know about but lets you use them in a more efficient way. We'll look at this in two small parts. Part 1 will show you the components of CTC. Part 2 will show you how to use them properly.
Part 1:
CTC hinges on the fact that rather than checking for a value yourself, like we've been doing (TCNT1 >= 15624), an inbuilt "flag" can be set whenever the value in Timer 1 goes past a certain point. This might not seem like a big difference right now, but it allows us to treat events based on timers more like interrupts TODOTODOTODOTODOTODOTODOTODOTODO.
Remember TCCR1B is just a way of setting up Timer 1. It holds the prescale settings we've already used. However, it also contains the switch required to turn on Clear Timer On Compare mode.
Let's take a look at table [TODOPOOOOOOOOOOOOOO] again:
Here we can see WGM12 needs to be set to turn it on.
We can also see a column called TOP that holds the value OCR1A. This is just a way of letting us know that the "flag" will be will be triggered when Timer1 goes over the top of the value stored in OCR1A (Output Compare Register 1A).
So, instead of checking the value of the timer, we are instead telling the timer when it should flag to us that the time has been reached.
Looking at table TODOTODOTODOTODO we can see that TIFR1 is the the register where the interrupt flags FOR TIMER 1???? TODO are held. OCF1A(Output Compare Flag 1A) is the bit that will be set. We can check whether the flag has been set manually by:
if (TIFR1 & (1 << OCF1A))
The last thing to note is some odd behaviour. The Flag is cleared by writing to it. Remember it, move on. A detailed explanation is available TODODO HERE TODO, but it would only confuse matters at the moment.
Again, I know the distinction between the basic way and this way doesn't seem very important at the moment, but stick with me. It'll make sense shortly.
`#includeint main (void) { DDRD |= (1 << PD0); TCCR1B |= (1 << WGM12); // Turn on CTC OCR1A = 15624; // Output Compare Register 1A TCCR1B |= ((1 << CS12) | (1 << CS10)); // /1024 prescale for(;;) { if (TIFR1 & (1 << OCF1A)){ PORTD ^= (1 << PD0); // Write 1 to the flag to reset. // Strange behaviour, but stick with it until the next program. // This bit isn't usually handled by the programmer. TIFR1 = (1 << OCF1A); } } return 0; } `
TIMSK1 is where we set up which ISRs are active. OCIE1A (Output Compare Interrupt Enable, channel A) is the bit or it we need to set to enable the ISR. Interrupt routine associated with Timer 1 Compare Register A gets called whenever the flag we set up earlier is. As well as no longer needing to check TIFR1 to see if the limit we set is hit. We also no longer need to do the weird bit setting in TIFR1. That's the point of CTC. Set the value to count to then forget about it. Everything else happens automatically.
`#include// This is new! #include `int main (void) { // This is old you know about this. DDRD |= (1 << PD0); TCCR1B |= (1 << WGM12); OCR1A = 15624; TCCR1B |= ((1 << CS12) | (1 << CS10)); // This is new! TIMSK1 |= (1 << OCIE1A); // Also new! For some reason, have to enable interrupts IN GENERAL. sei(); for(;;) { // No longer anything in main. } return 0; } // Look at "interrupts" in the datasheet to find param names for ISR's. // Remember we're looking for whan Output Compare Register A is hit. ISR(TIMER1_COMPA_vect) { PORTD ^= (1 << PD0); }