Forum: PC-Programmierung Inkrementalgeber mit Pin Interrupt Raspberry Pi


von Patrick B. (funkuchen)


Lesenswert?

Hallo

Ich suche nun schon länger nach einer Möglichkeit, einen 
Inkrementalgeber "solide" auszulesen.
Leider hab ich das bisher nicht geschafft. Ich arbeite mit Pin interrupt 
und nicht mit timerinterrupt (das als "solide" zu bezeichnen finde ich 
schon ziemlich gewagt).
Pininterrupt hat/sollte den Vorteil haben, dass erst Schritte verloren 
gehen wenn der uC zu langsam ist (in meinem Fall ein Raspberry Pi) und 
keine unnötigen Abtastungen erfolgen zudem lässt sich die genauigkeit 
steigern.

Nun hab ich schon einiges im Netz gesucht und bisher nur irgendwelche 
Bastellösungen mit Timerinterrupt und flankenauswertung von A oder B 
gefunden.

Ich arbeite Aktuell mit Flankenauswertung von A und B (positiv und 
negativ) somit erreiche ich die 4fache auflösung. Der Geber prellt nicht 
(liess sich zumindest nicht messen), Software seitig werden aber die 
Signale trotzdem "entprellt". Ausserdem dreht der Schrittmotor an den 
der Geber gekoppelt ist ziemlich langsam (geschwindigkeit ist egal).
Wenn ich nun einen testlauf starte, dann gehen ständig Schritte 
verloren, so dass die Position teilweise um fast 10° daneben ist nach 
100mal hin und her fahren.

Nun meine Frage, hat irgendjemand dass schon eimal gemacht und kann mir 
seinen Code oder Teile draus zur Verfügung stellen?

Andere Frage, ist es möglich, dass das GPIO Interface (über wiringPi.h, 
C-Programm) zu langsam ist um 1000 bzw. 4000 Inkrements pro Umdrehung 
auszuwerten (motor dreht vll mit 10RPM oder so)?

Danke und Gruss Funkuchen
1
void encoderISR_A(void){
2
    if(enableInterrupt == 1){           //for disabling interrupt after 1 execution (to avoid jitter)
3
        setEncoderPosition();
4
        enableInterrupt = 0;
5
    }
6
}
7
8
void encoderISR_B(void){
9
    if(enableInterrupt == 0){           //for disabling interrupt after 1 execution (to avoid jitter)
10
        setEncoderPosition();
11
        enableInterrupt = 1;
12
    }
13
}
14
15
void setEncoderPosition(void){
16
17
    if(enableInterrupt){ //interrupt A
18
        B = digitalRead(ENCODER_B);
19
        if(B){
20
            if(A){
21
                motorPosition--;
22
            }else{
23
                motorPosition++;
24
            }
25
        }
26
        else{
27
            if(A){
28
                motorPosition++;
29
            }else{
30
                motorPosition--;
31
            }
32
        }
33
    }
34
    else{// interrupt B
35
        A = digitalRead(ENCODER_A);
36
        if(A){
37
            if(B){
38
                motorPosition++;
39
            }
40
            else{
41
                motorPosition--;
42
            }
43
        }
44
        else{
45
            if(B){
46
                motorPosition--;
47
            }
48
            else{
49
                motorPosition++;
50
            }
51
        }
52
    }
53
}

: Bearbeitet durch User
von Noch einer (Gast)


Lesenswert?

Hast du schon kontrolliert, ob es ein mechanisches Problem ist? Wenn 
Belastung und Elastizität nicht stimmt, springt ein Schrittmotor bei 
einem elektrischen Schritt mehrere mechanische Schritte.

Einfach mal herumprobieren. Weniger Strom oder mit dem Finger leicht 
abbremsen. Oder erst mal mit einem primitiven durchsichtigem Programm 
100 mal hin und her fahren.

von Patrick B. (funkuchen)


Lesenswert?

