Timers and Pulse Width Modulation

Having explored interrupts stemming from external sources via signals applied to Arduino pins, it is now time to take a proper look at internally generated interrupts based upon timers. Timers can be used to trigger an action at precisely timed intervals – both repeatedly and as a one off for a single event. The frequency of timed events can be very short and it is short timed interrupts that drive Pulse Width Modulation (PWM) and the Arduino tone() function – plus anything else you, the programmer, desires.

We will start with the simplest use of PWM, which is to modulate a voltage on an Arduino pin which can be nicely demonstrated with a minimum of parts.

Parts

1 RGB LED with a common cathode
3 330 ohm resistors (any value from 270 to perhaps 1k will do)
1 solderless breadboard
4 jumper wires
and your trusty Arduino

Laying out the breadboard

PWM colours breadboard layout

The common cathode had a longer lead on my tri-colour LED which made it easy to orientate. The cathode lead was connected to the Arduino GND (ground) pin. Three jumpers from the Arduino pins 9, 10 and 11 were connected to the other LED leads via one of the resistors used to limit the current flowing from the Arduino. I was lucky to have jumper leads with the appropriate colours (red, green and blue) which helped in connecting things up correctly.

Now the software. The chapter that introduced interrupts showed that setting and controlling interrupts by manipulating registers was straightforward enough to be the method you might choose, even for external interrupts. Timer interrupts have to be set directly by manipulating values stored in registers. For the moment though we can use timers indirectly by using PWM via the analogWrite() function. First thing is to check that the tri-colour LED is hooked up correctly.

const int RED_PIN = 11;
const int GREEN_PIN = 5;
const int BLUE_PIN = 6;

void setup() {
  pinMode(RED_PIN, OUTPUT);
  pinMode(GREEN_PIN, OUTPUT);
  pinMode(BLUE_PIN, OUTPUT);  
}

void loop() {
  setColour(255, 0, 0);  // red
//setColour(0, 255, 0);  // green
//setColour(0, 0, 255);  // blue
//setColour(255,255,255); // white (well blueish white)
  delay(1000);
  setColour(0, 0, 0);  //off
  delay(1000);
}
void setColour(byte red, byte green, byte blue)
{
  analogWrite(RED_PIN, red);
  analogWrite(GREEN_PIN, green);
  analogWrite(BLUE_PIN, blue);  
}

The statements starting setColour() within the loop() function were commented and uncommented in turn to check that the tri-colour LED displayed the correct colour.

The LED I used, had the red and blue leads nearest the cathode and so I had to switch the blue and green jumper leads as shown in the previous diagram (yours may differ). I could of course have changed the pins and constants.

Any colour can theoretically be achieved by combining different intensities of the three colours red, green and blue. A reasonable match to the Arduino Uno blue would be:

setColour(15, 115, 145); // Arduino blue

You can find colour mix values in a number of places. I often use the colour picker in Paint.NET on Windows (a great piece of free and open source software if you don’t know it). You might also search for HTML colour codes on-line where most will be presented as three hexadecimal values. One example I found was #884EA0, The 88 is the hexadecimal value for the red colour component, the 4E is the green and A0 the blue. You could convert those hex values to decimal (136, 78, 160) or just use the hex.

setColour(0x88, 0x4e, 0xa0);

The colour effect is enhanced if you are lucky enough to have secured a tri-colour LED with a diffusing outer or alternately you can find something to pop over the LED to have the same effect. Having said that, most LEDs have a “blue bias”; mine certainly did. That can require some adjustment to the RGB values you might find online as they anticipate their use with modern computer screens and printers. Some colours are just not going to be achieved with a tri-coloured LED – yellows in particular may prove problematic.

What are the calls to analogWrite() doing?

The LED is connected to three of the Arduino pins that are designated as PWM pins. We would rightly infer that the output is being modulated (regulated or adjusted) using a pulse. P WM generates a square wave on/off pattern that can simulate an analogue voltage value between zero and five volts (3.3 volts on a Due). The longer the pulses, the closer the approximation to 5 volts. Pulses that cumulatively last for half any given time interval (analogWrite(pin, 127); where 127 is halfway between 0 and 255) simulates an approximate voltage of 2.5 volts. The rapidity of the pulse switching between on and off means our eyes see a constant emission of light from the LED – with the intensity proportional to the simulated voltage.

The Arduino analogWrite() function is a simplification of the available PWM facilities on the ATmega chip that is sufficient for a wide range of use cases.

The ATmega328 chip has three PWM timers and each timer has two compare registers that can be used to control the pulse width at the timer’s designated pins. It is therefore possible to have greater control over PWM than that afforded by the analogWrite() functionality but that might require an oscilloscope to debug. We can make use of at least one of the timers.

Simulate the built in PWM on any digital pin

Just to underline the straightforward nature of PWM output we could create some code to manage generating a suitable pulse for one of the LED colours from a pin that is not PWM enabled. I have chosen Uno pin 7 and added a jumper wire from that pin to the breadboard location adjacent to the existing jumper lead from pin 11 ensuring that any voltage applied from pin 7 was also be limited by the existing 330 ohm resistor.

