Let's Make Robots!

Arduino 101: Timers and Interrupts

AttachmentSize
Arduino101-Timers.zip32.9 KB
ArduinoTimer101.zip2.74 KB

 

 

Update 07/24/2013 04/24/2013

  • Example 3 has been updated to work with Arduino v1.x.
  • Added ArduinoTimer101.zip examples source code for Arduino v1.x .

 


 

This tutorial shows the use of timers and interrupts for Arduino boards. As Arduino programmer you will have used timers and interrupts without knowledge, bcause all the low level hardware stuff is hidden by the Arduino API. Many Arduino functions uses timers, for example the time functions:  delay(), millis() and micros() and delayMicroseconds(). The PWM functions analogWrite() uses timers, as the tone() and the noTone() function does. Even the Servo library uses timers and interrupts.
 
What is a timer?
A timer or to be more precise a timer / counter is a piece of hardware builtin the Arduino controller  (other controllers have timer hardware, too). It is like a clock, and can be used to measure time events.
The timer can be programmed by some special registers. You can configure the prescaler for the timer, or the mode of operation and many other things.
The controller of the Arduino is the Atmel AVR ATmega168 or the ATmega328. These chips are pin compatible and only differ in the size of internal memory. Both have 3 timers, called timer0, timer1 and timer2. Timer0 and timer2 are 8bit timer, where timer1 is a 16bit timer. The most important difference  between 8bit and 16bit timer is the timer resolution. 8bits means 256 values where 16bit means 65536 values for higher resolution.
The controller for the Arduino Mega series is the Atmel AVR ATmega1280 or the ATmega2560.  Also identical only differs in memory size. These controllers have 6 timers. Timer 0, timer1 and timer2 are identical to the ATmega168/328. The timer3, timer4 and timer5 are all 16bit timers, similar to timer1.
All timers depends on the system clock of your Arduino system. Normally the system clock is 16MHz, but for the Arduino Pro 3,3V it is 8Mhz. So be careful when writing your own timer functions.
The timer hardware can be configured with some special timer registers. In the Arduino firmware all timers were configured to a 1kHz frequency and interrupts are gerally enabled.

Timer0:
Timer0 is a 8bit timer.
In the Arduino world timer0 is been used for the timer functions, like delay(), millis() and micros(). If you change timer0 registers, this may influence the Arduino timer function. So you should know what you are doing.

Timer1:
Timer1 is a 16bit timer.
In the Arduino world the Servo library uses timer1 on Arduino Uno (timer5 on Arduino Mega).

Timer2:
Timer2 is a 8bit timer like timer0.
In the Arduino work the tone() function uses timer2.

Timer3, Timer4, Timer5:
Timer 3,4,5 are only available on Arduino Mega boards. These timers are all 16bit timers.


Timer Register
You can change the Timer behaviour through the timer register.  The most important timer registers are:
TCCRx - Timer/Counter Control Register. The prescaler can be configured here.



TCNTx - Timer/Counter Register. The actual timer value is stored here.

OCRx - Output Compare Register

ICRx - Input Capture Register (only for 16bit timer)

TIMSKx - Timer/Counter Interrupt Mask Register. To enable/disable timer interrupts.

TIFRx - Timer/Counter Interrupt Flag Register. Indicates a pending timer interrupt.

Clock select and timer frequency

Different clock sources can be selected for each timer independently. To calculate the timer frequency (for example 2Hz using timer1) you will need:

  1. CPU frequency 16Mhz for Arduino
  2. maximum timer counter value (256 for 8bit, 65536 for 16bit timer)
  3. Divide CPU frequency through the choosen prescaler (16000000 / 256 = 62500)
  4. Divide result through the desired frequency (62500 / 2Hz = 31250)
  5. Verify the result against the maximum timer counter value (31250 < 65536 success) if fail, choose bigger prescaler.

Timer modes

Timers can be configured in different modes.

PWM mode. Pulth width modulation mode. the OCxy outputs are used to generate PWM signals

