Interrupts

There are two fundamental ways of dealing with events that can be detected by a microprocessor board such as an Arduino. An event might be a change in a value from a temperature sensor or the proximity of a machine part to a limit or an incoming signal from some external device – perhaps even a human “minder”. One approach is to repeatedly check the values on all relevant input pins waiting for some change of state and this is known as polling. The alternative is to arrange things such that a state change interrupts any other ongoing process and triggers the execution of a function known as an Interrupt Service Routine (ISR).

The interrupt approach has the advantage that another continuous process can be executed safe in the knowledge that important events will be dealt with in a timely manner as and when they occur. Managing interrupts usually means following best practice to optimise their usage and to avoid some potential hazards.

The Arduino environment supports three varieties of interrupt. There are timer interrupts, external interrupts and pin change interrupts.

The Arduino Uno has two pins associated with external interrupts while the Mega has 6 such pins and the later Zero and Due Arduinos can utilise (almost) any digital pin. External interrupt pins can be programmed to trigger an interrupt on a rising or falling signal edge or on detecting a low voltage level.

Pin change interrupts can be programmed to occur on a state change detected on digital pins. The interrupt is triggered by both rising or falling signal edges with the ISR being left to determine which of those states applies. As mentioned in the last chapter, there are only 3 “ports” on the processor to service pin change interrupts and thus only 3 possible concurrent ISRs which might further complicate resolving a single event. Using multiple pin change interrupts probably requires some planning to match the hardware connections to the program structure.

External interrupts are faster and more efficient than pin change interrupts and are likely to be the preferred choice unless there is contention for those key pins.

Timer interrupts can be triggered by any of the available timers (three for an Arduino Uno while the Arduino Mega has six). As one or more of the available timers might be tied up managing other activities (such as PWM output) the programmer has to make a selection from those available. As the name suggests, timer interrupts are programmed to occur at specific time intervals. Timer interrupts make use of predefined ISR vectors but otherwise they can execute custom code statements in the same way as any other ISR.

Watchdog Time-Out?

OK, coming clean, there is another timer that can generate an interrupt.

The watchdog timer has its own 128Khz clock and has the ability to generate an interrupt or reset the Arduino. The timer is implemented as a simple counter and if you enable this timer then you need to ensure that it is reset before the counter overflows and automatically resets the board. The reset feature can be used to recover from a process that gets stuck in some way although this crude backup remedy is only that system restart.

The watchdog timer is not particularly accurate and so does not make for a precision timed interrupt but could be used as a guard for delayed responses (on say a dodgy serial interface) with a little ingenuity. There is some sample code coming up later.

When an interrupt is triggered

The processor completes the current machine instruction and saves the next instruction (program counter address) on the stack. This enables control to be returned to the interrupted code once the interrupt has been serviced.

The processor then looks in the interrupt vector table for the address of the associated ISR and starts running the code within the ISR. When the ISR completes, the previously saved program counter is read and removed from the stack and (unless another Interrupt intervenes) the original function continues to execute where it left off.

While an ISR is executing, no other interrupts will be serviced although they will be registered and retained for execution in priority order.

Interrupt priorities

There is a hierarchy among interrupts to ensure that the most urgent are processed ahead of those deemed to be generally less urgent. If one or more interrupt were to occur while the ISR for another was executing then the higher priority interrupt would be executed ahead of any other when the running ISR completed. The list below starts with the highest priority (a system reset) and then lists the interrupts in descending priority order.

