Incremental rotary encoders (Part 3)

Part 1234567, 8, 9, 10

The very first rotary encoder that I used had no integrated push button, which is a pity for designing human interfaces. I ordered a couple of ALPS encoders which integrate this feature. Note that the ALPS encoders (at least the ones that I received) generate 4 state changes during one rotational step.

Then I had to revise my code in order to interpret the push button state. I left apart the -1, 0, 1 returned values from the early design, and I used constants instead. The interpretation of the interval between two counts has been moved in the calling procedure (enc_test()).

The following code works great, as long as the delay between two calls is not too long (say less than 5 ms). So that care shall be taken while integrating this procedures in complex code.

/* 
Rotary encoder read example 
  ---     ---     ---            ---     ---     ---
 |   |   |   |   |   |     A    |   |   |   |   |   |  
-     ---     ---     -        -     ---     ---     --
---     ---     ---                ---     ---     ---
   |   |   |   |   |       B      |   |   |   |   |   | 
    ---     ---     ---        ---     ---    ---  
       Turn Left                       Turn right
A 1 1 0 0 1                    A 1 1 1 0 1
B 1 0 0 1 1                    B 0 1 0 0 0
*/
#define ENC_PIN_BUTTON PINB2
#define ENC_PIN_A PINB3
#define ENC_PIN_B PINB4

#define ENC_STT_IDLE 0x00
#define ENC_STT_CW 0x01
#define ENC_STT_CCW 0x02
#define ENC_STT_BTN_UP 0x00
#define ENC_STT_BTN_DOWN 0x04

#define LED_PIN PINB5
byte cwRotorState[4] = {B10, B00, B11, B01};
byte ccwRotorState[4] = {B01, B11, B00, B10};
byte pulsesPerStep = 4; // This value may change according to encoders specifications
unsigned int samplingTime = 100; // In us

void setup() {
	// Setup encoder pins as inputs
	DDRB  &= ~(1 << ENC_PIN_B);
	DDRB  &= ~(1 << ENC_PIN_A);
	DDRB  &= ~(1 << ENC_PIN_BUTTON);
	// Bias pins
	PORTB |=  (1 << ENC_PIN_B); 
	PORTB |=  (1 << ENC_PIN_A);
	PORTB |=  (1 << ENC_PIN_BUTTON);	
	blinkLed(5, 200);
	Serial.begin (115200);
	Serial.println("Ready"); 
}

void loop() {
	delay(1);
	enc_test();
}

 void enc_test(void) {
	static int counts = 0;
	static long tim_lastRead = 0;
	long tim_now = millis();
	byte encoderRead = enc_read();
	if (encoderRead) {
		int multiplier = 1;
		int interval = (tim_now - tim_lastRead);
		if (interval < 20)
			multiplier = (200 / interval);
		tim_lastRead = tim_now; // Record last read time
		if (encoderRead & ENC_STT_BTN_DOWN)
			counts = 0;		
		else if (encoderRead & ENC_STT_CW)
			counts += multiplier;
		else if (encoderRead & ENC_STT_CCW)
			counts -= multiplier;	
		Serial.print(counts, DEC); 	
		Serial.println();
	}
}

 void blinkLed(int nbrBlinks, int intervals){
// Blink status led
		// Set led pin as output
	DDRB  |=  (1 << LED_PIN);
	for (int i = 0; i < (nbrBlinks * 2); i++) {
		PORTB ^=  (1 << LED_PIN); // toggle status led
		delay (intervals);
	}
}

byte state(void) {
	return(((PINB >> ENC_PIN_B) & 0x01) | (((PINB >> ENC_PIN_A) & 0x01) << 1) | (((~PINB >> ENC_PIN_BUTTON) & 0x01) << 2));
}

byte enc_read(void) {
// returns change in encoder state (-1: ccw, 0: no change, 1: cw) 
  byte result = 0;
	static byte prevRotorState = 0;
	static byte prevButtonState = 0;
	static int bufferedCounts = 0;
	byte startState = state(); // Get current state
  delayMicroseconds(samplingTime); // Wait safety bounce time
	byte stopState = state(); // Get current state
	if (startState == stopState) { // Check if the state was stable
		// Interpret button state
		byte buttonState = stopState & B00000100;
		if (buttonState != prevButtonState) { // Check if button state has changed
			if (buttonState) 
				result |= ENC_STT_BTN_DOWN;			
			prevButtonState = buttonState;
		}
		// Interpret rotor state
		byte rotorState = stopState & B00000011; // That's the A and B pin state
		if (rotorState != prevRotorState) { // Check if rotor state has changed
			if (rotorState == cwRotorState[prevRotorState]) 
				bufferedCounts++; 
			else if (rotorState == ccwRotorState[prevRotorState]) 
				bufferedCounts--; 
			else 
				bufferedCounts = 0;
			if (abs(bufferedCounts) == pulsesPerStep) {
				if (bufferedCounts > 0) 
					result |= ENC_STT_CW;
				if (bufferedCounts < 0) 
					result |= ENC_STT_CCW;	
				bufferedCounts = 0;
			}
			prevRotorState = rotorState; // Record state for next pulse interpretation
		}
	}
	return(result);
}

Next post on same subject

Leave a Reply

You must be logged in to post a comment.