Incremental rotary encoders (Part 4)

Part 1234567, 8, 9, 10

I must confess here that I was not feeling so comfortable at interrupts while writing the early posts on rotary encoders. But now things have changed and we even investigated a very interesting function which allows us to trigger a external interrupt on any change in state on one or more (up to all pins from one particular PORT). Note that the code is designed to be versatile so that the rotary encoder terminals may be wired to any pin from the same PORT.

The set up now contains:

	// Setup encoder pins as inputs
	DDRD  &= ~((1 << ENC_PIN_B) | (1 << ENC_PIN_A) | (1 << ENC_PIN_BUTTON));
	// Bias pins
	PORTD |=  ((1 << ENC_PIN_B) | (1 << ENC_PIN_A) | (1 << ENC_PIN_BUTTON));
	// Attach interrupt to PIND5, PIND6
	PCMSK2 = ((1 << PCINT23) | (1 << PCINT22) | (1 << PCINT21)); // Pin Change Mask Register 2
	PCICR = (1 << PCIE2); // Pin Change Interrupt Enable 2

And the Interrupt Service Request looks like:

ISR(PCINT2_vect) {
// Interpret switch
	static byte prevState = B11;
	byte startState = state(); // Get current state
	for (int i=0; i < 1600; i++) asm volatile ("NOP"); // Simple debouncing. 1 nop = 62.5 ns, 1600 nops approx 100 us
	byte stopState = state(); // Get current state
	if ((startState == stopState) && (stopState != prevState)) { // check if the previous state was stable and different from the previous one
		enc_buttonState = (startState & B1); // Update button state
		if ((startState >> 1) == B11) { // If in idle state
		  if (enc_patternBuffer == enc_patternCW) 
				enc_counter++; 		
		  if (enc_patternBuffer == enc_patternCCW) 
				enc_counter--; 		
			enc_patternBuffer = 0x00; // reset buffer
		}
		else {
			enc_patternBuffer <> 1) ;
		}
		prevState = stopState; // Record state for next change
	}
}

The debouncer is pretty trivial and proved to work great. The delay is performed by looping a No Operation function. Have you seen the way pulses are managed? Each pulse is pushed in a stack buffer. The count up/down decision is made on the pattern of the buffer content, with

byte enc_patternCW =  B010010;
byte enc_patternCCW = B100001;

based on

  ---     ---     ---             ---     ---     ---
 |   |   |   |   |   |     A     |   |   |   |   |   |  
-     ---     ---     -         -     ---     ---     --
---     ---     ---                 ---     ---     ---
   |   |   |   |   |       B       |   |   |   |   |   | 
    ---     ---     ---         ---     ---    ---  

A 1 1 0 0 1                       A 1 0 0 1 1
B 1 0 0 1 1                       B 1 1 0 0 1

Turn Left                         Turn right
in idle state A=B=1

This is the state() function, which is called twice from the ISR:

byte state(void) {
// Analyze input lines state
	byte statusA = ((PIND >> ENC_PIN_A) & 0x01); // Read input line A state
  byte statusB = ((PIND >> ENC_PIN_B) & 0x01); // Read input line B state
  byte statusC = ((PIND >> ENC_PIN_BUTTON) & 0x01); // Read input line B state
	return((statusA << 2) | (statusB << 1) | statusC);
}

The loop() routine below illustrate how to use the enc_buttonState and the enc_counter global variables

void loop() {
	static int lastCount = 0;
	if (enc_buttonState == 0x00) 
		enc_counter = 0;
	if (lastCount != enc_counter) {
		Serial.print(enc_counter, DEC); 	
		Serial.println();
		lastCount = enc_counter;
	}
	delay(10);
}

Next post on same subject

Leave a Reply

You must be logged in to post a comment.