Priority Interrupt Vector
1 System reset
2 External interrupt request 0 (Uno pin 2) INT0_vect
EXT_INT0_vect *
3 External interrupt request 1 (Uno pin 3) INT1_vect
4 Pin change Interrupt request 0 (Uno pins D8 to D13) PCINT0_vect
5 Pin Change Interrupt request 1 (Uno pins A0 to A5) PCINT1_vect
6 Pin Change Interrupt request 2 (Uno pins D0 to D7) PCINT2_vect
7 Watchdog time-out interrupt WDT_vect
8 Timer2 Compare Match A TIMER2_COMPA_vect
9 Timer2 Compare Match B TIMER2_COMPB_vect
10 Timer2 Counter Overflow TIMER2_OVF_vect
11 Timer1 capture event TIMER1_CAPT_vect
12 Timer1 Compare Match A TIMER1_COMPA_vect
13 Timer1 Compare Match B TIMER1_COMPB_vect
14 Timer1 Counter Overflow TIMER1_OVF_vect
15 Timer0 Compare Match A TIMER0_COMPA_vect
16 Timer0 Compare Match B TIMER0_COMPB_vect
17 Timer0 Counter Overflow TIMER0_OVF_vect
18 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 TWI_vect
26 Store Program Memory Ready SPM_READY_vect
Other vectors (see text)
catch all interrupt vector BADISR_vect
nested interrupts ISR_NOBLOCK
Interrupt vector code sharing ISR(ALIASOF(vect)
* Some MCUs

Rules for ISRs

ISRs have no return type and have no arguments passed to them.

The code within an ISR should execute quickly as by default (and purpose) any further interrupts are blocked from executing until the current ISR is complete. It is generally considered best practice for an ISR to set one or more informative flags that can be interpreted by any main function executing on the Arduino rather than managing any complex or long running processing from within the ISR.

An ISR can read and write to any variable in scope. However, if a given variable is also accessed by another function then it should have the volatile modifier. Such volatile variables are best implemented as simple counters or flags.

An ISR can’t execute any statements that themselves make use of interrupts (as by default they are not enabled). These include:

Serial.print();
tone();
delay();

The function delayMicroseconds() will work within an ISR but remember an ISRs should be fast and not blocking. It is considered acceptable to obtain the time while within an ISR but issues can arise if too long is spent processing an ISR as a timer overflow event that updates millis() may be missed making the time retrieved incorrect. Worth noting perhaps that a read of millis() is faster than a read of micros().

Allowing Interrupts from within an ISR

Having said that interrupts are disabled during ISR execution, there are some potential situations where it is desirable to allow further interrupts to occur. These are rare and we should stick to the idea that interrupts do not occur (but may queue) while an ISR executes. It is very undesirable to allow further interrupts within ISRs for external interrupts or indeed UART interrupts. However, interrupts can be enabled during the execution of an ISR by adding ISR_NOBLOCK as an argument to the vector or using the sei() instruction (or interrupts() function) at the beginning of the ISR code.

ISR(xxx_vect, ISR_NOBLOCK) {

}

Empty ISRs

Sometimes all an interrupt might be doing is waking an Arduino from a sleep mode and no explicit code needs to be executed. However, the relevant ISR needs to exist even if it contains no executable statements and this can be implemented using the following macro (inserting the relevant vector name).

EMPTY_INTERRUPT(xxx_vect);

If a suitable handler is not available for an interrupt then the BADISR_vect code will be triggered which will normally trigger a board re-set. If you think that an interrupt with no matching ISR might be triggered then you could add code to an ISR for the BADISR_vect to catch the error without a re-set. I am a bit stumped for why you might plan for that to be necessary – but you can do it.

Shared ISRs

It is possible that two interrupts could share the same code. It may be that (say) pin change interrupts could occur on two different ports but that the handler does not need to differentiate between them. An ISR can be declared to manage both vectors. First define the ISR for one of the vectors:

ISR(PCINT0_vect) {
… handler statements here …
}

 then declare that as an alias for the second vector:

ISR(PCINT1_vect, ISR_ALIASOF(PCINT0_vect));

Arduino attachInterrupt() and ISRs

The Arduino documentation includes reference to two functions attachInterrupt() and detachInterrupt() used to set up and turn off external interrupts.

The attachInterrupt() function takes as an argument the name of a programmer defined function intended to handle the relevant interrupt. You might wonder how that relates to the ISR and vector combinations detailed so far in this chapter. What happens is that the attachInterrupt() function sets up the interrupt and adds a call to the named function to the ISR for the interrupt vector. While this indirection probably adds a little to the interrupt response time this approach has the advantage that it is easily possible to change the function associated with a given interrupt during program execution.

There is also the obvious advantage in using the simpler mechanism for defining the detail of an interrupt afforded by the attachInterrupt() function together with the implicit portability of this approach. The example coming up shortly will attempt to use both methods for managing interrupts.

Timer interrupts can only be managed using the relevant ISRs and bit settings and they are covered in detail in the chapter on Timers and Pulse Width Modulation.

The volatile variable modifier

Any global variable can have the volatile modifier added to make it suited for access from ISRs as well as regular program functions. If a variable is only used by an ISR then it does not need the volatile modifier. Volatile is for variables shared by an ISR and another function (including setup() and loop()).

The volatile keyword directs the compiler to ensure that the variable value is loaded from SRAM and is not held in a storage register where, under some circumstances, any value may be temporarily out of date.

The best variable candidates for the volatile modifier are integers and Booleans where they can be updated using a single machine instruction (which is device dependent). For true code portability, try and confine such variables to bytes and Booleans. Updating other variable types can require multiple machine instructions and this means that an interrupt could occur at some point after an update has started and before it is complete. If it is necessary to share such variable types between an ISR and other functions the best defence is to disable interrupts in the function while updating the volatile variable at risk. Interrupts can then be immediately re-enabled. As interrupts are disabled while an ISR is executing the code there can access the variable without risk.

An alternate approach is to wrap the code addressing a volatile variable in an ATOMIC_BLOCK(){}. This approach is demonstrated and explained in the section relating to watchdog interrupts later in this chapter.

The volatile keyword normally precedes the variable type when being declared or defined.

volatile int status = SET; // where SET is some predefined constant perhaps

Enabling and Disabling Interrupts

Interrupts are enabled during normal program execution unless explicitly disabled. The two command are:

noInterrupts(); // disable all interrupts
interrupts(); // re-enable interrupts

You will often come across sample code that uses the following alternatives.

cli(); // disables interrupts
sei(); // enables interrupts

Both forms are equally valid although the Arduino style noInterrupts() is “self-documenting”. Once compiled, there would be no difference in the machine instructions generated and therefore the program memory space used.

It seems common practice to disable interrupts while setting up a new interrupt although this should not always be necessary. I suspect that it is a good habit and removes the need to consider each circumstance individually.

When interrupts are enabled from within a function then one more machine instruction from that function is guaranteed to be executed before any interrupt queued waiting to fire is executed. The best case underlying the importance of that I have seen would be the following code:

interrupts(); // enable interrupts
sleep_cpu(); // put the cpu to sleep

 and the issue is to do with timing.

If interrupts could occur before the sleep_cpu() command was executed then the interrupt that was intended to wake the board from the sleep state could just conceivably fire before the CPU was put in sleep mode – when it should have fired during the sleep. For obvious reasons, I hope, the statements could not be reversed.

Making use of the three interrupt types

The chapter on Timers and PWM deals with timer interrupts in some detail and shows how to set them up and consume the interrupt events. So, we will concentrate on the interrupts based upon the Arduino pins before looking at the watchdog timer.

External Interrupts

We can start with the simplest possible test rig for an external interrupt.

We simply require a push to make switch and two jumper wires connecting the legs of the switch with pin 2 and ground on the Arduino.

External interrupt breadboard layout

The idea is that pressing the switch button pulls pin 2 down and we can use that to trigger an external interrupt on the falling edge. The only fiddly bit is determining which legs are connected when the switch button is pressed and orienting the switch appropriately.

If you don’t have a suitable switch to hand then you could just use a jumper wire to connect pin 2 to ground briefly at intervals while the code for this demonstration is running.

Program Design

We can set up an external interrupt using the attachInterrupt() function or directly setting register bits. You are going to come across sample code using both methods so we will explore them both. First the register approach:

The external interrupt mask register has bits set for the pin.

EIMSK |= (1 << INT0); // or the constant INT1 for pin 3

Existing flags can be cleared on the Interrupt Flag Register

EIFR |= (1 << INTF0); // or INTF1

The External Interrupt Control Register A, EICRA is then set to decide which state should trigger the external interrupt. Two of the bits define the state to trigger INT0 and two others the state to trigger INT1.

Register EICRA is laid out as follows

Bit 7 6 5 4 3 2 1 0
ISC11 ISC10 ISC01 ISC00

(the default bit state is 0)

We can then use this table to define the required state change to be detected.

ISC01 ISC00 Interrupt 0 sense control
0 0 Low level on INT0 generates an interrupt
0 1 Any logic change generates an interrupt
1 0 A falling edge generates an interrupt
1 1 A rising edge generates an interrupt


ISC11 ISC10 Interrupt 1 sense control
0 0 Low level on INT1 generates an interrupt
0 1 Any logic change generates an interrupt
1 0 A falling edge generates an interrupt
1 1 A rising edge generates an interrupt

The logic change or edge changes must last for a clock cycle to be sure of triggering an interrupt. The low-level state detection requires that state to exist for the duration of the currently executing instruction on the processor to result in an interrupt request.

The code for our external interrupt trial using the interrupt registers should be something like:

void setup() {
   pinMode(2, INPUT_PULLUP);    // pin 2 for ext interrupt 0
   pinMode(LED_BUILTIN, OUTPUT);
   cli();                       // disable interrupts
   EIFR |= (1 << INTF0);        // clear any existing flags
   EIMSK |= (1 << INT0);        // Enable external interrupt INT0
   EICRA |= (1 << ISC01);       // Trigger request on falling edge
   sei();                       // re-enable interrupts
}
ISR(INT0_vect)
{
    digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) ^ 1); // Toggle
                                                             //LED
}

