Brauche Hilfe bei Drehzahlauswertung

Bin momentan mal wieder am Selbstfahrauto dran. Hab nun Servo, Ultraschall und 2 Motoren sauber am Laufen, will nun aber noch die Rückkopplung der Drehzahl für die Regelung. Und da stosse ich auf Timingprobleme. Der Flankenwechsel muss meines erachtens via Interrupt ausgewertet werden.

Kurze Projektbeschreibung
2 Motoren an Hinterachse, Einzelradsteuerung mit Lenkrolle vorne (Dreirad). Motoren haben Lochscheibe mit 20 Felder über Lichtschranke erfasst. Ultraschall erkennt Hinderniss und weicht aus, dazu pendelt der Sensor via Servo radarähnlich nach links und rechts.

Ziel: Zur möglichst schnellen und genauen Drehzahlregelung möchte ich die Zeitabstände jeder Lichtschranke von positiver Flanke zu positiver Flanke ermitteln. Wir reden dabei von 9ms. Reifenumfang 18cm, Speed max 1m/s, ergibt ca 9ms zwischen zwei Schlitzen, bei 20 Schlitzen pro Umdrehung.

Was ich eben noch nicht gemacht habe: Interrupt selbst programmieren. Wo fang ich an, auf was habe ich im Interrupt zugriff? Kann ich in der IDE meine Variablen einfach auch im Interrupt benutzen? Wieviel Zeit verliere ich, wenn ich dabei die micros() einlese und eine Differenz zum vorherigen Wert berechne

Beispielcode für Linke Seite

B_links = micros();
Diff_links = B_links - A_links;
A_links = B_links;
Diff_links_rechts = Diff_links - Diff_rechts

Soweit ich interrupts noch im Kopf habe bei anderen CPU-Typen, blockiert der gerade laufende Interrupt das Ausführen eines weiteren, der wird aber dann im Anschluss ausgeführt. Welche Zeit würde mein Beispielcode wohl so brauchen?

Jede Lichtschranke muss ja ihren eigene Interupt haben, da sie Zeitversetzt laufen. Aus Diff_links_rechts erzeuge ich dann meine Speed-Korrektur im Hauptprogramm.

Brauch kein fertiges Script, nur eine Einstiegshilfe in Interruptprogrammierung. Den Beispielcode auf arduino.cc - language kapier ich zwar soweit, aber was muss ich beachten bei den Variablen. Darf ich irgendwas nicht benutzen (zb micros() im Interrupt). Hat jemand schon Codeteile um Lichtschrankensignale an Radumdrehungen auszuwerten? Blockiert sich da irgendwas während mein Interupt läuft. zB andere Interrupts für die Motoransteuerung oder den Servo.

Danke
chefin

Du darfst kein Delay() nutzen
Millis() wird dir immer den gleichen Wert liefern.
micros() schafft den nächsten ms Sprung nicht.

Im Interrupt genutzte globale Variablen müssen das volatile Attribut bekommen.
Das auslesen dieser Variablem muss im Hauptprogramm atomar erfolgen.


Wenn du meinst, Interrupts dafür nutzen zu müssen, dann ok...

Ich würde ja einen 1ms (o.ä.) timer Interrupt verwenden und in diesem beider Räder gleichzeitig lesen.
Einwände?
Verfahren

Bei meinen Drehenkodern, welche händisch bedient werden, habe ich ATTiny85 dran geklemmt, welche ich dann von jedem I2C Master lesen kann.

halte die ISR schön klein, führe die Berechnungen außerhalb der ISR durch, Variable die innerhalb und außerhalb der ISR benutzt werden als VOLATILE deklarieren
EDIT: zu spät

combie:
Du darfst kein Delay() nutzen
Millis() wird dir immer den gleichen Wert liefern.
micros() schafft den nächsten ms Sprung nicht.

Im Interrupt genutzte globale Variablen müssen das volatile Attribut bekommen.
Das auslesen dieser Variablem muss im Hauptprogramm atomar erfolgen.


Wenn du meinst, Interrupts dafür nutzen zu müssen, dann ok...

Ich würde ja einen 1ms (o.ä.) timer Interrupt verwenden und in diesem beider Räder gleichzeitig lesen.
Einwände?
Verfahren

Bei meinen Drehenkodern, welche händisch bedient werden, habe ich ATTiny85 dran geklemmt, welche ich dann von jedem I2C Master lesen kann.

