How do I properly use the PCINT2 interrupt and TIMER1 to turn on an LED two seconds after a button is pressed?

Hello, I am very new to messing with Interrupts and Timers, and I'm having trouble getting this particular code to work. I'm trying to program Arduino interrupt PCINT2 to Pin 10 so that when the push button is pressed, the Timer 1 will be enabled. It must produce a delay of 2 second, and after 2 seconds, the LED must turn ON. Timer 1 will then count another 3 seconds, and after that, the LED will turn OFF. Additionally, the microcontroller must be in the possible lowest power sleep mode otherwise.

Note that the circuit has a push switch from 3.3V to Pin 10 with a 1KΩ shunt resistor, and an LED in series with a 220Ω resistor connected to Pin 13. Below is a semi-accurate picture, though the button is placed across the divide in the center of the breadboard so that the button actually opens and closes the circuit and the GND column was moved from the red + column to the blue - column:

Below is the current code:

#include <avr/interrupt.h>
#include <avr/sleep.h>
volatile char tick = 0;

#define PINB _SFR_IO8(0x03)
#define PINB2 2
#define PINB5 5
#define PORTB _SFR_IO8(0x05)
#define PORTB2 2
#define PORTB5 5
#define DDB2 2
#define DDB5 5
#define PCINT2_vect _VECTOR(5)
#define TIMER1_OVF_vect _VECTOR(13)
#define PCICR _SFR_MEM8(0x68)
#define PCIE2 2
#define PCMSK0 _SFR_MEM8(0x6B)
#define PCINT2 2
#define TCCR1A _SFR_MEM8(0x80)
#define TCCR1B _SFR_MEM8(0x81)
#define CS12 2
#define CS10 0
#define WGM12 3
#define TCNT1 _SFR_MEM16(0x84)
#define OCR1A _SFR_MEM16(0x88)
#define TIMSK1 _SFR_MEM8(0x6F)
#define TOIE1 0

void setup() {
  //pinMode(13, OUTPUT);
  //digitalWrite(13, LOW);
  cli(); // Clear global interrupt
  // Set the Pin 13 as output and 10 as input
  DDRB |= (1 << DDB5);
  DDRB &= ~(1 << DDB2);
  
  // Control regs for Timer 1, disable unless sw press
  TCCR1A = 0; // Set to Mode Normal
  TCCR1B = 0; // Disable timer
  TIMSK1 = (1 << TOIE1); // Turn ON timer overflow mask
  TCNT1 = 0;
  OCR1A = 65535;
  
  // Control regs for PCINT
  PCICR |= PCIE2; // Enable PCINT0 (PortB)
  PCMSK0 |= PCINT2; // PCINT0 = Pin 10 of Digital port
  
  Serial.begin(9600); // Initialize serial monitor, only for debug
  sei(); // Set global Interrupt

  pinMode(13, OUTPUT);
}

// ISR for pin change interrupt capture, set Timer 1 for 2 sec count
ISR(PCINT2_vect) {
  // Set the LED to OFF
  PORTB &= ~(PORTB5);
  tick = 0;


  TCNT1 = 34285; // set value for 2 sec count
  // Clock cycle needed = 2 * 16,000,000 / 1024
  // CNT value = 65,535 - 31,250
  TCCR1B = (1 << WGM12) | (1 << CS12) | (1 << CS10); // Prescale to 1024, start timer
  PORTB |= (PORTB5); // Only turn ON pin 13
  tick = 1;
}

ISR(TIMER1_OVF_vect) {
  // First time, LED will be ON; second time, LED will be OFF
  if (tick == 0) {
    tick = 1;
    PORTB |= (PORTB5); // Turn ON pin 13
    TCNT1 = 18660; // 3 sec count
    // anything else?
  } 
  else {
    PORTB &= ~(PORTB5); // Turn OFF pin 13
    TCCR1B = 0; // Disable timer
    // anything else?
    tick = 0;
    set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set sleep mode to power down
    sleep_mode();
  }
}

void loop() {
  Serial.print(String(digitalRead(13)) + " " + String(digitalRead(10)) + "\n"); // Display in serial monitor for debug
  delay(250);
  // Otherwise, do nothing!
  // Use an appropriate sleep mode
}

I know there is a lot wrong here as I barely understand the Interrupt syntax, but any help would be appreciated.

Thank you.

1 Like

You might find it more understandable if you use the Arduino interrupt functions.

is it about learing Interrupts or do you just need this solved in a reliable way:

that is all?

you are aware of that the UNO is very bad at saving power during deep sleep? It's just the wrong board to target "low power consumption".

p.s.: I left a like because you are the first newcomer today who has provided his code in code tags and a reasonable schematic. Well done.

Yes, that is all, and I do need to use Interrupts and Timers as I want to understand how to use them. And I am aware that the sleep mode isn't particularly useful as its stated use; it is just another thing I wanted to mess with. As such, any help in figuring this out will be appreciated.

You don't need interrupts or timers to solve your problem. This is done using the millis function and can be done with a dozen lines of code.... instead of your too long and too complicated sketch

There are plenty of tutorials for you to study. This one on Arduino timers is among the very best.

Leave your study of interrupts until you actually need them. For beginners, using interrupts almost always creates more problems than they solve.

1 Like

I am fully aware that I don't need to. I've already run an alternate program that uses millis() to make sure the circuit was working properly, but now I want to mess with Interrupts and Timers to see how to use them properly in the code.

Yes, I have found a few tutorials (like this one which helped me a lot on understanding the basics of the syntax and the hardware elements), and I am fully aware that this is an entirely unreasonable usage of Interrupts. But its just me messing around with it so I can begin to figure them out. I'm not an Arduino beginner; I'm just a beginner at using Interrupts and Timers.

Clearly. it is a complex topic, with lots of rules and plenty of pitfalls, some of them processor specific.

Start by using "#include <avr/io.h>" instead of your pile of #defines. That way a typo can't produce unexpected results.

The bit values are always 0 to 7 so you always have to use 1<<val or, better, the macro _BV(val) to get a bit mask.

Here is my best first guess at how I would do it. Rather than making the ISR's do work, I take advantage of the fact that the interrupts will wake the processor from sleep. The loop() function will sleep and then look at the state of the world when it is awakened.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
volatile boolean Timer1Overflowed = false;

void setup()
{
  Serial.begin(9600);  // Initialize serial monitor, only for debug
  delay(200);

  // Set the Pin 13 as output
  DDRB |= _BV(DDB5);    // pinMode(13, OUTPUT);
  PORTB &= ~_BV(PORTB5);  // digitalWrite(13, LOW);

  DDRB &= ~_BV(DDB2);  // pinMode(10, INPUT);

  // Turn off Timer1 for now
  cli();                // noInterrupts();
  TCCR1A = 0;           // Set to Mode Normal
  TCCR1B = 0;           // Stop timer
  TIMSK1 = _BV(TOIE1);  // Turn ON timer overflow interrupt
  sei();                // interrupts();

  // Control regs for PCINT
  PCICR |= _BV(PCIE0);    // Enable PCIE0 (PortB)
  PCMSK0 |= _BV(PCINT2);  // PCINT2 = Pin 10 (pin 2 of PORTB)
}

// ISR for pin change on PORTB
ISR(PCINT0_vect)
{
  // No need to do anything here since the interrupt will
  // allow loop() to continue and check for a button
  // press.
}


ISR(TIMER1_OVF_vect)
{
  Timer1Overflowed = true;
  TCCR1B = 0;  // Stop Timer1
}

enum States
{
  WAITING_FOR_PUSHBUTTON,
  DELAYING_TWO_SECONDS,
  LED_ON_FOR_3_SECONDS
} State = WAITING_FOR_PUSHBUTTON;

