Rotary encoder uitlezen

GJ_, ik had al een aantal van dat soort tutorials gevonden, maar meestal maakt men gebruik van digitalRead, wat performance-wise niet bepaald optimaal is. https://code.google.com/p/digitalwritefast/issues/attachmentText?id=2&… digitalWriteFast is al een heel stuk beter, een goede implementatie van dat systeem is bijvoorbeeld http://yameb.blogspot.nl/2012/11/quadrature-encoders-in-arduino-done.h… .

Ik heb er nu even een rPi bij gepakt, om te kijken hoe snel ik daar de interrupts van kan gebruiken. Een arduino zou mogelijk ook werken, maar is waarschijnlijk niet krachtig genoeg voor wat ik er mee wil. Ik zou graag ook een simpele webinterface willen en eventueel de mogelijkheid om later de printer aan te sturen vanaf mijn hardware.

Als het mogelijk is om een klein stukje hardware toe te voegen, kun je je CPU behoorlijk ontlasten. Door de eerste paar LSB's in hardware te tellen in bijvoorbeeld een 8-bits teller, hoeft je interrupt routine al een factor 256 minder te worden aangeroepen. (voorbeeld: HCTL-2017 quadrature counter).

EricP

mét CE

Het is vrij logisch dat er nergens staat hoeveel interrupts er per seconde afgehandeld kunnen worden: dat bepaal jij.

Uitgaande van een AVR: De meeste instructies zijn single-cycle. We werken in assembly - met een compiler weet je nooit precies wat die er van bakt. Dan ga je dus kijken wat zo'n ISR moet doen. Dan weet je welke instructies er voorbij komen. Dan weet je dus ook hoe lang een ISR duurt (uitgedrukt in clock cycles). Vergeet de aanroep en RETI niet.
Daaruit zou je dus kunnen berekenen hoeveel interrupts je per seconde af kunt handelen. Daarbij doe je de aanname dat ze mooi verdeeld in de tijd binnen komen en dat je niks anders doet. Neem daar 30% tot de helft van en dan zou het meestal kunnen werken.

Los het lekker in hardware op: een stel counters wat door je rotary encoders lekker staat te tellen. Als de de actuele waarde wilt weten, dan lees je dat uit je counter. Typisch iets wat je niet in software op moet willen lossen.

GJ_

Moderator

Op 19 februari 2014 14:46:06 schreef EricP:...een stel counters wat door je rotary encoders lekker staat te tellen.

Hoe doe je dat als die encoder een beetje heen en weer staat te wiebelen? Je wil in principe toch het aantal pulsen per omwenteling x4 tellen, hoe los je dat op in de HW tellers?

EricP

mét CE

Ik mis je 'x4' even. De meeste encoders die zoveel pulsen geven per omwenteling, leveren ook fatsoenlijke signalen (je hebt die shaping toch al nodig). Dus geen dender ed. De ene output laat je pulsen genereren, de andere de direction. Heen & weer wiebelen is iets voor mechanische encoders. Voor zover mij bekend, doen optische encoders daar niet aan.

GJ_

Moderator

met "x4" bedoelde ik dat je bij een encoder die bv 1024 delen per omwentelingen geeft natuurlijk wel graag per omwenteling 4096 wil tellen. En een encoder kan twee kanten op draaien, dan wil je wel optellen en aftrekken van je huidige positie. Hoe doe je dat met de teller in een uC? Kunnen die dat? En heen en weer wiebelen: als een een encoder op een positie "stil" staat kan er altijd een van de twee uitgangen knipperen natuurlijk. En half graadje heen en weer wiebelen dus :-)

Als je het allemaal handmatig in software zou willen doen, en dat de enige taak is die draait op je microcontroller, dan wil je juist interrupts vermijden. Waarom? Omdat dat alleen maar overhead oplevert bij de aanroep en return. Je hebt toch altijd een worst-case scenario waarmee je moet rekenen om te uiterste timing vast te stellen. Een interrupt levert in zo'n geval geen voordeel op mbt tijdswinst. Dus doe je alles in een zorgvuldig uitgekiende control loop; gewoon in de main.

Uiteraard kun je ook zonder interrupt-routine binnen je main-loop soms fijn gebruik maken van interrupt FLAGS die door hardware gezet (kunnen) worden, maar dat is een ander verhaal en zadelt je niet op met de overhead van context switching.

MAAR...
Het juiste beestje voor de juiste taak lijkt me hier de oplossing.

Dus een AVR met QDEC of een PICmicro met QEI. In beide gevallen gaat het om een hardwarematige ondersteuning voor een quadrature encoder interface. In je software is het dan een kwestie van een registertje/countertje uitlezen.

If you want to succeed, double your failure rate.
EricP

mét CE

met "x4" bedoelde ik dat je bij een encoder die bv 1024 delen per omwentelingen geeft natuurlijk wel graag per omwenteling 4096 wil tellen.

Waarom zou je dat willen?? Als dat al interessant zou zijn, dan los je dat in software op als je de teller uitleest...

En een encoder kan twee kanten op draaien, dan wil je wel optellen en aftrekken van je huidige positie. Hoe doe je dat met de teller in een uC? Kunnen die dat?

Nee, in discrete hardware. Niks met μC. Gewoon een counter-chippie (zie post Jochem). En ja, die kunnen dat.

En heen en weer wiebelen: als een een encoder op een positie "stil" staat kan er altijd een van de twee uitgangen knipperen natuurlijk.

Als het goed is, dan doen optische encoders dat dus niet. Daar zit een stuk signal-conditioning hardware in. Als het een mechanische encoder is, dan heb je helemaal gelijk.

GJ_

Moderator

per puls op het eerste spoor heb je 4 flanken die je moet tellen, anders is het prutswerk. En een optische encoder waarvan de as niet 100% stilstaat kan knipperen. Dat is onmogelijk met signaalconditionering op te lossen.

Op 19 februari 2014 18:22:00 schreef Jochem:
Als je het allemaal handmatig in software zou willen doen, en dat de enige taak is die draait op je microcontroller, dan wil je juist interrupts vermijden. Waarom? Omdat dat alleen maar overhead oplevert bij de aanroep en return. Je hebt toch altijd een worst-case scenario waarmee je moet rekenen om te uiterste timing vast te stellen. Een interrupt levert in zo'n geval geen voordeel op mbt tijdswinst. Dus doe je alles in een zorgvuldig uitgekiende control loop; gewoon in de main.

Uiteraard kun je ook zonder interrupt-routine binnen je main-loop soms fijn gebruik maken van interrupt FLAGS die door hardware gezet (kunnen) worden, maar dat is een ander verhaal en zadelt je niet op met de overhead van context switching.

Absoluut NIET meet eens! Je draait er altijd wel iets bij want je MOET iets met die signalen willen doen!
De enige manier om dit soort snelle signalen te meten is om interrupts te gebruiken.
De beide ingangen van de encoder (A/B) zo instellen dat ze een interrupt genereren op een flank.

In de code komt dan zoiets (als pseudo code):

code:


ISR-pin A:

  save werkregisters (status/accu + evt andere regs)

  read pin B

  if B = 1
     increment counter
  else
     decrement counter
  endif

  restore registers

  reti

ISR-pin B:

  save werkregisters (status/accu + evt andere regs)

  read pin A

  if A = 0
     increment counter
  else
     decrement counter
  endif

  restore registers

  reti

Bovenstaande code is misschien iets van 10 instructies per ISR. (Afhankelijk van de counter resolutie heb je iets meer nodig)
Op een simpele AVR duurt dat 10 x 250 nS = 2,5 uS.

In de main loop lees je de counter uit.

Hier een HELE belangrijke opmerking:

De counter declareer je in je main C code als volatile.
Als je counter meer dan 1 byte breed is MOET je zorgen dat je ELKE read van de counter atomic maakt.
Het simpelst is om dat met een ISR disable/enable te doen in C:

code:


 cli
 var = counter;
 sti

P.S. Linux op een embedded board is absoluut NIET geschikt want het is niet "hard" realtime!
Wel kun je een multiprocessor board/speciale CPU gebruiken en een tweede core dedicated voor deze taak inzetten.

MAAR...
Het juiste beestje voor de juiste taak lijkt me hier de oplossing.