const int DIG_RED_PIN = 7;
const int MSECS_ON = 250; // 25% of 1,000
const int MS_SEC = 1000;

void setup() {
  pinMode(DIG_RED_PIN, OUTPUT);
}

void loop() {
  digitalWrite(DIG_RED_PIN, HIGH);
  delayMicroseconds(MSECS_ON);
  digitalWrite(DIG_RED_PIN, LOW);
  delayMicroseconds(MS_SEC - MSECS_ON);
}

Which when uploaded illuminates the red component of the LED with 25% of the full voltage by setting the pin HIGH for 25% of the time. This is the same as setting analogWrite() to a value of 64 on a PWM enabled pin although not at the same frequency. You can adjust the value of the const MSECS_ON between 0 and 1000 to simulate different PMW settings. As we are using delayMicroseconds() we have an effective frequency of 1,000Hz – there are 1,000,000 microseconds in a second so the loop() function should run the 4 program statements approximately 1,000 times a second.

That experiment was worth doing in the spirit of finding out how things work. However, the implementation is too crude for any practical application. The code ties up the processor except when it might be responding to interrupts, which in turn might undermine the timing of the pulse simulation and thus the simulated analogue voltage level. The true PWM pins are managed by their own process interrupts and so we can leave them to sort themselves. Of course, our initial program used to set up the PWM pins also fails the practicality test as it used the process blocking delay() functionality. That would be fine if all we wanted to do was light up an RGB LED. I suspect though, that in any project you are designing, an LED output would only be peripheral to the main task. So, we had better explore some alternate ways of programming an interaction with the LED in anticipation that one of them may suit a more demanding purpose.

The Arduino Timers

We already know that Arduino Uno has three timers (the Arduino Mega has 6) with Timer0 controlling PWM on pins 5 and 6. Timer1 has PWM control over pins 9 and 10 while Timer2 manages pins 3 and 11. Timer1 is a 16 bit timer while the other two are 8 bit timers. The number of bits limits the maximum counter value for a timer. An 8 bit counter is limited to 255 but a 16 bit timer counter can run up to 65535. A timer can trigger an interrupt when one of a number of conditions apply.

The timer counter is incremented by signals from the processor “clock” subdivided by a number set with bit values on the relevant timer registers (known as a prescaler).

Timer interrupts can be selectively triggered when the counter reaches its maximum value (an overflow interrupt) or when a specified counter value is reached (a compare match interrupt). There is also a timer input capture interrupt but let’s stick to the first two.

A timer interrupt can be used to call a function and that is something we can make use of to trigger actions at predetermined frequencies. However, timers are used for specific timing based tasks running on an Arduino. We have looked at Pulse Width Modulation but Timer0 is more generally used for the two delay functions while also keeping mills() and micros() updated. The other 8 bit timer, Timer2, is used by the tone() generating function. If you use the Arduino servo library then this will make use of Timer1. Clearly the number and availability of timers could influence your programming choices as well as your board selection for a given task.

As Timer0 is so crucial to timekeeping on an Arduino it is probably best left alone in most instances. We can therefore confine our activities to the remaining timers and (assuming an Arduino Uno) this means we might as well start with Timer1 as it is the most flexible.

N.B. The use of the tone() function demonstrates some of the restrictions that inevitably follow from having a limited range of timers. The tone() function generates a (square wave) output to any available pin but while in operation PWM is not available on pins 3 and 11 (the Mega is an exception to this).

Let’s use Timer1 to create a timer interrupt to replace the blocking delay() function in the first program in this chapter. To do that we need to divide the 16Mhz Arduino clock frequency by the 256 prescaler which results in a frequency of 62,500Hz. If we then divide that again by 62,500 we should end up with a frequency of 1Hz. This can be used to execute a short function every second.

Here are the two short programs demonstrating the two main timer interrupt techniques using the built in LED – but feel free to make the minor change to stick with the tri-colour setup.

Timer Overflow Interrupt

void setup() {
  pinMode(LED_BUILTIN, OUTPUT); // setup the LED pin
  noInterrupts();               // disable all interrupts
  TCCR1A = 0;                   // Timer control register
  TCCR1B = 0;

  // bit twiddling to set signal division and mode
  TCNT1 = 3036; // Set counter to start value 65536 - (16Mhz / 256)
  TCCR1B |= (1 << CS12);  // 256 prescaler
  TIMSK1 |= (1 << TOIE1); // set timer overflow interrupt
  interrupts();           // re-enable interrupts
}
ISR(TIMER1_OVF_vect){
  TCNT1 = 3036; // reset timer for 1 sec blink then toggle LED
  digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) ^ 1);
}
void loop() {
}

Compare Match Interrupt