Following, is the functionally equivalent program written Arduino style which is much easier to understand. It is just that such simplicity is not available to use in every circumstance. We have to be prepared to work at a “low level” as well.

const byte intPin = 2;
void setup() {
  pinMode(intPin, INPUT_PULLUP); // pullup resistor enabled
  pinMode(LED_BUILTIN, OUTPUT);
  noInterrupts();
  attachInterrupt(digitalPinToInterrupt(intPin), intHandler, FALLING);
  interrupts();
}
void intHandler() {
  digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) ^ 1);//Toggle LED
}

The Arduino documentation recommends using the digitalPinToInterrupt() function for portability reasons. The attachInterrupt() function takes the interrupt number, a pointer to the interrupt handler function (just the name of the function) and a constant to set the state change to trigger the interrupt. The available constant values are: LOW, CHANGE, RISING and FALLING although the Arduino Due and Zero also support HIGH.

[N.B. The digitalPinToInterrupt() function is probably superfluous if coding solely for the Arduino Due or Zero as the pin number is the interrupt number on those devices.]

Try entering and running one version or both. Once the program has loaded then a press of the switch button should turn the built in LED on or off. A little playing around with short and long presses should also reveal symptoms of button “bounce” which would have to be dealt with in most projects using switches.

An interrupt can be cancelled by resetting the relevant register bits or by calling detachInterrupt().

The detachInterrupt() function should be passed the interrupt number and again using the digitalPinToInterrupt() function is recommended.

Once the detachInterrupt() function has disabled an interrupt then it can be re-enabled at any time – perhaps with a different function acting as an Interrupt Service Routine (ISR).

External interrupts are a feature of software projects in later chapters so there will be plenty of opportunity to become more familiar with their usage.

Pin Change Interrupts

We can also set up interrupts for state changes on pins. The 32 bit Arduinos allow external interrupts on all digital pins but the ATmega family has to fall back on pin change interrupts which group pins into three sets that share most setup values and have a common vector.

Port Pin Pin Change Name Mask Register Enable / Clear Vector
0 D8 PCINT0 PCMSK0 PCIE0 and PCIF0 PCINT0_vect
D9 PCINT1
D10 PCINT2
D11 PCINT3
D12 PCINT4
D13 PCINT5
1 A0 PCINT8 PCMSK1 PCIE1 and PCIF1 PCINT1_vect
A1 PCINT9
A2 PCINT10
A3 PCINT11
A4 PCINT12
A5 PCINT13
2 D0 PCINT16 PCMSK2 PCIE2 and PCIF2 PCINT2_vect
D1 PCINT17
D2 PCINT18
D3 PCINT19
D4 PCINT20
D5 PCINT21
D6 PCINT22
D7 PCINT23

The following code would set up a pin change interrupt on pin 8 which is part of the port 0 group. To set up any other pin just substitute the relevant values from the table above add use the port vector for the ISR.