Der Schrittmotor der fährt unbelastet genau die angegebenen Schritte, 
das habe ich schon in mehreren Tests ausprobiert, lediglich im 
Mikroschrittbetrieb ist die Positionierung, wie zu erwarten, innerhalb 
eines Vollschrittes ungenau.
Ich hab auch schon von Hand ausprobiert, ob er den Nullpunkt verliert 
und tatsächlich war der Nullpunkt des Inkrementalgebers plötzlich 
verschoben, zwar jeweils nur minimal, aber dennoch am falschen Ort.

von Peter D. (peda)


Lesenswert?

Patrick B. schrieb:
> nur irgendwelche
> Bastellösungen mit Timerinterrupt

Hast Du mal versucht, sie zu verstehen, warum sie so solide sind?

Ich habe sie z.B. zur Probenpositionierung auf einem AT89C51CC03 mit 
50kHz Abtastinterrupt implementiert, funktioniert einwandfrei.

von Patrick B. (funkuchen)


Lesenswert?

Wer sagt denn dass das wirklich solide ist? Von mir aus kann man das für 
einen Drehknopf nehmen, aber nicht für einen Encoder an einem Motor (der 
eigentlich schneller drehen sollte als bisher getestet).
und ja, ich hab verstanden was die Einwände gegen die Pininterrupt 
Methode sind, aber hast du schon versucht meinen Code zu verstehen? Der 
müsste zumindest theoretisch die Vorteile beider Varianten umsetzten.

- 4fache Auflösung
- entprellung, resistent gegen pendeln
- effiziente CPU nutzung

Ich hab mir hier im Forum einige Beiträge dazu durchgelesen und 
anscheinend haben die wenigsten verstanden, wie man es mit Interrupts 
machen können sollte. In diesem Abschnitt auf Wikipedia ist eine Idee 
die zumindest in der Theorie funktionieren sollte beschrieben:

"Wenn der Inkrementalgeber genau auf der Grenze einer Flanke steht, so 
können durch kleinste Erschütterungen oder andere Effekte 
(Tastenprellen, elektromagnetische Störungen) zusätzliche Impulse 
auftreten. Sogenannte Schrittabellen elimieren Fehler durch 
Mehrfachimpulse an den Schaltzeitpunkten: Angenommen B steht auf 1 und A 
wechselt von 1 auf 0 und prellt dabei: Amem und Bmem sollen die 
gespeicherten Werte vor einer neuen Flanke sein. Beim ersten Wechsel von 
A auf 0 wird anschliessend Amem = 0 und Bmem = 1 gespeichert. Wechselt 
jetzt A wieder auf 1 so wird kein Impuls gezählt, weil das Aktuelle B = 
Bmem ist. Solange sich B nicht verändert hat dürfen Flanken bei A nicht 
mehr gezählt werden. Kurz gesagt: Die Logik setzt das Apriori-Wissen, 
dass nach einem Wechsel von A erst ein Wechsel von B kommen muss (und 
umgekehrt) um. Dies gilt auch bei der Drehrichtungsumkehr."

Die gepostete Version meines Codes müsste dieser Idee entsprechen. Und 
ich würde das gerne so umsetzten. Aber irgendetwas funktioniert noch 
nicht richtig, aber komplett falsch kann hier wohl nichts mehr sein, 
sonst würde der Nullpunkt wohl beliebig hin und her springen.
Ich Vermute mal, dass der Rpi einfach zu lahm ist, bzw. die "Interrupts" 
nicht wie Interrupts funktionieren. Deshalb als nächstes test mit einem 
"einfachen" uC und tests mit einem Hardware decoder. Mal schauen was die 
so zählen.

von Clemens L. (c_l)


Lesenswert?

Patrick B. schrieb:
> timerinterrupt (das als "solide" zu bezeichnen finde ich schon ziemlich
> gewagt)

