79. Simultaneous Blinking LEDs

Using tone() function:

We can use the tone() function for toggling LEDs. However, it has some limitations

  • tone() function generates frequency on only 1 pin at a time.
  • tone() function can generate a lowest frequency of 31 Hz.

So we will use Timer1 and Timer2 for toggling of pins. 

  • From the given problem statement we can understand that:
    • The controller will be busy in a void loop().
    • We need to blink 2 LEDs at different intervals while it is blocked in a void loop.
  • To achieve blinking we can use Timers and Interrupts (Timer1 and Timer2).
  • We will also connect the 2 LEDs on Pin 9 and 10 respectively.
  • We will use the CTC mode of timers for our solution and the interrupt associated.
  • We will do the following:
    • Timer1 for LED1 (100ms Interval)
      • CTC Mode is enabled using (1 << WGM12).
      • Prescaler of 256 makes Timer1 count at 62.5 kHz (16MHz/256).
      • OCR1A Calculation for 100ms:                                                                                          OCR1A=(100×(16000000/1024)/1000)−1=6249
      • ISR (TIMER1_COMPA_vect) toggles LED1 every 100ms.
    • Timer2 for LED2 (360ms Interval)
      • CTC Mode is enabled using (1 << WGM21).
      • Timer2 is an 8-bit timer, so OCR2A must be ≤ 255.
      • The maximum time before Timer2 overflows is:                                                                 255 * 64us = 16.32 ms
      • This is much shorter than the required 360ms.
      • Solution: Use OCR2A = 155 to generate an interrupt every ~10ms, then use a software counter to count 36 interrupts to reach 360ms.

Circuit Connection

  • D9 → LED1
  • D10 → LED2

Code

#include <avr/io.h>
#include <avr/interrupt.h>

// Pin Definitions
#define LED1_PIN 9   // LED1 toggles every 100ms
#define LED2_PIN 10  // LED2 toggles every 360ms

// LED States
volatile bool led1State = LOW;
volatile bool led2State = LOW;

// Software counter for LED2
volatile uint8_t led2Counter = 0;

void setup() {
  pinMode(LED1_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);

  // Timer1 Configuration (CTC Mode for LED1 - 100ms interval)
  TCCR1A = 0;
  TCCR1B = (1 << WGM12) | (1 << CS12);  // CTC Mode, Prescaler 256
  OCR1A = 6249;                         // Corrected OCR1A for 100ms
  TIMSK1 = (1 << OCIE1A);               // Enable Timer1 Compare Match Interrupt

  // Timer2 Configuration (CTC Mode for LED2 - ~10ms interval)
  TCCR2A = (1 << WGM21);                             // CTC Mode
  TCCR2B = (1 << CS22) | (1 << CS21) | (1 << CS20);  // Prescaler 1024
  OCR2A = 155;                                       // Generates an interrupt every ~10ms
  TIMSK2 = (1 << OCIE2A);                            // Enable Timer2 Compare Match Interrupt

  sei();  // Enable Global Interrupts
}

void loop() {
  while (true) {
    // Microcontroller remains busy in this loop continuously monitoring a critical task.
  }
}

// Timer1 ISR - Toggles LED1 every 100ms
ISR(TIMER1_COMPA_vect) {
  led1State = !led1State;
  digitalWrite(LED1_PIN, led1State);
}

// Timer2 ISR - Toggles LED2 every 360ms using a software counter
ISR(TIMER2_COMPA_vect) {
  led2Counter++;

  if (led2Counter >= 36) {  // 36 x 10ms = 360ms
    led2Counter = 0;
    led2State = !led2State;
    digitalWrite(LED2_PIN, led2State);
  }
}

 

Code Explanation

Timer1 Configuration (for LED1 - 100ms interval)

TCCR1A = 0;

TCCR1B = (1 << WGM12) | (1 << CS12); // CTC Mode, Prescaler 256

OCR1A = 6249; // Corrected OCR1A for 100ms

TIMSK1 = (1 << OCIE1A); // Enable Timer1 Compare Match Interrupt

TCCR1A = 0: Clears Timer1 Control Register A.

 

TCCR1B: Configures Timer1 in Clear Timer on Compare Match (CTC) mode with a prescaler of 256.

OCR1A = 6249: Sets the compare match value for 100 ms intervals.

TIMSK1: Enables the Timer1 Compare Match A interrupt.

 

Timer2 Configuration (for LED2 - ~10ms interval)

TCCR2A = (1 << WGM21); // CTC Mode

TCCR2B = (1 << CS22) | (1 << CS21) | (1 << CS20); // Prescaler 1024

OCR2A = 155; // Generates an interrupt every ~10ms

TIMSK2 = (1 << OCIE2A); // Enable Timer2 Compare Match Interrupt

TCCR2A: Configures Timer2 in CTC mode.

 

TCCR2B: Sets the prescaler to 1024.

OCR2A = 155: Sets the compare match value for ~10ms intervals.

TIMSK2: Enables the Timer2 Compare Match A interrupt.

 

 

ISR is an Interrupt Service Routine, it is serviced when an associated interrupt is triggered. 

Timer1 ISR (for LED1)


ISR(TIMER1_COMPA_vect) {

  led1State = !led1State;

  digitalWrite(LED1_PIN, led1State);

}

This ISR is triggered every 100ms (as configured by Timer1).

It toggles the state of LED1_PIN by flipping led1State and updating the pin output.

 


Timer2 ISR (for LED2)

 

ISR(TIMER2_COMPA_vect) {

  led2Counter++;

  

  if (led2Counter >= 36) { // 36 x 10ms = 360ms

    led2Counter = 0;

    led2State = !led2State;

    digitalWrite(LED2_PIN, led2State);

  }

}

This ISR is triggered every ~10ms (as configured by Timer2).

It increments the led2Counter variable.

When led2Counter reaches 36 (36 x 10 ms = 360ms), it resets the counter, toggles the state of LED2_PIN, and updates the pin output.

 

Summary

LED1: Toggles every 100ms using Timer1.

LED2: Toggles every 360ms using Timer2 and a software counter.

The microcontroller uses hardware timers and interrupts to achieve precise timing without blocking the main program execution.

The loop() function is intentionally left empty, allowing the microcontroller to focus on handling interrupts and other tasks.

 

 

Output

Hardware setup


 

 DSO waveform output:



GIF Of Output


 

Video

 

 

 

 

 

 

 

Submit Your Solution

Note: Once submitted, your solution goes public, helping others learn from your approach!