PCMSK0 |= (1 << PCINT0); // set interrupt for pin 8
PCIFR |= (1 << PCIF0); // clear any outstanding interrupt flags
PCICR |= (1 << PCIE0); // enable pin change interrupts for this port

with an ISR

ISR(PCINT0_vect)
{
… ISR code statements …
}

To explore the difference between pin change interrupts and external interrupts, shift the jumper that connected pin 2 to the switch on the breadboard in our first test to the nearby pin 4. Then load the following program.

const byte intPin = 4;
void setup() {
  pinMode(intPin, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
  noInterrupts();
  PCMSK2 |= (1 << PCINT20);  // set interrupt for pin D4
  PCIFR  |= (1 << PCIF2);    // clear any outstanding interrupt flags
  PCICR  |= (1 << PCIE2);    // enable pin change interrupts for port
  interrupts();
}
ISR(PCINT2_vect) {
  digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) ^ 1);  // Toggle
}

With a little experiment you should now see that the interrupt is triggered when the switch is pressed (pulling the pin LOW) and when the switch is released (allowing the pin to return to the HIGH state). This time, it is the change that is triggering the interrupt and not a single specified state.

Try modifying the program a little to read as below. The revised code uses the volatile keyword to allow shared access to a flag variable with the loop() function sending state change data to the Serial Monitor.

const byte intPin = 4;
volatile byte flag = 0;
byte lastFlag = flag;
void setup() {
  Serial.begin(115200);
  pinMode(intPin, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
  noInterrupts();
  PCMSK2 |= (1 << PCINT20);  // set interrupt for pin D4
  PCIFR  |= (1 << PCIF2);   // clear any outstanding interrupt flags
  PCICR  |= (1 << PCIE2);   // enable pin change interrupts for port
  interrupts();
}
ISR(PCINT2_vect) {
  digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) ^ 1); // Toggle
  flag = (digitalRead(intPin) == LOW) ? 1 : 2;
}
void loop() {
  if(lastFlag != flag) {
    Serial.println((flag == 1) ? "LOW" : "HIGH");
    lastFlag = flag;
  }
}

It is clear that if you want an interrupt to only fire on one of the pin states then it will be necessary to keep a record of the prior state. In fact, that could get more interesting. If you need to implement pin change interrupts on more than one pin associated with a given port (and there are only 3 options on a Uno) then you will also need to write code to store the last known values for each pin sending pin change interrupts to the ISR for that port. You would then need to poll each pin to check to see which one triggered the interrupt based upon a comparison between the current state and the last recorded state.

Fear not though. While the code required to manage pin state change interrupts is not terribly challenging libraries exist to manage it all for you. The PinChangeInt library available on the Arduino web site looks like a good example and that supports Rising Edge, Falling Edge and vanilla Change interrupts while abstracting away all of the implementation details.

Software Interrupts

You can just treat this small section as a curiosity but one day perhaps you might need to trigger the equivalent of an interrupt process from a process state recognised by software. There is a mechanism where you can trigger a pre-configured interrupt from software and thus emulate a software interrupt which is not a thing on the Arduino platform.

On computer systems that run an operating system, software interrupts can be used to call facilities in the supervisor code from a more restricted user level. I see the potential for use within (say) a finite state machine if interrupts are generally being used to trigger state changes.

We are going to take advantage of a point made in the AVR data sheet that reads “Observe that, if enabled, the interrupts will trigger even if the INT0 and INT1 or PCINT23...0 pins are configured as outputs. This feature provides a way of generating a software interrupt”. Do search for and read data sheets as they are full of fascinating detail and sometimes useful insights.

To try emulating a software interrupt, leave the Arduino connected as before and copy and/or modify the last program so it is the same as the one on the next page.

