Skip to content

Instantly share code, notes, and snippets.

@markadr
Last active November 15, 2021 23:56
Show Gist options
  • Save markadr/5857df614b0603b9d710 to your computer and use it in GitHub Desktop.
Save markadr/5857df614b0603b9d710 to your computer and use it in GitHub Desktop.
Example code to deal with a rotary encoder via the PI4J API on a RaspberryPi.
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPinDigitalInput;
import com.pi4j.io.gpio.Pin;
import com.pi4j.io.gpio.PinPullResistance;
import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent;
import com.pi4j.io.gpio.event.GpioPinListenerDigital;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author Mark de Reeper
*/
public class RotaryEncoder {
private final GpioPinDigitalInput inputA;
private final GpioPinDigitalInput inputB;
private final GpioController gpio;
private long encoderValue = 0;
private int lastEncoded = 0;
private boolean firstPass = true;
private RotaryEncoderListener listener;
// based on [lastEncoded][encoded] lookup
private static final int stateTable[][]= {
{0, 1, 1, -1},
{-1, 0, 1, -1},
{-1, 1, 0, -1},
{-1, 1, 1, 0}
};
private static final Logger LOGGER = Logger.getLogger(RotaryEncoder.class.getName());
public RotaryEncoder(Pin pinA, Pin pinB, long initalValue) {
encoderValue = initalValue;
gpio = GpioFactory.getInstance();
inputA = gpio.provisionDigitalInputPin(pinA, "PinA", PinPullResistance.PULL_UP);
inputB = gpio.provisionDigitalInputPin(pinB, "PinB", PinPullResistance.PULL_UP);
GpioPinListenerDigital inputAListener = new GpioPinListenerDigital() {
@Override
public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent gpdsce) {
int stateA = gpdsce.getState().getValue();
int stateB = inputB.getState().getValue();
calcEncoderValue(stateA, stateB);
LOGGER.log(Level.FINE, "{0}{1} encodedValue: {2}", new Object[]{stateA, stateB, encoderValue});
}
};
inputA.addListener(inputAListener);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
LOGGER.info("RotarySwitch: Shutting down....");
if (gpio != null) {
gpio.removeAllListeners();
gpio.shutdown();
}
}
});
LOGGER.log(Level.INFO, "RotarySwitch initialised on pinA {0} and pinB {1}",
new String[]{pinA.getName(), pinB.getName()});
}
public long getValue() {
return encoderValue;
}
public void setListener(RotaryEncoderListener listener) {
this.listener = listener;
}
private void calcEncoderValue(int stateA, int stateB) {
// converting the 2 pin value to single number to end up with 00, 01, 10 or 11
int encoded = (stateA << 1) | stateB;
if (firstPass) {
firstPass = false;
} else {
// going up states, 01, 11
// going down states 00, 10
int state = stateTable[lastEncoded][encoded];
encoderValue += state;
if (listener != null) {
if (state == -1) {
listener.down(encoderValue);
}
if (state == 1) {
listener.up(encoderValue);
}
}
}
lastEncoded = encoded;
}
}
/**
*
* @author Mark de Reeper
*/
public interface RotaryEncoderListener {
void up(long encoderValue);
void down(long encoderValue);
}
@marcandreuf
Copy link

Hello Mark, tanks for sharing your code.

I am tring to use a rotary encoder with pi4j and your RotaryEncoderListener looks very good. However I have a strange effect while rotating the encoder, I get two events fired up for on position turn in the encoder so the numbers goes up or down twice instead of one at a time.

Do you have an idea what would be the fault ? Maybe the encoder itself ? I am using this encoder http://szkeyes.en.alibaba.com/product/1942288144-222339414/KEYES_Rotary_encoder_module_for_arduino_with_demo_code.html it may be not compatible?

Many thanks Mark
Marc,

@fredoche
Copy link

Thanks for the code Mark :)
@marcandreuf I have a similar problem: Going left is fine, but going right triggers one 'up' event and ont 'down' event . Did you find the cause of your problem ?

@fredoche
Copy link

i'm not really proficient with pi4j but something bothers me with, since the events implementation are in the end managed by a thread executor, I believe that, since we read from two values, we will run into race conditions pretty much all the time. What do you think ?

@fredoche
Copy link

I made a version that works a bit better for my usage here https://gist.github.com/fredoche/69c5f59cead4b7828653 .
Because of the threading issue, I use a Lock to avoid concurrent and buggy reactions. I also noticed that the method described here worked better for me: http://theatticlight.net/posts/Reading-a-Rotary-Encoder-from-a-Raspberry-Pi/
Thanks for the original submission.

@kachurovskiy
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment