Rotary encoder, gray code og kontaktprel
En af mine indkøbte dimser fra ebay er en “rotary encoder”.
Det er en dims der kan konvertere roterende bevægelser til digitale signaler. Den jeg har købt har 20 positioner på en omgang – og leverer output i to digitale pins der er “gray coded”.
Detekteringen sker mekanisk. Det betyder at det er kontakter der clicker ind og ud når man drejer akslen.
En udfordring ved den konstruktion er at mekaniske kontakter laver noget man kalder kontaktprel. Altså at kontakten hopper til og fra når den skifter position. På engelsk kalder man det bounching. Der er forskellige løsninger til at fjerne kontaktprel – på engelsk kaldes metoderne under et “de-bouncing”.
Der er en hel del mellemregninger – men jeg lover en (i min optik) lille lækkerbisken nederst.
Gray code
Gray code er en utroligt simpel men meget effektiv teknik der kan benyttes alle de steder hvor det kan give problemer når mere end ét bit ændrer sig ad gangen.
Et eksempel… binært 1 øges til 2:
01 -> 10
Lad os nu forestille os at vi læser de 2 bit af to gange. Første gang vi læser er lige før der lægges “1” til “01”. Bit 0 læses som “1”. Anden gang vi læser er lige efter opdateringen så nu er bit 1 = “1”. Når vi samler de to bit får vi “11” – og ikke “10” som er den korrekte værdi. Så – afhængig af hvornår man læser kan man få tre forskellige værdier hvoraf den ene er helt i skoven: “01”, “10” og “11”.
Nå – men hvad kan grey code da gøre ved det? Jo… Som nævnt er det smarte at kun ét bit ændrer sig ad gangen:
| Index | bit 1 | bit 0 | decimal |
| 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | 1 |
| 2 | 1 | 1 | 3 |
| 3 | 1 | 0 | 2 |
Så – i eksemplet fra før – så vil man når man går fra 1 -> 2 i stedet få:
01 -> 11
Så læsning før / efter vil give “01” eller “11” – uanset timing.
Gray code og rotary encoders
En anden anvendelse af gray code er ved rotary encoders hvor gray code kan bidrage til at afsløre hvilken retning akslen drejes i. Det kaldes også quadrature encoding. Det er baseret på to bit og mekanisk eller optisk afkodning af en kode-skive der sidder på akslen:
Hvis der placeres to sensorer (A og B) ved den røde firkant og man roterer skiven så kan de sorte og hvide felter oversættes til “ettere” og “nuller”.
Rotation med uret:
| A | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
| B | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 |
Rotation mod uret så får man:
| A | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 |
| B | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
Hvis man nærstuderer signalerne kan man se at når det ene eller andet signal ændrer sig så kan man bruge niveauet på den anden til at se hvilken vej der drejes:
Med uret: ↑A+!B og ↓A+B og ↑B+A og ↓B+!A
Mod uret: ↑A+B og ↓A+!B og ↑B+!A og ↓B+A
Så med gray code kan vi med to bit detektere rotation og omdrejningsretning.
Debouncing
Debouncing går i al sin enkelthed ud på at eliminere det kontaktprel som alle mekaniske kontakter laver:

Her et ikke nærmere specificeret eksempel der illustrerer hvordan kontakten hopper mellem åben og lukket.

Udfordringen er at fjerne de falske on/off transitioner uden at forsinke signalet…
Det er en lidt umulig opgave – men den største del af problemet kan løses med et simpelt kredsløb som dette:
I al sin enkelthed går det ud på at bruge en kondensator til at forsinke ændringen i signalet indtil det er stabilt.
Når switchen er off vil modstanden lade kondensatoren op til forsyningsspændingen. Når kontakten lukker vil kondensatoren hurtigt aflades. Korte hop tilbage til åben position vil oplade kondesatoren lidt mere. Da impedansen til stel er mindre end den til forsyningen vil den hurtigt lande på 0V og blive der når kontakten holder op med at bounce. Forholdet mellem R og C fortæller noget om hvor lang tid de kan kompensere for prel. Bounce kan tage alt fra under 1 ms til 50 ms for relæer – og endnu længere for store kontaktorer. Hvis man designer efter ~5ms er man godt hjulpet med “almindelige” små kontakter. Tau = R*C. Tau er tiden fra 0 til 63% (link). Dimensionerer man efter 3*Tau er man på ~95%. Så hvis man sætter Tau = 5/3ms; R*C=Tau; så får man med en 10K pull up en kondensator på ~2uF. Så – vælg noget i den størrelsesorden.
Bare lige inden jeg får en kæmpe sviner… Kredsløbet ovenfor er i princippet ikke i orden. Hvis man kortslutter kondensatoren med en switch vil der løbe en meget høj strøm gennem switch og kondensator. Det kan kondensatoren ikke holde til i længden – så der burde være en strømbegrænsermodstand i serie med switchen… Nu er du advaret og jeg slipper for tæv :-). I praksis i fuskeropstillinger er det fint og velfungerende.
Inverteren (trekanten med bolle på udgangen) har et “schmitt trigger” symbol. Det betyder at der er tale om en speciel logikkreds der kan fungere korrekt når den udsættes for signaler mellem høj og lav. Normalt er logikkredses logik kun veldefineret når deres input ligger mellem Vil (input low) og Vih (input high):

