5

I have recently been working on a project that I had initially been designing with an Arduino Nano, and I was able to create functioning code for my application, but for cost / space reduction I decided to port the application to an ATTiny13 (because I had a few on hand). The problem is that for some reason, I cannot get the interrupt on the device to behave properly, and I was wondering if anybody could possibly give me a hand. I already performed a few experiments to narrow the problem down, and I think I understand the nature of the problem, but I have no idea on how to mitigate it.

Here is a rough schematic of my setup:

enter image description here

And here is the code I started with:

// Pin defines
#define ALERT_LED 4

// Variables
bool LED_STATE = false;


ISR(PCINT0_vect) {
  LED_STATE = true;
}

void setup() {
  pinMode(ALERT_LED, OUTPUT);

  // Configure the button-pin to have a pull-up
  DDRB |= (1<<DDB3);
  PORTB |= (1<<PORTB3);

  // Enable a pin-change interrupt on the PCINT3 (falling-edge)
  MCUCR |= (1 << ISC01);  // This and following line setup for falling-edge
  MCUCR &= ~(1 << ISC00);
  GIMSK |= (1 << PCIE);   // Enable pin-change interrupts
  PCMSK |= (1 << PCINT3); // Setup the pin-mask so that PB3 triggers the interrupt
  sei();                  // enable all interrupts
  
}

void loop() {
  if(LED_STATE == true) {
    digitalWrite(ALERT_LED, HIGH);
    delay(2000);
    digitalWrite(ALERT_LED, LOW);
    delay(2000);
    LED_STATE = false;
  }
}

In theory, this should setup the pin that the button is on to have a pull-up resistor internally, and when the button is pressed, it should trigger a falling-edge interrupt which would thus set "LED_STATE" equal to 1. When the code returns to the main loop, it should see the flag and blink the LED before clearing the flag.

Now before I go any further, here is what I validated up to this point:

1.) The LED circuit is definitely wired up properly because I can manually set the "LED_STATE" flag to true in the main loop, and it will constantly blink at a rate of about once a second (given the clock frequency of 600kHz)

2.) The pull-up is definitely being configured properly, as I can see the voltage sitting at 5V until I press the button at which point the voltage falls to 0V.

So I decided to experiment a bit, and I set the LED high inside the interrupt, and removed that line from the main loop to see if the interrupt was even working, and to my surprise the LED was actually turning on, but it was not turning off:

// Pin defines
#define ALERT_LED 4

// Variables
bool LED_STATE = false;


ISR(PCINT0_vect) {
  LED_STATE = true;
  digitalWrite(ALERT_LED, HIGH);
}

void setup() {
  pinMode(ALERT_LED, OUTPUT);

  // Configure the button-pin to have a pull-up
  DDRB |= (1<<DDB3);
  PORTB |= (1<<PORTB3);

  // Enable a pin-change interrupt on the PCINT3 (falling-edge)
  MCUCR |= (1 << ISC01);  // This and following line setup for falling-edge
  MCUCR &= ~(1 << ISC00);
  GIMSK |= (1 << PCIE);   // Enable pin-change interrupts
  PCMSK |= (1 << PCINT3); // Setup the pin-mask so that PB3 triggers the interrupt
  sei();                  // enable all interrupts
  
}

void loop() {
  if(LED_STATE == true) {
    //digitalWrite(ALERT_LED, HIGH);
    delay(2000);
    digitalWrite(ALERT_LED, LOW);
    delay(2000);
    LED_STATE = false;
  }
}

At this point, I started to think that the ISR was not returning to the main loop or something, but by adding an 'else' case to shut the LED off in the main loop, I could see that the loop was definitely running after the ISR, because the LED would not turn on (implying that the main loop had to be shutting it off the moment the ISR returned to the loop).

// Pin defines
#define ALERT_LED 4

// Variables
bool LED_STATE = false;


ISR(PCINT0_vect) {
  LED_STATE = true;
  digitalWrite(ALERT_LED, HIGH);
}

void setup() {
  pinMode(ALERT_LED, OUTPUT);

  // Configure the button-pin to have a pull-up
  DDRB |= (1<<DDB3);
  PORTB |= (1<<PORTB3);

  // Enable a pin-change interrupt on the PCINT3 (falling-edge)
  MCUCR |= (1 << ISC01);  // This and following line setup for falling-edge
  MCUCR &= ~(1 << ISC00);
  GIMSK |= (1 << PCIE);   // Enable pin-change interrupts
  PCMSK |= (1 << PCINT3); // Setup the pin-mask so that PB3 triggers the interrupt
  sei();                  // enable all interrupts
  
}

void loop() {
  if(LED_STATE == true) {
    //digitalWrite(ALERT_LED, HIGH);
    delay(2000);
    digitalWrite(ALERT_LED, LOW);
    delay(2000);
    LED_STATE = false;
  }
  else {
    digitalWrite(ALER_LED, LOW);
  }
}

At this point, I can really only think that there must be a problem with how I am defining the "LED_STATE" parameter, because all signs point towards this variable not actually holding the information properly when traversing from the ISR back to the loop. I don't see what would be the problem though since I defined the parameter globally. Can anybody lend a hand here, this is honestly the last thing I need to finish this part of the project, and I am honestly at a bit of a loss at this point.

7

You would want your bool LED_STATE = false; to be volatile qualified like volatile bool LED_STATE = false; otherwise the compiler's optimizer may assume that the value cannot be changing between loads and this can cause bugs in the generated code. This sounds like the principal problem you're having.

The ISC01 and ISC00 in MCUCR are for controlling edges detected on the "external interrupt" INT0 pin, which is PB1, package pin 6. This is essentially what attachInterrupt() does, which should be available to you in your board support package. If you can, it's probably best to just use that. In the case of using attachInterrupt() the use of volatile would still apply.

The other pins, like the one you've chosen, only only do Pin Change interrupts, hence PCINTn rather than INTn. Pin change interrupts only fire to tell that a pin changed, but not which pin (out of the group) or in what direction it changed. That is, it's ignoring what you've set in MCUCR.

If you want an external interrupt's ability to detect falling or rising edges using PCINTn interrupts, you must effectively synthesize this in software by tracking the old and new states of the pins, XORing them to determine what changed, and then either looking at the old or new state to determine what direction. NicoHood's PinChangeInterrupt library does this for you, and appears to support the ATTiny13. Likewise, volatile usage applies here as well.

2
  • 2
    Holy crap, that’s actually crazy! I never knew the compiler would do me so dirty like that! I really appreciate it man, that literally fixed it! – Kevin Sullivan Dec 15 '20 at 3:30
  • 2
    Good deal. I will probably add in the above later that if you use attachInterrupt() or the PinChangeInterrupt library, everything said about volatile stands. Because with those. whatever callback function you give is likewise executing in the context of an interrupt, no different from your more direct ISR() code. – timemage Dec 15 '20 at 3:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.