ATtiny85 Pin Change Interrupt Problem

I’m trying to use pin change interrupts with the ATtiny because I have to use the INT0 pin for SCK in I2C.

I can trigger the interrupt, but after it fires, it runs through setup() again. It may be just rebooting - with no bootloader it’s hard to tell.

The attachInterrupt() with INT0 works fine.
I’ve tried a few different techniques and vectors [ISR (PCINT1_vect), SIGNAL (SIG_PCINT)] with similar results.

I may be doing something basically wrong here, but I can’t figure out what.

Here is a sample that demonstrates the problem . . .

/* IR Receiver Test 2       GOES BACK TO MAIN WHEN I USE ISR 
 * ATtiny Pin 1 = (RESET) N/U                      ATtiny Pin 2 = (D3) IR OUTPUT PIN
 * ATtiny Pin 3 = (D4) to LED1                     ATtiny Pin 4 = GND
 * ATtiny Pin 5 = D0/SDA on GPIO                   ATtiny Pin 6 = (D1) Piezo
 * ATtiny Pin 7 = D2/INT0/SCK on GPIO              ATtiny Pin 8 = VCC (2.7-5.5V)

#define LED1_PIN         4              // ATtiny Pin 3
#define PIEZO_PIN        1              // ATtiny Pin 6
#define IR_PIN           3              // IR sensor - ATtiny Pin 2

void setup(){
  pinMode(LED1_PIN,OUTPUT);             // for general DEBUG use
  pinMode(IR_PIN,INPUT);                // IR on pin 3
  digitalWrite(IR_PIN, HIGH);           // turn on pullup resistor
  Blink(LED1_PIN,3);                    // shows it's in setup
  //tone(PIEZO_PIN,2500,1000);          // shows it's in setup (not supported with MIT core)
  //  attachInterrupt(0,IR_ISR,FALLING); // THIS METHOD OF ISR WORKS

  // Setup pin change interrupt . . .
  // METHOD 4: *************
  PCMSK |= (1<<PCINT3);    //  tell pin change mask to listen to D3
  GIMSK  |= (1<<PCIE);     // enable PCINT interrupt in the general interrupt mask
  sei();                   // Enable all interrupts

void loop(){
  //tone(PIEZO_PIN,2000,200); // when used makes different pitch than in setup()
  delay (1500);

SIGNAL (SIG_PCINT) {    // METHOD 4: *************
  //void IR_ISR(){      // STANDARD METHOD WORKS


void Blink(byte led, byte times){ // poor man's GUI
  for (byte i=0; i< times; i++){
    delay (400);
    delay (175);

I’m stumped. Any help is appreciated.

Rather than help troubleshoot, can I interest you in a working library? It uses less memory and should be a bit faster than the "stock" pin-change interrupt code.

Sure! A working library for what? Interrupts? IR?
If it’s for IR, I’d prefer an int only when it receives a signal, rather than a timer int that going off every x ms.
As I said, I need pin 7 free for I2c.

In any case, I’ll be happy to look at a lib. : :smiley:

I'm afraid you'll be a bit disappointed. It's just for pin-change interrupts on tiny processors. But it is well debugged.

The Tiny Core includes a write-only software Serial (Tiny Debug Serial). All you need is a TTL serial converter (an Arduino will work). It beats the heck out of LED debugging. ;)

Not disappointed at all! Pin change interrupts is exactly what I need. (The ability to detect rising and falling would be great too!)

The serial sounds interesting. I have a cable. Yep, counting LED flashes is no fun. (Normally I use an I2C 2x20 display.)

In any case, thanks! I look forward to checking it out.


Just read the datasheet.... Pin change interrupts are sort of global to each port, after having a PCINT you must write some code to determine wich pin as changed and then react accordind, its all in the datasheet... That and google avr pin change interrupt..


I assumed
PCMSK |= (1<<PCINT3); // tell pin change mask to listen to D3

Would only cause the INT to fire with D3, but I’ll look at the data sheet and google some more.

My understanding of what your saying is that I need some code in the ISR - correct?
In any case, thanks for the hint.

My understanding of what your saying is that I need some code in the ISR - correct?

Only if there are two or more possible interrupt sources. If you only have one then by definition the interrupt comes form that pin and there's no need to figure anything out.


Graynomad: If you only have one then by definition the interrupt comes form that pin and there's no need to figure anything

Bah! Where's the fun in that!


The ability to detect rising and falling would be great too!

Call attach*Pc*Interrupt in exactly the same fashion as attachInterrupt (LEVEL interrupts are not supported)...

void Rising( void )
  digitalWrite( 2, ! digitalRead( 2 ) );

void setup( void )
  attachPcInterrupt( 1, Rising, RISING );

The code is available here...

The readme.txt file is very sparse. I simply don't have time to write up install / usage instructions. If you get stuck, ask here for help.

The serial sounds interesting

Some instruction here (I'll try to get 16 MHz support published this weekend)...

Good luck and please let me know how you make out!

Good luck and please let me know how you make out!

I had good luck and this is how I made out . . .
The PinChangeInterrupt lib worked great! It even perfectly handled the FALLING mode.
I’m reading the IR remote every time and it had no effect on ISR0 so my I2C is working fine for the LCD display.
Major thanks CB. Thats a fine little lib. :smiley:

Here’s the code FWIW. (The IR_ISR() may be worth taking a look at.)

/* ATtiny85 IR Receiver Test               BroHogan                        2/11/11
 * Tests IR receiver (using pin change interrupt) while I2C LC display is used. 
 * ATtiny Pin 1 = (RESET) N/U                      ATtiny Pin 2 = (PB3) IR OUTPUT PIN
 * ATtiny Pin 3 = (PB4) to LED1                    ATtiny Pin 4 = GND
 * ATtiny Pin 5 = PB0/SDA on GPIO                  ATtiny Pin 6 = (PB1) N/U
 * ATtiny Pin 7 = PB2/INT0/SCK on GPIO             ATtiny Pin 8 = VCC (2.7-5.5V)

#include <PinChangeInterrupt.h>         // mimics attachInterrupt() but a PCI for ATtiny
#include "TinyWireM.h"                  // uses TinyWireM lib low level functions
#include <LiquidCrystal_I2C.h>          // for LCD w/ GPIO MODIFIED for the ATtiny85

#define LED1_PIN         4              // ATtiny Pin 3
#define IR_PIN 3                        // IR sensor - ATtiny Pin 2
#define GPIO_ADDR        0x3F           // (PCA8574A A0-A2 @5V) typ. A0-A3 Gnd 0x20 / 0x38 for A

// IR receiver defines
#define ENTER 12
#define UP    17
#define DOWN  18
#define RIGHT 19
#define LEFT  20
#define OK    21
#define POWER 22
#define SLEEP 55
#define INFO  59

// vars for IR_ISR() (must be global)
volatile boolean IR_Avail;              // flag set if IR has been  read
volatile unsigned int IR_Return;        // returns IR code received
volatile unsigned long ir_mask;         // Loads variable ir_string 
volatile unsigned int ir_bit_seq;       // Records bit sequence in ir string
volatile unsigned int ir_string;        // Stores ir string

LiquidCrystal_I2C lcd(GPIO_ADDR,16,2);  // set address & 16 chars / 2 lines

void setup(){
  pinMode(LED1_PIN,OUTPUT);             // for general DEBUG use
  pinMode(IR_PIN,INPUT);                // IR on pin 3
  digitalWrite(IR_PIN, HIGH);           // turn on pullup resistor
  lcd.init();                           // initialize the lcd & TinyWireM
  lcd.backlight();                      // Print a message to the LCD.
  lcd.print("Hello, IR!");
  attachPcInterrupt(3,IR_ISR,FALLING);  // Catch IR sensor on PB3 with pin change interrupt 

void loop(){

void Check_IR(){ // check if remote used and process     
  int remoteInput;                      // normalized input from remote

  if(IR_Avail){                         // check if key on IR has been pressed
    remoteInput = IR_Return + 1;        // convert raw return to actual digit values
    if (remoteInput == 10) remoteInput = 0; // special case for zero key
    switch (remoteInput) {              // a case for each key 

    case 0 ... 9:                       // digits 0-9
    case ENTER:
    case UP:
    case DOWN:
    case RIGHT:
    case LEFT: 
    case OK: 
    case POWER:
    case SLEEP:
    case INFO:
    IR_Avail = false;                   // allow IR again
    digitalWrite(LED1_PIN, LOW);        // turn off LED

void IR_ISR(){ // This ISR is called for EACH pulse of the IR sensor
  if(ir_bit_seq == 0){                  // it is the long start pulse
    for(int i = 0; i<20; i++){          // see if it lasts at least 2 ms
      delayMicroseconds(100); // 100
      if(digitalRead(IR_PIN)) return;   // it's doesn't so get out (low active)
    ir_bit_seq = 1;                     // mark that the start pulse was received
    ir_mask = 1;                        // set up a mask for the next bits
  delayMicroseconds(900);               // wait 900 us and test 
  if(!digitalRead(IR_PIN)) ir_string = ir_string | ir_mask;  // Stores 1 in bit
  ir_mask = ir_mask << 1;               // shifts ir_mask by one to the left
  ir_bit_seq++;                         // ir_bit_seq is incrimented by one
  if(ir_bit_seq == 12){                 // after remote sends 12 bits it's done
    ir_mask = 63;                       // only want the last 6 bits - the command
    ir_string = ir_string & ir_mask;    // only keep last 6 bits
    IR_Return = ir_string;              // final result
    IR_Avail = true;                    // indicate new command received
    digitalWrite(LED1_PIN, HIGH);       // turn on LED to show you got something
    ir_bit_seq = 0;                     // clean up
    ir_string = 0;
    ir_mask = 0;
    for(int i = 0; i<25; i++){          // delay to stop repeat 10ms / loop ~250ms about right
      delayMicroseconds(10000);         // 16383 is maximum value so need to repeat

Excellent! Thank you for the follow-up and thank you for posting your Sketch!