CTC mode. Clear timer on compare match. When the timer counter reaches the compare match register, the timer will be cleared

What is an interrupt?
The program running on a controller is normally running sequentially instruction by instruction. An interrupt is an external event that interrupts the running program and runs a special interrupt service routine (ISR). After the ISR has been finished, the running program is continued with the next instruction. Instruction means a single machine instruction, not a line of C or C++ code.
Before an pending interrupt will be able to call a ISR the following conditions must be true:

  • Interrupts must be generally enabled
  • the according Interrupt mask must be enabled


Interrupts can generally enabled / disabled with the function interrupts() / noInterrupts(). By default in the Arduino firmware interrupts are enabled. Interrupt masks are enabled / disabled by setting / clearing bits in the Interrupt mask register (TIMSKx).
When an interrupt occurs, a flag in the interrupt flag register (TIFRx) is been set. This interrupt will be automatically cleared when entering the ISR or by manually clearing the bit in the interrupt flag register.

The Arduino functions attachInterrupt() and detachInterrupt() can only be used for external interrupt pins. These are different interrupt sources, not discussed here.

Timer interrupts
A timer can generate different types of interrupts. The register and bit definitions can be found in the processor data sheet (Atmega328 or Atmega2560) and in the I/O definition header file (iomx8.h for Arduino, iomxx0_1.h for Arduino Mega in the hardware/tools/avr/include/avr folder). The suffix x stands for the timer number (0..5), the suffix y stands for the output number (A,B,C), for example TIMSK1 (timer1 interrupt mask register) or OCR2A (timer2 output compare register A).

Timer Overflow:
Timer overflow means the timer has reached is limit value. When a timer overflow interrupt occurs, the timer overflow bit TOVx will be set in the interrupt flag register TIFRx. When the timer overflow interrupt enable bit TOIEx in the interrupt mask register TIMSKx is set, the timer overflow interrupt service routine ISR(TIMERx_OVF_vect)  will be called.

Output Compare Match:
When a output compare match interrupt occurs, the OCFxy flag will be set in the interrupt flag register TIFRx . When the output compare interrupt enable bit OCIExy in the interrupt mask register TIMSKx is set, the output compare match interrupt service ISR(TIMERx_COMPy_vect) routine will be called.

Timer Input Capture:
When a timer input capture interrupt occurs, the input capture flag bit ICFx will be set in the interrupt flag register TIFRx. When the input capture interrupt enable bit  ICIEx in the interrupt mask register TIMSKx is set, the timer input capture interrupt service routine ISR(TIMERx_CAPT_vect) will be called.

PWM and timer
There is fixed relation between the timers and the PWM capable outputs. When you look in the data sheet or the pinout of the processor these PWM capable pins have names like OCRxA, OCRxB or OCRxC (where x means the timer number 0..5). The PWM functionality is often shared with other pin functionality.
The Arduino has 3Timers and 6 PWM output pins. The relation between timers and PWM outputs is:
Pins 5 and 6: controlled by timer0
Pins 9 and 10: controlled by timer1
Pins 11 and 3: controlled by timer2

On the Arduino Mega we have 6 timers and 15 PWM outputs:
Pins 4 and 13: controlled by timer0
Pins 11 and 12: controlled by timer1
Pins 9 and10: controlled by timer2
Pin 2, 3 and 5: controlled by timer 3
Pin 6, 7 and 8: controlled by timer 4
Pin 46, 45 and 44:: controlled by timer 5


Usefull 3rd party libraries
 
Some 3rd party libraries exists, that uses timers:


I have ported these libraries to different timers for Arduino Mega. All ported libraries can be found in the attached file.


