Don't call us, we'll call you
In part 1 of this series, we learned how to use millis() for timing. But in order to make that work, we had to call millis() every time through the loop to see if it was time to do something. It is kind of a waste to be calling millis() more than once a millisecond, only to find out that the time hasn't changed. Wouldn't it be nice if we only had to check once per millisecond?
Timers and timer interrupts let us do exactly that. We can set up a timer to interrupt us once per millisecond. The timer will actually call us to let us know it is time to check the clock!
Arduino Timers
The Arduino Uno has 3 timers: Timer0, Timer1 and Timer2. Timer0 is already set up to generate a millisecond interrupt to update the millisecond counter reported by millis(). Since that is what we are looking for, we'll get Timer0 to generate an interrupt for us too!
Frequency and Counts
Timers are simple counters that count at some frequency derived from the 16MHz system clock. You can configure the clock divisor to alter the frequency and various different counting modes. You can also configure them to generate interrupts when the timer reaches a specific count.
Timer0 is an 8-bit that counts from 0 to 255 and generates an interrupt whenever it overflows. It uses a clock divisor of 64 by default to give us an interrupt rate of 976.5625 Hz (close enough to a 1KHz for our purposes). We won't mess with the freqency of Timer0, because that would break millis()!
Comparison Registers
Arduino timers have a number of configuration registers. These can be read or written to using special symbols defined in the Arduino IDE. For comprehensive description of all these registers and their functions, see the links in "For further reading" below.
We'll set up a up a comparison register for Timer 0 (this register is known as OCR0A) to generate another interrupt somewhere in the middle of that count. On every tick, the timer counter is compared with the comparison register and when they are equal an interrupt will be generated.
The code below will generate a 'TIMER0_COMPA' interrupt whenever the counter value passes 0xAF.
// Timer0 is already used for millis() - we'll just interrupt somewhere // in the middle and call the "Compare A" function below OCR0A = 0xAF; TIMSK0 |= _BV(OCIE0A);
Then we'll define an interrupt handler for the timer interrupt vector known as "TIMER0_COMPA_vect". In this interrupt handler we'll do all the stuff we used to do in the loop.
// Interrupt is called once a millisecond, SIGNAL(TIMER0_COMPA_vect) { unsigned long currentMillis = millis(); sweeper1.Update(currentMillis); //if(digitalRead(2) == HIGH) { sweeper2.Update(currentMillis); led1.Update(currentMillis); } led2.Update(currentMillis); led3.Update(currentMillis); }
Which leaves us with a completely empty loop.
void loop() { }
You can do anything you want in your loop now. You can even be decadent and use a delay()! The flashers and sweepers won't care. They will still get called once per millisecond regardless!
For further reading:
This is just a simple example of what timers can do. For more in-depth information on the different types of timers and ways to configure them, check out the "Libraries and Links" page.
The Source Code:
Here's the whole code, including the flashers and sweepers:
#include <Servo.h> class Flasher { // Class Member Variables // These are initialized at startup int ledPin; // the number of the LED pin long OnTime; // milliseconds of on-time long OffTime; // milliseconds of off-time // These maintain the current state int ledState; // ledState used to set the LED unsigned long previousMillis; // will store last time LED was updated // Constructor - creates a Flasher // and initializes the member variables and state public: Flasher(int pin, long on, long off) { ledPin = pin; pinMode(ledPin, OUTPUT); OnTime = on; OffTime = off; ledState = LOW; previousMillis = 0; } void Update(unsigned long currentMillis) { if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime)) { ledState = LOW; // Turn it off previousMillis = currentMillis; // Remember the time digitalWrite(ledPin, ledState); // Update the actual LED } else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime)) { ledState = HIGH; // turn it on previousMillis = currentMillis; // Remember the time digitalWrite(ledPin, ledState); // Update the actual LED } } }; class Sweeper { Servo servo; // the servo int pos; // current servo position int increment; // increment to move for each interval int updateInterval; // interval between updates unsigned long lastUpdate; // last update of position public: Sweeper(int interval) { updateInterval = interval; increment = 1; } void Attach(int pin) { servo.attach(pin); } void Detach() { servo.detach(); } void Update(unsigned long currentMillis) { if((currentMillis - lastUpdate) > updateInterval) // time to update { lastUpdate = millis(); pos += increment; servo.write(pos); if ((pos >= 180) || (pos <= 0)) // end of sweep { // reverse direction increment = -increment; } } } }; Flasher led1(11, 123, 400); Flasher led2(12, 350, 350); Flasher led3(13, 200, 222); Sweeper sweeper1(25); Sweeper sweeper2(35); void setup() { sweeper1.Attach(9); sweeper2.Attach(10); // Timer0 is already used for millis() - we'll just interrupt somewhere // in the middle and call the "Compare A" function below OCR0A = 0xAF; TIMSK0 |= _BV(OCIE0A); } // Interrupt is called once a millisecond, to update the LEDs // Sweeper2 s not updated if the button on digital 2 is pressed. SIGNAL(TIMER0_COMPA_vect) { unsigned long currentMillis = millis(); sweeper1.Update(currentMillis); if(digitalRead(2) == HIGH) { sweeper2.Update(currentMillis); led1.Update(currentMillis); } led2.Update(currentMillis); led3.Update(currentMillis); } void loop() { }
Text editor powered by tinymce.