void loop()
{
  // Make local copy of volative variables
  cli();  // noInterrupts();
  boolean T1Overflow = Timer1Overflowed;
  Timer1Overflowed = false;
  sei();  // interrupts();

  switch (State)
  {
    case WAITING_FOR_PUSHBUTTON:
      if (PINB & _BV(PINB2))  // if (digitalRead(10) == HIGH)
      {
        // Set Timer1 to overflow in 2 seconds
        TCNT1 = 0x10000 - (F_CPU * 2ul) / 1024ul;  // 2 seconds at prescale 1024
        TCCR1B = _BV(CS12) | _BV(CS10);            // Start timer at Prescale = 1024

        // Clear overflow flag
        State = DELAYING_TWO_SECONDS;
      }
      break;

    case DELAYING_TWO_SECONDS:
      if (T1Overflow)
      {
        // Turn on LED
        PORTB |= _BV(DDB5);  //digitalWrite(13, HIGH);

        // Set Timer1 to overflow in 3 seconds
        TCNT1 = 0x10000 - (F_CPU * 3ul) / 1024ul;  // 3 seconds at prescale 1024
        TCCR1B = _BV(CS12) | _BV(CS10);            // Start timer at Prescale = 1024

        // Clear overflow flag
        State = LED_ON_FOR_3_SECONDS;
      }
      break;

    case LED_ON_FOR_3_SECONDS:
      if (Timer1Overflowed)
      {
        // Turn off LED
        PORTB &= ~_BV(DDB5);  // digitalWrite(13, LOW);

        // Wait for next button press
        State = WAITING_FOR_PUSHBUTTON;
      }
      break;
  }

  // Use an appropriate sleep mode
  // Turn off the millis() (TIMER0) interrupts during sleep
  uint8_t Timer0Interrupts = TIMSK0;
  TIMSK0 = 0;                           // All TIMER0 interrupts disabled
  set_sleep_mode(SLEEP_MODE_PWR_SAVE);  // Set sleep mode to power save
  sleep_mode();
  TIMSK0 = Timer0Interrupts;  // re-enable TIMER0 interrupts
}
1 Like

Thank you all for the help. I was able to figure it out eventually. The biggest problem was, of course, syntax errors and assignment errors that led to some of the required registers not being declared and assigned values properly (most notably PCICR and PCMSK0). So, I fixed them and added a better way to mess with the sleep mode and got the following code that works:

#include <avr/sleep.h>

char tick = 0;  // Describes the state of the LED
 
void setup() { 
  cli(); // Clear global interrupt 

  // Set the Pin 13 as output and 10 as input 
  DDRB |= (1 << DDB5);
  DDRB &= ~(1 << DDB2);

  // Control regs for Timer 1, disable timer unless sw press
  TCCR1A = 0; // Set to Mode Normal
  TCCR1B = 0; // Disable timer
  TIMSK1 = (1 << TOIE1); // Turn ON timer overflow mask

  // Control regs for PCINT
  PCICR = (1 << PCIE0); // Enable PCINT0 (PortB)
  PCMSK0 = (1 << PCINT2); // PCINT0 = Pin 10 of Digital port

  //Serial.begin(9600); // Initialize serial monitor, only for debug
  SMCR = B00000101; // Enable sleep and set Power Down Sleep Mode
  sei(); // Set global Interrupt
} 
 
// ISR for pin change interrupt capture, set Timer 1 for 2 sec count 
ISR(PCINT0_vect) {
  // Set the LED to OFF
  PORTB &= ~(1 << DDB5);

  // Clock cycle needed = 2 * 16,000,000 / 1024 = 31,250
  // CNT value = 65,535 - 31,250 = 34,285
  TCNT1 = 34285; // set value for 2 sec count
  TCCR1B = (1 << CS12) | (1 << CS10); // Prescale to 1024, start timer, 16E6/1024 = 15,625 Hz
} 

ISR(TIMER1_OVF_vect) {
  // First time, LED will be ON; second time, LED will be OFF
  if (tick == 0) {
    PORTB |= (1 << DDB5);  // Turn ON pin 13
    
    // Clock cycle needed = 3 * 16,000,000 / 1024 = 46,875
    // CNT value = 65,535 - 46,875 = 18,660
    TCNT1 = 18660;  // 3 sec count
    tick = 1;
  } else {
    PORTB &= ~(1 << DDB5); // Turn OFF pin 13
    TCCR1B = 0; // Disable timer
    tick = 0;
    sleep_cpu(); // Proper Sleep Mode
  }
} 
 
void loop() {
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.