Det betyder at mellem 0.8 og 2V ved kredsen ikke hvad den skal gøre og det kan give de mest mærkværdige resultater.
En schmitt trigger kreds derimod har en indbygget hysterese. Det betyder at den vil betragte et input som “high” når den passerer “upper threshold”. Det bliver den ved med indtil signalet går under “lower threshold”:

Da vores debounce kredsløb netop benytter sig at at lade det digitale signal være “analogt” i et stykke tid til der er ro på situationen – ja så er en schmitt trigger indgang at foretrække. Arduino (ATmega328) har schmitt triggere på alle inputs – og har i øvrigt synchronizers på også for at sikre meta-stabilitet. Ok – måske en blog om det senere :-).
Der findes et hav af metoder til at løse denne udfordring. Jeg faldt over en helt anden metode som jeg vil beskrive herunder. Så nu – til den lovede lækkerbisken 🙂
Algoritme til rotation og de-bounching
Jeg er lidt høj på en virkeligt smart løsning til at dekode gray code og samtidig eliminere kontaktprel. Jeg faldt over den på nettet mens jeg faktisk ledte efter noget helt andet :-). Metoden er genial fordi den ikke betragter encoderen som to kontakter og forsøger at debounche dem hver i sær. I stedet udnytter man den egenskaber som gray code har – nemlig at der kun er nogle bestemte kombinationer der er valide. Og det virkelig smukke er at den er utroligt nem at implementere uden at påvirke flowet i koden – og ikke kræver interrupts.
Der er en vigtig antagelse der ligger til grund for denne metodik… Nemlig at der ikke er prel på begge signaler på én gang. Er der bounche på begge kontakter samtidig så er der problemer da vi ikke kan vide hvordan de bouncher i forhold til hinanden. Akslens omdrejningshastighed og hvor lang tid kontakterne bouncher afgør altså om metoden virker.
Artiklen er ikke om specifikt den encoder jeg har – men er helt identisk i funktion – altså mekanisk aflæsning og gray code. Hvis du vil læse originalen (skrevet af Oleg Mazurov) kan du finde den her: link. Jeg skal advare med at sige at der er en fin forklaring – bare ikke på hvorfor det virker… Jeg har prøvet at forklare det lidt dybere herunder.
Grunden til at denne algoritme er så genial er at den kan udføres i sekventiel række uden hensyn til timing og ventetid på at signalerne har stabiliseret sig. Det er en kæmpe fordel når man egentligt bare gerne vil koncentrere sig om funktionaliteten eller ikke vil ofre interrupt-ben og timere på håndteringen.
Men først – hvis du vil lege med har jeg lavet et library som du kan hente her: Download
Lad os starte med at se på hvad vi kan konkludere hvis vi kigger på alle kombinationer af skift i hhv. A og B signalerne. Det er samme betragtning som ovenfor – bare oversat til et univers hvor vi sampler signalerne og detekterer flanker som ændringer fra 0 til 1 og 1 til 0. Jeg benævner for nemheds skyld det nyeste signal for hhv. A og B. De signaler der er målt for “et øjeblik siden” (sidste måling) kalder jeg A’ og B’. Dvs. A’A=00 og =11 betyder uændret og =01 og =10 betyder en hhv. rising og falling flanke.
| Invalid / no rotation | Clock wise | counter clock wise |
| A’=A; B’=B
(ingen ændringer) |
A rise & B low | A rise & B high |
| A’!=A; B’!=B
(begge ændret) |
A fall & B high | A fall & B low |
| B rise & A high | B rise & A low | |
| B fall & A low | B fall & A high |
Dvs. rising / falling flank på enten det ene eller andet signal mens det modsatte er stabilt betyder altså rotation. Er signalerne stabile eller ændrede på begge signaler så er der ingen rotation eller signalerne bouncher.
Gul: rising / falling flanke
Blå: stable position direction
Grøn: med uret
Rød: mod uret
Lys grå: no change
Grå: invalid
Oleg Mazurov oversætter dette diagram til et array hvor index er A’B’AB og outputtet er Q som kan adderes til en tæller for at få den position man har bevæget sig til (-1 er mod uret, 1 er med uret og 0 er invalid/no rotation):
static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
Index’et (A’B’AB) er indrettet så listigt at det er utroligt nemt at udregne… AB er A og B værdierne som bit 1 og bit 0. Old_AB er en statisk variabel (altså holder sin værdi mellem kald til funktionen):
old_AB <<= 2; //move old AB to A'B'
old_AB |= AB & 0x03; //insert AB as bit 1-0; old_AB is now A'B'AB
Hvis man ikke har AB i samme variabel (det har man ofte kun hvis man læser en port og ikke enkelte pins) så kan man bare skifte linje 2 ud med:
old_AB |= ((A<<1) | (B&0x01))&0x3;
Hvis du er sikker på at der ikke er overflødige 1’ere i A og B kan du droppe “&0x01” og “&0x03”. De har kun til formål at beskytte mod “støj” i signalerne.
Det eneste der nu mangler er at returnere Q (enc_states i Oleg Mazurov’s terminologi). Det gøres ved at bruge de nederste 4 bit af old_AB (A’B’AB) som index:
return enc_states[ (old_AB & 0x0f) ];

Meeen – hvorfor virker det egentligt? Hvis B er low og A hopper op og ned – ja så må vi da stadig få fleredetekteringer. Og ja – det gør vi faktisk! Det unikke er at uanset hvor mange “hop” vi når at opdage – så vil den sidste flanke sikre at vi ender på det rigtige. Årsagen er at vi tæller op på hver stigende flanke og ned på den faldende. Da usikkerheden er overstået på måske 5 ms vil det næppe være noget brugeren når at opdage – hvis man eks. bruger det til at styre en menu eller lignende. Hvis man måler på omdrejningshastighed og retning på en motor eller andet hvor det er kritisk, kan man kompensere med elektrisk debouncing som beskrevet ovenfor. Metoden er dog stadig god fordi der kan afvikles i konstant tid (altså tager lige lang tid at udføre hver gang) og fordi der ikke indgår delays eller avancerede timere og interrupts.
Som nævnt skal man bare være sikker på at bounching er ophørt inden man når frem til næste kontakt…
Praktisk med DHV encoderen
Den encoder jeg har er markeret “DHV” og den har tydelig click-markering af step i rotationen. Da ét step giver en flanke på både A og B giver den viste metode 2 step hver gang. For at undgå det har jeg modificeret listen over “lovlige” kombinationer af A’B’AB så ændringer i B ignoreres (ændret til mørkegrå og Q=0):
Det betyder at arrayet ændres til:
static int8_t enc_states[] = {0,0,1,0,0,0,0,-1,-1,0,0,0,0,1,0,0};
Og voila – kun ét step per trin på encoderen…





Der var den centralt placeret til at modtage “tryk” fra afbrydere (der skulle opgraderes med en fjeder) og havde adgang til lamperne der skulle styres. Mock-up og prototyperne på billederne er af dette modul. Vi designede et ret fedt koncept hvor printene fungerede både som køleflader og stik-forbindelser.



Arduino er et “format” som reelt bare dækker over en Atmel ATmega processer hvor det grundlæggende (oscillator og forsyning) ligger på boardet og alle pins er ført ud til pinheaders så det er nemt at slutte andre enheder til.
Stifterne bag Arduino røg for nogle år siden i totterne på hinanden. Det var noget med at de hver især mente at eje domæne og registrerede varemærker. Så i dag kan Arduino også købes under navnet Genuino…
Arduino giver faktisk ret meget for pengene. Og kan man ikke være i formatet kan man skifte til Arduino Mega2560 der er baseret på ATmega2560. Den har 16 analoge inputs og 54 digitale i/o. Det kan man hurtigt få lyst til hvis man begynder at sætte display, 2-3 temperatursensorer, relæudgange og andet sjov på samme controller…
Uanset hviket miljø man vælger er der utroligt mange ting der bare virker “out of the box”. Ting man som traditionel udvikler selv må klare – såsom biblioteker med adgang til hardwaren og håndtering af interrupts etc. Alt det og meget mere er der taget hånd om – men som gammel embedded udvikler kræver det altså lidt tilvænning ikke at have indflydelse på alting 🙂
Et lille tip til brug af ebay… Hvis du vælger “Lowest price + P&P” så får du alle dem med gratis fragt 🙂 først i listen.
Nano har ikke indbygget USB-adaptor – så det første var at slutte USB-serial port til. Det var ret nemt 🙂 – 6 ben lige igennem. Det er her “female to female jumper cables” kommer i brug. Det er en dejligt nem måde at klaske ting sammen på. Ikke egnet til den endelige applikation men fin til test.
Jeg stod for nylig med en udfordring – en af mine arduino sensorer holdt op med at virke når jeg kommunikerede med den for hurtigt… Min eneste indsigt i kommunikationen var gennem det softwaren gav – og det bragte mig ikke videre… Interfacet er via SPI som jeg bit-banger – dvs. styrer manuelt i stedet for at bruge den indbyggede SPI-controller i ATmega microcontrolleren.

Da jeg fik den opstod der ca. en halv times total forvirring om drivere. Pulseview ville ikke genkende min logikanalysator… Det viste sig at Hobby Components har lavet en opdatering i efteråret og forvirringen skyltes dels at jeg ikke vidste hvilken version jeg havde og dels at jeg ikke tænkte på at tage USB-stikket ud og ind og genstarte Pulseview flere gange. Så – hermed viderebragt 🙂



Tiden bliver så samplerate * samples… “Run” knappen sætter det hele i gang. Hvis man ikke gør andet starter sampling med det samme – men man kan også definere triggere. Det gøres ved at clicke på et signal og definere hvilke flanker der skal fungere som triggere.
