[From sandbox] Arduino and interrupt timer

[From sandbox] Arduino and interrupt timer


Hello, Habr! I present to you the translation of the article "Timer interrupts" of the author


Preface


The Arduino board allows you to quickly and easily 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, tells a good article , the translation of which is proposed your attention.



This article discusses the AVR and Arduino timers and how to use them in Arduino projects and user circuits.


What is a timer?


As in everyday life, in microcontrollers, a 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, work independently of the main program. Instead of performing loops or repeating a delay call with millis () , you can assign a timer to do its job, while your code does other things.


So, let's assume 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 in time, regardless of the actions of the main program.


How does the timer work?


It acts by incrementing a variable called countable 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 the specified code when the timer overflows. ISR will reset the overflow flag itself, so using interrupts is usually the best choice because of its simplicity and speed.


To increase 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, the timer resolution (or timer period) 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.


Timer Types


The standard Arduino boards on an 8 bit AVR chip have 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 of 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 tone () Arduino 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.


Register Configuration


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 the datasheet from Atmega328:


TCCR1A
Bit 7 6 5 4 3 2 1 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 5 4 3 2 1 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 Action
0 0 0 No clock source (Timer/Counter stopped)
0 0 1 clk_io/1 (no division)
0 1 0 clk_io/8 (frequency divider)
0 1 1 clk_io/64 (frequency divider)
1 0 0 clk_io/256 (frequency divider)
1 0 1 clk_io/1024 (frequency divider)
1 1 0 External clock source at T1. Clocking by recession
1 1 1 External clock source at 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. Supporters of pure AVR can adapt the code at their discretion.


First, we initialize the timer:


 //avr-libc library includes
 #include & lt; avr/io.h & gt;
 #include & lt; avr/interrupt.h & gt;
 #define LEDPIN 13

 void setup ()
 {
  pinMode (LEDPIN, OUTPUT);

//initialize Timer1
  cli ();//disable global interrupts
  TCCR1A = 0;//set the TCCR1A register to 0
  TCCR1B = 0;

//enable interrupt Timer1 overflow:
  TIMSK1 = (1 & lt; & lt; TOIE1);
//Set the CS10 bit so that the timer operates at a clock frequency:
  TCCR1B | = (1 & lt; & lt; CS10);

  sei ();//enable global interrupts
 }  

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 is called (TIMER1_OVF_vect). This always happens when the timer is full.


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.


How often will the LED flash?


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.


Timer Divider and CTC Mode


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 & lt; & lt; CS10);
 TCCR1B | = (1 & lt; & lt; 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 score matches 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 the division factor is still 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 have to 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, an error in one period is not too significant, but in high-precision tasks it can be critical.


The setup () function will be as follows:


  void setup ()
 {
  pinMode (LEDPIN, OUTPUT);

//initialize Timer1
  cli ();//disable global interrupts
  TCCR1A = 0;//set registers to 0
  TCCR1B = 0;

  OCR1A = 15624;//set the register of matches
  TCCR1B | = (1 & lt; & lt; WGM12);//inclusion in CTC mode

//Set the bits CS10 and CS12 to the division ratio 1024
  TCCR1B | = (1 & lt; & lt; CS10);
  TCCR1B | = (1 & lt; & lt; CS12);

  TIMSK1 | = (1 & lt; & lt; OCIE1A);//enable interrupts by coincidence
  sei ();//enable global interrupts
 }  

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 the basis for your own projects:


 //Arduino CTC Interrupt Timer//avr-libc library includes
 #include & lt; avr/io.h & gt;
 #include & lt; avr/interrupt.h & gt;
 #define LEDPIN 13

 void setup ()
 {
  pinMode (LEDPIN, OUTPUT);

//initialize Timer1
  cli ();//disable global interrupts
  TCCR1A = 0;//set registers to 0
  TCCR1B = 0;

  OCR1A = 15624;//set the register of matches

  TCCR1B | = (1 & lt; & lt; WGM12);//enable CTC mode
  TCCR1B | = (1 & lt; & lt; CS10);//Set the bits on the division factor 1024
  TCCR1B | = (1 & lt; & lt; CS12);

  TIMSK1 | = (1 & lt; & lt; OCIE1A);//enable interrupt by timer match
  sei ();//enable global interrupts
 }

 void loop ()
 {
//main program
 }

 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;  

afterword of the translator


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 text: [From sandbox] Arduino and interrupt timer