void setup() {
  pinMode(LED_BUILTIN, OUTPUT); // setup the LED pin
  noInterrupts(); // disable all interrupts
  TCCR1A = 0; // Timer control register
  TCCR1B = 0;
  TCNT1 = 0; // Zero timer counter register for Timer1

  // now the register bit twiddling
  OCR1A = 62500; // Set the compare match register for 1Hz
  TCCR1B |= (1 << WGM12); // CTC mode
  TCCR1B |= (1 << CS12); // 256 prescaler
  TIMSK1 |= (1 << OCIE1A); // set timer compare interrupt
  interrupts(); // re-enable all interrupts
}
// An output compare interrupt on Timer1 calls this ISR
ISR(TIMER1_COMPA_vect){ // timer compare interrupt service routine
  digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) ^ 1);
}
void loop() {
}

The timer interrupt set-up process was described in the comments of both programs but just in case you are allergic to green writing I will run through the common elements of each before looking at the differences.

In each case, the first step was to disable interrupts as the next few lines of code were going to be adjusting the register values that control interrupts on Timer1. The next two lines of code unset any existing values on the two timer control registers. Following that, the program demonstrating a timer overflow interrupt pre-set the timer counter value to 3036 and in the second instance, zeroed the counter.

In both programs, a prescaler value of 256 was set in the TCCR1B register. This sets the number of clock cycles needed to advance the timer counter TCNT1 by 1. Then both programs set the TIMSK1 register to a value that would trigger the appropriate interrupt process. In the first program, when the TCNT1 counter overflowed and in the second, when the TCNT1 register value equalled the value on OCR1A.

Both of the programs are very similar although the way the timer counter was set varied. In the first program we set an initial value such that the counter will hit the maximum after one second and thus trigger the interrupt. In the second program, we set the value in the compare register to be matched by the counter to trigger the interrupt.

How were the “magic numbers” calculated? The second (compare match) program was simplest. All we needed was the number of clock cycles in a second divided by the prescaler value – 16Mhz/256 – so 62500. In the first program the interrupt was triggered by the timer counter overflow so we needed to pre-set the counter to a value that ensured the overflow occurred in 1 second. So, the value set was 65535 (maximum unsigned 16 bit int value) minus 62500, which is 3035. Then add 1 to force the overflow – final total 3036.

If we had wanted a timer event every half second (2Hz) then in the first program we would have set the counter to 34286 and in the second the compare match register to 31250.

Both programs make use of the standard Interrupt Service Routine (ISR) function format with our LED toggle statement added to the function body.

Timer0 and Timer2 both have 8 bit counters with a maximum value of 255 which makes triggering interrupts as slow as a second impossible and they are normally used to set timer events in the kilohertz range. Of course, nothing precludes your code from “counting” Timer2 interrupts (or those of Timer1) to take programmed actions after much longer periods of time than those available directly from the timers themselves. The C programmer is the lord of Arduino time.

The next program demonstrates using an interrupt from Timer2. I could just about make out the flicker of the built in LED using the maximum prescaler for the 8 bit register of Timer2. Don’t feel you have to run this program but do take a few moments to review it. You will note that the registers and counter have names that reflect the fact they work with Timer2 and that the prescaler settings also use different named constants.

void setup() {
  pinMode(LED_BUILTIN, OUTPUT); // setup the LED pin
  noInterrupts(); // disable all interrupts
  TCCR2A = 0;     // Timer control register
  TCCR2B = 0;
  TCNT2 = 0;      // Set counter to 0

  OCR2A = 240;    //Compare match register
  TCCR2A |= (1 << WGM21); // CTC Mode
  TCCR2B |= (1 << CS22) | (1 << CS20) | (1 << CS21); // max prescaler
  TIMSK2 |= (1 << OCIE2A); // set timer compare interrupt
  interrupts();            // re-enable interrupts
}
ISR(TIMER2_COMPA_vect){
  digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) ^ 1);
}

There is a good range of timer libraries available that can help abstract away the details and add additional functionality to the basics. In most instances, the use of one of these libraries is recommended now you have seen how they work. Given though, that you are sometimes just going to need to “grab” a timer event to support some other process the available register settings are documented here.

Timer1 Timer2 Register
TCNT1 TCNT2 Timer counter
OCR1 OCR2 Output Compare
ICR1 - Input capture
TIMSK1 TIMSK2 Enable/disable interrupts
TIFR1 TIFR2 Flags a pending interrupt


Prescaler Timer1 Timer2
0 CS10 CS20
8 CS11 CS21
64 CS11 and CS10 CS21 and CS20
256 CS12 CS22
1024 CS12 and CS10 CS22 and CS20


Timer ISR vectors
Timer1 Timer2
TIMER1_COMPA_vect TIMER2_COMPA_vect
TIMER1_OVF_vect TIMER2_OVF_vect
TIMER1_CAPT_vect n/a

Just a quick note on the “Input Capture" interrupt supported by Timer1. This interrupt can be used to measure the exact time an event occurs on pin 8 (Uno). When the interrupt occurs, the time value is copied to the ICR1 register and the ISR triggered. This is the most accurate method for measuring the precise time (or ultimately, time interval) for a pin event. An alternative (although slightly less accurate) would be to use an external interrupt and to read the TCNT1 value inside the external interrupt ISR.

The PWM story continues where it is most applicable – the fine control of servo motors.