Pitfalls
There exists some pitfalls you may encounter, when programming your Arduino and make use of functions or libraries that uses timers.

  • Servo Library uses Timer1. You can’t use PWM on Pin 9, 10 when you use the Servo Library on an Arduino. For Arduino Mega it is a bit more difficult. The timer needed depends on the number of servos. Each timer can handle 12 servos. For the first 12 servos timer 5 will be used (loosing PWM on Pin 44,45,46). For 24 Servos timer 5 and 1 will be used (loosing PWM on Pin 11,12,44,45,46).. For 36 servos timer 5, 1 and 3 will be used (loosing PWM on Pin 2,3,5,11,12,44,45,46).. For 48 servos all 16bit timers 5,1,3 and 4 will be used (loosing all PWM pins).
  • Pin 11 has shared functionality PWM and MOSI. MOSI is needed for the SPI interface, You can’t  use PWM on Pin 11 and the SPI interface at the same time on Arduino. On the Arduino Mega the SPI pins are on different pins.
  • tone() function uses at least timer2. You can’t use PWM on Pin 3,11 when you use the tone() function an Arduino and Pin 9,10 on Arduino Mega.



Examples

Time for some examples.

Blinking LED with compare match interrupt

The first example uses the timer1 in CTC mode and the compare match interrupt to toggle a LED. The timer is configured for a frequency of 2Hz. The LED is toggled in the interrupt service routine.

/* Arduino 101: timer and interrupts
   1: Timer1 compare match interrupt example 
   more infos: http://www.letmakerobots.com/node/28278
   created by RobotFreak 
*/

#define ledPin 13

void setup()
{
  pinMode(ledPin, OUTPUT);
  
  // initialize timer1 
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 31250;            // compare match register 16MHz/256/2Hz
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS12);    // 256 prescaler 
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  interrupts();             // enable all interrupts
}

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{
  digitalWrite(ledPin, digitalRead(ledPin) ^ 1);   // toggle LED pin
}

void loop()
{
  // your program here...
}


 Blinking LED with timer overflow interrupt

same example like before but now we use the timer overflow interrupt. In this case timer1 is running in normal mode.

The timer must be preloaded every time in the interrupt service routine.

/* 
 * Arduino 101: timer and interrupts
 * 2: Timer1 overflow interrupt example 
 * more infos: http://www.letmakerobots.com/node/28278
 * created by RobotFreak 
 */

#define ledPin 13

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

  // initialize timer1 
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;

  TCNT1 = 34286;            // preload timer 65536-16MHz/256/2Hz
  TCCR1B |= (1 << CS12);    // 256 prescaler 
  TIMSK1 |= (1 << TOIE1);   // enable timer overflow interrupt
  interrupts();             // enable all interrupts
}

ISR(TIMER1_OVF_vect)        // interrupt service routine that wraps a user defined function supplied by attachInterrupt
{
  TCNT1 = 34286;            // preload timer
  digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
}

void loop()
{
  // your program here...
}



Reading quadrature encoders with a timer

The next example is part of my Ardubot project. It uses timer2 and the compare match interrupt to read the encoder inputs. Timer2 is initialized by default to a frequency of 1kHz (1ms period). In the interrupt service routine the state of all encoder pins is read and a state machine is used to eliminate false readings. Using the timer interrupt is much easier to handle than using 4 input change interrupts.

The signals of a quadrature encoder is a  2bit Gray code. Only 1 bit is changing from state to state. A state machine is perfect to check the signal and count the encoder ticks. The timer must be fast enough, to recognize each state change. For the Pololu wheel encoders used here, the 1ms timer is fast enough.

 

The following example has been modified to work with Arduino V1.x

/*
/* 
 * Arduino 101: timer and interrupts
 * 3: Timer2 compare interrupt example. Quadrature Encoder
 * more infos: http://www.letmakerobots.com/node/28278
 * created by RobotFreak 
 *
 * Credits:
 * based on code from Peter Dannegger
 * http://www.mikrocontroller.net/articles/Drehgeber
 */


#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WConstants.h"
#endif

// Encoder Pins
#define encLtA 2
#define encLtB 3
#define encRtA 11 
#define encRtB 12
#define ledPin 13

#define LT_PHASE_A		digitalRead(encLtA)
#define LT_PHASE_B		digitalRead(encLtB)
#define RT_PHASE_A		digitalRead(encRtA)
#define RT_PHASE_B		digitalRead(encRtB)