Dus een AVR met QDEC of een PICmicro met QEI. In beide gevallen gaat het om een hardwarematige ondersteuning voor een quadrature encoder interface. In je software is het dan een kwestie van een registertje/countertje uitlezen

Hier ben ik het wel mee eens, een groot aantal CPU's heeft altijd wel een counter aan boord, en met de juiste configuratie kan die ook gewoon op/af tellen. Misschien dat er extern nog een flipflopje toegevoegd moet worden om de fase/draairichting te bepalen.
In de software maak je dan (weer) een counter overflow/underflow interrupt handler die er voor zorgt dat je meer kunt tellen dan de woordbreedte die de CPU ondersteund.

1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.
GJ_

Moderator

Je moet van het A en het B signaal zowel de stijgende als de dalende flanken nemen om te tellen.

Ik wist dat je zou komen met die opmerking. Ik had het even bewaard voor de oplettende lezer!

Je kunt dat oplossen door in de ISR de flank om te programmeren of interrupt controller zo te programmeren dat deze op beide edges een INT af geeft.

Is de flank NIET de configureren kun je twee poort pinnetjes gebruiken en twee XOR gates tussen de A/B lijn hangen.

In de software moet je wat vlaggetjes bijhouden welke kant je precies op moet tellen.

Nog steeds is dat zeer snel te doen als je dat slim in elkaar zet.
Laat de isr er 20uS over doen, dan kun je nog steeds een kleine 50k gegarandeerd meten. Maar je houdt dan wel niet veel tijd meer over in de main loop.

[Bericht gewijzigd door henri62 op woensdag 19 februari 2014 20:05:12 (13%)

1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.
EricP

mét CE

@henri: je kunt het ook omdraaien. Laat de bemonstering in je main lopen en schrijf een (erg efficiënte / korte) main die je als ISR schopt. Nadeel is dat je timing wat onvoorspelbaar wordt. Maar het kan... Of je het moet willen???

Als je nog iets meer nuttigs in de main loop wilt doen, kun je zeer snel over de minimale periode tijd heen gaan waardoor je pulsen gaat missen.

De de toepassing hierboven neem ik aan dat de POSITIE extreem belangrijk is niet de snelheid dus. Een puls missen is dan funest voor de positionering van bijvoorbeeld je printkop of tafel.

Als je 28000 pulsen MOET kunnen meten is de periode tijd ca 35,7 uS.
Knappe jongen als je kunt garanderen dat je main loop hier NEVER NOOIT overheen komt kwa timing.

Voor een ISR kan ik op maximaal 2 clock cycles EXACT de performance voorspellen en 100% garanderen dat ik geen pulsen mis bij een bepaalde CPU snelheid.

Verder is polling van dit soort signalen in de main loop gewoon NOT DONE. (Zie ander discussie topic).

1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.
EricP

mét CE

Als je 28000 pulsen MOET kunnen meten is de periode tijd ca 35,7 uS.
Knappe jongen als je kunt garanderen dat je main loop hier NEVER NOOIT overheen komt kwa timing.

Daarmee ben je er niet. Je hebt meerdere encoders die allemaal tegelijk bezig kunnen zijn.

Als elke encoder z'n eigen interrupts heeft (en daar ontkom je niet echt aan), dan heb je dat probleem met je delays toch - al zul je een ISR wat beter in de hand hebben dan een main loop.

Dus dat je het op 2 cycles nauwkeurig kunt voorspellen... No way. Dat lukt voor 1 handler. Als die ook nog de enige is. Tenzij je interruptable interrupts maakt. Dat kan. Dan weet je nog steeds niet wanneer de interrupted handler verder kan. Heb je nog steeds hetzelfde probleem en wordt de zaak nodeloos complex. Ofwel: ook met interrupts wordt het tricky. Dus vandaar mijn opmerking: doe het in hardware. Dat werkt asynchroon en autonoom.

GJ_

Moderator

Hardwarematig kan met een paar poortjes geloof ik:
http://www.uploadarchief.net/files/download/encoderdirectiondecoder.jpg
http://www.uploadarchief.net/files/download/encoderdirectiondecoder01.jpg
EricP heeft zeker een punt dat dat verstandiger is.

Het zal duidelijk zijn dat ik de plaatjes niet zelf bedacht heb.

Op 19 februari 2014 20:24:51 schreef EricP:
[...]Daarmee ben je er niet. Je hebt meerdere encoders die allemaal tegelijk bezig kunnen zijn.

Effe niet helemaal gezien, de TS heeft er meer, dan moet je inderdaad wat meer moeite doen.
Als de ene ISR klaar is doet ie meteen de tweede van de andere encoder er achteraan. Zelfs nested interrupts is geen probleem.

Is niet nodeloos complex, ik vind het zelfs veel overzichtelijker.
Je hoeft in de main code niet te knoeien om ergens een of andere functie in te korten of in delen op te splitsen omdat je anders je loop time niet haalt.

Voor EEN encoder is het wel fixed, de ISR van de A en B lijn kunnen nooit gelijktijdig komen.

Maar als je inderdaad wat meer encoders wilt dan ontkom je niet aan extra hardware. Maar met de count up/down alleen ben je er ook nog niet GJ! De ISR rate wordt wel lager, dat scheelt.

Gebruik je externe counters dan heb je er weer een ander serieus probleem bij van het uitlezen van de hardware, je moet iets verzinnen om de counters dubbel te bufferen zodat je het HELE WORD atomic kunt lezen. En dat valt niet mee om dat correct te doen!! Vandaar dat er speciale (nogal exotische) IC's voor zijn.

[Bericht gewijzigd door henri62 op woensdag 19 februari 2014 21:01:37 (10%)

1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.
EricP

mét CE

Als de ene ISR klaar is doet ie meteen de tweede van de andere encoder er achteraan. Zelfs nested interrupts is geen probleem.

Je weet alleen absoluut niks meer van response tijden...

Is niet nodeloos complex, ik vind het zelfs veel overzichtelijker.

Dat is het niet. Je moet op de een of andere manier zien dat je stack niet ontploft als het wat drukker wordt. Nested interrupts is vragen om ellende. Houd je ISR kort. Is nesten nergens voor nodig.

Je hoeft in de main code niet te knoeien om ergens een of andere functie in te korten of in delen op te splitsen omdat je anders je loop time niet haalt.

Dat hoef je bij non-nested interrupts ook niet.

Maar als je inderdaad wat meer encoders wilt dan ontkom je niet aan extra hardware. Maar met de count up/down alleen ben je er ook nog niet GJ! De ISR rate wordt wel lager, dat scheelt.

Je hebt helemaal geen interrupts nodig. Laat de hardware lekker naar die decoders kijken. Lees de hardware uit als je de actuele positie wilt weten. Er zijn dedicated chippies die dat kunnen! Dataprotocolletje. Klaar. Enige is dat je niet 3 assen atomic uit kunt lezen. Ik *denk* dat dat niet zo'n punt is.

Gebruik je externe counters dan heb je er weer een ander serieus probleem bij van het uitlezen van de hardware, je moet iets verzinnen om de counters dubbel te bufferen zodat je het HELE WORD atomic kunt lezen. En dat valt niet mee om dat correct te doen!! Vandaar dat er speciale (nogal exotische) IC's voor zijn.

Inderdaad. Als het een simpele counter is, dan gaat dat fout. Vroeg of laat.

Op 19 februari 2014 21:27:13 schreef EricP:
[...]Je weet alleen absoluut niks meer van response tijden...

Voor dit geval hoeft dat ook niet, als de totale tijd van de isr routines maar binnen je kritische window valt. En dat is zeer goed voorspelbaar.

...Dat is het niet. Je moet op de een of andere manier zien dat je stack niet ontploft als het wat drukker wordt. Nested interrupts is vragen om ellende.

Dat wordt (vaak) bepaald door de architectuur van de gebruikte CPU.
Sommige ondersteunen dat, andere niet, soms is het een feit dat het zo is.
Normaal laat ik de interrupts op dezelfde prioriteit lopen, dan wordt er niks genest. Per CPU type zoek ik dat uit hoe het precies in elkaar zit.

Houd je ISR kort. Is nesten nergens voor nodig.[...]Dat hoef je bij non-nested interrupts ook niet.[...]

Ik heb absoluut niet beweerd dat een ISR niet kort hoeft te zijn, in tegendeel in de ISR MOET de code zo kort mogelijk zijn. In de voorbeeld pseudo code is ie ook maar iets meer dan 10 instructies. Kortom hier heb je gelijk in.

Je hebt helemaal geen interrupts nodig.

Zonder context is dit een gevaarlijke uitspraak.

Laat de hardware lekker naar die decoders kijken. Lees de hardware uit als je de actuele positie wilt weten. Er zijn dedicated chippies die dat kunnen! Dataprotocolletje. Klaar. Enige is dat je niet 3 assen atomic uit kunt lezen. Ik *denk* dat dat niet zo'n punt is.Vroeg of laat.

Kijk, nu haal je de context er wel bij: Laat de hardware het doen, net beweerde je nog dat je alles in de main-loop moet doen en dat werkt hier nu juist net niet.

1-st law of Henri: De wet van behoud van ellende. 2-nd law of Henri: Ellende komt nooit alleen.
EricP

mét CE

Voor dit geval hoeft dat ook niet, als de totale tijd van de isr routines maar binnen je kritische window valt. En dat is zeer goed voorspelbaar.

Hoe dan? 3 encoders die (in deze context) at random interrupts genereren... Dat gaat alleen goed als het 'snel' is. Als het 'snel' is, is die hele interrupted interrupts ongein niet nodig...

Dat wordt (vaak) bepaald door de architectuur van de gebruikte CPU.
Sommige ondersteunen dat, andere niet, soms is het een feit dat het zo is. Normaal laat ik de interrupts op dezelfde prioriteit lopen, dan wordt er niks genest. Per CPU type zoek ik dat uit hoe het precies in elkaar zit.

Dat is onzin. Als je de boel gaat nesten, moet je oppassen dat het niet te diep genest wordt. Daar doe je niks aan. Je kunt wat met priorities doen - als de interrupt controller dat kan - maar dan nog is het oppassen geblazen. En tot op heden heb ik nog niet meegemaakt dat het noodzakelijk is - wel dat een programmeur wilde laten zien 'hoe goed hij de controller begreep'. En feitelijk dus liet zien dat-ie niet in staat is om efficiënte code te schrijven / er net iets langer over na te denken zodat dat niet nodig is.

Zonder context is dit een gevaarlijke uitspraak.

De context is er: hardware counters die de boel bijhouden. Jij vraagt de status op op het moment dat je er interesse in hebt. That's is. Komt geen interrupt aan te pas.

[...]Kijk, nu haal je de context er wel bij: Laat de hardware het doen, net beweerde je nog dat je alles in de main-loop moet doen en dat werkt hier nu juist net niet.

Eh... Dat beweerde ik niet. Dat beweerde Jochem. Wel in de juiste context blijven hoor.
Mijn enige reactie was dat het technisch wel(licht) kan, met de retorische vraag of je het moet willen. Stack ontploft?? :)

Dat 'beweerde' Jochem niet zomaar, want ook dat was in de context dat dat gunstiger is op het moment dat je in je mainloop toch verder bijna niks doet, omdat je dan de overhead van de aanroep van een interruptroutine bespaart.

En wat EricP aankaart is eigenlijk de belangrijkste reden dat wij in onze sector (m.n. luchtvaart, en industriële fail-safe en real-time environments) doorgaans interrupts proberen te vermijden voor de 'gewone' taken. Want als je een gecontroleerde main-loop hebt, waarin je berekent wat je worst-case af moet handelen, dan is dat veel voorspelbaarder. Gebruik je wel heel veel interrupts, dan moet je namelijk alsnog uitrekenen wat er worst-case nog voor tijd voor je mainloop overblijft, en dat is veel complexer. Dan maar liever elke deeltaak z'n eigen tijdsframe geven. En ja, als je dat ver doorvoert lever je gemiddeld wat performance in in ruil voor echte real-time eigenschappen.

En natuurlijk snap ik de voordelen van een korte interrupt (eigenlijk in alle gevallen waarbij die routine z'n naam eer aan doet). Ik zeg dus niet dat interrupts altijd moeten worden vermeden, zeker niet.

Ik zeg ook niet dat het in dit geval van TS per se de oplossing is om alles in een main-loop te doen. Maar ik ben het 100% met EricP eens dat dat een veel meer voorspelbare methode is dan maar allerlei interruptable interruptroutines elkaar te laten onderbreken.

Maar aan het eind van de rit zijn we het gewoon met elkaar eens, en zeggen we: pak een stukje hardware (al dan niet on-chip bij je uC) dat de afhandeling van het tellen van die flanken doet. Software is daarna een eitje.

If you want to succeed, double your failure rate.

Ha wow, had niet verwacht dat dit zo veel reacties op zou leveren, zeer interessant allemaal!

Op zich is het niet noodzakelijk om alle drie de assen atomic (die kende ik nog niet) uit te lezen, op het moment wil ik mij meer gaan richten op het vergelijken van de positie per as, niet zozeer de combinatie van de drie assen.

Zoals ik inderdaad al aangaf, is de 28k interrupts per seconde echt het hardwarematige maximum, dat is als alle drie de assen op maximale snelheid draaien. In de praktijk draait de z-as maar nauwelijks en als deze draait staan de andere assen over het algemeen stil tijdens het printen. Ook ben ik er van uit gegaan dat de x en y as 150mm/s kunnen gaan, terwijl dat eigenlijk de maximale gecombineerde snelheid is. In de praktijk is het natuurlijk goed om van de worst case uit te gaan, maar er zit zeker nog wel wat marge op.

Wat ook al aangegeven werd is dat het niet zozeer om de snelheid gaat, maar om de positie. De printer wordt aangestuurd met 1/32e microstepping, wat in mijn situatie een precisie geeft van 5micron. Omdat het aardig onmogelijk is om dat te meten, heb ik mij gericht op het meten van 32 micron; anders zou ik nog een idiotere hoeveelheid interrupts per seconde af moeten halen...
Immers, een afwijking van 32 micron, 1/3e van een menselijke haar, geeft nagenoeg geen significante afwijking op de prints, zeker als je andere afwijkingen meerekent die veel groter zijn (verschillen in filament-dikte, afwijkingen in de nozzle dikte, backlash, verkeerde berekeningen in de slicer, etc.)

Ik zit er in ieder geval aan te denken om een dual-core Single board linux pc te nemen, een kernel-module te maken voor het afhandelen van de interrupts en proberen er 1 core volledig voor te reserveren.Ik durf niet te zeggen of dat uberhaupt kan, maar ik ga er eens naar kijken of het zou kunnen met een Cubieboard 2 (dual core 1GHZ ARM)

Ik heb door jullie reacties in ieder geval even genoeg dingen die ik moet gaan uitzoeken...

Als je inderdaad voor een linux-gebaseerde oplossing gaat, overweeg dan om daar een real-time extension overheen te leggen. Zoektermen: RTAI, LXRT, Xenomai. Daarmee krijg je de beschikking over harde garanties m.b.t. bijvoorbeeld maximale interrupt latencies.
Verder zijn er sowieso wat aanvullende maatregelen nodig als je pc-gebaseerd gaat. Bijv: als het BIOS besluit om de CPU-frequentie aan te passen (frequency scaling, power management etc), dan koppelen sommige moederborden de klok tijdelijk los van de CPU, passen dan de frequentie aan, en bieden hem pas weer aan de CPU aan als de nieuwe klok stabiel is. In zo'n geval kan je software nog zo realtime zijn, als de klok niet loopt gaat het toch fout...

Heb even wat berekeningen opnieuw gedaan, omdat ik het idee had dat ik mis zat.
Ik had inderdaad wat verkeerd gerekend. De aansturing van de kop gebeurt met een nauwkeurigheid van 5 micron. Omdat ik minimaal 2x die resolutie wil halen om een zinnige meting te doen, zou ik uitkomen, op maximale snelheid, op een worst-case scenario van 60000 pulsen per seconde, per as. Omdat ik het voorlopig niet bepaald zie zitten om zo snel te kunnen meten, ga ik me voorlopig richten op het meten van andere onderdelen.
De meting zou immers niet enkel snel moeten zijn, ook moeten de pulsen snel verwerkt worden, omgerekend worden naar afstand, maar ook moet deze vergeleken worden met de beoogde positie van de kop, het liefst <1ms.
Eerst eens even wat meer ervaring opdoen met embedded hardware voor ik mij daar op ga richten.