const byte intPin = 4;
void setup() {
  Serial.begin(115200);
  Serial.println("Send '1' to interrupt");
  pinMode(intPin, OUTPUT);     // note pin set for output
  digitalWrite(intPin, HIGH);  // and to an initial state
  pinMode(LED_BUILTIN, OUTPUT);
  noInterrupts();
  PCMSK2 |= (1 << PCINT20); // set interrupt for pin D4
  PCIFR  |= (1 << PCIF2);   // clear any outstanding interrupt flags
  PCICR  |= (1 << PCIE2);   // enable pin change interrupts for port
  interrupts();
}
ISR(PCINT2_vect) {
  digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) ^ 1); // Toggle
}
void loop() {
  if (Serial.available() > 0) {
    char nChar = Serial.read();
    if(nChar == '1') {
      Serial.println("Going LOW");
      digitalWrite(intPin, LOW);    // trigger the PC interrupt
      unsigned long tmr = millis() + 1000;
      while (millis() < tmr) {
            // wait a second
      }
      Serial.println("Going HIGH");
      digitalWrite(intPin, HIGH);  // trigger the interrupt again
    }
  }
}

You need eyes in two places to watch this program execute. First the pin we are setting up for a pin change Interrupt is initialised for OUTPUT and with a HIGH state. The code inside the loop() function monitors the Serial Interface and awaits a single char from you with a value == ‘1’. If that arrives then the target pin is set LOW which triggers the interrupt where the ISR sets the built in LED on. The code then idles for a second and then resets the pin HIGH. This triggers the pin change interrupt for a second time and the ISR turns the built in LED off.

It would be easy to adapt this demonstration technique to be executed based upon almost any conditional situation that can be detected during normal program execution.

It would have been slightly simpler to demonstrate this feature using an external interrupt but for the moment I have assumed that they are likely to be fully employed managing some other program features. So we might need to make a small modification if we only want the interrupt to fire once.

We can use the following code to unset the interrupt on pin 4 as it xors the relevant bit in the mask register for the pin 4 port. However, you would need to set a Boolean value to make sure that the statement was only executed once as repeating it would just toggle the interrupt on and off.

Actually, that could be handy. So, pop the unset statement into a function thus:

void resetPCInt() {
  PCMSK2 ^= (1 << PCINT20); // reset interrupt for pin D4
}

Define a Boolean like

bool wasUnset = false;

and then add the following to the loop() function after the interrupt has been triggered for the first time:

if(! wasUnset) {
  resetPCInt();
  wasUnset = true;
}

After uploading and running these additions, you should see the interrupt being triggered to turn on the built in LED but that subsequent pin state changes do not fire the interrupt.

You could be creative and write code to reset the interrupt by sending an alternate character from the Serial Monitor where all that would be required was another call to resetPCInt() to re-establish the pin change interrupt.

We have now established a software trigger for an interrupt. Job done.

Watchdog interrupt

When you read the following chapter on timers you will come across values called prescalers. Prescaler values are set to influence the rate at which a timer counter is incremented in response to the Arduino board clock “ticks”. If a prescaler value of 64 is set, for instance, then the associated timer counter will only be incremented by 1 every 64 clock cycles (ticks). Depending upon the prescaler value used, the watchdog timer can also be programmed to fire in as short a period as 16ms or as long a period as 8 seconds. In fact, as mentioned earlier, the clock used by this timer is not terribly accurate and tends to run slow so these values are approximate.

One potential use for the watchdog timer might be to wake a board that was in a low power sleep mode (see next section) every few seconds to read some sensors and then return to the low power mode. I could see this working for something like a battery powered module monitoring moisture levels and perhaps responding by turning an irrigation water supply on or off. This sort of application does not need split second response times and maximising battery life could be a crucial design requirement.

The demo program is designed to allow a sequence of watchdog interrupts with an int counter used to track them. This was a good excuse to use the ATOMIC_BLOCK{} macro to ensure that the integer is correctly read in the loop() function. Programs to demonstrate the usage and importance of this macro follow shortly after.

#include <avr/wdt.h> #include <avr/sleep.h> #include <util/atomic.h> volatile int wdiCount = 0; void setup() { Serial.begin(115200); Serial.println("Program Started"); noInterrupts(); wdt_reset(); // watchdog timer reset //now code to set up WDT interrupt WDTCSR = (1<<WDCE)|(1<<WDE); //set timer counter with 8s ish prescaller WDTCSR = (1<<WDIE)|(1<<WDP0)|(1<<WDP3); interrupts(); } // ISR for watchdog interrupt ISR(WDT_vect) { wdiCount++; // increment a counter for fun } void loop() { int wdCount; Serial.flush(); // just to be sure gotoSleep(); ATOMIC_BLOCK(ATOMIC_RESTORESTATE){     wdCount = wdiCount; } Serial.print("Wake up number: "); Serial.println(wdCount); }