static volatile int8_t encDeltaLt, encDeltaRt;
static int8_t lastLt, lastRt;
int encLt, encRt;

ISR( TIMER2_COMPA_vect )
{
  int8_t val, diff;

  digitalWrite(ledPin, HIGH);   // toggle LED pin
  val = 0;
  if( LT_PHASE_A )
    val = 3;
  if( LT_PHASE_B )
    val ^= 1;					// convert gray to binary
  diff = lastLt - val;				// difference last - new
  if( diff & 1 ){				// bit 0 = value (1)
    lastLt = val;				// store new as next last
    encDeltaLt += (diff & 2) - 1;		// bit 1 = direction (+/-)
  }

  val = 0;
  if( RT_PHASE_A )
    val = 3;
  if( RT_PHASE_B )
    val ^= 1;					// convert gray to binary
  diff = lastRt - val;				// difference last - new
  if( diff & 1 ){				// bit 0 = value (1)
    lastRt = val;				// store new as next last
    encDeltaRt += (diff & 2) - 1;		// bit 1 = direction (+/-)
  }
  digitalWrite(ledPin, LOW);   // toggle LED pin
}


void QuadratureEncoderInit(void)
{
  int8_t val;

  cli();
  TIMSK2 |= (1<<OCIE2A);
  sei();
  pinMode(encLtA, INPUT);
  pinMode(encRtA, INPUT);
  pinMode(encLtB, INPUT);
  pinMode(encRtB, INPUT);

  val=0;
  if (LT_PHASE_A)
    val = 3;
  if (LT_PHASE_B)
    val ^= 1;
  lastLt = val;
  encDeltaLt = 0;

  val=0;
  if (RT_PHASE_A)
    val = 3;
  if (RT_PHASE_B)
    val ^= 1;
  lastRt = val;
  encDeltaRt = 0;

  encLt = 0;
  encRt = 0;
}

int8_t QuadratureEncoderReadLt( void )			// read single step encoders
{
  int8_t val;

  cli();
  val = encDeltaLt;
  encDeltaLt = 0;
  sei();
  return val;					// counts since last call
}

int8_t QuadratureEncoderReadRt( void )			// read single step encoders
{
  int8_t val;

  cli();
  val = encDeltaRt;
  encDeltaRt = 0;
  sei();
  return val;					// counts since last call
}

void setup()
{
  Serial.begin(38400);
  pinMode(ledPin, OUTPUT);
  QuadratureEncoderInit();
}


void loop()
{
  encLt += QuadratureEncoderReadLt();
  encRt += QuadratureEncoderReadRt();
  Serial.print("Lt: ");
  Serial.print(encLt, DEC);
  Serial.print(" Rt: ");
  Serial.println(encRt, DEC);
  delay(1000);
}


 

Update 02/11/2014:

Q: Why have some variables the volatile keyword, like encodeDeltaLt and encodeDeltaRt in the examples above?

A: Read about using  the volatile keyword here: http://arduino.cc/en/Reference/Volatile

 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
merser's picture

You have a good command of the language. Very clear explanation of many minute details. I woke up with a headache last night and couldn't sleep.  Found this surfaced again and read it. I've decided this is one of the best written tutorials ever here on LMR. Thankyou Robotfreak, information worth paying for.
One thing I spotted I need to ask you about is in the first examples you use noInterrupts() and Interrupts() but in the final
encoder example you used cli and sei.
For one thing I was surprised you could run inline assembly with arduino. The question is if you look at the arduino commands for noInterrupts() and Interrupts() is this all they are doing running the assembly commands or is there more going on?

RobotFreak's picture

Thank you for the kind words, merser.

The 3rd example is an older examples I converted from AVR C to Arduino.

In the arduino.h Header file you will find the declarations for both functions. 

#define interrupts() sei()
#define noInterrupts() cli() 

