The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Day: December 11, 2012

  • Arduino Snippets: Quadrature Knob

    Any gadget goes better with a knob and what better knob than one with detents all the way around? This one even has a push-on momentary switch on the shaft, so you can switch gain / change modes / control power while turning:

    Quadrature Knob with Push Switch
    Quadrature Knob with Push Switch

    Using the Arduino’s internal pullups means you just plug the wires into the header sockets and it works:

    Quadrature Knob and Switch - breadboard
    Quadrature Knob and Switch – breadboard

    The code uses the same interrupt-driven state machine I described there to avoid glitches and twitches. The test code dumps the accumulated knob rotation count and switch state out the serial port:

    Knob encoder
    Ed Nisley - KE4ZNU - November 2012
    Knob count: 0 Switch: 0
    Knob count: 0 Switch: 1
    Knob count: 1 Switch: 1
    Knob count: 1 Switch: 0
    Knob count: 2 Switch: 0
    Knob count: 3 Switch: 0
    Knob count: 4 Switch: 0
    Knob count: 4 Switch: 1
    Knob count: 3 Switch: 1
    Knob count: 2 Switch: 1
    Knob count: 1 Switch: 1
    Knob count: 0 Switch: 1
    Knob count: -1 Switch: 1
    Knob count: -2 Switch: 1
    Knob count: -3 Switch: 1
    Knob count: -4 Switch: 1
    Knob count: -5 Switch: 1
    Knob count: -5 Switch: 0
    Knob count: -4 Switch: 0

    Yes, you (well, I) can spin the knob fast enough to accumulate a few counts between the 10 ms updates and it will return to zero at the same physical location, so it’s not losing any counts in the process.

    Knob encoder
    Ed Nisley - KE4ZNU - November 2012
    Knob count: 0 Switch: 0
    Knob count: -4 Switch: 0
    Knob count: -6 Switch: 0
    Knob count: -7 Switch: 0
    Knob count: -6 Switch: 0
    Knob count: -4 Switch: 0
    Knob count: -3 Switch: 0
    Knob count: -6 Switch: 0
    Knob count: -8 Switch: 0
    Knob count: -9 Switch: 0
    Knob count: -10 Switch: 0
    Knob count: -12 Switch: 0
    Knob count: -18 Switch: 0
    Knob count: -19 Switch: 0
    Knob count: -20 Switch: 0
    Knob count: -19 Switch: 0
    Knob count: -18 Switch: 0
    Knob count: -15 Switch: 0
    Knob count: -9 Switch: 0
    Knob count: -7 Switch: 0
    Knob count: -6 Switch: 0
    Knob count: -4 Switch: 0
    Knob count: -3 Switch: 0
    Knob count: -2 Switch: 0
    Knob count: -1 Switch: 0
    Knob count: 0 Switch: 0

    Not very exciting as it stands, but it’s got plenty of upside potential.

    The Arduino source code:

    // Quadrature knob with switch
    // Ed Nisley - KE4ANU - November 2012
    // Based on:
    // https://softsolder.com/2009/03/03/reading-a-quadrature-encoded-knob-in-double-quick-time/
    
    //----------
    // Pin assignments
    
    const byte PIN_KNOB_A = 2;			// knob A switch - must be on ext interrupt 2
    const byte PIN_KNOB_B = 4;			//  .. B switch
    const byte PIN_KNOB_SW  = A5;		//  .. push-close momentary switch
    
    const byte PIN_SYNC = 13;			// scope sync
    
    //----------
    // Constants
    
    const int UPDATEMS = 10;				// update LEDs only this many ms apart
    
    #define TCCRxB 0x02						// Timer prescaler
    
    //----------
    // Globals
    
    enum KNOB_STATES {KNOB_CLICK_0,KNOB_CLICK_1};
    
    volatile char KnobCounter = 0;
    volatile char KnobState;
    
    char PrevKnobCounter = 0;
    char KnobSwitch, PrevSwitch = 0;
    
    unsigned long MillisNow;
    unsigned long MillisThen;
    
    //-- Helper routine for printf()
    
    int s_putc(char c, FILE *t) {
      Serial.write(c);
    }
    
    //-- Knob interrupt handler
    
    void KnobHandler(void)
    {
    	byte Inputs;
    	Inputs = digitalRead(PIN_KNOB_B) << 1 | digitalRead(PIN_KNOB_A);  // align raw inputs
    //	Inputs ^= 0x02;                             // fix direction
    
    	switch (KnobState << 2 | Inputs) {
    	case 0x00 : 				// 0 00 - glitch
            break;
    	case 0x01 : 				 // 0 01 - UP to 1
            KnobCounter++;
    		KnobState = KNOB_CLICK_1;
    		break;
    	case 0x03 : 				 // 0 11 - DOWN to 1
            KnobCounter--;
    		KnobState = KNOB_CLICK_1;
    		break;
    	case 0x02 : 				 // 0 10 - glitch
            break;
    	case 0x04 : 				 // 1 00 - DOWN to 0
            KnobCounter--;
    		KnobState = KNOB_CLICK_0;
    		break;
    	case 0x05 : 				 // 1 01 - glitch
            break;
    	case 0x07 : 				 // 1 11 - glitch
            break;
    	case 0x06 : 				 // 1 10 - UP to 0
            KnobCounter++;
    		KnobState = KNOB_CLICK_0;
    		break;
    	default :  					// something is broken!
            KnobCounter = 0;
    		KnobState = KNOB_CLICK_0;
    	}
    }
    
    //-- Read switch, flip sense
    
    char ReadSwitch(int PinNumber) {
    	return !digitalRead(PinNumber);
    }
    
    //------------------
    // Set things up
    
    void setup() {
    	pinMode(PIN_SYNC,OUTPUT);
    	digitalWrite(PIN_SYNC,LOW);	// show we arrived
    
    //	TCCR1B = TCCRxB;					// set frequency for PWM 9 & 10
    //	TCCR2B = TCCRxB;					// set frequency for PWM 3 & 11
    
    	pinMode(PIN_KNOB_B,INPUT_PULLUP);
    	pinMode(PIN_KNOB_A,INPUT_PULLUP);
    	pinMode(PIN_KNOB_SW,INPUT_PULLUP);
    
    	KnobState = digitalRead(PIN_KNOB_A);
    	PrevSwitch = digitalRead(PIN_KNOB_SW);
    	attachInterrupt((PIN_KNOB_A - 2),KnobHandler,CHANGE);
    
    	Serial.begin(9600);
    	fdevopen(&s_putc,0);				// set up serial output for printf()
    
    	printf("Knob encoder\r\nEd Nisley - KE4ZNU - November 2012\r\n");
    
    	MillisThen = millis();
    
    }
    
    //------------------
    // Run the test loop
    
    void loop() {
    
    	MillisNow = millis();
    
    	if ((MillisNow - MillisThen) &amp;gt; UPDATEMS) {
    
    		digitalWrite(PIN_SYNC,HIGH);
    
    		KnobSwitch = ReadSwitch(PIN_KNOB_SW);
    		if ((PrevKnobCounter != KnobCounter) || (PrevSwitch != KnobSwitch)) {
    			printf("Knob count: %d Switch: %d\n",KnobCounter,KnobSwitch);
    			PrevKnobCounter = KnobCounter;
    			PrevSwitch = KnobSwitch;
    		}
    
    		digitalWrite(PIN_SYNC,LOW);
    
    		MillisThen = MillisNow;
    	}
    
    }