Interrupts and Timers

The previous post ended with a dilemma: how can we toggle the LED every second and read input from the serial port? One of the ways we can do this is with a timer interrupt. To introduce the topic, we’ll first take a quick tour before diving into the subject in more detail by looking at some actual hardware.

The fast tour

The code for the fast tour is in the 02_interrupts_and_timers directory in the example code repo.

Interrupts

An interrupt is some signal that interrupts the microcontroller current execution, indicating that some event has occurred that requires the microcontroller’s immediate attention. The microcontroller will jump from the code it is currently executing to an interrupt service routine (ISR). Once the ISR has completed, execution resumes at the previous point.

There are different kinds of interrupts; the pin descriptions given below are for the Arduino Uno:

Two fairly common errors during development are enabling an interrupts without registering the corresponding ISR, and forgetting to enable interrupts. Forgetting to register an ISR will cause the program to jump to the ISR when the interrupt is trigger, but no code is defined. This usually leads to the microcontroller resetting as it attempts to execute invalid memory. Forgetting to enable interrupts means the ISR will never be triggered. If you’re working on a project, and the microcontroller seems to be continually resetting, check to make sure all your ISRs are defined. If your interrupt handler doesn’t seem to be getting called, make sure you’ve enabled interrupts.

The Makefile for this article will be virtually identical to the one in the previous article; a good check on learning is to see if you can set up the Makefile for this project.

Timers: a quick overview

The timers can be used to trigger an interrupt on a given time interval. At 16 MHz, the processor will perform 16 cycles every microsecond; every second the processor performs 16 million cycles. If we wanted to use a timer to trigger an interrupt every 16 million cycles, we’d need a 32-bit number to do this. However, the Arduino Uno only has an eight-bit and two sixteen-bit timers.

This is where prescaling comes in: we can cause this to scale the timer’s operation. On page 110 of the ATmega328P datasheet, there is a list of prescaler values for the 8-bit timer, and on page 137 a list of prescaler values for the 16-bit timer. Both have a maximum prescaler value of 1024: this means every 63.98 microseconds, the timer will tick. After 15,630 ticks, we’ll have reached one second. This is within the range of our 16-bit timers resolution, so we’ll use Timer1 for this.

The timer can operate in several modes (a description of these modes for timer 1 starts on page 125). The simplest mode is normal mode; the timer is always incrementing, and the interrupt will be triggered every tick. There’s another mode called clear timer on compare match (CTC) mode: tell the timer how many ticks to trigger an event on, and an interrupt will be triggered then and the timer reset. This is what we’ll use. The OCR1A register will store the trigger value, and we’ll register an ISR that toggles the LED. OCR is an output compare register; Timer1 has two (A and B) but only A can be used for the functionality we want.

What actually happens in CTC is that the value in OCR1A is set as the timer’s top value. This is the value at which the timer will consider itself to overflow, causing an interrupt to be triggered. In normal mode, the top value is 0xFFFF, which is the maximum possible value for a 16-bit unsigned integer.

Initialising the timer

Setting up Timer1 takes a few steps:

  1. We need to tell the timer to use CTC mode, and to use OCR1A as the top value.
  2. We’ll want to set the appropriate prescaler value.
  3. We need to tell the timer to trigger an interrupt on OCR1A.
  4. We’ll set our blink interval to 15,630.
  5. Finally, we need to enable interrupts.

The setup function ends up looking like

static void
init_timer1(void)
{
	/*
	 * Set the waveform generation mode to CTC with OCR1A as the
	 * top value.
	 */
	TCCR1B |= _BV(WGM12);

	/* Use a prescaler of 1024. */
	TCCR1B |= _BV(CS12) | _BV(CS10);

	/* Trigger an interrupt on output compare A. */
	TIMSK1 |= _BV(OCIE1A);

	/* Set the output compare register to the update interval. */
	OCR1A = BLINK_INTERVAL;

	/* Enable interrupts. */
	sei();
}

The sei function is defined in avr/interrupt.h and actually enables interrupts globally; sei stands for set enable interrupts. There’s an analogous cli function (clear interrupts) that disables interrupts globally.

The ISR

Registering an ISR is done using the ISR macro. Our ISR is registered with

ISR(TIMER1_COMPA_vect)
{
	LED_PORT ^= _BV(LED_PIN);
}

The argument to this macro is the interrupt to register. The list of these names is given in the avr-libc manual; the interrupt table itself is given on page 65 of the datasheet for the ATmega328P.

If you’ve ever written a signal handler in C for a Linux or BSD system, the same sorts of thinking applies to an ISR. It should be a short as possible, it shouldn’t block (which includes calling our UART routines, as those block until a bit is set), and any variables that are shared between other code and the ISR must be marked as volatile.

The revised main

The main function is now much shorter, but there’s one point that bears mentioning:

int
main(void)
{
	init_timer1();
	LED_DDR |= _BV(LED_PIN);

	while (1) {}

	return 0;
}

Note the while loop at the end: an embedded program should never exit except in case of error. All execution would stop then, including our ISR. Instead, we just tell it to loop forever and the timer and interrupt will take care of the rest.

Real-world Example

Our blinking light has a frequency of 1/2 Hz: every two seconds it goes through a complete cycle. If we sped it up, we could strobe the LED. The LED in an IR proximity detector usually works like this: the IR receiver is set to trigger on a 38 kHz signal, and therefore the LED must be pulsed at this interval. If any of the light reflects back, the receiver will pick it up. Here’s an oscilloscope trace of what the signals look like:

A closer look shows that the receiver goes from high to low when an object is detected, and that when it detects an object, it stays low for at least the duration of the strobe.

The IR strobe barely registers on the display due to its rather short duration; here it is in higher detail:

For this setup, we’ll have an IR LED connected to PB5 (digital pin 13) and the receiver connected to PCINT18/PD2 (digital pin 2). When we want to perform a proximity detection, we’ll start the strobe timer and enable the PCINT18 interrupt. Once the strobe completes, we’ll disable the interrupt. One of the useful things that the oscilloscope shows us is that the detector will respond before the strobe has completed. We’ll use this to simplify things later. p

The code for this study is in the 03_strobe directory in the example code repo.

Setting up the strobe

Initialising the strobe is similar to the previous example, except that we’ll also enable the PCINT18 interrupt.

static void
setup_strobe(void)
{
	/* The timer should be in CTC mode. */
	TCCR1A |= _BV(WGM01);

	/* Use a prescaler of 8. */
	TCCR1B |= _BV(CS01);

	TCNT1 = 0;	/* Reset the counter. */
	TIFR1 |= _BV(OCF1A);
	TIMSK1 |= _BV(OCIE1A);

	/* Enable only PCINT18 in the PCINT2 register. */
	PCMSK2 = _BV(PCINT18);

	/* Set up the pins. */
	STROBE_DDR |= _BV(STROBE_PIN);
	IND_DDR |= _BV(IND_PIN);

	/*
	 * The default for a port is to be in input mode. However,
	 * the pullup resistor needs to be enabled.
	 */
	RCV_PORT |= _BV(RCV_PIN);
}

The biggest difference in the actual timer setup than in the blinking LED example is the different prescaler. We want to be able to measure ticks with a resolution of 26 or so microseconds because at 38 kHz, the LED is strobing that often. With a prescaler of 8, we get a tick every half microsecond. Note that the 38 kHz cycle is a complete off→on→off cycle; we want to toggle the pin every 13 microseconds. This is the reason that we’ll set OCR1A to 26: multiplying out 13 microseconds by two ticks per microsecond gives us the value of 26.

Now, we want to trigger PCINT18 when the pin is reset, so what’s all this talk about PCINT2? This part can be a little confusing, but the deal is each PCINT pin doesn’t get its own interrupt. Instead, they’re grouped into 8 pins for a specific port change interrupt. Pins 0-7 are on PCINT0, pins 8-15 are on PCINT1, and pins 16-23 are on PCINT2. Even though we’re interested only in PCINT18, the pin, we mostly have to deal with PCINT2, the register. PCINT2 is a register, so a pin change on any of the pins 16-23 will trigger the interrupt. The PCINT mask is used to only pay attention to specific pins. In this case, we set PCMSK2 (the PCINT2 mask) to the value of a single pin. Now, it will only trigger the interrupt for a change on that pin. If we had multiple pins enabled, the ISR would have to check which pin was set and act accordingly. A final point about PCINT2 is that because we know that the pin will only ever change to low, if it changes, is that the ISR doesn’t have to determine what state the pin is in. Depending on what the interrupt needs to do in other projects, that’s something that we’ll have to pay attention to.

In the previous post, we only covered digital output. Now we need to figure out how to do digital input. It turns out that for an input pin, the data direction register doesn’t need to be touched: a cleared bit marks an input pin, and the register starts out with all the bits cleared. However, the AVR uses pullup resistors for its inputs. The pullup resister causes the pin to have a default value of high; in the oscilloscope trace, you can see that the receiver’s pin is high until it detects an object. To turn on the pullup resistors for the pin, you’d set the pin in the port.

Remember at this point that we haven’t enabled interrupts; the timer won’t start until we do so.

Triggering a strobe

To kick off a strobe, we want to do a few things:

/*
 * A 38 kHz cycle requires the strobe is toggled every 13ms; multiplied
 * by two cycles per microsecond yields 26.
 */ 
#define STROBE_CYCLE	26