So, these are only different names for the same functions. sei and cli are C macros with inline assembly code. You can find there declarations in avr\include\avr\interrupt.h:

 

# define sei()  __asm__ __volatile__ ("sei" ::)
# define cli()  __asm__ __volatile__ ("cli" ::)

So, yes you can do inline assembly in your C/C++ or Arduino programm too. I saw a lot of inline assembly in Adafruits NeoPixel light painter code for time critical functions.

Essentially, I've started experimenting with timer interrupts with the goal of using the Arduino Uno Plus to control GE Color Effects lights, which are apparently fussy about timing. So, to start, I decided to try making a 20us time delay using an interrupt and basically copying your example and adding the clock pre-scaling table in the comments so I don't have to keep looking it up.

Watching an LED blink for 20us is not informative :-), so I've swapped out the LED toggle line for setting a semaphore and reported the state of the semaphor back via the Serial monitor. I've introduced the variable "semaphore" because I ultimately want my main program to be able to tell if the 20us is up before moving on the the next instruction. The library I was originally working from uses delay() to do this and the author admits that it needs to be manually tuned for each arduino. I've tried this, and short of dragging out my old oscilloscope to verify the timing, it's very hit-and-miss, so I thought I would turn to the timer driven interrupt method.:

===============================================

/*Table 14-9.
Clock Select Bit Description from ATmega328P Documentation p.137
CS12 CS11 CS10 Description
0 0 0 No clock source (Timer/Counter stopped)
0 0 1 clk I/O /1 (No prescaling)
0 1 0 clk I/O /8 (From prescaler)
0 1 1 clk I/O /64 (From prescaler)
1 0 0 clk I/O /256 (From prescaler)
1 0 1 clk I/O /1024 (From prescaler)
1 1 0 External clock source on T0 pin. Clock on falling edge.
1 1 1 External clock source on T0 pin. Clock on rising edge.

Delays should be 10, 20 and 30us
10us = .000 01s which is obtained by dividing the 16MHz clock divided by 160
20us is therefore the 16Mhz clock divided by 80
30us is 16Mhz / 53

Therefore a good clock pre-scale setting would be 8 (CS11=1)

Timer will be running at 16Mhz/8 = 2Mhz or .5us per tick
Count to 20 for 10us
Count to 40 for 20us
Count to 60 for 30us
*/

int semaphore=0;

void setup()
{
Serial.begin(115200);
pinMode(ledPin, OUTPUT);
Serial.println("Semaphore cleared");
// initialize timer1
noInterrupts(); // disable all interrupts
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;

OCR1A = 20; // compare match register for 10us delay
TCCR1B |= (1 << WGM12); // CTC mode
TCCR1B |= (1 << CS12); // 8 prescaler
TCCR1B |= (1 << CS10);
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
interrupts(); // enable all interrupts
}

ISR(TIMER1_COMPA_vect) // timer compare interrupt service routine
{
 semaphore=1;
}

void loop()
{
while(semaphore==0){Serial.println("semaphore = 0");}
Serial.println("Semaphore set");
while (true){};
 }

==================================================

This produces the output ...

Semaphore cleared
semaphore = 0
semaphore = 0
semaphore = 0
semaphore = 0
semaphore = 0
Semaphore set

... which is what I would expect.

 Obviously, since I'm really using the interrupt in place of a delay(), I don't want to be constantly printing "semaphore = 0", so I simply removed it. From here on I'll focus only on the void loop() portion of the program. I've changed it to ...

=============================================

void loop()
{
while(semaphore==0){}
Serial.println("Semaphore set");
while (true){};
 }

=============================================

... but now all I get is:

 Semaphore cleared

The semaphore is not being set, or at least, the main program isn't seeing it.

I had thought that while(semaphore==0){} may not be valid with an empty proceedure, so I tested it ...

========================================

void setup(){Serial.begin(115200);}
void loop(){
Serial.println("before loop");
while(true){}
Serial.println("after loop");
}

======================================

 which only prints "before loop" so that wasn't it.

 So, back to original loop(). This also works ...