Mit Timer-Interrupt gibt es aber Code, der funktioniert ...

> Der Geber prellt nicht (liess sich zumindest nicht messen)

Wie hast du das gemessen? Mit einem Oszilloskop?

> Software seitig werden aber die Signale trotzdem "entprellt"

Ich vermute, dass der Fehler dort liegt.

> ist es möglich, dass das GPIO Interface (über wiringPi.h,
> C-Programm) zu langsam ist

Ja, das ist möglich.


Du interessierst dich nicht für den genauen Zeitpunkt der Flanken, 
sondern nur für den Zustand. Deshalb ist es egal, ob du die Signale 
sofort nach dem Flankenwechsel oder erst beim nächsten Timer-Tick liest.

Also nimm eine funktionierende "Bastel"lösung, verschiebe den Code vom 
Timer-Interrupt in die Pin-Interrupts, und es sollte immer noch 
funktionieren.

: Bearbeitet durch User
von Konrad (Gast)


Lesenswert?

4000 Inkremente bei 10 Umdrehungen/s ergibt fuer mich Interruptfrequenz 
von 40kHz oder eine maximale Interruptbehandlungszeit von 25us. 
Sportlich!

Benutzt Du einen Linux-Kernel mit RT-Patch? Ohne ist das wohl 
hoffnungslos, mit immer noch ambitioniert.

Wenn Du vermutest, dass Interrupts verlorengehen, schnapp Dir ein ftrace 
und miss Latenzen.

von Patrick B. (funkuchen)


Lesenswert?

Danke Konrad, für die erste hilfreiche antwort.

An die anderen : habt ihr den code überhaupt schon verstanden?
@Clemens, du kannst dir ja im code anschauen, wie entprellt wurde!

na ja, zu Konrad^^
Ich benutze ein stink normales Raspbian. Ich bin davon ausgegangen, dass 
so ein uC (ach ja übrigens, ich verwende den Pi2) mit 4 kernen schnell 
genug sein sollte um das zu schaffen. Aber ja, das bestätigt dann wohl 
dass er zu langsam ist.
Ich werde mal versuchen die Priorität meines Threads hoch zu stufen, 
evtl. bringt das ja eine Verbesserung.
Mit extrem tiefen drehzahlen hab ich es auch tatsächlich geschafft den 
Motor ohne inkrements zu verlieren zu drehen.

von Konrad (Gast)


Lesenswert?

Zum Vergleich: ich hatte letztens auf einem 500MHz PPC-Derivat zu tun, 
mit RT-Patch, und ich musste kaempfen, um ein Latenzziel von ~100us 
einigermassen sauber zu treffen (CAN-Baustein MCP2515 per SPI ansteuern, 
oh Graus).

Selbst mit 4 Kernen gibt es immer noch reichlich, was so ein Kernel erst 
noch machen muss, bevor Dein Interrupt drankommt. Um so schlimmer, wenn 
Deine Interruptbehandlung erst im User-Code stattfindet, dann hast Du 
noch Context-Switch-Overhead von sagen wir 10us pro Richtung.

Wenn Du das auch zu Bildungszwecken machst, kann ich nur sehr empfehlen, 
mal mit ftrace draufzugucken, was alles passiert, bevor Dein Interrupt 
gehandelt wird -- man lernt einiges vom Linux-Kernel kennen.

Der RPi-Prozessor ist mE nicht der richtige Prozessor dafuer. Lieber ein 
uC mit einem RTOS, oder ohne OS. Vielleicht auch ein BeagleBone mit 
seiner PRU.

von Peter D. (peda)


Lesenswert?

Patrick B. schrieb:
> habt ihr den code überhaupt schon verstanden?

Warum sollen wir uns die Mühe machen, wenn Du ihn nichtmal kommentierst 
und die Funktionsweise beschreibst?
Du willst doch Hilfe, also mußt Du auch ein bischen dafür tun.