/*
 * We'll perform 76 toggles: this turns out to be right around 1.1ms. This
 * is 1000 microseconds (1 millisecond) divided by 13 microseconds per
 * toggle. This gives us 77 ticks, but we'll subtract one to account for
 * minor delays in processing. An even number also ensures we end with the
 * strobe off; this could also be accomplished by explicitly turning the
 * strobe off at the end.
 */
#define MAX_TICKS	76


/*
 * This is a convience macro for toggling a pin.
 */
#define toggle_bit(port, pin)	port ^= _BV(pin)


/*
 * alarm is set to true if the IR strobe detected an object. It is
 * reset each time the strobe is fired.
 */
static volatile bool	alarm = false;

static void
strobe(void)
{
	/* Enable PCINT18. */
	PCIFR |= _BV(PCIF2);	/* Drop any pending interrupts. */
	PCICR |= _BV(PCIE2);	/* Enable PC interrupt bank 2. */

	/* Reset the alarm. */
	alarm = false;

	/* Reset Timer1. */
	TCNT1 = 0;		/* Reset the counter. */
	TIMSK1 |= _BV(OCIE1A);	/* Drop any pending interrupts. */
	OCR1A = STROBE_CYCLE;

	/*
	 * Activate Timer0. PRR is the power-reduction register; any
	 * cleared timer bits will enable that timer, while any set
	 * bits will disable it.
	 */
	PRR &= ~_BV(PRTIM1);
}

The first thing we do is define a volatile value that we’re going to set in one of the ISRs. We’ll use a boolean: if it’s true, an object was detected.

The next thing we need to do is set up PCINT2. We drop any pending interrupts on it to prevent false alarms when we first start up. This isn’t always necessary, but sometimes the interrupts pile up and they’ll trigger immediately. That isn’t what we want, and clearing any pending interrupts will likely save you some headaches in the future.

The last bits to do are to reset the timer and make sure the timer isn’t in powersave mode.

The strobe ISR

The strobe ISR will be triggered every STROBE_CYCLE ticks; once we’ve reached the maximum number of ticks, we’ll want to disable the timer and port change interrupt.

/*
 * The strobe's ISR handles setting up the 38kHz strobe and triggering
 * it for roughly 10ms.
 */
static void
strobe_ISR(void)
{
	static uint8_t	ticks = 0;

	/*
	 * Once we've reached the maximum number of ticks, stop
	 * the timer and disable the port change interrupt.
	 */
	if (ticks == MAX_TICKS) {
		PRR |= _BV(PRTIM1);	/* Put Timer0 in powersave mode. */
		PCICR &= ~_BV(PCIE2);	/* Disable PCINT2. */
		PCIFR |= _BV(PCIF2);	/* Drop pending PCINT2 interrupts. */

		ticks = 0;		/* Reset tick counter. */
	}
	/*
	 * Otherwise, toggle the strobe and increase the tick count.
	 */
	else {
		toggle_bit(STROBE_PORT, STROBE_PIN);

		/* Reset the timer count and trigger on next cycle. */
		TCNT1 = 0;
		OCR1A = STROBE_CYCLE;
		ticks++;
	}
}


ISR(TIMER1_COMPA_vect)
{
	strobe_ISR();
}

Each time the strobe is toggled, we’ll reset the counter and make sure OCR1A has the right value.

The PCINT2 ISR

This is remarkably simple:

/*
 * If the receiver pin changes, it's a transition from high to low, so
 * the alarm needs to be set.
 */
ISR(PCINT2_vect)
{
	alarm = true;
}

All we need to do is set the alarm to true if this is triggered. As seen in the oscilliscope, the only time the pin will change while PCINT2 is active is when something is detected.

The main program

int
main(void)
{
	init_UART();
	setup_strobe();
	write_string("Boot OK.");
	newline();

	sei();

	while (1) {
		strobe();

		/*
		 * Give the strobe time to register its results.
		 */
		_delay_ms(20);

		/*
		 * If the alarm has been triggered, turn on the indicator
		 * LED.
		 */
		if (alarm) {
			write_string("object detected");
			newline();
			IND_PORT |= _BV(IND_PIN);
		}
		/*
		 * Otherwise, make sure the LED is off.
		 */
		else {
			IND_PORT &= ~_BV(IND_PIN);
		}

		/*
		 * Finish delaying for 100ms.
		 */
		_delay_ms(80);
	}

	return 0;
}

We’ll start by setting up our UART and strobe, and printing out our boot message on the serial port.

The program loop fires the strobe, then delays 20ms to give the strobe time to work. After that, we’ll check the value of the alarm: if an object was detected, we’ll turn on the indicator LED and print a message to the serial port. Otherwise, we’ll make sure the indicator is off. Finally, we delay the remainder of a 100ms cycle.

Interrupts and timers allow the microcontroller to asynchronously perform operations; this clears up our main program and allows us to react to events as they occur.