Programming With Interrupts. Apps
Programming With Interrupts. Apps
http://www.gammon.com.au/interrupts
Intro
This article discusses interrupts on the Arduino Uno (Atmega328) and similar processors,
using the Arduino IDE. The concepts however are very general. The code examples
provided should compile on the Arduino IDE (Integrated Development Environment).
TL;DR :
When writing an Interrupt Service Routine (ISR):
Keep it short
Don't use delay ()
Don't do serial prints
Make variables shared with the main code volatile
Variables shared with main code may need to be protected by "critical
sections" (see below)
Don't try to turn interrupts off or on
What are interrupts?
Most processors have interrupts. Interrupts let you respond to "external" events while
doing something else. For example, if you are cooking dinner you may put the potatoes
on to cook for 20 minutes. Rather than staring at the clock for 20 minutes you might set
a timer, and then go watch TV. When the timer rings you "interrupt" your TV viewing to
do something with the potatoes.
Interrupts are not for simply changing your mind and doing something different. For
example:
The above are examples of doing something different. They should not be done in an
interrupt routine. The most that an interrupt (service) routine might do would be to
remember that the red button was pressed. This fact would then be tested in the main
program loop.
Example of interrupts
void setup ()
{
pinMode (LED, OUTPUT); // so we can update the LED
digitalWrite (BUTTON, HIGH); // internal pull-up resistor
attachInterrupt (0, pinChange, CHANGE); // attach interrupt handler
} // end of setup
void loop ()
{
// loop doing nothing
}
This example shows how, even though the main loop is doing nothing, you can turn the
LED on pin 13 on or off, if the switch on pin D2 is pressed.
To test this, just connect a wire (or switch) between D2 and Ground. The internal pullup
(enabled in setup) forces the pin HIGH normally. When grounded, it becomes LOW. The
change in the pin is detected by a CHANGE interrupt, which causes the Interrupt Service
Routine (ISR) to be called.
In a more complicated example, the main loop might be doing something useful, like
taking temperature readings, and allow the interrupt handler to detect a button being
pushed.
Available interrupts
1 Reset
2 External Interrupt Request 0 (pin D2) (INT0_vect)
3 External Interrupt Request 1 (pin D3) (INT1_vect)
4 Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
5 Pin Change Interrupt Request 1 (pins A0 to A5) (PCINT1_vect)
6 Pin Change Interrupt Request 2 (pins D0 to D7) (PCINT2_vect)
7 Watchdog Time-out Interrupt (WDT_vect)
8 Timer/Counter2 Compare Match A (TIMER2_COMPA_vect)
9 Timer/Counter2 Compare Match B (TIMER2_COMPB_vect)
10 Timer/Counter2 Overflow (TIMER2_OVF_vect)
11 Timer/Counter1 Capture Event (TIMER1_CAPT_vect)
12 Timer/Counter1 Compare Match A (TIMER1_COMPA_vect)
13 Timer/Counter1 Compare Match B (TIMER1_COMPB_vect)
14 Timer/Counter1 Overflow (TIMER1_OVF_vect)
15 Timer/Counter0 Compare Match A (TIMER0_COMPA_vect)
16 Timer/Counter0 Compare Match B (TIMER0_COMPB_vect)
17 Timer/Counter0 Overflow (TIMER0_OVF_vect)
18 SPI Serial Transfer Complete (SPI_STC_vect)
19 USART Rx Complete (USART_RX_vect)
20 USART, Data Register Empty (USART_UDRE_vect)
21 USART, Tx Complete (USART_TX_vect)
22 ADC Conversion Complete (ADC_vect)
23 EEPROM Ready (EE_READY_vect)
24 Analog Comparator (ANALOG_COMP_vect)
25 2-wire Serial Interface (I2C) (TWI_vect)
26 Store Program Memory Ready (SPM_READY_vect)
Internal names (which you can use to set up ISR callbacks) are in brackets.
Warning: If you misspell the interrupt vector name, even by just getting the capitalization
wrong (an easy thing to do) the interrupt routine will not be called, and you will not get a
compiler error.
Summary of interrupts
The "data transfers" can be used to let a program do something else while data is being
sent or received on the serial port, SPI port, or I2C port.
Wake the processor
The "low" pin interrupts, pin-change interrupts, and the watchdog timer interrupt, can
also be used to wake the processor up. This can be very handy, as in sleep mode the
processor can be configured to use a lot less power (eg. around 10 microamps). A "low
pin" interrupt can be used to wake up a gadget (eg. if you press a button on it), or a
"watchdog timer" interrupt might wake it up periodically (eg. to check the time or
temperature).
The processor can also be awoken by a timer interrupt (eg. a timer reaching a certain
value, or overflowing) and certain other events, such as an incoming I2C message.
[EDIT] 15/Dec/2013 : We now believe that all external interrupts (not just LOW ones) will
wake the processor from sleep. This seems more consistent with the fact that pin change
interrupts can wake the processor.
The "reset" interrupt cannot be disabled. However the other interrupts can be
temporarily disabled by clearing the interrupt flag.
Enable interrupts
You can enable interrupts with the function call "interrupts" or "sei" like this:
Disable interrupts
If you need to disable interrupts you can "clear" the interrupt flag like this:
The default in the Arduino is for interrupts to be enabled. Don't disable them for long
periods or things like timers won't work properly.
There may be time-critical pieces of code that you don't want interrupted, for example
by a timer interrupt.
Also if multi-byte fields are being updated by an ISR then you may need to disable
interrupts so that you get the data "atomically". Otherwise one byte may be updated by
the ISR while you are reading the other one.
For example:
noInterrupts ();
long myCounter = isrCounter; // get value set by ISR
interrupts ();
Temporarily turning off interrupts ensures that isrCounter (a counter set inside an ISR)
does not change while we are obtaining its value.
Warning: if you are not sure if interrupts are already on or not, then you need to save
the current state and restore it afterwards. For example, the code from the millis()
function does this:
return m;
}
Note the lines in bold save the current SREG (status register) which includes the interrupt
flag. After we have obtained the timer value (which is 4 bytes long) we put the status
register back how it was.
Since there are 25 interrupts (other than reset) it is possible that more than one
interrupt event might occur at once, or at least, occur before the previous one is
processed. Also an interrupt event might occur while interrupts are disabled.
The priority order is the sequence in which the processor checks for interrupt events. The
higher up the list, the higher the priority. So, for example, an External Interrupt Request
0 (pin D2) would be serviced before External Interrupt Request 1 (pin D3).
Interrupts events (that is, noticing the event) can occur at any time, and most are
remembered by setting an "interrupt event" flag inside the processor. If interrupts are
disabled, then that interrupt will be handled when they are enabled again, in priority
order.
Variables shared between ISR functions and normal functions should be declared
"volatile". This tells the compiler that such variables might change at any time, and thus
the compiler must reload the variable whenever you reference it, rather than relying
upon a copy it might have in a processor register.
For example:
void setup ()
{
attachInterrupt (0, isr, CHANGE); // attach interrupt handler
} // end of setup
void loop ()
{
if (flag)
{
// interrupt has occurred
}
} // end of loop
You write an ISR (interrupt service routine). This is called when the interrupt
occurs.
You tell the processor when you want the interrupt to fire.
Writing an ISR
Interrupt Service Routines are functions with no arguments. Some Arduino libraries are
designed to call your own functions, so you just supply an ordinary function (as in the
examples above), eg.
However if a library has not already provided a "hook" to an ISR you can make your own,
like this:
In this case you use the "ISR" define, and supply the name of the relevant interrupt
vector (from the table earlier on). In this case the ISR is handling an SPI transfer
completing. (Note, some old code uses SIGNAL instead of ISR, however SIGNAL is
deprecated).
For interrupts already handled by libraries, you just use the documented interface. For
example:
void setup ()
{
Wire.onReceive(receiveEvent);
}
In this case the I2C library is designed to handle incoming I2C bytes internally, and then
call your supplied function at the end of the incoming data stream. In this case
receiveEvent is not strictly an ISR (it has an argument) but it is called by an inbuilt ISR.
void setup ()
{
attachInterrupt (0, pinChange, CHANGE); // attach interrupt handler
} // end of setup
In this case the attachInterrupt function adds the function pinChange to an internal
table, and in addition configures the appropriate interrupt flags in the processor.
The next step, once you have an ISR, is to tell the processor that you want this particular
condition to raise an interrupt.
As an example, for External Interrupt 0 (the D2 interrupt) you could do something like
this:
EICRA (External Interrupt Control Register A) would be set according to this table from
the Atmega328 datasheet (page 71).
Fortunately you don't need to remember those numbers because attachInterrupt does
that for you. However that is what is actually happening, and for other interrupts you
may have to "manually" set interrupt flags.
When an ISR is entered, interrupts are disabled. Naturally they must have been enabled
in the first place, otherwise the ISR would not be entered. However to avoid having an
ISR itself be interrupted, the processor turns interrupts off.
When an ISR exits, then interrupts are enabled again. The compiler also generates code
inside an ISR to save registers and status flags, so that whatever you were doing when
the interrupt occurred will not be affected.
However you can turn interrupts on inside an ISR if you absolutely must, eg.
} // end of pinChange
Normally you would need a pretty good reason to do this, as another interrupt now
could result in a recursive call to pinChange, with quite possibly undesirable results.
According to the datasheet, the minimal amount of time to service an interrupt is 4 clock
cycles (to push the current program counter onto the stack) followed by the code now
executing at the interrupt vector location. This normally contains a jump to where the
interrupt routine really is, which is another 3 cycles.
Then an ISR routine (declared with the ISR define) does something like this:
That's another 16 cycles (the cycles are in brackets). So from the moment the interrupt
occurs, to the first line of code being executed, would be 16 + 7 cycles (23 cycles), at 62.5
nS per clock cycle, that would be 1.4375 S. That's assuming a 16 MHz clock.
That's another 19 clock cycles (1.1875 S). So in total, an ISR using the ISR define will
take you 2.625 S to execute, plus whatever the code itself does.
However the external interrupts (where you use attachInterrupt) do a bit more, as
follows:
SIGNAL(INT0_vect) {
182: 1f 92 push r1 (2)
184: 0f 92 push r0 (2)
186: 0f b6 in r0, 0x3f (1)
188: 0f 92 push r0 (2)
18a: 11 24 eor r1, r1 (1)
18c: 2f 93 push r18 (2)
18e: 3f 93 push r19 (2)
190: 4f 93 push r20 (2)
192: 5f 93 push r21 (2)
194: 6f 93 push r22 (2)
196: 7f 93 push r23 (2)
198: 8f 93 push r24 (2)
19a: 9f 93 push r25 (2)
19c: af 93 push r26 (2)
19e: bf 93 push r27 (2)
1a0: ef 93 push r30 (2)
1a2: ff 93 push r31 (2)
if(intFunc[EXTERNAL_INT_0])
1a4: 80 91 00 01 lds r24, 0x0100 (2)
1a8: 90 91 01 01 lds r25, 0x0101 (2)
1ac: 89 2b or r24, r25 (2)
1ae: 29 f0 breq .+10 (2)
intFunc[EXTERNAL_INT_0]();
1b0: e0 91 00 01 lds r30, 0x0100 (2)
1b4: f0 91 01 01 lds r31, 0x0101 (2)
1b8: 09 95 icall (3)
}
1ba: ff 91 pop r31 (2)
1bc: ef 91 pop r30 (2)
1be: bf 91 pop r27 (2)
1c0: af 91 pop r26 (2)
1c2: 9f 91 pop r25 (2)
1c4: 8f 91 pop r24 (2)
1c6: 7f 91 pop r23 (2)
1c8: 6f 91 pop r22 (2)
1ca: 5f 91 pop r21 (2)
1cc: 4f 91 pop r20 (2)
1ce: 3f 91 pop r19 (2)
1d0: 2f 91 pop r18 (2)
1d2: 0f 90 pop r0 (2)
1d4: 0f be out 0x3f, r0 (1)
1d6: 0f 90 pop r0 (2)
1d8: 1f 90 pop r1 (2)
1da: 18 95 reti (4)
This various somewhat. The figures quoted above are the ideal ones where the interrupt
is immediately processed. A few factors may delay that:
If the processor is asleep, there are designated "wake-up" times, which could be
quite a few milliseconds, while the clock is spooled back up to speed. This time
would depend on fuse settings, and how deep the sleep is.
Some code turns interrupts off. For example, calling millis() briefly turns interrupts
off. Therefore the time for an interrupt to be serviced would be extended by the
length of time interrupts were turned off.
An event that turns interrupts back on (eg. returning from an interrupt service
routine) is guaranteed to execute at least one more instruction. So even if an ISR
ends, and your interrupt is pending, it still has to wait for one more instruction
before it is serviced.
Performance considerations
Interrupts can increase performance in many situations because you can get on with the
"main work" of your program without having to constantly be testing to see if switches
have been pressed. Having said that, the overhead of servicing an interrupt, as discussed
above, would actually be more than doing a "tight loop" polling a single input port. If you
absolutely need to respond to an event within, say, a microsecond, then an interrupt
would be too slow. In that case you might disable interrupts (eg. timers) and just loop
looking for the pin to change.
Some set a flag and they are handled in priority order, even if the event that
caused them has stopped. For example, a rising, falling, or changing level interrupt
on pin D2.
Others are only tested if they are happening "right now". For example, a low-level
interrupt on pin D2.
The ones that set a flag could be regarded as being queued, as the interrupt flag remains
set until such time as the interrupt routine is entered, at which time the processor clears
the flag. Of course, since there is only one flag, if the same interrupt condition occurs
again before the first one is processed, it won't be serviced twice.
Something to be aware of is that these flags can be set before you attach the interrupt
handler. For example, it is possible for a rising or falling level interrupt on pin D2 to be
"flagged", and then as soon as you do an attachInterrupt the interrupt immediately fires,
even if the event occurred an hour ago. To avoid this you can manually clear the flag. For
example:
#include <avr/sleep.h>
void sleepNow ()
{
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
sleep_enable (); // enables the sleep bit in the mcucr register
attachInterrupt (0, wake, LOW); // wake up on low level
sleep_mode (); // here the device is actually put to sleep!!
detachInterrupt (0); // stop LOW interrupt
} // end of sleepNow
Note that the interrupt is detached immediately after we wake up. However we should
be cautious about putting the detachInterrupt actually inside the "wake" ISR, because it
is possible the interrupt might fire between attaching the interrupt and going to sleep, in
which case we would never wake up.
Correction:
The improved version below solves this problem by disabling interrupts before doing the
attachInterrupt call. Since you are guaranteed that one instruction will be executed after
re-enabling interrupts, we are sure that the sleep_cpu call will be done before the
interrupt occurs.
#include <avr/sleep.h>
void sleepNow ()
{
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
noInterrupts (); // make sure we don't get interrupted before we sleep
sleep_enable (); // enables the sleep bit in the mcucr register
attachInterrupt (0, wake, LOW); // wake up on low level
interrupts (); // interrupts allowed now, next instruction WILL be executed
sleep_cpu (); // here the device is put to sleep
} // end of sleepNow
These examples illustrates how you can save power with a LOW interrupt, because the
LOW interrupt is activated even if the processor is asleep (whereas the other ones -
RISING, FALLING, CHANGE - are not). (Amended 15 Dec 2013 : We now believe all four
external interrupt types will wake the processor).
In brief, keep them short! While an ISR is executing other interrupts cannot be
processed. So you could easily miss button presses, or incoming serial communications, if
you try to do too much. In particular, you should not try to do debugging "prints" inside
an ISR. The time taken to do those is likely to cause more problems than they solve.
A reasonable thing to do is set a single-byte flag, and then test that flag in the main loop
function. Or, store an incoming byte from a serial port into a buffer. The inbuilt timer
interrupts keep track of elapsed time by firing every time the internal timer overflows,
and thus you can work out elapsed time by knowing how many times the timer
overflowed.
Remember, inside an ISR interrupts are disabled. Thus hoping that the time returned by
millis() function calls will change, will lead to disappointment. It is valid to obtain the
time that way, just be aware that the timer is not incrementing. And if you spend too
long in the ISR then the timer may miss an overflow event, leading to the time returned
by millis() becoming incorrect.
A test shows that, on a 16 MHz Atmega328 processor, a call to micros() takes 3.5625 S.
A call to millis() takes 1.9375 S. Recording (saving) the current timer value is a
reasonable thing to do in an ISR. Finding the elapsed milliseconds is faster than the
elapsed microseconds (the millisecond count is just retrieved from a variable). However
the microsecond count is obtained by adding the current value of the Timer 0 timer
(which will keep incrementing) to a saved "Timer 0 overflow count".
Warning: Since interrupts are disabled inside an ISR, and since the latest version of the
Arduino IDE uses interrupts for Serial reading and writing, and also for incrementing the
counter used by "millis" and "delay" you should not attempt to use those functions
inside an ISR. To put it another way:
There are two ways you can detect external events on pins. The first is the special
"external interrupt" pins, D2 and D3. These general discrete interrupt events, one per
pin. You can get to those by using attachInterrupt for each pin. You can specify a rising,
falling, changing or low-level condition for the interrupt.
However there are also "pin change" interrupts for all pins (on the Atmega328, not
necessarily all pins on other processors). These act on groups of pins (D0 to D7, D8 to
D13, and A0 to A5). They are also lower priority than the external event interrupts. You
could make an interrupt handler to handle changes on pins D8 to D13 like this:
ISR (PCINT0_vect)
{
// one of pins D8 to D13 has changed
}
Clearly extra code is needed to work out exactly which pin changed (eg. comparing it to a
"before" value).
Pin change interrupts each have an associated "mask" byte in the processor, so you could
actually configure them to only react to (say) D8, D10 and D12, rather than a change to
D8 to D13. However you still then need to work out which of those changed.
#define LED 13
// watchdog interrupt
ISR (WDT_vect)
{
wake ();
} // end of WDT_vect
void loop()
{
pinMode (LED, OUTPUT);
digitalWrite (LED, HIGH);
delay (5000);
digitalWrite (LED, LOW);
delay (5000);
} // end of loop
The above code, running on a "bare bones" board (that is, without voltage regulator, USB
interface etc.) uses the following:
You can see that using the watchdog timer combined with sleep mode, you can save a
considerable amount of power during times when the processor might not be needed.
It also illustrates how you can recover from sleep in two different ways. One is with a
button press (ie. you ground pin D2), the other is waking up periodically (every 8
seconds), although you could make it every 1, 2 4, or 8 seconds (or even shorter if you
consult the data sheet).
Warning: after being woken the processor might need a short time to stabilize its clock.
For example, you may see "garbage" on a serial comms port while things are
synchronizing. If this is a problem you may want to build in a short delay after being
woken.
Note re brownout detection: The figures measured above are with brownout detection
disabled. To generate a reference voltage for the brownout detection takes a bit of
current. With it enabled, the sleep mode uses closer to 70 uA (not 6 uA). One way of
turning off the brownout detection is to use avrdude from the command line, like this:
That sets the "efuse" (extended fuse) to be 7, which disables brownout detection. This
example assumes you are using the USBtinyISP programmer board.
A "normal" Arduino environment already is using interrupts, even if you don't personally
attempt to. The millis() and micros() function calls make use of the "timer overflow"
feature. One of the internal timers (timer 0) is set up to interrupt roughly 1000 times a
second, and increment an internal counter which effectively becomes the millis()
counter. There is a bit more to it than that, as adjustment is made for the exact clock
speed.
Also the hardware serial library uses interrupts to handle incoming serial data (and in the
more recent library versions, outgoing serial data as well). This is very useful as your
program can be doing other things while the interrupts are firing, and filling up an
internal buffer. Then when you check Serial.available() you can find out what, if anything,
has been placed in that buffer.
After a bit of discussion and research on the Arduino forum, we have clarified exactly
what happens after you enable interrupts. There are three main ways I can think of that
you can enable interrupts, which were previously not enabled:
sei ();
sleep_cpu ();
If not for this guarantee, the interrupt might occur before the processor slept, and then
it might never be awoken.
Empty interrupts
If you merely want an interrupt to wake the processor, but not do anything in particular,
you can use the EMPTY_INTERRUPT define, eg.
EMPTY_INTERRUPT (PCINT1_vect);
This simply generates a "reti" (return from interrupt) instruction. Since it doesn't try to
save or restore registers this would be the fastest way to get an interrupt to wake it up.
More information about interrupts, timers, etc. can be obtained from the data sheet for
the processor.
http://atmel.com/dyn/resources/prod_documents/8271S.pdf
Example code of a pump timer
This was posted on the Arduino forum (by me) of an example of how to make a pump
timer. The hardware consisted of a switch (the on/cancel switch) connected to D2 and a
"pump time" rotary switch (not a rotary encoder) connected to pins 3 to 11. For
example, if the switch was in the 3rd position you would get the pump running for 10
minutes.
// Pump timer
// Author: Nick Gammon
// Date: 7th January 2012
// Released into the public domain.
#include <avr/sleep.h>
const int pumpPin = 13; // output pin for the pump and solenoid (goes to the relay)
const int buttonPin = 2; // input pin (for a pushbutton switch)
const int firstSwitch = 3; // first switch pin for time rotary button
const int lastSwitch = 11; // last switch pin
const int debounceTime = 20; // debounce in milliseconds
const unsigned long delayTime [] = { 2, 5, 10, 15, 20, 30, 45, 46, 120}; // Pump run times
in minutes
unsigned long startPumpTime = 0; // when pump started
unsigned long pumpTime; // how long to run pump
volatile boolean buttonPressed; // set when button pressed
void deBounce ()
{
unsigned long now = millis ();
do
{
// on bounce, reset time-out
if (digitalRead (buttonPin) == LOW)
now = millis ();
}
while (digitalRead (buttonPin) == LOW ||
(millis () - now) <= debounceTime);
} // end of deBounce
void setup()
{
pinMode(pumpPin, OUTPUT);
digitalWrite (buttonPin, HIGH); // pull-up on button
for (int i = firstSwitch; i <= lastSwitch; i++)
digitalWrite (i, HIGH); // pull-up on switch
} // end of setup
void loop ()
{
// pump is not running and we woke up, work out how long to run it
deBounce ();
pumpTime = 0;
if (pumpTime == 0)
return; // no time selected
// start pump
startPumpTime = millis ();
digitalWrite (pumpPin, HIGH);
} // end of loop
Of interest is the deBounce function - that handles the inevitable "bounces" of switch
contacts as you open or close them. Without some sort of debouncing handler you might
interpret a single button press as 20 presses, making it very hard to actually do anything.
Above is an image of a switch bouncing, captured on the logic analyzer. You can see from
the above, that a simple keypress might result in a dozen or so transitions. On the image
each bounce is around 5 mS apart. So we really need to wait for a longer interval to
elapse, in which the switch doesn't open/close again.
The debounce handler above waits for 20 milliseconds for the switch to stop bouncing,
and if the switch closes again, resets the time interval so it waits another 20 milliseconds.
This seemed to work quite well.
The code also demonstrates putting the processor to sleep if it isn't needed (eg. at
midnight) so it uses less power. It also shows how the "low" interrupt can be used to
wake it up, and the falling interrupt to notice if the switch is pressed while the pump is
running (eg., you have watered your plants enough already).
Timer interrupts
The "normal" Arduino IDE uses timer 0 to provide a "clock" being a number that is
incremented, and adjusted, to give you a count per millisecond. You can read that by
doing something like this:
You can also find a more accurate timer value by using micros (), which takes the current
value from the millis counter, and adds in the current reading from timer 0 (thus
correcting for "right now"). eg.
However you can make your own interrupts, say using Timer 1, like this:
ISR(TIMER1_COMPA_vect)
{
static boolean state = false;
state = !state; // toggle
digitalWrite (LED, state ? HIGH : LOW);
}
void setup() {
pinMode (LED, OUTPUT);
// set up Timer 1
TCCR1A = 0; // normal operation
TCCR1B = bit(WGM12) | bit(CS10); // CTC, no pre-scaling
OCR1A = 999; // compare A register value (1000 * clock speed)
TIMSK1 = bit (OCIE1A); // interrupt on Compare A Match
} // end of setup
void loop() { }
This uses CTC (Clear Timer on Compare), so it counts up to the specified value, clears the
timer, and starts again. This toggles pin D13 faster than you can see (once every 62.5 S).
(It turns on every second toggle, that is every 125 S, giving a frequency of 1/0.000125 =
8 KHz).
Note: The counter is zero-relative. So if the counter is 1, then it actually counts two times
the clock_speed/pre-scaler (0 and 1). So for an interval of 1000 slower than the internal
clock, we need to set the counter to 999.
If you change the prescaler it toggles every 64 mS, slow enough for you to see it flashing.
(That is, the LED lights every 128 mS, being 1/0.128 = 7.8125 Hz).
ISR(TIMER1_COMPA_vect)
{
static boolean state = false;
state = !state; // toggle
digitalWrite (LED, state ? HIGH : LOW);
}
void setup() {
pinMode (LED, OUTPUT);
// set up Timer 1
TCCR1A = 0; // normal operation
TCCR1B = bit(WGM12) | bit(CS10) | bit (CS12); // CTC, scale to clock / 1024
OCR1A = 999; // compare A register value (1000 * clock speed / 1024)
TIMSK1 = bit (OCIE1A); // interrupt on Compare A Match
} // end of setup
void loop() { }
And even more simply, you can output a timer result without even using interrupts:
// set up Timer 1
TCCR1A = bit (COM1A0); // toggle OC1A on Compare Match
TCCR1B = bit(WGM12) | bit(CS10) | bit (CS12); // CTC, scale to clock / 1024
OCR1A = 4999; // compare A register value (5000 * clock speed / 1024)
} // end of setup
void loop() { }
Plug an LED (and resistor in series, say 470 ohms) between D9 and Gnd, and you will see
it toggle every 320 mS. (1/(16000000 / 1024) * 5000).
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=50106
Ignition timing timing with timers and interrupts
Below is another example of interrupts and timers from the Arduino forum. The basic
problem was to turn something on when an interrupt occurred (eg. on pin D2) but after a
delay. The code below uses an interrupt to start the process, sets a timer for 0.5 mS (as
an initial delay). Then when that timer fires, the spark is turned on for a different interval
(2 mS) and then when that time is up, the spark is turned off, and the process resumes.
#include <digitalWriteFast.h>
void activateInterrupt0 ()
{
EICRA &= ~(bit(ISC00) | bit (ISC01)); // clear existing flags
EICRA |= bit (ISC01); // set wanted flags (falling level interrupt)
EIFR = bit (INTF0); // clear flag for interrupt 0
EIMSK |= bit (INT0); // enable it
} // end of activateInterrupt0
void deactivateInterrupt0 ()
{
EIMSK &= ~bit (INT0); // disable it
} // end of deactivateInterrupt0
} // end of TIMER1_COMPA_vect
// set up Timer 1
TCCR1A = 0; // normal mode
TCNT1 = 0; // count back to zero
TCCR1B = bit(WGM12) | bit(CS11); // CTC, scale to clock / 8
// time before timer fires - zero relative
// multiply by two because we are on a prescaler of 8
OCR1A = (sparkDelayTime * 2) - (isrDelayFactor * 2) - 1;
TIMSK1 = bit (OCIE1A); // interrupt on Compare A Match
void setup()
{
TCCR1A = 0; // normal mode
TCCR1B = 0; // stop timer
TIMSK1 = 0; // cancel timer interrupt
activateInterrupt0 ();
} // end of setup
void loop()
{
// read sensors, compute time to fire spark
The graphic shows (ignore the switch bounce) that after the switch is closed there is a
delay of 500 S (between the green flags) and then a delay of 1999 S while the spark is
"on".
[EDIT] Changes made on 19th October 2014.
The sketch uses the digitalwritefast library to do quick writes to the spark pin. It also uses
the ISR (INT0_vect) function rather than attachInterrupt, because attachInterrupt takes
longer to run.
https://code.google.com/p/digitalwritefast/
Of course this general concept could be applied to anything where you want to time a
brief pulse. Tested down to a 50 S pulse, but it could probably go a bit shorter, as it
takes around 5 S to enter and leave an ISR.
started = !started;
} // end of shutter
void setup ()
{
Serial.begin (115200);
Serial.println ("Shutter test ...");
attachInterrupt (0, shutter, CHANGE);
} // end of setup
void loop ()
{
if (endTime)
{
Serial.print ("Shutter open for ");
Serial.print (endTime - startTime);
Serial.println (" microseconds.");
endTime = 0;
}
} // end of loop
Robot race timer example
Another example uses a laser beam to shine on a LDR (light dependent resistor). The
laser is so powerful this simple circuit seemed to work OK:
The sketch below times intervals between RISING interrupts (the resistance goes up
when less light shines on the LDR):
void isr ()
{
// wait until we noticed last one
if (triggered)
return;
triggerTime = micros ();
triggered = true;
} // end of isr
void setup()
{
digitalWrite (2, HIGH); // pull-up
attachInterrupt(0, isr, RISING);
Serial.begin (115200);
Serial.println ("Started timing ...");
} // end of setup
void loop()
{
if (!triggered)
return;
lastTriggerTime = triggerTime;
triggered = false; // re-arm for next time
Serial.print (minutes);
Serial.print ("m ");
Serial.print (seconds);
Serial.print ("s ");
Serial.print (ms);
Serial.println ("ms.");
} // end of loop
Example output:
I shone a cheap 1 mW laser pointer onto the LDR. The resistance is low with the light on
it (I read about 1.9V on pin D2) which registers as a LOW input. When you break the
beam the resistance is high and the pin gets about 3V (being a HIGH). Thus the beam is
broken on a RISING interrupt.
The code times the intervals between rising interrupts and reports them to the serial
monitor. Of course you could use a LCD or something. There is a test for short intervals
(which might be the light passing through a part of the robot) so that if the time is less
than a second it doesn't start timing again.
Read the Analog-to-Digital converter asynchronously
The code example below demonstrates reading the ADC converter (pin A0 in this case)
whilst doing something else at the same time.
Each ADC conversion takes around 104 uS, and since that would be 1664 clock cycles
(something like 832 instructions) it would be useful to do something (like process the
previous reading) while you are doing it.
void setup ()
{
Serial.begin (115200);
// set the analog reference (high two bits of ADMUX) and select the
// channel (low 4 bits). this also sets ADLAR (left-adjust result)
// to 0 (the default).
ADMUX = bit (REFS0) | (adcPin & 0x07);
} // end of setup
void loop ()
{
// if last reading finished, process it
if (adcDone)
{
adcStarted = false;
adcDone = false;
}
} // end of loop
Once the reading has started you continue to execute the main loop, checking to see if it
completed in the ISR. Once it does you can process it, print it, or whatever.
Sleep during ADC conversion
Another approach is to go to sleep during the reading. In this case, of course, you cannot
do other things, but being asleep reduces digital noise inside the chip, leading to a better
analog conversion. The code below is similar to the above, but adds in a
"SLEEP_MODE_ADC" sleep for a better conversion.
#include <avr/sleep.h>
void setup ()
{
Serial.begin (115200);
// set the analog reference (high two bits of ADMUX) and select the
// channel (low 4 bits). this also sets ADLAR (left-adjust result)
// to 0 (the default).
ADMUX = bit (REFS0) | (adcPin & 0x07);
} // end of setup
void loop ()
{
// if last reading finished, process it
if (adcDone)
{
adcStarted = false;
adcDone = false;
}
} // end of loop
Also consider putting a 0.1 uF capacitor between the AREF and GND pins (adjacent to
each other on the Uno board) to reduce noise in the analog reference voltage.
Pin Change Interrupts
Pin change interrupts let you detect changes to any of the Arduino pins. However they
are a bit more fiddly to use than the external interrupts because they are grouped into
batches. So if the interrupt fires you have to work out in your own code exactly which pin
caused the interrupt.
Example code:
ISR (PCINT0_vect)
{
// handle pin change interrupt for D8 to D13 here
} // end of PCINT0_vect
ISR (PCINT1_vect)
{
// handle pin change interrupt for A0 to A5 here
} // end of PCINT1_vect
ISR (PCINT2_vect)
{
// handle pin change interrupt for D0 to D7 here
} // end of PCINT2_vect
void setup ()
{
// pin change interrupt (example for D9)
PCMSK0 |= bit (PCINT1); // want pin 9
PCIFR |= bit (PCIF0); // clear any outstanding interrupts
PCICR |= bit (PCIE0); // enable pin change interrupts for D8 to D13
}
Specify which pin in the group. This is the PCMSKn variable (where n is 0, 1 or 2
from the table below). You can have interrupts on more than one pin.
Enable the appropriate group of interrupts (0, 1 or 2)
Supply an interrupt handler as shown above
Table of pins -> pin change names / masks
D0 PCINT16 (PCMSK2 / PCIF2 / PCIE2)
D1 PCINT17 (PCMSK2 / PCIF2 / PCIE2)
D2 PCINT18 (PCMSK2 / PCIF2 / PCIE2)
D3 PCINT19 (PCMSK2 / PCIF2 / PCIE2)
D4 PCINT20 (PCMSK2 / PCIF2 / PCIE2)
D5 PCINT21 (PCMSK2 / PCIF2 / PCIE2)
D6 PCINT22 (PCMSK2 / PCIF2 / PCIE2)
D7 PCINT23 (PCMSK2 / PCIF2 / PCIE2)
D8 PCINT0 (PCMSK0 / PCIF0 / PCIE0)
D9 PCINT1 (PCMSK0 / PCIF0 / PCIE0)
D10 PCINT2 (PCMSK0 / PCIF0 / PCIE0)
D11 PCINT3 (PCMSK0 / PCIF0 / PCIE0)
D12 PCINT4 (PCMSK0 / PCIF0 / PCIE0)
D13 PCINT5 (PCMSK0 / PCIF0 / PCIE0)
A0 PCINT8 (PCMSK1 / PCIF1 / PCIE1)
A1 PCINT9 (PCMSK1 / PCIF1 / PCIE1)
A2 PCINT10 (PCMSK1 / PCIF1 / PCIE1)
A3 PCINT11 (PCMSK1 / PCIF1 / PCIE1)
A4 PCINT12 (PCMSK1 / PCIF1 / PCIE1)
A5 PCINT13 (PCMSK1 / PCIF1 / PCIE1)
The interrupt handler would need to work out which pin caused the interrupt if the mask
specifies more than one (eg. if you wanted interrupts on D8/D9/D10). To do this you
would need to store the previous state of that pin, and work out (by doing a digitalRead
or similar) if this particular pin had changed.
#include <avr/sleep.h>
ISR (PCINT1_vect)
{
// handle pin change interrupt for A0 to A5 here
// toggle LED
digitalWrite (LEDWAKE, !digitalRead (LEDWAKE));
} // end of PCINT1_vect
void setup ()
{
pinMode (LEDWAKE, OUTPUT);
pinMode (LEDLOOP, OUTPUT);
digitalWrite (A0, HIGH); // enable pull-up
} // end of setup
void loop ()
{
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
sleep_mode ();
There are some subtle issues regarding variables which are shared between interrupt
service routines (ISRs) and the main code (that is, the code not in an ISR).
Since an ISR can fire at any time when interrupts are enabled, you need to be cautious
about accessing such shared variables, as they may be being updated at the very
moment you access them.
A variable should only be marked volatile if it is used both inside an ISR, and outside one.
Eg.
Marking a variable as volatile tells the compiler to not "cache" the variables contents into
a processor register, but always read it from memory, when needed. This may slow down
processing, which is why you don't just make every variable volatile, when not needed.
Atomic access
ISR (TIMER1_OVF_vect)
{
count = 10;
}
void setup ()
{
}
void loop ()
{
count++;
}
There are two danger points here, as marked. If the interrupt fires after the lds (load
register 24 with the variable "count"), but before the sts (store back into the variable
"count") then the variable might be changed by the ISR (TIMER1_OVF_vect),
however this change is now lost, because the variable in the register was used instead.
We need to protect the shared variable by turning off interrupts briefly, like this:
ISR (TIMER1_OVF_vect)
{
count = 10;
} // end of TIMER1_OVF_vect
void setup ()
{
} // end of setup
void loop ()
{
noInterrupts ();
count++;
interrupts ();
} // end of loop
Now the update of "count" is done "atomically" ... that is, it cannot be interrupted.
Multi-byte variables
Let's make "count" a 2-byte variable, and see the other problem:
ISR (TIMER1_OVF_vect)
{
count++;
} // end of TIMER1_OVF_vect
void setup ()
{
pinMode (13, OUTPUT);
} // end of setup
void loop ()
{
if (count > 20)
digitalWrite (13, HIGH);
} // end of loop
OK, we are not changing count any more, so is there still a problem? Sadly, yes. Let's look
at the generated code for the "if" statement:
So again we have to "protect" the access to the shared variable, like this:
void loop ()
{
noInterrupts ();
if (count > 20)
digitalWrite (13, HIGH);
interrupts ();
} // end of loop
There is a final "gotcha" here. What if interrupts might be off already? Then turning them
back on afterwards is a Bad Idea.
In that case you need to save the processor status register like this:
This "safe" code saves the current status of interrupts, turns them off (they may already
be off), gets the shared variable into a temporary variable, turns interrupts back on if
they were off, and then returns the copy of the shared variable.
Summary
Code may "appear to work" even if you don't take the above precautions. That is
because the chances of the interrupt occurring at the exact wrong moment is fairly low
(maybe 1 in 100, depending on the code size). But sooner or later it will happen at the
wrong moment, and your code will either crash, or occasionally return the wrong
results.
The table below shows the ways they are mapped in the internal libraries:
ATtiny85 sleep mode and wake on pin change
The sketch below illustrates putting the ATtiny85 to sleep, and then waking on a pin-
change interrupt on D4 (pin 3 on the chip). The pin is put into input-pullup mode so all
you have to do is ground the pin to wake the chip.
ISR (PCINT0_vect)
{
// do something interesting here
}
void setup ()
{
pinMode (LED, OUTPUT);
pinMode (SWITCH, INPUT);
digitalWrite (SWITCH, HIGH); // internal pull-up
} // end of setup
void loop ()
{
digitalWrite (LED, HIGH);
delay (500);
digitalWrite (LED, LOW);
delay (500);
goToSleep ();
} // end of loop
void goToSleep ()
{
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
ADCSRA = 0; // turn off ADC
power_all_disable (); // power off ADC, Timer 0 and 1, serial interface
sleep_enable();
sleep_cpu();
sleep_disable();
power_all_enable(); // power everything back on
} // end of goToSleep
With the brownout disable turned off, the above sketch uses 500 nA of current when
asleep, as predicted by the datasheet, page 183:
Fuses were:
I have seen example code suggesting you count things for a second by turning interrupts
off for a second and then on again. I don't recommend this, because for one thing, you
can't do serial prints if interrupts are off.
void eventISR ()
{
events++;
} // end of eventISR
void setup ()
{
Serial.begin(115200);
attachInterrupt (0, eventISR, FALLING);
} // end of setup
void loop ()
{
events = 0; // reset counter
interrupts (); // allow interrupts
delay (INTERVAL); // wait desired time
noInterrupts(); // stop interrupts
That may work for simple situations, but turning interrupts off means that the Serial
prints won't work until they are turned on again (which they may not be in a more
complex sketch).
It is better to use the millis() result and just detect when the time limit is up.
Improved sketch:
void eventISR ()
{
if (counting)
events++;
} // end of eventISR
void setup ()
{
Serial.begin (115200);
Serial.println ();
attachInterrupt (0, eventISR, FALLING);
} // end of setup
void showResults ()
{
Serial.print ("I counted ");
Serial.println (events);
} // end of showResults
void loop ()
{
if (counting)
{
// is time up?
if (millis () - startTime < INTERVAL)
return;
counting = false;
showResults ();
} // end of if
noInterrupts ();
events = 0;
startTime = millis ();
EIFR = bit (INTF0); // clear flag for interrupt 0
counting = true;
interrupts ();
} // end of loop
In loop() here we wait for the interval (1 second in this case) to be up, otherwise we
return, effectively doing nothing. You could of course do other things instead of just
returning.
That code seemed to work OK up to 100 kHz, although the counts were getting a bit
inaccurate. You can do more precise timings by using the hardware counters / timers.
Details here:
The Atmega328 (as on the Arduino Uno) has three timers/counters on-board the chip.
Timer 0 is set up by the init() function (which is automatically called by the code
generated by the IDE, before setup() is called). This is used to count approximately every
millisecond. This provides you with the figure that the millis() function returns.
You can use these timers easily enough by using the analogWrite function - that just
generates a PWM (pulse width modulated) output on the various pins that the timer
hardware supports.
But for a more in-depth analysis, let's look at using the timers/counters in our own way.
The example code below provides a "frequency counter" which counts the number of
events which cause a rising edge on digital pin D5 during a specified interval.
For example, if you put a 5 kHz signal on pin D5, and time it for one second, the count
will be 5000. You could also time it for 1/10 of a second (giving you a count of 500) and
then multiply the result by 10, again giving you a figure of 5 kHz.
A longer timing period will give higher accuracy, and also average out any small
variations during the sample time. However, of course, a longer timing period takes
longer to execute.
Counter 1 - used to count pulses
In the code below Timer 1 is configured to count the number of times that a leading edge
(rising pulse) is detected on D5. Each event increments the internal counter in the timer.
When the 16-bit timer overflows an overflow interrupt is executed which counts the
number of overflows.
When the time is up, the number of counts is the current counter contents of timer 1,
plus the number of overflows multiplied by 65536.
The counts are meaningless unless we know over what interval they occurred, which is
what we use Timer 2 for. It is set up to take the internal clock (normally 16 MHz on a
Uno), and "pre-scale" it by dividing it by 128. The pre-scaled clock will then "tick" every 8
microseconds (since the clock itself runs with a period of 1/16000000 or 62.5 nS).
Note that Timer 2 has a higher priority than Timers 0 and 1, so neither the millis() timer,
nor the Timer 1 counter will take precedence over this interrupt.
In the Timer 2 interrupt we see if time is up (basically whether the required number of
milliseconds is up). Is not, we just keep going. If time is up, we turn off both Timers 1 and
2, calculate the total count (by multiplying the number of overflows by 65536 and adding
in the remaining counts) and exit.
// Input: Pin D5
// Reset prescalers
GTCCR = bit (PSRASY); // reset prescaler now
// start Timer 2
TCCR2B = bit (CS20) | bit (CS22) ; // prescaler of 128
// start Timer 1
// External clock source on T1 pin (D5). Clock on rising edge.
TCCR1B = bit (CS10) | bit (CS11) | bit (CS12);
} // end of startCounting
ISR (TIMER1_OVF_vect)
{
++overflowCount; // count number of Counter1 overflows
} // end of TIMER1_OVF_vect
//******************************************************************
// Timer2 Interrupt Service is invoked by hardware Timer 2 every 1ms = 1000 Hz
// 16Mhz / 128 / 125 = 1000 Hz
ISR (TIMER2_COMPA_vect)
{
// grab counter value before it changes any more
unsigned int timer1CounterValue;
timer1CounterValue = TCNT1; // see datasheet, page 117 (accessing 16-bit registers)
unsigned long overflowCopy = overflowCount;
void setup ()
{
Serial.begin(115200);
Serial.println("Frequency Counter");
} // end of setup
void loop ()
{
// stop Timer 0 interrupts from throwing the count out
byte oldTCCR0A = TCCR0A;
byte oldTCCR0B = TCCR0B;
TCCR0A = 0; // stop timer 0
TCCR0B = 0;
while (!counterReady)
{ } // loop until count over
// restart timer 0
TCCR0A = oldTCCR0A;
TCCR0B = oldTCCR0B;
Accuracy
Pumping in a 5 MHz signal from a signal generator, the sketch outputs around 5001204
(give or take a couple of counts). The error (assuming the signal generator is accurate) is
therefore 1204/500000 or about 0.02% error.
Trying with a 5 kHz signal, the sketch outputs around 5000 to 5002, an error of 2/5000 or
0.04% error.
So, pretty accurate. Tests on my Arduino clock showed that the clock itself was around
0.2% wrong, so we can't really expect better accuracy than that.
Range
I measured up to 8 MHz with about 0.5% error. At 5 MHz the error was down to 0.02% as
described above. At the other end of the scale, it measured down to 10 Hz without any
obvious error. Below that errors crept in, particularly as the sample period is only 500
mS.
TCNT2 = 0;
TCNT5 = 0; // Both counters to zero
// Reset prescalers
GTCCR = bit (PSRASY); // reset prescaler now
// start Timer 2
TCCR2B = bit (CS20) | bit (CS22) ; // prescaler of 128
// start Timer 5
// External clock source on T4 pin (D47). Clock on rising edge.
TCCR5B = bit (CS50) | bit (CS51) | bit (CS52);
} // end of startCounting
ISR (TIMER5_OVF_vect)
{
++overflowCount; // count number of Counter1 overflows
} // end of TIMER5_OVF_vect
//******************************************************************
// Timer2 Interrupt Service is invoked by hardware Timer 2 every 1ms = 1000 Hz
// 16Mhz / 128 / 125 = 1000 Hz
ISR (TIMER2_COMPA_vect)
{
// grab counter value before it changes any more
unsigned int timer5CounterValue;
timer5CounterValue = TCNT5; // see datasheet, (accessing 16-bit registers)
void setup () {
Serial.begin(115200);
Serial.println("Frequency Counter");
} // end of setup
void loop () {
while (!counterReady)
{ } // loop until count over
// restart timer 0
TCCR0A = oldTCCR0A;
TCCR0B = oldTCCR0B;
} // end of loop
To help work out what prescaler/count you need for setting up timers, consult this table:
All these figures assume a 16 MHz clock, and thus a clock period of 62.5 nS.
The "count" column is the number of counts you use for the Output Compare Register
(eg. for OCR2A, OCR2B and so on) when counting up to a "compare" value. The count of
256 can also be used to see how long until Timer 0 and Timer 2 overflow (Timer 1 is a 16-
bit timer and overflows after 65536 counts).
Remember that the count registers are zero-relative, so to get 100 counts, you actually
put 99 into the register.
So for example, with a prescaler of 64, Timer 0 will overflow every 1.024 mS (which in
fact it normally does for use by the millis() function).
To set some other frequency choose a prescaler which appears reasonably close, and
then apply this formula:
Bear in mind, too, that if you are using the hardware to toggle a pin, it needs to get
toggled at twice the target frequency (because 100 Hz is counting 100 times a complete
cycle, not half a cycle).
The table below shows the relationship between various pins (with the Arduino pin
number in brackets) and the respective timers.
For example, to count an external source with Timer 1, you connect that to Arduino pin
D5 (pin 11 on the Atmega328).
Timer 0
Timer 1
Timer 2
The frequency counter below works a bit differently. The one in the earlier post used a
timer to count the number of "ticks" in a given interval, so it was literally counting the
frequency.
The sketch below turns that around, and uses a timer to work out the interval between
two consecutive rising edges on pin D2. This time we use a "rising" interrupt on D2 to
notice the leading edge. We also set up a high-precision timer (Timer 1) which is a 16-bit
timer.
By using no prescaler, Timer 1 counts 1 for every clock cycle (say, every 62.5 nS at 16
MHz). By multiplying the number of counts between the leading edges by 62.5, and then
taking the inverse, we can deduce the frequency.
The advantage of this method is that we get a very quick calculation. For example, at 10
kHz the period is 1/10000, namely 100 S, so we get our result 100 S later.
// Frequency timer
// Author: Nick Gammon
// Date: 10th February 2012
// Input: Pin D2
if (first)
{
startTime = (overflowCount << 16) + counter;
first = false;
return;
}
void prepareForInterrupts ()
{
// get ready for next time
EIFR = bit (INTF0); // clear flag for interrupt 0
first = true;
triggered = false; // re-arm for next time
attachInterrupt(0, isr, RISING);
} // end of prepareForInterrupts
void setup ()
{
Serial.begin(115200);
Serial.println("Frequency Counter");
// reset Timer 1
TCCR1A = 0;
TCCR1B = 0;
// Timer 1 - interrupt on overflow
TIMSK1 = bit (TOIE1); // enable Timer1 Interrupt
// zero it
TCNT1 = 0;
overflowCount = 0;
// start Timer 1
TCCR1B = bit (CS20); // no prescaling
} // end of setup
void loop ()
{
if (!triggered)
return;
// so we can read it
delay (500);
prepareForInterrupts ();
} // end of loop
Note that due to the time taken to service the interrupts on the data's leading edges, the
maximum achievable frequency you can sample is around 100 kHz (which would mean
the ISR is taking around 10 S).
Timer setup
Below are some namespaces for easily setting up timers. They can be a bit tedious to get
the various bit combinations right for the various modes.
The sketch has three "namespaces" (Timer0, Timer1, Timer2) which inside have a table
of modes, and some enums for the various settings).
So for example, to set Timer 1 into mode 4 (CTC, top = OCR1A) with a prescaler of 1 (ie.
no prescaler) and clearing timer output port 1A on compare you would do this:
/*
Timer Helpers library.
See: http://www.gammon.com.au/forum/?id=11504
Example:
// set up Timer 1
TCNT1 = 0; // reset counter
OCR1A = 999; // compare A register value (1000 * clock speed)
*/
#ifndef _TimerHelpers_h
#define _TimerHelpers_h
/* ---------------------------------------------------------------
Timer 0 setup
--------------------------------------------------------------- */
namespace Timer0
{
// TCCR0A, TCCR0B
const byte Modes [8] [2] =
{
}; // end of Timer0::Modes
// Activation
// Note: T0 is pin 6, Arduino port: D4
enum { NO_CLOCK, PRESCALE_1, PRESCALE_8, PRESCALE_64, PRESCALE_256,
PRESCALE_1024, T0_FALLING, T0_RISING };
// choose a timer mode, set which clock speed, and which port to toggle
void setMode (const byte mode, const byte clock, const byte port)
{
if (mode < 0 || mode > 7) // sanity check
return;
/* ---------------------------------------------------------------
Timer 1 setup
--------------------------------------------------------------- */
namespace Timer1
{
// TCCR1A, TCCR1B
const byte Modes [16] [2] =
{
}; // end of Timer1::Modes
// Activation
// Note: T1 is pin 11, Arduino port: D5
enum { NO_CLOCK, PRESCALE_1, PRESCALE_8, PRESCALE_64, PRESCALE_256,
PRESCALE_1024, T1_FALLING, T1_RISING };
// choose a timer mode, set which clock speed, and which port to toggle
void setMode (const byte mode, const byte clock, const byte port)
{
if (mode < 0 || mode > 15) // sanity check
return;
/* ---------------------------------------------------------------
Timer 2 setup
--------------------------------------------------------------- */
namespace Timer2
{
// TCCR2A, TCCR2B
const byte Modes [8] [2] =
{
}; // end of Timer2::Modes
// Activation
enum { NO_CLOCK, PRESCALE_1, PRESCALE_8, PRESCALE_32, PRESCALE_64,
PRESCALE_128, PRESCALE_256, PRESCALE_1024 };
// choose a timer mode, set which clock speed, and which port to toggle
void setMode (const byte mode, const byte clock, const byte port)
{
if (mode < 0 || mode > 7) // sanity check
return;
#endif
http://gammon.com.au/Arduino/TimerHelpers.zip
Just unzip and put the TimerHelpers folder into your libraries folder.
Example of use:
#include <TimerHelpers.h>
/* ---------------------------------------------------------------
Test sketch
--------------------------------------------------------------- */
void setup() {
pinMode (SHUTTER, INPUT);
digitalWrite (SHUTTER, HIGH);
} // end of setup
ISR(TIMER1_COMPA_vect)
{
TCCR1A = 0; // reset timer 1
TCCR1B = 0;
} // end of TIMER1_COMPA_vect
void loop() {
delay (250); // debugging
// set up Timer 1
TCNT1 = 0; // reset counter
OCR1A = 999; // compare A register value (1000 * clock speed)
} // end of loop
One-shot timer
The example code above demonstrates a one-shot timer. This sets up Timer 1 to activate
a camera shutter for 62.5 uS (1000 x the clock speed of 62.5 nS), and then the interrupt
service routine cancels the timer, so the shutter is only activated once.
Operation modes of Timer 0
Every time I go to use the Arduino (Atmega328) timers I wonder what the heck is the
difference between the various modes. Do I want normal? CTC? PWM? Fast PWM?
Below are the results of investigating what each mode does, for timer 0.
I'm guessing that Timer 2 (which is also an 8-bit timer) works in a similar way.
This mode just counts to the maximum (0xFF) and wraps around. An interrupt can be set
to go off at the wrap-around point.
The counters (OCR0A and OCR0B) control at what point in the counting sequence the
pins are toggled.
#include <TimerHelpers.h>
// Timer 0
void setup() {
pinMode (timer0OutputA, OUTPUT);
pinMode (timer0OutputB, OUTPUT);
TIMSK0 = 0; // no interrupts
Timer0::setMode (0, Timer0::PRESCALE_1, Timer0::TOGGLE_A_ON_COMPARE |
Timer0::TOGGLE_B_ON_COMPARE);
OCR0A = 150;
OCR0B = 200;
} // end of setup
void loop() {}
The period here was 16 uS which is 256 * 62.5 nS. Since I set the output pin to toggle on
compare, it toggled every 16 uS.
This mode counts up to the maximum and then down again. On the first cycle (counting
up) and if you have CLEAR_A_ON_COMPARE set, then the output is initially high for
OCR0A/255 of the period (in the example: 150/255 which is a duty cycle of 58.82%), and
then goes low. For the second cycle (counting down) it stays low, and flips back to high
when the count is reached.
#include <TimerHelpers.h>
// Timer 0
void setup() {
pinMode (timer0OutputA, OUTPUT);
pinMode (timer0OutputB, OUTPUT);
OCR0A = 150;
OCR0B = 200;
TIMSK0 = 0; // no interrupts
Timer0::setMode (1, Timer0::PRESCALE_1, Timer0::CLEAR_A_ON_COMPARE |
Timer0::CLEAR_B_ON_COMPARE);
} // end of setup
void loop() {}
The total period here is 32 uS (and the duty cycle of the A output is 58.824%, that is:
150/255).
This could be regarded as "slow PWM" because an entire cycle takes 512 clock cycles
(256 up, 256 down). All you get to adjust is the point in that cycle where the output is
toggled.
If you change CLEAR to SET then the output is inverted, like this:
Now, the higher the counter, the longer the output is low (because it is set when the
counter is reached).
This mode lets you control the timer frequency. CTC is Clear Timer on Count.
Modes 0 and 1 simply counted up to 256 or 512 respectively, thus giving a fixed
frequency output. You could only alter the frequency by changing the timer prescaler.
However in CTC mode the timer resets when it reaches the count. In the example, the
period is 151 * 62.5 nS which is 9.438 uS. You multiply by 151 and not 150 because the
timer is zero-relative (the first count is zero).
#include <TimerHelpers.h>
// Timer 0
// input T0 pin 6 (D4)
// output OC0A pin 12 (D6)
// output OC0B pin 11 (D5)
void setup() {
pinMode (timer0OutputA, OUTPUT);
pinMode (timer0OutputB, OUTPUT);
TIMSK0 = 0; // no interrupts
Timer0::setMode (2, Timer0::PRESCALE_1, Timer0::TOGGLE_A_ON_COMPARE |
Timer0::TOGGLE_B_ON_COMPARE);
OCR0A = 150;
OCR0B = 200;
} // end of setup
void loop() {}
Notice that setting the B pin to toggle does not seem to work. I presume the timer can
only count to one value, and that is the A match value.
Also note that you need to set the counter after starting the timer (as shown above) or
you get rather strange results like this:
The timer for the above screenshot was started like this:
OCR0A = 150;
OCR0B = 200;
Timer0::setMode (2, Timer0::PRESCALE_1, Timer0::TOGGLE_A_ON_COMPARE |
Timer0::TOGGLE_B_ON_COMPARE);
Notice that the period of the timer seems to be just 62.5 nS (the processor clock cycle).
This mode counts up to the counter and toggles it when reached. In the example:
151/256 which is a duty cycle of 58.984%.
#include <TimerHelpers.h>
// Timer 0
void setup() {
pinMode (timer0OutputA, OUTPUT);
pinMode (timer0OutputB, OUTPUT);
TIMSK0 = 0; // no interrupts
Timer0::setMode (3, Timer0::PRESCALE_1, Timer0::CLEAR_A_ON_COMPARE |
Timer0::CLEAR_B_ON_COMPARE);
OCR0A = 150;
OCR0B = 200;
} // end of setup
void loop() {}
Notice that compared to the phase correct mode the frequency is doubled (it only counts
up, not down again). Thus for a prescaler of 1, the period is going to be 256 * 62.5 nS,
namely 16 uS. Also note how channel A and B are lined up, compared to how the pulses
are centered in phase-correct mode.
This mode counts up to the value in OCR0A and then down again. On the first cycle
(counting up) and if you have CLEAR_A_ON_COMPARE set, then the output is initially
high for OCR0B/OCR0A of the period (in the example: 150/255 which is a duty cycle of
75%), and then goes low. For the second cycle (counting down) it stays low, and flips
back to high when the count is reached.
#include <TimerHelpers.h>
// Timer 0
void setup() {
pinMode (timer0OutputA, OUTPUT);
pinMode (timer0OutputB, OUTPUT);
TIMSK0 = 0; // no interrupts
Timer0::setMode (5, Timer0::PRESCALE_1, Timer0::CLEAR_A_ON_COMPARE |
Timer0::CLEAR_B_ON_COMPARE);
OCR0A = 200; // number of counts for a cycle
OCR0B = 150; // duty cycle within OCR0A
} // end of setup
void loop() {}
Note that OCR0A sets the frequency of the timer - since it counts up to that figure and
back again. So you can use this to make a timer with a frequency other than simply 256
times the clock period. Of course the lower OCR0A is, the less resolution you have for the
PWM duty cycle.
The overall frequency in this example was 40 kHz (period of 25 uS) because that is 200 *
62.5 nS * 2 (you multiply by two because it counts up and back down again).
This mode counts up to OCR0B and toggles it when reached. It then counts (the rest of
the way) up to OCR0A. This is the fast PWM version of mode 5.
#include <TimerHelpers.h>
// Timer 0
void setup() {
pinMode (timer0OutputA, OUTPUT);
pinMode (timer0OutputB, OUTPUT);
TIMSK0 = 0; // no interrupts
Timer0::setMode (7, Timer0::PRESCALE_1, Timer0::CLEAR_A_ON_COMPARE |
Timer0::CLEAR_B_ON_COMPARE);
OCR0A = 200;
OCR0B = 150;
} // end of setup
void loop() {}
Because it counts up to 200 the period is 200 * 62.5 nS (12.5 uS) and the duty cycle is
now 75.5% (151/200).
http://gammon.com.au/Arduino/TimerHelpers.zip
More information
Some more useful information about PWM here, including some nice graphics that show
how the phase-correct PWM works:
What is PWM?
The code below uses Timer 1 to generate a 38 kHz pulse using fast PWM mode (mode
15). It then modulates the duty cycle from 0% to 100% based on a figure read from a
potentiometer connected to A0.
void setup()
{
pinMode (LED, OUTPUT);
void loop()
{
// alter Timer 1 duty cycle in accordance with pot reading
OCR1B = (((long) (analogRead (POTENTIOMETER) + 1) * timer1_OCR1A_Setting) /
1024L) - 1;
// Example of modulating a 38 kHz carrier frequency at 500 Hz with a variable duty cycle
// Author: Nick Gammon
// Date: 24 September 2012
// Clock frequency divided by 500 Hz frequency desired (allowing for prescaler of 128)
const long timer2_OCR2A_Setting = F_CPU / 500L / 128L;
ISR (PCINT2_vect)
{
} // end of PCINT2_vect
void setup() {
pinMode (LED, OUTPUT);
pinMode (3, OUTPUT); // OC2B
} // end of setup
void loop()
{
// alter Timer 2 duty cycle in accordance with pot reading
OCR2B = (((long) (analogRead (POTENTIOMETER) + 1) * timer2_OCR2A_Setting) /
1024L) - 1;
This particular case uses a prescaler of 8 (hence the divide by 8 in the calculation of
OCR2A).
You could change the duty cycle in the calculation of OCR2B (divide by 3, for example).
void setup()
{
pinMode (LED, OUTPUT);
TCCR2A = bit (WGM20) | bit (WGM21) | bit (COM2B1); // fast PWM, clear OC2B on
compare
TCCR2B = bit (WGM22) | bit (CS21); // fast PWM, prescaler of 8
OCR2A = ((F_CPU / 8) / frequency) - 1; // zero relative
OCR2B = ((OCR2A + 1) / 2) - 1; // 50% duty cycle
} // end of setup
void loop()
{
// do other stuff here
}
With a 16 MHz clock, and with the prescaler of 8 you can generate frequencies in the
range 7813 Hz to 1 MHz.
TCCR2A = bit (WGM20) | bit (WGM21) | bit (COM2B1); // fast PWM, clear OC2A on
compare
TCCR2B = bit (WGM22) | bit (CS20); // fast PWM, no prescaler
OCR2A = (F_CPU / frequency) - 1; // zero relative
OCR2B = ((OCR2A + 1) / 2) - 1; // 50% duty cycle
} // end of setup
void loop()
{
// do other stuff here
}
With a 16 MHz clock, this can generate frequencies in the range 62.5 kHz to 4 MHz (8
MHz ought to work but has artifacts on the measured output).
Frequencies and periods for various counter values
Below is a table of values for OCR2A (n) which shows the period and frequency you get
for each possible value and prescaler, assuming a 16 MHz clock rate. The same figures
apply to Timers 0 and 1 with the same prescalers. Note that the value in the first column
is already zero-relative. This is what you plug into OCR2A to get this frequency. Thus the
value of 255, for example, actually counts to 256.
The value in OCR2B controls the duty cycle. It must be less than the counter number. For
example, if you count to 3 (which is really a count of 4 because the 3 is zero-relative)
then for a 50% duty cycle you would want to put 1 (really a count of 2) into OCR2B.
void setup()
{
pinMode (OUTPUT_PIN, OUTPUT);
TCCR2A = bit (WGM20) | bit (WGM21) | bit (COM2B1); // fast PWM, clear OC2A on
compare
TCCR2B = bit (WGM22) | bit (CS20); // fast PWM, no prescaler
OCR2A = n; // from table
OCR2B = ((n + 1) / 2) - 1; // 50% duty cycle
} // end of setup
void loop() { }
void setup()
{
pinMode (OUTPUT_PIN, OUTPUT);
TCCR2A = bit (WGM20) | bit (WGM21) | bit (COM2B1); // fast PWM, clear OC2A on
compare
TCCR2B = bit (WGM22) | bit (CS21); // fast PWM, no prescaler
OCR2A = n; // from table
OCR2B = ((n + 1) / 2) - 1; // 50% duty cycle
} // end of setup
void loop() { }
void setup()
{
pinMode (OUTPUT_PIN, OUTPUT);
TCCR2A = bit (WGM20) | bit (WGM21) | bit (COM2B1); // fast PWM, clear OC2A on
compare
TCCR2B = bit (WGM22) | bit (CS22); // fast PWM, prescaler of 64
OCR2A = n; // from table
OCR2B = ((n + 1) / 2) - 1; // 50% duty cycle
} // end of setup
void loop() { }
void setup()
{
pinMode (OUTPUT_PIN, OUTPUT);
TCCR2A = bit (WGM20) | bit (WGM21) | bit (COM2B1); // fast PWM, clear OC2A on
compare
TCCR2B = bit (WGM22) | bit (CS21) | bit (CS22); // fast PWM, prescaler of 256
OCR2A = n; // from table
OCR2B = ((n + 1) / 2) - 1; // 50% duty cycle
} // end of setup
void loop() { }
void setup()
{
pinMode (OUTPUT_PIN, OUTPUT);
TCCR2A = bit (WGM20) | bit (WGM21) | bit (COM2B1); // fast PWM, clear OC2A on
compare
TCCR2B = bit (WGM22) | bit (CS20) | bit (CS21) | bit (CS22); // fast PWM, prescaler
of 1024
OCR2A = n; // from table
OCR2B = ((n + 1) / 2) - 1; // 50% duty cycle
} // end of setup
void loop() { }
Timer 2 also offers the prescalers of 32 and 128. On timers 0 and 1 these "clock source"
settings are used for an external clock (rising or falling edge).
void setup()
{
pinMode (OUTPUT_PIN, OUTPUT);
TCCR2A = bit (WGM20) | bit (WGM21) | bit (COM2B1); // fast PWM, clear OC2A on
compare
TCCR2B = bit (WGM22) | bit (CS20) | bit (CS21); // fast PWM, prescaler of 32
OCR2A = n; // from table
OCR2B = ((n + 1) / 2) - 1; // 50% duty cycle
} // end of setup
void loop() { }
void setup()
{
pinMode (OUTPUT_PIN, OUTPUT);
TCCR2A = bit (WGM20) | bit (WGM21) | bit (COM2B1); // fast PWM, clear OC2A on
compare
TCCR2B = bit (WGM22) | bit (CS20) | bit (CS22); // fast PWM, prescaler of 128
OCR2A = n; // from table
OCR2B = ((n + 1) / 2) - 1; // 50% duty cycle
} // end of setup
void loop() { }
Attiny85 example
The code below was developed in answer to a question on the Arduino forum. It shows
how you can have 3 x PWM outputs on an Attiny85 chip:
// For Attiny85
// Author: Nick Gammon
// Date: 29 November 2012
void setup()
{
pinMode (0, OUTPUT); // chip pin 5 // OC0A - Timer 0 "A"
pinMode (1, OUTPUT); // chip pin 6 // OC0B - Timer 0 "B"
pinMode (4, OUTPUT); // chip pin 3 // OC1B - Timer 1 "B"
// Timer 0, A side
TCCR0A = bit (WGM00) | bit (WGM01) | bit (COM0A1); // fast PWM, clear OC0A on
compare
TCCR0B = bit (CS00); // fast PWM, top at 0xFF, no prescaler
OCR0A = 127; // duty cycle (50%)
// Timer 0, B side
TCCR0A |= bit (COM0B1); // clear OC0B on compare
OCR0B = 63; // duty cycle (25%)
// Timer 1
TCCR1 = bit (CS10); // no prescaler
GTCCR = bit (COM1B1) | bit (PWM1B); // clear OC1B on compare
OCR1B = 31; // duty cycle (25%)
OCR1C = 127; // frequency
} // end of setup
void loop() { }
Timer 0 is set as fast PWM, counting up to 0xFF (255). Timer 1 is also PWM, counting up
to 127 (OCR1C). This means you can adjust the frequency of Timer 1.
The advantage of a 32.768 crystal is that, by dividing it by 32768, you get exactly one
second per timer firing (less or more by playing with the timer counter interval).
This lets you sleep for an accurate interval (more accurate than the watchdog timer), and
indeed, a long interval, like 8 seconds.
Whilst asleep, the processor uses very little power. In the example sketch I measured
1.46 A current consumption, when the output was LOW, if running from 5V power
supply, and 1.1 A if running from 3.3V power supply.
#include <avr/sleep.h>
#include <avr/power.h>
void setup()
{
pinMode (tick, OUTPUT);
// disable ADC
ADCSRA = 0;
} // end of setup
void loop()
{
// sleep, finally!
sleep_cpu ();
} // end of loop
Tone library for timers
The small library below simplifies generating tones with the hardware timers. Unlike the
"Tone" library that comes with the Arduino this one directly uses the outputting
capability of the hardware timers, and thus does not use interrupts.
http://www.gammon.com.au/Arduino/TonePlayer.zip
#include <TonePlayer.h>
void setup()
{
pinMode (9, OUTPUT); // output pin is fixed (OC1A)
tone1.tone (440);
delay (500);
tone1.noTone ();
tone1.tone (880);
delay (500);
tone1.noTone ();
}
void loop() { }
The library is written for the 16-bit timers, and supports Timer 1 on the Atmega328, and
Timers 1, 3, 4, 5 on the Atmega2560. Comments inside the library show how to set up
the other timers.
Timing an interval using the input capture unit
The code below is a modified version of the sketch above (in reply #1) that calculates a
frequency by inverting the period.
In other words, it can be used to time the interval between two consecutive pulses.
This one uses the "input capture" input to capture the value in Timer 1 at the moment an
event occurs. Since the hardware captures the timer value in a separate register, this
eliminates the delay caused by entering the interrupt routine (itself around 2 S).
// Input: Pin D8
ISR (TIMER1_CAPT_vect)
{
// grab counter value before it changes any more
unsigned int timer1CounterValue;
timer1CounterValue = ICR1; // see datasheet, page 117 (accessing 16-bit registers)
unsigned long overflowCopy = overflowCount;
if (first)
{
startTime = (overflowCopy << 16) + timer1CounterValue;
first = false;
return;
}
void prepareForInterrupts ()
{
noInterrupts (); // protected code
first = true;
triggered = false; // re-arm for next time
// reset Timer 1
TCCR1A = 0;
TCCR1B = 0;
TIFR1 = bit (ICF1) | bit (TOV1); // clear flags so we don't get a bogus interrupt
TCNT1 = 0; // Counter to zero
overflowCount = 0; // Therefore no overflows yet
void setup ()
{
Serial.begin(115200);
Serial.println("Frequency Counter");
// set up for interrupts
prepareForInterrupts ();
} // end of setup
void loop ()
{
// wait till we have a reading
if (!triggered)
return;
// so we can read it
delay (500);
prepareForInterrupts ();
} // end of loop
// Input: Pin D8
ISR (TIMER1_CAPT_vect)
{
// grab counter value before it changes any more
unsigned int timer1CounterValue;
timer1CounterValue = ICR1; // see datasheet, page 117 (accessing 16-bit registers)
unsigned long overflowCopy = overflowCount;
if (first)
{
startTime = (overflowCopy << 16) + timer1CounterValue;
TIFR1 |= bit (ICF1); // clear Timer/Counter1, Input Capture Flag
TCCR1B = bit (CS10); // No prescaling, Input Capture Edge Select (falling on D8)
first = false;
return;
}
void prepareForInterrupts ()
{
noInterrupts (); // protected code
first = true;
triggered = false; // re-arm for next time
// reset Timer 1
TCCR1A = 0;
TCCR1B = 0;
TIFR1 = bit (ICF1) | bit (TOV1); // clear flags so we don't get a bogus interrupt
TCNT1 = 0; // Counter to zero
overflowCount = 0; // Therefore no overflows yet
void setup ()
{
Serial.begin(115200);
Serial.println("Duty cycle width calculator");
// set up for interrupts
prepareForInterrupts ();
} // end of setup
void loop ()
{
// wait till we have a reading
if (!triggered)
return;
// so we can read it
delay (500);
prepareForInterrupts ();
} // end of loop
Testing shows that the displayed pulse width is within 100 nS of the actual width (two
clock cycles basically) which isn't too bad.
Example strobe for car brake light
This example shows how we can use Timer 1 to strobe a brake light for a second, and
then settle down into a constant "on" light, until the brake pedal is released.
const int ON_TIME = 1000; // This is the amount of time that the strobe will flash
before going solid
const int STROBE_FREQ = 100; // sets the delay between strobe pulses in milliseconds
const unsigned long countTo = (F_CPU / 1024) / (1000 / STROBE_FREQ);
void setup()
{
pinMode (LED, OUTPUT);
pinMode (PEDAL_PIN, INPUT_PULLUP);
void loop()
{
// switch from strobing to steady after ON_TIME
if (pressed && (millis () - whenPressed >= ON_TIME))
{
bitClear (TCCR1A, COM1B1);
digitalWrite (LED, HIGH); // turn light on fully
}
} // end of loop
Example of flashing an LED with minimal code
This flashes D10 at a frequency of 2 Hz (off for one second, on for one second)
Motivation
There are at least two reasons to measure the voltage supplied to our Arduino (Vcc). One
is if our project is battery powered, we may want to monitor that voltage to measure
battery levels. Also, when battery powered, Vcc is not going to be 5.0 volts, so if we wish
to make analog measurements we need to either use the internal voltage reference of
1.1 volts, or an external voltage reference. Why?
A common assumption when using analogRead() is that the analog reference voltage is
5.0 volts, when in reality it may be quite different. The official Arduino
documentation even leads us to this wrong assumption. The fact is the default analog
reference is not 5.0 volts, but whatever the current level of Vcc is being supplied to the
chip. If our power supply is not perfectly regulated or if we are running on battery power,
this voltage can vary quite a bit. Here is example code illustrating the problem:
double Vcc = 5.0; // not necessarily true
int value = analogRead(0);
double volt = (value / 1023.0) * Vcc; // only correct if Vcc = 5.0 volts
In order to measure analog voltage accurately, we need an accurate voltage reference.
Most AVR chips provide three possible sources an internal 1.1 volt source (some have a
2.56 internal voltage source), an external reference source or Vcc. An external voltage
reference is the most accurate, but requires extra hardware. The internal reference is
stable, but has about a +/- 10% error. Vcc is completely untrustworthy in most cases. The
choice of the internal reference is inexpensive and stable, but most of the time, we
would like to measure a broader range, so the Vcc reference is the most practical, but
potentially the least accurate. In some cases it can be completely unreliable!
How-To
Many AVR chips including the ATmega series and many ATtiny series provide a means to
measure the internal voltage reference. Why would anyone want to do so? The reason is
simple by measuring the internal reference, we can determine the value of Vcc. Heres
how:
long readVcc() {
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) ||
defined(__AVR_ATmega2560__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) ||
defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) ||
defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
uint8_t low = ADCL; // must read ADCL first - it then locks ADCH
uint8_t high = ADCH; // unlocks both
long readVcc() {
long result;
// Read 1.1V reference against AVcc
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
result = ADCL;
result |= ADCH<<8;
result = 1126400L / result; // Back-calculate AVcc in mV
return result;
}
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.println( readVcc(), DEC );
delay(1000);
}
Usage
Improving Accuracy
While the large tolerance of the internal 1.1 volt reference greatly limits the accuracy of
this measurement, for individual projects we can compensate for greater accuracy. To do
so, simply measure your Vcc with a voltmeter and with our readVcc() function. Then,
replace the constant 1125300L with a new constant:
Conclusion
You can do a lot with this little function. You can use a stable voltage reference close to
5.0 volts without having to rely on your Vcc actually being 5.0 volts. You can measure
your battery voltage or even see if you are running on battery or A/C power.
Lastly, the code provided will support all the Arduino variants, including the new
Leonardo, as well as the ATtinyX4 and ATtinyX5 series chips.