======================================

void loop()
{
while(semaphore==0){delay(0);}
Serial.println("Semaphore set");
while (true){};
Serial.println("not stuck in a loop");
 }

=====================================

but this does not ...

 ...

while(semaphore==0){true==true;}

...

It occurs to me that both "delay(0);" and "Serial.println(""); are using timers whereas {} and {true=true;} do not, so it's possible that these functions is doing something with the timers that my code is not. This would imply that there's something wrong with my setup() routine, but then again, the original example (which toggled the LED) worked, so clearly I'm not understanding something.

In fairness, I realize that putting delay(0); is not a hardship, but this is more about clarifying my understanding of why it doesn't work without it than about saving instruction cycles.

 

LEGOManiac

RobotFreak's picture

I guess, you will need to declare your semaphore variable as 'volatile'. To let the compiler know, that this value can be changed anytime outside the loop() function.

volatile int semaphore;

It's not about trying to change the value of the variable "semaphore". It does change successfully within the Interrupt Service Routine, as indicated in my examples.

What I'm trying to understand is why the the while(){} loop that tests the value of "semaphore" works properly if I use:

 while(..){delay(0);}

or

while(..){Serial.println("Print something");}

but

while(..){}

does not.

In the other example I gave, while(..){} works perfectly well as an endless loop until a contition is satisfied.

 

It doesn't make sense.

RobotFreak's picture

That's what I'm trying to tell you. You will need to declare the semaphore as volatile. Point. 

Read (and understand) about using the volatile keyword:  http://arduino.cc/en/Reference/Volatile

Than it will (hopefully) make sense for you.

Thanks for the help. I changed the line "int semaphore = 0" to "volatile int semaphore = 0" and it works consistantly now.

The use of the keyword "volatile" solves the overall problem. Thanks for pointing that out because I would never have looked at it on my own.

 

The question I had (and still have) is: in the original program, why do

while(semaphore==0){delay(0);}            and

while(semaphore==0){Serial.println("anything");}

both work but

while(semaphore==0){}

does not?

 That's what's puzzling me. Logically, if it was strictly a failure to designate "semaphore" as volatile, It wouldn't have mattered what I put in the code block of thw while statement.

RobotFreak's picture

The compiler is playing tricks with you. As OddBot mentioned, you will need to look at the assembly code to find out what goes wrong. The volatile keyword tells the compiler: "don't play any tricks"

OddBot's picture

Here is a quote from the explanation of the 'volatile' qualifier.

"A variable should be declared volatile whenever its value can be changed by something beyond the control of the code section in which it appears"

As for your question concerning why this "while(semaphore==0){}" does not work. You would really need to study the assembly code generated. I am guessing that since the loop is empty the value of semaphore is not being updated. Call it a bug if you like.

The fact is that the Arduino IDE and perhaps the "C" compiler running behind the scenes tends to have a few quirks that you need to work around. They can be very frustrating sometimes.

One example is that the letter "f" cannot be used as a function name. In my Doodle Bot sample code I was using the letters a-z as names for the functions that drew that letter and was getting a confusing error for no reason I could see. A day of disabling sections of code finally narrowed it down to the function name "f".

Once I changed the names to capital letters and the function was called "F" then everything worked fine.

 

Had RobotFreak not pointed me in the right direction, I would still be pulling my hair out over this one. Of course, his pointing out that I did not declare the variable to be "volatile" fixed the problem but didn't address what really bothered me: that there was an apparent incongruity with the while(){} function. Thank you for confirming that there are indeed quirks to be found. I initially thought it was just me.

I was a 68705 programmer decades ago when I worked for an industrial controls company, but it was all in assembly. C is still a bit alien to me although I've used it before in other contexts, but not extensively, so I assumed it was my lack of understanding that was the problem.

BTW. while reasearching that problem, I stumbled across the f() function. It's used to force a declared variable into Flash memory rather than using precious RAM. You gain more storage space at the cost of slower access times.