Außerdem ist eine Beschreibung nie unnütz, sie hilft Dir selber, den 
Code nach ein paar Jahren noch zu verstehen.


Die Timerversion kann übrigens genauso gut im Pin-Change-Interrupt für 
die beiden Pins laufen.

von Georg (Gast)


Lesenswert?

Patrick B. schrieb:
> aber hast du schon versucht meinen Code zu verstehen? Der
> müsste zumindest theoretisch die Vorteile beider Varianten umsetzten.

Ganz toll, abgesehen von der unbedeutenden Kleinigkeit, dass er ja nicht 
funktioniert.

Patrick B. schrieb:
> An die anderen : habt ihr den code überhaupt schon verstanden?

Es ist schon seltsam, wenn jemand, der das überhaupt noch nicht 
hingekriegt hat, alle anderen belehren will, die wissen wie es geht. Und 
eine gute Voraussetzung Fehler zu finden ist das auch nicht.

Georg

von Patrick B. (funkuchen)


Lesenswert?

Nun ja, der RPi soll eben auch noch übers netzwerk mit einem PC 
kommunizieren und evtl. soll er auch noch ein Display ansteuern, deshalb 
habe ich ihn verwendet. Ich werde noch ein paar versuche mache, um evtl. 
hier eine Verbesserung zu erzielen, ansonsten werde ich dann wohl einen 
Hardware decoder oder einen anderen uC nehmen (einer ohne Linux ;D ich 
programmier lieber direkt auf der Hardware).

Werde mir ftrace mal anschauen, interessiert mich schon, was da so alles 
läuft was ich nicht sehe.

Danke und Gruss

von Patrick B. (funkuchen)


Lesenswert?

Eine beschreibung wie der Code funktioniert hab ich eigentlich schon mal 
abgegeben. Da ich ja davon ausgehe, dass ihr die experten seid, solltet 
ihr ja eigentlich verstanden haben was ich so beschrieben habe, ihr habt 
euch sicher auch schon gedanken zu dem Thema gemacht.

Georg schrieb:
> Patrick B. schrieb:
>> aber hast du schon versucht meinen Code zu verstehen? Der
>> müsste zumindest theoretisch die Vorteile beider Varianten umsetzten.
>
> Ganz toll, abgesehen von der unbedeutenden Kleinigkeit, dass er ja nicht
> funktioniert.
>
> Patrick B. schrieb:
>> An die anderen : habt ihr den code überhaupt schon verstanden?
>
> Es ist schon seltsam, wenn jemand, der das überhaupt noch nicht
> hingekriegt hat, alle anderen belehren will, die wissen wie es geht. Und
> eine gute Voraussetzung Fehler zu finden ist das auch nicht.
>
> Georg

Abgesehen davon, dass du eigentlich keine Ahnung hast oder was? Dass er 
nicht funktioniert hat mir noch keiner bewiesen. Mit ziemlich grosser 
wahrscheinlichkeit kann er aber nicht funktionieren, da ja scheinbar der 
Linux Kernel zu lange braucht.

von Dr. Sommer (Gast)


Lesenswert?

Patrick B. schrieb:
> ansonsten werde ich dann wohl einen
> Hardware decoder oder einen anderen uC nehmen (einer ohne Linux ;D ich
> programmier lieber direkt auf der Hardware).
Wenn du einen STM32 nimmst kannst du einen Timer die Encoder-Signale 
direkt in Hardware auswerten lassen. Dann musst du nichtmal mehr 
irgendwas zeitkritisches (Interrupts) programmieren, sondern kannst 
einfach nur gemütlich die Timer-Register auslesen um die aktuelle 
Encoder-Position zu erhalten. Entprellen ist inklusive. Musst nur 
aufpassen dass du die Signale auf Channel 1 und 2 eines Timers legst der 
das auch kann - genau im Reference Manual gucken, bei manchen Timern 
steht recht versteckt dass sie das nicht können.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.