INT1 not triggering?

Hey all, I'm trying to incorporate a rotary encoder to my standalone ATMega328 proyect and I'm struggling with said interrupt on the MCU.

I've tried many libraries already, like Encoder, MD_REncoder, Rotary, and a couple more which I've deleted and can't remember their names. These ones I have seem to have pretty clear code and aim to be easy-to-use, but maybe the libraries might not be the problem here.
They all recommend using both interrupt pins for outputs A & B of the encoder (INT1 & INT0, respectively), which I am doing, and I've also tried with A connected to INT1 and B connected to the non-interrupt-pin PD4 (Pin 6). In that configuration I was using INT0 to trigger the switch of the encoder to enter a setup screen on my LCD. In my actual configuration, the switch of the encoder is connected to PD4 and triggers with PCINT20. I'll attach pictures of the schematic and the PCB.

In the end, I couldn't get it to work properly in any of said configurations. The encoder is mechanically OK and functional, though. I've tried many examples that came with the libraries and none worked, only INT0 seems to be responsive to input, but its output isn't coherent since it should work in conjunction with INT1, which is not working. I am doing a simple test now in which the serial monitor simply outputs text when the corresponding INT is triggered. Here's a snippet of my code:

volatile bool intFlag = false;
volatile bool int0 = false;
volatile bool int1 = false;

void setup() {
  Wire.begin();
  Serial.begin(57600);

  PORTD |= (1 << PORTD2) | (1 << PORTD3) | (1 << PORTD4);

  // Disable Global Interrupts to set them
  cli();

  // Enable interrupt for button
  PCMSK2 |= (1 << PCINT20);
  PCICR |= (1 << PCIE2);

  // Enable interrupts INT0 & INT1 on CHANGE
  EIMSK |= (1 << INT0) | (1 << INT1);
  EICRA |= (1 << ISC00) | (1 << ISC10);

  // Reset timer1
  TCCR1A = 0;

  // Restart timer on compare
  TCCR1B &= ~(1 << WGM13);
  TCCR1B |= (1 << WGM12);

  // Prescaler to 1024
  TCCR1B |= (1 << CS12);
  TCCR1B &= ~(1 << CS11);
  TCCR1B |= (1 << CS10);

  // Set compare value
  OCR1A = 62500;

  // Set interrupt for Timer1 overflow
  TIMSK1 = (1 << OCIE1A);

  // Enable global interrupts
  sei();
}

ISR(TIMER1_COMPA_vect) {
  count++;
}

ISR(PCINT2_vect) {
  intFlag = true;
}

ISR(INT0_vect) {
  int0 = true;
}

ISR(INT1_vect) {
  int1 = true;
}

void loop() {
  if(int0) {
    int0 = false;
    Serial.println("INT0");
  } else if (int1) {
    int1 = false;
    Serial.println("INT1");
  }
}

