The answer to my question is yes.
Normally you only count the falling edge of one of the outputs of an incremental encoder as a pulse. That gives you the specified number of ticks for that kind of encoder - 12 ticks per turn in the case of my encoder.
If you instead write your code to respond to either rising or falling edge changes on one of the pins, you get 24 ticks per revolution.
If you instead write your code to respond to rising or falling edge changes on either pin, you get 48 ticks per revolution.
The code I came up with looks like this:
void setup() {
//Use this setup code for 12 ticks/revolution
attachInterrupt(digitalPinToInterrupt(rotaryPinA), encode, FALLING);
/*
Use this setup code for 24 ticks/revolution
(trigger interrupt on rising and falling edge)
attachInterrupt(digitalPinToInterrupt(rotaryPinA), encode, CHANGE);
*/
/*
Use this setup code for 48 ticks/revolution
(trigger interrupts on rising and falling edge of either pin)
attachInterrupt(digitalPinToInterrupt(rotaryPinA), encode, CHANGE);
attachInterrupt(digitalPinToInterrupt(rotaryPinB), encodePinB, CHANGE);
*/
//The rest of your setup code...
}
And the interrupt service routines (ISRs):
void encode() {
rotaryValueChanged = true;
//Read the PinA & PinB (Digital Pins 2 & 3) using port register PINE, 4th and 5th bit
//Fast equivalent to pinAState = digitalRead(rotaryPinA) == LOW
bool pinAState = (PINE & (1 << 4)) == 0;
bool pinBState = (PINE & (1 << 5)) == 0;
rotaryValue += (pinAState == pinBState) ? -1 : 1;
}
void encodePinB() {
rotaryValueChanged = true;
//Read the PinA & PinB (Digital Pins 2 & 3) using port register PINE, 4th and 5th bit
bool pinAState = (PINE & (1 << 4)) == 0;
bool pinBState = (PINE & (1 << 5)) == 0;
rotaryValue += (pinAState != pinBState) ? -1 : 1;
}
If you only set up one interrupt on rotaryPinA, the 2nd interrupt method never gets called.
The code above is specific to the Arduino Mega 2560 and other boards that have the same pin to register mappings. It uses interrupts so it doesn't miss state changes in the pins, and reads the pin states using port registers for speed.
Note that if you're only adding a falling edge interrupt you can skip the read of pin A (since when the pin state falls it will, by definition be at a logic 0 whenever the ISR gets called.)
To get this to work well I had to build the RC filter circuit described in the spec sheet for my encoder:
https://www.bourns.com/docs/Product-Datasheets/PEC12R.pdf
The diagram looks like this:
And I had to add Schmitt triggers between the filter outputs shown above and the input lines to the Arduino.
Without the Schmitt triggers it gets jitter when the voltage from the filter doesn't switch between HIGH and LOW values fast enough. The Schmitt triggers exclude spurious readings from those "dead zone" voltages.
With those changes the readings from the encoder are quite good, although it still measure ± 1 tick or so per revolution for some reason.
I wish now I had bought the version of the encoder that has detents so it rotates in discrete clicks.
got 24 steps