Hi, Habr! I present to your attention the translation of the article "Timer interrupts" by E.
Arduino board allows you to quickly and with minimal resources solve a variety of tasks. But where arbitrary time intervals are needed (periodic interrogation of sensors, high-precision PWM signals, long duration pulses) standard library delay functions are not convenient. At the time of their action, the sketch is suspended and it becomes impossible to control it.
In this situation, it is better to use the built-in AVR timers. How to do this and not get lost in the technical jungle of datasheets, says a successful article , the translation of which is offered to your attention.
This article discusses the AVR and Arduino timers and how to use them in Arduino projects and user circuits.
As in everyday life in microcontrollers, the timer is some thing that can give a signal in the future, at the moment that you set. When this moment comes, the microcontroller is interrupted, reminding him to do something, for example, run a certain piece of code.
Timers, like external interrupts, operate independently of the main program. Instead of performing loops or repeating the millis () delay, you can assign a timer to do its work, while your code does other things.
So, suppose that there is a device that needs to do something, for example, the LED flashes every 5 seconds. If you do not use timers, and write the usual code, then you need to set the variable at the time of ignition of the LED and constantly check whether the moment of its switching has come. With a timer interrupt, you just need to set up an interrupt, and then start the timer. The LED will flash exactly on time, regardless of the action of the main program.
It acts by incrementing a variable called a counting register . The counting register can read up to a certain value, depending on its size. The timer increases its counter time after time until it reaches the maximum value, at this point the counter will overflow and reset back to zero. The timer usually sets the flag bit to let you know that an overflow has occurred.
You can check this flag manually or you can make a timer switch — trigger an interrupt automatically when the flag is set. Like any other interrupt, you can assign an interrupt service routine ( Interrupt Service Routine or ISR ) to execute a given code when the timer overflows. ISR will reset the overflow flag itself, so using interrupts is usually the best choice because of simplicity and speed.
To increase the counter values at precise intervals, the timer must be connected to a clock source. The clock source generates a constantly repeating signal. Each time the timer detects this signal, it increments the counter value by one. Since the timer operates from a clock source, the smallest unit of time is the clock period. If you connect a 1 MHz clock signal, then the resolution of the timer (or the period of the timer) will be:
T = 1 / f (f is the clock frequency)
T = 1/1 MHz = 1/10 ^ 6 Hz
T = (1 ∗ 10 ^ -6) with
So the timer resolution is one millionth of a second. Although you can use an external clock source for timers, in most cases the internal source of the chip itself is used.
In standard Arduino boards on an 8-bit AVR chip, there are several timers at once. Atmega168 and Atmega328 chips have three timers Timer0, Timer1 and Timer2. They also have a watchdog timer that can be used for crash protection or as a software reset mechanism. Here are some features of each timer.
Timer0:
Timer0 is an 8-bit timer, which means that its counting register can store numbers up to 255 (i.e., unsigned byte). Timer0 is used by standard Arduino time functions such as delay () and millis () , so it’s best not to confuse it if you care about the consequences.
Timer1:
Timer1 is a 16-bit timer with a maximum count value of 65535 (unsigned integer). This timer uses the library Arduino Servo, consider this if you use it in your projects.
Timer2:
Timer2 - 8 bit and very similar to Timer0. It is used in the Arduino tone () function.
Timer3, Timer4, Timer5:
The ATmega1280 and ATmega2560 chips (installed in Arduino Mega variants) have three additional timers. All of them are 16 bit and work similarly to Timer1.
In order to use these timers in the AVR there are registers of settings. Timers contain many such registers. Two of them are the timer / counter control registers and the setting variables are called TCCRxA and TCCRxB, where x is the timer number (TCCR1A and TCCR1B, etc.). Each register contains 8 bits and each bit stores a configuration variable. Here is information from the Atmega328 datasheet:
TCCR1A | ||||||||
---|---|---|---|---|---|---|---|---|
Bit | 7 | 6 | five | four | 3 | 2 | one | 0 |
0x80 | COM1A1 | COM1A0 | COM1B1 | COM1B0 | - | - | WGM11 | WGM10 |
ReadWrite | Rw | Rw | Rw | Rw | R | R | Rw | Rw |
Initial value | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
TCCR1B | ||||||||
---|---|---|---|---|---|---|---|---|
Bit | 7 | 6 | five | four | 3 | 2 | one | 0 |
0x81 | ICNC1 | ICES1 | - | WGM13 | WGM12 | CS12 | CS11 | CS10 |
ReadWrite | Rw | Rw | R | Rw | Rw | Rw | Rw | Rw |
Initial value | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
The most important are the last three bits in TCCR1B: CS12, CS11 and CS10. They determine the clock frequency of the timer. By choosing them in different combinations, you can order the timer to operate at different speeds. Here is a datasheet that describes the effect of the select bits:
CS12 | CS11 | CS10 | Act |
---|---|---|---|
0 | 0 | 0 | No clock source (Timer / Counter stopped) |
0 | 0 | one | clk_io / 1 (no division) |
0 | one | 0 | clk_io / 8 (frequency divider) |
0 | one | one | clk_io / 64 (frequency divider) |
one | 0 | 0 | clk_io / 256 (frequency divider) |
one | 0 | one | clk_io / 1024 (frequency divider) |
one | one | 0 | External clock source at pin T1. Clock down |
one | one | one | External clock source at pin T1. Front Clocking |
By default, all these bits are set to zero.
Suppose you want Timer1 to work at a clock frequency with one count per period. When it overflows, you want to call an interrupt subroutine that switches the LED connected to pin 13 to the on or off state. For this example, we write the Arduino code, but we will use the procedures and functions of the avr-libc library whenever it does not make things too complicated. Proponents of pure AVR can customize the code as they see fit.
First, we initialize the timer:
// avr-libc library includes #include <avr/io.h> #include <avr/interrupt.h> #define LEDPIN 13 void setup() { pinMode(LEDPIN, OUTPUT); // Timer1 cli(); // TCCR1A = 0; // TCCR1A 0 TCCR1B = 0; // Timer1 overflow: TIMSK1 = (1 << TOIE1); // CS10 , : TCCR1B |= (1 << CS10); sei(); // }
The TIMSK1 register is the Timer / Counter1 interrupt mask register. It controls the interrupts that the timer can trigger. Setting the TOIE1 bit tells the timer to interrupt when the timer overflows. More on this later.
When you set the CS10 bit, the timer starts counting and, as soon as an overflow interrupt occurs, an ISR (TIMER1_OVF_vect) is called. This always happens when the timer overflows.
Next, we define the ISR interrupt function:
ISR(TIMER1_OVF_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); }
Now we can define the loop loop () and switch the LED regardless of what happens in the main program. To turn off the timer, set TCCR1B = 0 at any time.
Timer1 is set to overflow interrupt and let's assume that you are using an Atmega328 with a clock frequency of 16 MHz. Since the timer is 16-bit, it can read up to the maximum value (2 ^ 16 - 1), or 65535. At 16 MHz, the cycle is executed 1 / (16 ∗ 10 ^ 6) seconds or 6.25e-8 s. This means that 65535 counts will occur in (65535 ∗ 6.25e-8 s) and the ISR will be called in approximately 0.0041 seconds. And so over and over again, every four thousandth seconds. It is too fast to see flicker.
If we give the LED a very fast PWM signal with 50% coverage, the glow will appear continuous, but less bright than usual. Such an experiment shows the amazing power of microcontrollers - even an inexpensive 8-bit chip can process information much faster than we are able to detect.
To control the period, you can use a divider that allows you to divide the clock signal into different powers of two and increase the period of the timer. For example, you would like the LED to flash at one second intervals. In the TCCR1B register, there are three bits of CS that set the most appropriate resolution. If you set the bits CS10 and CS12 using:
TCCR1B |= (1 << CS10); TCCR1B |= (1 << CS12);
then the clock source frequency will be divided by 1024. This gives the resolution of the timer 1 / (16 ∗ 10 ^ 6/1024) or 6.4e-5 s. Now the timer will overflow every (65535 * 6.4e-5s) or 4,194s. This is too long.
But there is another AVR timer mode. It is called a reset timer by coincidence or CTC. Instead of counting before overflow, the timer compares its counter with the one previously stored in the register. When the count coincides with this variable, the timer can either set the flag or trigger an interrupt, just like in the case of overflow.
To use CTC mode, you need to understand how many cycles you need to get an interval of one second. Suppose that the division factor is still equal to 1024.
The calculation will be as follows:
(target time) = (timer resolution) * (# timer counts + 1) (# timer counts + 1) = (target time) / (timer resolution) (# timer counts + 1) = (1 s) / (6.4e-5 s) (# timer counts + 1) = 15625 (# timer counts) = 15625 - 1 = 15624
You must add an extra one to the number of samples because in CTC mode, if the counter coincides with the specified value, it will reset itself to zero. The reset takes one clock period, which must be taken into account in the calculations. In many cases, the error in one period is not too significant, but in high-precision tasks it can be critical.
The setup () setting function will be as follows:
void setup() { pinMode(LEDPIN, OUTPUT); // Timer1 cli(); // TCCR1A = 0; // 0 TCCR1B = 0; OCR1A = 15624; // TCCR1B |= (1 << WGM12); // CTC // CS10 CS12 1024 TCCR1B |= (1 << CS10); TCCR1B |= (1 << CS12); TIMSK1 |= (1 << OCIE1A); // sei(); // }
You also need to replace the overflow interrupt with the interrupt by coincidence:
ISR(TIMER1_COMPA_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); }
Now the LED will light up and go out for exactly one second. And you can do anything in the loop (). While you do not change the timer settings, the program has nothing to do with interrupts. You have no restrictions on using the timer with different modes and settings of the divider.
Here is a complete starting example that you can use as a basis for your own projects:
// Arduino CTC // avr-libc library includes #include <avr/io.h> #include <avr/interrupt.h> #define LEDPIN 13 void setup() { pinMode(LEDPIN, OUTPUT); // Timer1 cli(); // TCCR1A = 0; // 0 TCCR1B = 0; OCR1A = 15624; // TCCR1B |= (1 << WGM12); // CTC TCCR1B |= (1 << CS10); // 1024 TCCR1B |= (1 << CS12); TIMSK1 |= (1 << OCIE1A); // sei(); // } void loop() { // } ISR(TIMER1_COMPA_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); }
Remember that you can use built-in ISR functions to extend timer functions. For example, you need to poll the sensor every 10 seconds. But the timer settings that provide such a long account without overflow are not. However, you can use ISR to increment the counting variable once a second and then poll the sensor when the variable reaches 10. Using the CTC mode from the previous example, the interruption could look like this:
ISR(TIMER1_COMPA_vect) { seconds++; if(seconds == 10) { seconds = 0; readSensor(); } }
Since the variable will be modified inside the ISR, it must be declared as volatile . Therefore, when describing variables at the beginning of the program, you need to write:
volatile byte seconds;
At one time, this article saved me a lot of time when developing a prototype measuring generator. I hope that it will be useful to other readers.
Source: https://habr.com/ru/post/453276/
All Articles