(the timer1 part I just left in 'cause it handles interrupts too, just in case)

When I test this, the monitor outputs INT0 whenever I spin the encoder, but never INT1 in any case. Also, the switch works great.
There was only one or two instances in which INT1 would show up on the monitor at the very top, as the first output I guess, but that's it.

At the moment of writing, all encoder-MCU connections have been checked for continuity and nothing seems to be in short.

Thanks to everyone in advance!

Rotary Encoder setup.png

ENC_2.png

First question that pops to mind, why not use the Arduino HAL? You are after all posting on an Arduino forum...

And quick look at the code and schematic, where are your (internal) pull up's?

And my thought, or go back to a library and get that working. You're not the first trying them so you must be doing something wrong.

Or, go minimal and first get only the interrupt working. Aka, delete all the timer stuff.

And stop misusing an interrupt for a simple button :wink:

First question that pops to mind, why not use the Arduino HAL? You are after all posting on an Arduino forum...

Didn't know it existed, and after reading about it on Arduino Playground, I have to say I still can't fully understand its functionality. As you might be able to tell I'm not an Arduino savvy myself :confused: I would much appreciate it if you'd care to explain it a bit simpler, or clearer, or more in-laymans-terms-er.

And quick look at the code and schematic, where are your (internal) pull up's?

4th line after setup()

PORTD |= (1 << PORTD2) | (1 << PORTD3) | (1 << PORTD4);

Here I set them to HIGH, and because all ports are configured as INPUT by default, it should turn on the internal pullups. Though thanks for not noticing it, 'cause I will specifically set them up as INPUT prior to that line, just to be on the safe side...

You're not the first trying them so you must be doing something wrong.

No doubt about that! I know something must be wrong it's just I'm too unexperienced to know what it is D:

Or, go minimal and first get only the interrupt working.

I have tried a couple of sketches, as I said in my post I tested all the examples that came with many different libraries (Rotary Encoder libraries, of course), but all of them seem to fail when reading or expecting input from INT1!

And stop misusing an interrupt for a simple button :wink:

If there's a better way to achieve the same result, I'd be happy to use it!
My program's constantly reading sensors, comparing variables and keeping track of time since it starts running, I couldn't find the spot in the code for a digitalRead() or any other way of polling for a button press. Seemed like interrupts were the thing I'd been looking for - a way to enter setup mode without any reading getting interrupted or the timing getting messed up.

I've been away for a while but now that I'm back I will try to trigger only the INT1 interrupt alone by other means rather than the rotary encoder.

Thanks for the reply!

INT0 has a higher priority than INT1 if these are both being triggered simultaneously.
Anyway, one thing to try is disabling INT0 and see if INT1 then responds as it should.
The other thing to do is use pinMode() and attachInterrupt() instead of working directly with the processor registers. The could highlight if you have forgotten to clear/set some flag or similar. Also the code is more portable.

R2eno:
Didn't know it existed, and after reading about it on Arduino Playground,

That is the main power of the Arduino, to use a set of higher level functions like digitalWrite(), digitalRead() etc to read pins ect without having to "mess" with registers.

R2eno:
4th line after setup()

PORTD |= (1 << PORTD2) | (1 << PORTD3) | (1 << PORTD4);

Ah, yeah, missed that. Like I said, it's an Arduino forum so I'm expecting HAL functions and am a bit rusty on the direct port manipulation

R2eno:
No doubt about that! I know something must be wrong it's just I'm too unexperienced to know what it is D:

I would say, go back to th bare minimum and try to get that working. And if you have trouble, post all details (code, library used, schematic, problem description) here and we can help you.

R2eno:
If there's a better way to achieve the same result, I'd be happy to use it!
My program's constantly reading sensors, comparing variables and keeping track of time since it starts running, I couldn't find the spot in the code for a digitalRead() or any other way of polling for a button press.

Why not? Button presses are verrrryyyyy slow because we humans do it. Checking it just 10 times a second or so is already realllly unlikely to miss a press. So unless you are blocking your code somewhere (which you should not :wink: ), polling is more than fine.

R2eno:
Seemed like interrupts were the thing I'd been looking for - a way to enter setup mode without any reading getting interrupted or the timing getting messed up.

A lot of newbies think so because of the name. Should actually be called "temporary interrupt and continue from the exact same place you left". But think that name is a bit long to stick :stuck_out_tongue: Aka, they are meant for tasks that happen fast and/or often.

6v6gt:
INT0 has a higher priority than INT1 if these are both being triggered simultaneously.
Anyway, one thing to try is disabling INT0 and see if INT1 then responds as it should.
The other thing to do is use pinMode() and attachInterrupt() instead of working directly with the processor registers. The could highlight if you have forgotten to clear/set some flag or similar. Also the code is more portable.

I just finished testing with a very simple program I made, here's the code:

volatile bool p2 = false;
volatile bool p3 = false;

void setup() {
  Serial.begin(9600);
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  digitalWrite(2, LOW);
  digitalWrite(3, LOW);
  attachInterrupt(digitalPinToInterrupt(2), pin2, CHANGE);
  attachInterrupt(digitalPinToInterrupt(3), pin3, CHANGE);
}

void pin2(){
  p2 = true;
}

void pin3(){
  p3 = true;
}
void loop() {
  delay(1000);
  digitalWrite(3, LOW);
  digitalWrite(2, HIGH);
  if (p2) {
    Serial.println("INT0");
  } else {
    Serial.println("No INT");
  }
  delay(1000);
  digitalWrite(2, LOW);
  digitalWrite(3, HIGH);
  if (p3) {
    Serial.println("INT1");
  } else {
    Serial.println("No INT");
  }

}

And this was the output on the serial monitor:

12:57:39.615 -> INT0
12:57:40.599 -> No INT
12:57:41.630 -> INT0
12:57:42.614 -> No INT
12:57:43.598 -> INT0
12:57:44.629 -> No INT
12:57:45.613 -> INT0
12:57:46.644 -> No INT
12:57:47.628 -> INT0
12:57:48.612 -> No INT
12:57:49.596 -> INT0
12:57:50.628 -> No INT
12:57:51.612 -> INT0

Really, what the hell is up with INT1? lol

My ATMega328 is still on the PCB along with the rotary encoder and the LCD connected to it, I'm uploading via RX and TX (and RST) with an Arduino UNO board.

Try your software interrupt sketch with pins 2 and 3 set as outputs. The digitalWrite() requires that. The external interrupt enables with attachInterrupt() will respond. If the code does not show both interrupts, then there is something wrong with the hardware.

From the Data Sheet section on external interrupts:
"The External Interrupts are triggered by the INT0 and INT1 pins or any of the PCINT23...0 pins.
Observe that, if enabled, the interrupts will trigger even if the INT0 and INT1 or PCINT23...0 pins
are configured as outputs."

volatile bool p2 = false;
volatile bool p3 = false;

void setup() {
  Serial.begin(9600);
  //pinMode(2, INPUT);
  //pinMode(3, INPUT);
  pinMode(2,OUTPUT);
  pinMode(3, OUTPUT);
  digitalWrite(2, LOW);
  digitalWrite(3, LOW);
  attachInterrupt(digitalPinToInterrupt(2), pin2, CHANGE);
  attachInterrupt(digitalPinToInterrupt(3), pin3, CHANGE);
}

void pin2(){
  p2 = true;
}

void pin3(){
  p3 = true;
}
void loop() {
  delay(1000);
  digitalWrite(3, LOW);
  digitalWrite(2, HIGH);
  if (p2) {
    Serial.println("INT0");
  } else {
    Serial.println("No INT");
  }
  delay(1000);
  digitalWrite(2, LOW);
  digitalWrite(3, HIGH);
  if (p3) {
    Serial.println("INT1");
  } else {
    Serial.println("No INT");
  }
}

Output seen from the code above

INT0
INT1
INT0
INT1
INT0
INT1
INT0
INT1
INT0
INT1
INT0
INT1
pinMode(2, INPUT);
pinMode(3, INPUT);

Huh? I changed that to

pinMode(2, OUTPUT);
pinMode(3, OUTPUT);

and now it does exactly what you would expect.

Complete sketch:

volatile bool p2 = false;
volatile bool p3 = false;

void setup() {
  Serial.begin(115200);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  digitalWrite(2, LOW);
  digitalWrite(3, LOW);
  attachInterrupt(digitalPinToInterrupt(2), pin2, CHANGE);
  attachInterrupt(digitalPinToInterrupt(3), pin3, CHANGE);
}

void pin2() {
  p2 = true;
}

void pin3() {
  p3 = true;
}
void loop() {
  delay(1000);
  digitalWrite(3, HIGH);
  digitalWrite(2, HIGH);
  if (p2) {
    Serial.println("INT0 HIGH");
  } else {
    Serial.println("No INT0");
  }
  if (p3) {
    Serial.println("INT1 HIGH");
  } else {
    Serial.println("No INT1");
  }
  delay(1000);
  digitalWrite(2, LOW);
  digitalWrite(3, LOW);
  if (p2) {
    Serial.println("INT0 LOW");
  } else {
    Serial.println("No INT0");
  }
  if (p3) {
    Serial.println("INT1 LOW");
  } else {
    Serial.println("No INT1");
  }

}

Output:

INT0 HIGH
INT1 HIGH
INT0 LOW
INT1 LOW

@ cattledog: sorry, I didn't notice your reply until after I saved mine...

Yes. digitalWrite() to an input pin affects only the pull up resistor status. I was not aware though of the trick to force an external interrupt by writing to a defined output pin, which looks like a nice trick to force a jump to an ISR in software. I'm sure I'll find a use for that.

crossed with @Erik_Baas

I was just in the middle of editing my last reply 'cause I realised I never ser the variables p2 and p3 to false again, so once they triggered it would always output INT0 or INT1 even if the ISRs were not executing.

Try your software interrupt sketch with pins 2 and 3 set as outputs. The digitalWrite() requires that. The external interrupt enables with attachInterrupt() will respond. If the code does not show both interrupts, then there is something wrong with the hardware.

Your code did indeed work for me too! I just made the variables p2 and p3 false after printing INT0 and INT1 to make sure the ISRs actually execute every time through the loop.

Now I can rest assured there's nothing wrong with hardware... but why doesn't my program work?
Could it be bad wiring for the encoder?
I guess that's my safest bet, 'cause I've found a lot of different ways to connect the encoder to the MCU, and they were all different

Pin 4 Pin 5
(INT0) (INT1)
| |
| |
| |
P1 P2 P3
|
|______GND
|
|
P4 P5
|
|
Pin 6
(PCINT20)

This is my setup for the 5 pins on the encoder. P4 is the switch, and it works. As seen in my original post, Pin 4 and Pin 5 on the Arduino are set as INPUT_PULLUP, 'cause that's what I've read online you should set your pins to when using rotary encoders...

I feel we're getting there, people.. thanks for the replies!!

I can't read these direct port manipulation codes, just hope someone else jumps in... Sorry!

PS.: Isn't it possible to use PCINT18 and 19 instead of INT0 and 1?

Hey all, I'm trying to incorporate a rotary encoder to my standalone ATMega328 proyect and I'm struggling with said interrupt on the MCU.

Which chip do you have? Your description of PD2/INT0 and PD3/INT1 on chip pins 4 and 5 are only valid for the 28 pin DIP.

If you indeed had this chip, then think any issues with the original sketch are with the trigger pulses. The syntax for input pullup on PD2 and PD3 looks OK. When I put pulses on pins 8 and 9 and jumper them to pins 2 and 3 on an UNO, you see the interrupts triggered.

volatile bool intFlag = false;
volatile bool int0 = false;
volatile bool int1 = false;

void setup() {
  Serial.begin(57600);
  //pins are input by default DDD2 = 0 DDD3= 0
  //write them high sets INPUT_PULLUP
  PORTD |= (1 << PORTD2) | (1 << PORTD3);

  // Disable Global Interrupts to set them
  cli();
  // Enable interrupts INT0 & INT1 on CHANGE
  EIMSK |= (1 << INT0) | (1 << INT1);
  EICRA |= (1 << ISC00) | (1 << ISC10);
  // Enable global interrupts
  sei();
  //trigger pulses jumper 8/9 to interrupt inputs
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
}

ISR(INT0_vect) {
  int0 = true;
}

ISR(INT1_vect) {
  int1 = true;
}

void loop() {
  delay(1000);
  //trigger interrupts with pulses on 8 and 9
  digitalWrite(9, LOW);
  digitalWrite(9, HIGH);
  digitalWrite(8, LOW);
  digitalWrite(8, HIGH);
  
  if(int0) {
    int0 = false;
    Serial.println("INT0");
  } 
  if (int1) {
    int1 = false;
    Serial.println("INT1");
  }
}

PS.: Isn't it possible to use PCINT18 and 19 instead of INT0 and 1?

Technically, I'm pretty sure I can.. but there are disadvantages and also all libraries for encoders w/interrupts already use INT0 & INT1 by default, so I would have to mess with the code a lot more.
Also, PCINTs trigger in groups of pins and I have many inputs and outputs, so it could be triggered by lots of things and not only that one thing I want to trigger it with.

Which chip do you have? Your description of PD2/INT0 and PD3/INT1 on chip pins 4 and 5 are only valid for the 28 pin DIP.

Indeed, I have the ATMega328P DIP.

When I put pulses on pins 8 and 9 and jumper them to pins 2 and 3 on an UNO, you see the interrupts triggered.

So you've basically created a sort of poor-mans-PWM to trigger the interrupts with? Haha, jokes aside, I see your point, but I'm too big of a noob to fully comprehend the practical use of this scenario for my proyect D:

any issues with the original sketch are with the trigger pulses.

OK great, I don't really understand anything a lot about this.. is there maybe some kind of solution I could try? Or not necesarily a solution but something I could try with the circuit as it is setup on the PCB (DIP on socket with rotary encoder, LCD and TX/RX/RST lines connected to the Uno board) so that I could maybe troubleshoot the issue?

Thanks a bunch to all of you, guys

So you've basically created a sort of poor-mans-PWM to trigger the interrupts with? Haha, jokes aside, I see your point, but I'm too big of a noob to fully comprehend the practical use of this scenario for my proyect D:

It tells me that the issues with your project are with the hardware and connection from the encoder to the input pins, and not some lack of response to an interrupt.

Run this simple test code while slowly turning the encoder to check your wiring. You should see a repeating sequence like;

11
10
00
01
11

Or the reverse, depending which direction the encoder is turned. You are likely to see repeats of the same value, but the pattern should be clear.

#define encoderPinA 2
#define encoderPinB 3

void setup() {
 Serial.begin (115200);

 pinMode(encoderPinA, INPUT_PULLUP);
 pinMode(encoderPinB, INPUT_PULLUP);

}

void loop() {
 byte a = digitalRead(encoderPinA);
 byte b = digitalRead(encoderPinB);
 if (a == 1)
   Serial.print("1");
 else
   Serial.print("0");
 if (b == 1)
   Serial.print("1");
 else
   Serial.print("0");
 Serial.println();
}

Run this simple test code while slowly turning the encoder to check your wiring. You should see a repeating sequence like;

11
10
00
01
11

Ok, I've run the code you posted and on the serial monitor I only got 11 and 01. No 00 nor 01!
As I said, my suspicion is in the connections between encoder-MCU, but I couldn't find a better way to do it... or should I say the correct way lol

As I said, my suspicion is in the connections between encoder-MCU, but I couldn't find a better way to do it

on the serial monitor I only got 11 and 01

Yes, it certainly looks like the INT1 line is shorted HIGH. The INT0 line is responding with HIGH and LOW normally.

I would try and identify the short. Clean up what you can around the connection. The soldering does not look very good.

I know it doesn't look like the best, but just now I checked for contnuity on all the connections and they're OK; I also checked for shorts, like solder trails touching eachother, and I got nothing. Everything seems to be OK in that way, but for me there's something wrong about the pinout of the encoder... online, I really did find many many different ways to connect this sort of encoder and others similar to this, but none seem to be the same. There was always either a capacitor, a resistor, a pull to 5v on A or B pins, or something of that sort. Everyone's using the module of this encoder, which has pinouts "5V, GND, A, B" and somethimes the "SW" pin. I don't get where does the module connect the 5V? That's why I think something is missing in my connection.

Thanks for all the help!

There was always either a capacitor, a resistor, a pull to 5v on A or B pins, or something of that sort. Everyone's using the module of this encoder, which has pinouts "5V, GND, A, B" and somethimes the "SW" pin. I don't get where does the module connect the 5V? That's why I think something is missing in my connection.

Is the encoder a Keyes KY040?

The external 5v is to pull up the A and B pins (so they don't "float")when they are not connected to the C pin which is grounded. You are using the internal pullups of the Arduino to do the same thing. The A and B pins should switch between GND and 5V as they close and open with the C pin.

Its possible that the Arduino internal pullups are not strong enough, but the signature of your problem is that the INT1 pin is always HIGH, and is not switching to ground when the B pin is connected internally to C.

Has the encoder ever worked properly? Was it tested before installation on the board? The encoder is likely either defective, damaged, or shorted in a way you can't find. Digital read or a multimeter on the INT1 pin should not always read HIGH.

May I add my 2c to either help or further confuse.

I have been using some cheap rotary encoders for quite a while - never had any problems. For breadboard work I solder them and some header pins to a piece of vero board. The encoder output pins were always A C B. the two signal pins with the common in the middle. I connect A and B directly to the interrupt pins and C to ground. Did not yet bother with decoupling caps (I actually should).

I recently bought some Bourns encoders (what a difference) and tried to get then working on a ESP8266. I got result with rotating one way functioning sort of as expected but the other way behaving very strangely - effectively not working. I eventually found that the pin arrangement was different - A B C. The common was on the right (viewed from the bottom with pins at top). Swapped the leads and all work great.

I do not use any library. I used some code floating on the web for a long time with all sorts of variations. Below is what I use - int pins adapted to suit. The code below is for a 328P and composed as an example.

// Rotary Encoder defines and global (volatile) variables
#define ENC_SWITCH  A0            // Encoder switch pin
#define PIN_A  2                  // Hardware interrupt pin 2 (interupt 0)
#define PIN_B  3                  // Hardware interrupt pin 3 (interupt 1)
volatile byte aFlag = 0;          // Flag to expect a rising edge on pinA to signal a detent
volatile byte bFlag = 0;          // Flag to expect a rising edge on pinB to signal a detent (opposite direction to when aFlag is set)
volatile int16_t encoderPos;      // Current encoder position.

int oldEncPos;                    // Variable to hold last encoder position

void setup() {
  Serial.begin (115200);          

  // Setup Rotary Encoder
  pinMode(ENC_SWITCH, INPUT_PULLUP);
  digitalWrite(ENC_SWITCH, HIGH);
  pinMode(PIN_A, INPUT_PULLUP);             // set pinA as an input, pulled HIGH to the logic voltage
  pinMode(PIN_B, INPUT_PULLUP);             // set pinB as an input, pulled HIGH to the logic voltage
  attachInterrupt(0,encoderPinA,RISING);    // set an interrupt on PinA, looking for a rising edge signal and 
                                            // executing the "PinA" Interrupt Service Routine (below)
  attachInterrupt(1,encoderPinB,RISING);    // set an interrupt on PinB, looking for a rising edge signal and 
                                            // executing the "PinB" Interrupt Service Routine (below)
  
  oldEncPos = encoderPos = 90;              // Set encoder position to some start value
}

///////////////////////////////////////////////////////////////////////////////
//
void encoderPinA(){
  byte reading; 
  
  cli(); // stop interrupts happening before we read pin values
  reading = PIND & 0xC; // read all eight pin values then strip away all but pinA and pinB's values
  if (reading == B00001100 && aFlag) {    // check that we have both pins at detent (HIGH) and that we are expecting 
                                          // detent on this pin's rising edge
    encoderPos -= 2;  // decrement the encoder's position count

    bFlag = 0; // reset flags for the next turn
    aFlag = 0; // reset flags for the next turn
  }
  else if (reading == B00000100) bFlag = 1; // signal that we're expecting pinB to signal the transition to 
                                            // detent from free rotation
  sei(); //restart interrupts
}

///////////////////////////////////////////////////////////////////////////////
//
void encoderPinB(){
  byte reading; 

  cli(); // stop interrupts happening before we read pin values
  reading = PIND & 0xC; // read all eight pin values then strip away all but pinA and pinB's values
  if (reading == B00001100 && bFlag) {  // check that we have both pins at detent (HIGH) and that we are expecting 
                                        // detent on this pin's rising edge
    encoderPos += 2;  // increment the encoder's position count

    bFlag = 0; // reset flags for the next turn
    aFlag = 0; // reset flags for the next turn
  }
  else if (reading == B00001000) aFlag = 1; // signal that we're expecting pinA to signal the transition to 
                                            // detent from free rotation
  sei(); //restart interrupts
}

void loop() {

  if( oldEncPos != encoderPos )  {                    // Encoder was rotated
    encoderPos = constrain( encoderPos, 0, 180 );     // Ensure encoderPos is within bounds
    Serial.println(encoderPos);                       // Display new value
    oldEncPos = encoderPos;
  }

}

When used with the ESP8266 the interrupt routine has to be defined as follows:

//Assume pins 12 and 13 are used.

#define pinA        12
#define pinB        13

attachInterrupt(digitalPinToInterrupt(pinA), ISR_PinA, RISING);   // set an interrupt on pinA, looking for a rising edge signal
attachInterrupt(digitalPinToInterrupt(pinB), ISR_PinB, RISING);   // set an interrupt on pinB, looking for a rising edge signal

ICACHE_RAM_ATTR void ISR_PinA() {}
ICACHE_RAM_ATTR void ISR_PinB() {}

If the rotation direction is wrong, swap A and B.

Willem