EC11 – Rotary encoder

Ze noemen het ook wel een digitale volumeregelaar, maar daar doe je de encoder tekort mee. Inmiddels wordt het component o.a. toegepast als thermostaatknop, als selectiewiel op een smartwatch of op een oven, of in de auto en ja, ook op moderne audio-installaties waarbij volume en toon digitaal geregeld worden. De meeste encoders zijn voorzien van een drukknop waarmee de ingestelde waarde geselecteerd kan worden.

In deze uitgebreide tutorial leg ik uit wat een roterende encoder is en laat ik zien hoe je een roterende encoder op de Raspberry Pi aansluit en lever ik een tweetal basisscripts.

Daar gaan we…

Basisprincipe
Een roterende encoder is een elektronisch component dat de rotatie en richting van de draaiende knop detecteert en pulsen genereert als de as verdraait. Het werkt door twee interne contacten die contact maken en verbreken als er aan de knop wordt gedraaid. Terwijl je aan de knop draait, voel je hem “klikken”, wat aangeeft dat er minimaal één positie is gedraaid.

Als de interne contacten oorspronkelijk HOOG waren (contact maakten) na een enkele klik, zouden ze nu allebei LAAG zijn (contact verbreken). Met een simpel stukje logica kun je de richting van de rotatie bepalen. Daar kom ik later op terug.

Het principe en daarmee de voorkomende modellen roterende encoders zijn beperkt. Voor deze uitleg zal ik mij beperken tot het meest voorkomende basismodel met een roterende as, type EC11.

De EC11 rotary encoder

Hoe roterende encoders werken?
Binnenin de encoder bevindt zich een schijf met open sleuven die is verbonden met pin C, de (gemeenschappelijke) ground. Het heeft ook twee contactpinnen A en B, zoals hieronder weergegeven.

Wanneer je aan de knop draait, maken A en B contact in een specifieke volgorde met de ground (pin C), afhankelijk van de richting waarin je de knop draait.

Wanneer de contactpinnen contact maken met de ground, worden er twee (logische) signalen gegenereerd. Deze signalen zijn 90° uit fase omdat de ene pin eerder contact maakt met de ground dan de andere. Dit wordt kwadratuurcodering genoemd.

Signalen A en B zijn 90° uit fase

Draairichting bepalen
Wanneer de knop met de klok mee wordt gedraaid, wordt pin A vóór pin B met ground verbonden. Wanneer de knop tegen de klok in wordt gedraaid, wordt pin B vóór pin A met ground verbonden.

Door te monitoren wanneer elke pin verbinding maakt of loskoppelt van de ground, kunnen we bepalen in welke richting de knop wordt gedraaid. Dit kan worden bereikt door simpelweg de toestand van B te observeren wanneer de toestand van A verandert.

Wanneer A van status verandert:

  • als B != A, dan wordt de knop met de klok mee gedraaid:
  • als B = A, wordt de knop tegen de klok in gedraaid:

De pinnen van een roterende encoder
Er zijn verschillende uitvoeringen van de encodermodule met nagenoeg dezelfde aansluitingen. Mogelijk dat de signalen net even anders genoemd worden. Een van mijn modules ziet er als volgt uit:

  • CLK (clock) is de primaire uitgangspuls die gebruikt wordt om de hoeveelheid rotatie te bepalen. Elke keer als de knop met slechts één klik in een van beide richtingen wordt gedraaid, doorloopt de ‘CLK’-uitvoer één cyclus van HOOG en vervolgens LAAG.
  • DT (data) is vergelijkbaar met CLK-uitvoer, maar blijft achter op CLK met een faseverschuiving van 90°. Deze uitgang wordt gebruikt om de draairichting te bepalen.
  • SW (switch) is de uitgang van de drukknopschakelaar (actief laag). Wanneer de knop wordt ingedrukt, wordt de spanning LAAG.
  • Vcc (voeding) is de positieve (+) voedingsspanning, die ligt doorgaans tussen 3,3 en 5 volt.
  • GND is de ground.
Schema van de rotary encoder module

De encoder aansluiten
Nu we begrijpen hoe de roterende encoder werkt, is het tijd om hem in gebruik te nemen! Laten we de encoder aansluiten op de Raspberry Pi. De verbindingen zijn vrij eenvoudig.

  • Sluit de CLK-pin van de module aan op pin 11 (GPIO17) van de Pi
  • Sluit nu de DT-pin van de module aan op pin 12 (GPIO18) van de Pi
  • De SW-pin van de module wordt in voorbeeld 1 niet gebruikt!
  • En de GND-pin van de module op een willekeurige GND-pin (zoals pin 14) van de Pi
  • Sluit de +pin van de module aan op de 3.3V-uitgang (pin 1 of pin 17) van de Pi
Aansluittabel om de pinnen van de encoder en de Pi met elkaar te verbinden

Andere vormfactor, andere pinaanduiding
Ik heb nog een tweede rotary encoder module die op een ronde print gemonteerd is. Bij deze is de pinaanduiding weliswaar anders, maar de signalen zijn hetzelfde als degene die hierboven besproken is. Het voordeel van deze ronde EC11 module is dat er een debounce (anti-stuiter) circuit is aangebracht. Mocht je een encoder willen aanschaffen, raad ik aan deze aan. Het kan behoorlijk frustreren als je circuit ‘stuitert’ en meer pulsen afgeeft dan wenselijk is.

Het is wel jammer dat ze andere benamingen aan de pinnen van de module hebben gegeven. Daarom heb ik de pinnen vertaald naar de hierboven genoemde pin namen.

Roterende encoder module met debounce circuit

De volgende afbeelding toont de bedrading.

Dat is het voor zover het de bedrading betreft. Je hoeft je geen zorgen te maken over weerstanden of andere componenten, aangezien de encoder ingebouwde weerstanden van 10k Ohm heeft, waardoor de stroom tot een minimum (0,33mA) wordt beperkt!


Python – voorbeeld 1 (zonder cijferselectie)
Je kunt de voorbeeldcode hieronder kopiëren en in de (nano) editor plakken.

De voorbeeldcode is een eenvoudige teller. Als je de encoder met de klok meedraait, wordt het getal bij elke “klik” met 1 verhoogd en als je het tegen de klok in draait, neemt het bij elke “klik” met 1 af. Ook wordt de draairichting getoond. Dit script maakt geen gebruik van de drukknop (SW).

import RPi.GPIO as GPIO
from time import sleep

counter = 10

CLK = 17
DT  = 18

# Mocht je de draai-/telrichting willen omkeren. Dan kan dit op twee manieren:
# 1 - Wissel de twee draden (clk en dt) om op de GPIO header
# 2 - Wissel de + en de - om hieronder in de code waar de counter wordt opgehoogd/verlaagd

def init():
    print ("\033[1;1;1m,\033c")
    print ("Rotary Encoder Test Programma\n")
    GPIO.setwarnings(True)
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(CLK, GPIO.IN)
    GPIO.setup(DT, GPIO.IN)
    GPIO.add_event_detect(CLK, GPIO.RISING, callback=rotation_decode, bouncetime=10)
    return

def rotation_decode(CLK):
    global counter
    sleep(0.002)
    Switch_A = GPIO.input(CLK)
    Switch_B = GPIO.input(DT)

    if (Switch_A == 1) and (Switch_B == 0):
        counter += 1
        print ("Draairichting -> ", counter)
        while Switch_B == 0:
            Switch_B = GPIO.input(DT)
        while Switch_B == 1:
            Switch_B = GPIO.input(DT)
        return

    elif (Switch_A == 1) and (Switch_B == 1):
        counter -= 1
        print ("Draairichting <- ", counter)
        while Switch_A == 1:
            Switch_A = GPIO.input(CLK)
        return
    else:
        return

def main():
    try:
        init()
        while True :
            sleep(1)

    except KeyboardInterrupt:
        GPIO.cleanup()

if __name__ == '__main__':
    main()
Demo van script dat draairichting aangeeft en waarden verhoogd/verlaagd

Python – voorbeeld 2 (met cijferselectie middels de drukknop)

Je kunt voorbeeldcode 2 hieronder kopiëren en in de (nano) editor plakken.

De voorbeeldcode is een eenvoudige teller met cijferselectie middels de drukknop. Als je de encoder met de klok meedraait, wordt het getal bij elke “klik” met 1 verhoogd en als je het tegen de klok in draait, neemt het bij elke “klik” met 1 af. Wil je het cijfer selecteren, druk dan bovenop de knop en het cijfer wordt geïsoleerd. Vervolgens wordt je in staat gesteld om zelf een functie toe te voegen om iets met het cijfer te gaan doen.

De aansluittabel is dezelfde als bij voorbeeld 1, met de aanvulling dat de switch nu ook aangesloten wordt.
from RPi import GPIO
from time import sleep

clk = 17
dt = 18
sw = 27

GPIO.setmode(GPIO.BCM)
GPIO.setup(clk, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(dt, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(sw, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

# Mocht je de draai-/telrichting willen omkeren. Dan kan dit op twee manieren:
# 1 - Wissel de twee draaden (clk en dt) om op de GPIO header
# 2 - Wissel de + en de - om hieronder in de code waar de counter wordt opgehoogd/verlaagd

def rotary_encoder():
    "Detecteer de draaipulsen en de drukknop"

    counter = 0
    clkLastState = GPIO.input(clk)

    try:
        while True:
            clkState = GPIO.input(clk)
            dtState = GPIO.input(dt)
            swState = GPIO.input(sw)
            if clkState != clkLastState:
                if dtState != clkState:
                    counter += 1
                else:
                    counter -= 1
                print (counter)
            if swState != True:
                print ("\033[1;32mGeselecteerde waarde:",counter,"\033[1;m")
                sleep(0.5)
                vervolgstap(counter)
            else:
                clkLastState = clkState
            sleep(0.01)
    finally:
        GPIO.cleanup()

def vervolgstap(counter):
    "Vervolgstap"

    # Dit is de functie die je als vervolgstap kunt gebruiken
    print ("\n\033[1;33mNote: Deze tekst is een voorbeeld. Hier kun je je eigen functie neerzetten.")
    print ("      Je zou hier bijvoorbeeld de geselecteerde waarde kunnen opslaan,")
    print ("      een waarde kunnen variëren zoals volume en/of op een display laten zien.\033[1;m")
    print ("\n      De geselecteerde waarde als input voor de functie is",counter,"\n")

# ---------------main--------------

print ("\033[1;1;1m,\033c")   # scherm wissen
print ("Draai aan de knop en druk erop als je de geselecteerde waarde wilt isoleren") 

rotary_encoder()
Demo van het script voorbeeld 2 dat waarden verhoogd/verlaagd en waarde selecteert met drukknop

Tip: Wist je dat je in de nano-editor met de toetscombinatie [Alt]-[Shift]-3 regelnummers inschakelt?

Nog even ter verduidelijking
De logica voor het bepalen van de draairichting is heel eenvoudig te coderen, maar niet zo gemakkelijk te begrijpen. Daarom hier nog even de stappen van de signaalwijzigingen bij de rotatie/’klik’:

Voor een rotatie met de klok mee:

  1. CLK en DT zijn beide HOOG
  2. CLK wordt LAAG, DT blijft HOOG -> clkLastState was HOOG, clkState is LAAG (clkState != clkLastState ) dus nu kunnen we de richting bepalen -> dtState is HOOG, clkState LOW, dtState != clkState, +1
  3. DT wordt LAAG, CLK blijft LAAG -> clkLastState was LAAG, clkState is LAAG -> Er gebeurt niets

Voor een rotatie tegen de klok in:

  1. CLK en DT zijn beide HOOG
  2. DT wordt LAAG, CLK blijft HOOG -> clkLastState was HOOG, clkState is HOOG -> Er gebeurt niets
  3. CLK wordt LAAG, DT blijft LAAG -> clkLastState was HOOG, clkState is LAAG (clkState != clkLastState ) dus nu kunnen we de richting bepalen -> dtState is LOW, clkState LOW, dtState == clkState, -1

Huiswerk
Om de functionaliteit van de encoder te oefenen is altijd goed om zelf iets te ontwikkelen. Zo zou je een roterend cijferslot kunnen maken dat na 5 goed ingevoerde getallen een rode led groen laat oplichten. Je gebruikt de drukknop functie om de cijfers te op te slaan. Laat me weten als het gelukt is!

Have A Nice Day!