Followed by the final gotoSleep() function.

// section on sleep modes coming up later in this chapter
void gotoSleep()
{
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // full sleep mode
  sleep_enable(); // set SE bit
  sleep_mode();   // board starts sleep here

  // ** code resumes here on wake **
  sleep_disable();
}

If you run this program the board will power down and wait. The watchdog timer will fire after approximately 8 seconds and wake the board. The board reports the number of times it has woken to the Serial Monitor and then powers down again.

You could, of course, use the counter to further delay key activity – perhaps making some periodic check only every few minutes.

If you want to try a 4 second prescaler then try this setting:

WDTCSR = (1 << WDIE) | (1 << WDE) | (1 << WDP3);

 but feel free to play with the timer prescaler bit settings with this program. As the board powers down between interrupts then you have to track the elapsed times yourself as you can’t use the Arduino time values.

There is another way of using the watchdog timer that is probably most effective when debugging.

Give this short program a try:

#include <avr/wdt.h> void setup() { Serial.begin(115200); Serial.println("Program Started/Restarted"); pinMode(LED_BUILTIN, OUTPUT); wdt_enable(WDTO_2S); // fire watchdog interrupt every 2 seconds } void loop() { for(unsigned int i = 0; ; i++){     wdt_reset(); // restart the interrupt timer     digitalWrite(LED_BUILTIN, HIGH);     delay(i * 250);     digitalWrite(LED_BUILTIN, LOW);     delay(500); } } #include <avr/wdt.h> void setup() { Serial.begin(115200); Serial.println("Program Started/Restarted"); pinMode(LED_BUILTIN, OUTPUT); wdt_enable(WDTO_2S); // fire watchdog interrupt every 2 seconds } void loop() { for(unsigned int i = 0; ; i++){     wdt_reset(); // restart the interrupt timer     digitalWrite(LED_BUILTIN, HIGH);     delay(i * 250);     digitalWrite(LED_BUILTIN, LOW);     delay(500); } }

If you run it you will see the program start/restart message on the Serial Monitor and then the built in LED will flash on for an increasing length of time (around 8 flashes) before the program restarts and the LED sequence recommences.

If the wdt_enable() function is used to start the watchdog interrupt timer then the wdt_reset() function must be used to restart the timer before the pre-set time limit is reached. The for loop in the loop() function increases the first delay() time in steps until eventually the watchdog timer is fired before the loop iterates and calls wdt_reset(); This is an artificial model for some process running on the Arduino that fails to complete promptly for some reason.

The wdt_enable() function can start the watchdog timer using a number of pre-set values. These are:

WDTO_15MS, WDTO_30MS, WDTO_60MS, WDTO_120MS, WDTO_250MS, WDTO_500MS then WDTO_1S, WDTO_2S, WDTO_4S and WDTO_8S

The wdt_disable() function can be called to stop the watchdog timer firing if required.

ATOMIC_BLOCK Demo

The following program demonstrates the advantage of using the ATOMIC_BLOCK macro. As this book tackles timers properly in the next chapter I decided to hide the timing component of this program by using a timer library. I chose the MsTimer2 library from those offered by the Arduino IDE “manage libraries” option under the “Sketch” menu. There are other good timer libraries and it should not be difficult to change the two code lines involved to use an alternative.

#include "MsTimer2.h" #include <util/atomic.h> volatile unsigned int volVal = 0xFF00; bool swtch = true; void setup() { Serial.begin(115200); MsTimer2::set(100, timeInterrupt); // 100ms period MsTimer2::start(); } void timeInterrupt() { //ISR toggles the value of test lowbyte or highbyte to 0xFF00 volVal = (swtch)? 0x00FF : 0xFF00; swtch = !swtch; } void loop() { unsigned int loopVal; //ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {      loopVal = volVal; } //Test value of loopVal should only ever be 0x00FF or 0xFF00 if(loopVal != 0xFF00 && loopVal != 0x00FF) {     //local value incorrect due to non-atomic access of volVal     Serial.println(loopVal, HEX); } }

The program is first run with the “ATOMIC_BLOCK(ATOMIC_RESTORESTATE)” line commented out as shown. The Serial Monitor will quickly start displaying invalid values for loopVal demonstrating that the code is regularly reading the volatile variable part way through a value change. Once you are satisfied that the issue is demonstrated then uncomment the “ATOMIC_BLOCK” line and re-upload the program. Now watch – well nothing really. No erroneous values should be reported.

On that slightly negative evidence we can consider another code feature added to our tool box.

Power Saving and Sleep Modes

In my mind, the opposite of the triggered response to a state change incorporated into an interrupt is the sleep state. In fact, they are closely bound because the way a processor is woken from a sleep state is by using an interrupt.

Around my office there are a number of Bluetooth Low Energy (BLE) enabled devices that can happily run for three years on a coin cell battery. The regular Arduino board designs precludes those sort of power savings even though the ATMega386 chip can consume as little as 6µa (6 microamperes) when in the deepest sleep. However, there are 5 sleep modes we can utilise to reduce power consumption during idle periods.

SLEEP_MODE_IDLE
SLEEP_MODE_ADC
SLEEP_MODE_PWR_SAVE
SLEEP_MODE_STANDBY
SLEEP_MODE_PWR_DOWN

Power reduction methods to support these sleep modes are contained in the <avr /power.h> and <avr /sleep.h> libraries.

SLEEP_MODE_IDLE stops the CPU but allows SPI, USART, Analog Comparator, ADC, Two-wire Serial Interface, Timer/Counters, Watchdog, and the interrupt system to continue operating.

SLEEP_MODE_ADC stops the CPU but allows the ADC, the external interrupts, the Two-wire Serial Interface address watch, Timer/Counter2 and the Watchdog to continue operating.

SLEEP_MODE_PWR_SAVE is almost the same as SLEEP_MODE_POWER_DOWN but can allow Timer2 to continue operating.

SLEEP_MODE_STANDBY is also very similar to SLEEP_MODE_POWER_DOWN except that the clock continues to run which allows for a faster restart on wake (6 clock cycles).

SLEEP_MODE_POWER_DOWN stops the CPU and all of the system clocks. Waking from this mode takes a little time as the system must wait for the system clock to stabilise. SLEEP_MODE_POWER_DOWN saves the most power by shutting down most of the activity of the board. Waking the board from this mode requires an external interrupt or a watchdog timer interrupt.

The SLEEP_MODE_IDLE option has the least impact upon power consumption but allows the board to be woken by an incoming signal on the USART serial interface. The effectiveness of this mode can be augmented by powering down additional Arduino functionality.

A look through the power.h header file and AVR documentation will reveal a range of board components that can be individually shut down. However, the following might be selectively used to good effect and the names give a good indication of their function.

power_adc_disable();
power_spi_disable();
power_timer0_disable();
power_timer1_disable();
power_timer2_disable();
power_twi_disable();

They each have a power_xxx_enable(); counterpart to restart the relevant functionality on waking but the statement

power_all_enable();

 will restart them all.

We can try some of this out with a simple program that puts the board into a power saving state from which it can be revived by sending a character from the Serial Monitor.

#include <avr/power.h> #include <avr/sleep.h> void setup() { Serial.begin(115200); Serial.println("Send a char to wake the board"); delay(1000); // Serial activity is asynchronous so wait                 // allows activity to cease gotoSleep(); } void gotoSleep() { set_sleep_mode(SLEEP_MODE_IDLE); // gentle sleep mode sleep_enable(); // set SE bit // set the power saving extras power_adc_disable(); power_spi_disable(); power_timer0_disable(); power_timer1_disable(); power_timer2_disable(); power_twi_disable(); sleep_mode(); // put the board to sleep // code resumes here on wake sleep_disable(); power_all_enable(); Serial.println("Now Awake!"); }

After running this, you might think that the board power on LED is pretty annoying, Then again it does not consume very much juice.

For an ultra-low power option for a project you might need to build a custom board and maybe run it at a lower clock speed to save power even when processing.