Micros habe ich deswegen benutzt, weil der Geradeauslauf davon abhängt das beide Räder synchron drehen. 1ms Abweichung und ich dreh einen Kreis statt geradeaus zu fahren.

https://www.amazon.de/SainSmart-Smart-Chassis-Tracing-Encoder/dp/B00NDXEUM0/ref=pd_cp_21_2?_encoding=UTF8&psc=1&refRID=3V5S1V351EBDVD24MXKZ

Das ist der Bausatz des Fahrzeugs. Modifiziert mit einem Servo vorne der um +-50° grad von der Mittelachse pendelt und dabei einen Ultraschallsensor bewegt.

Wenn ich bei 9ms Impulszeit zwischen zwei Drehzahlimpulsen 1ms +- habe, sind das 11% Abweichung. Meiner Meinung nach zuviel um geradeaus zu fahren. Deswegen wollte ich Micros im Interrupt abfragen und die differenz ermitteln die zwischen zwei Interupts aka Impulsen vergangen ist. Das ganze dann Links und rechts noch vergleichen und man bekommt eine Zahl raus, die man zur Motor-PWM Korrektur benutzen kann. Die findet natürlich im Loop statt. Im Interrupt will ich nur oben genannte 4 Befehle abarbeiten.

Wie ich mit der Abfrage alle 1ms das so genau hinbekommen kann, will mir gerade nicht klar werden, aber deswegen frage ich ja.

Vieleicht nochmal meine Timingberechnung

6cm Durchmesser * Pi = 18,9cm. 1m/s entspricht also 5,3 Radumdrehungen/s. Zusammen mit 20 Schlitzen auf der Encoderscheibe bekomme ich also 105,8 Impulse/sec. Folglich alle 9ms ein Schlitz.

Was mir Kopfzerbrechen bereitet und ich leider auch nicht austesten kann ohne großen Messaufbau ist das Timingverhalten im Interrupt und die Genauigkeit von Micros. Würde ich seriel Montor dazu schalten zur Kontrolle, würde mir das massiv das Timing stören.

Dein Verfahren zu Drehgebern ist schön, nur habe ich keine Drehgeber sondern eine einfach Schlitzscheibe. daher muss ich wohl "zu Fuss" zum Ziel kommen indem ich mir eigene Interrupts schreibe dazu. Brauche halt genauigkeiten von weniger als 100µs, besser sogar 50µs, aber das nur alle 9ms maximal. Natürlich wird die Zeit immer größer, wenn das Fahrzeug langsamer wird, aber ich will eben bis 1m/s schnell werden können.

Kann ich in der IDE meine Variablen einfach auch im Interrupt benutzen? Wieviel Zeit verliere ich, wenn ich dabei die micros() einlese und eine Differenz zum vorherigen Wert berechne [...] Soweit ich interrupts noch im Kopf habe bei anderen CPU-Typen, blockiert der gerade laufende Interrupt das Ausführen eines weiteren, der wird aber dann im Anschluss ausgeführt. Welche Zeit würde mein Beispielcode wohl so brauchen?

Einmal micros() aufrufen in der ISR schadet nicht.
Liefert aber nur Werte mit einer Auflösung von 4 µs.
Das ist auch etwa die Größenordnung, die deine Berechnungen brauchen, schätze ich mal.
Kommst also auf ca. 0,1 % Genauigkeit/Auflösung bei ca 9000 µs für Diff. Aber das ist doch schonmal was.

Globale Variable, die von mehreren ISR und loop benutzt werden, müssen als volatile deklariert sein.
loop() muss, wenn es auf diese Variablen zugreift und sie > 1 Byte sind, dies "atomic" machen (z.B. noInterrupts() )
Ansonsten gilt deine Annahme: Eine ISR wird nicht unterbrochen, auch nicht durch eine zweite ISR für das andere Rad. Die kommt dann etwas später dran.
Zugriff auf gemeinsame globale Variable ist also (fast) kein Thema.

Ob du deinDiff_links_rechts bei jedem Interrupt eines der beiden Räder oder nur in loop() ausrechnest? geht wohl beides

Ja, ich glaube es ist im Loop besser aufgehoben. Letztendlich ja egal, wann ich es ausrechne und die Speed korrigiere.

Naja...Lichtschranken sind gelötet, nun muss ich sie dran bauen und testen.

Das mit dem volatile hatte ich mal gelesen, aber geistig nicht genau zuordnen können. Nun ergibt es aber Sinn.

Danke mal, ich bin wieder basteln :slight_smile: