Roboter programmieren mit Arduino (Teil 3: Grundlagen Encoder)

 

Im letzten Tutorial haben wir uns die Grundlagen der Antriebstechnik angeschaut. Wenn unser Roboter geradeaus fahren sollten, haben wir einfach beiden Motoren den gleichen Wert für die Geschwindigkeit übergeben. Im Idealfall würden so beide Motoren mit der gleichen Spannung vesorgt, doch das ist noch lange keine Garantie, das sie sich auch gleich schnell drehen. Ein ähnliches Problem tritt auf, wenn wir z.. B. exakt eine bestimmte Strecke vorwärts fahren oder den Roboter um einen definierten Winkel drehen wollen.

In diesem Fall helfen uns sogenannte Rad-Encoder. Dabei gibt es das inkrementelle und das absolute Verfahren.

Inkremental-Encoder werden beim Start auf 0 gesetzt und zählen ab diesem Zeitpunkt jeden Impuls, den sie erfassen:

 

Inkremental_Enc

 

Bei den Absolut-Encodern wird hingegen auch noch die genaue Position erfasst. Dazu sind aber mehrere optische Sensoren notwendig, denn die Position wird in diesem Fall codiert dargestellt. Die 4 Sensoren im Bild bilden jeweils eindeutig eine Zahl zwischen 0 und 15 in binärer Darstellung. 

 

Encoder_absolut

 

Im Gegensatz zu einem einfachen Inkremental-Encoder kann ein Absolut-Encoder auch die Drehrichtung erfassen, weil er herauf- und herunterzählen kann. Das ist notwendig wenn der Encoder z. B. als Drehgeber eingesetzt wird. Für unsere Zwecke reicht die inkrementale Variante, denn wir wissen ja aufgrund unserer Ansteuerung ob der Motor links oder rechts herum läuft. 

 

3-600x600

 

Der Rad-Encoder des RoverROM-B3000 befindet sich direkt unterhalb der Getriebemotoren und besteht aus 2 kleinen Infrarot-Reflektionssensoren (auf der Platine am rechten Rand, oben und unten zu sehen). Dreht sich das Rad, wird das Infrarotlicht von den Zähnen reflektiert und an den Stellen der Zahnlücken durchgelassen. Es entsteht also ein Rechtecksignal. 

 

Rechtecksignal

 

Die beiden Rad-Encoder sind jedoch leicht gegeneinder versetzt angeordnet, wie dies im Bild zu sehen ist:

 

4-600x600

 

Dadurch wird erreicht, dass auch beide Signale versetzt erscheinen, nämlich genau um 90 ° phasenverschoben:

 

Rechtecksignal_90

 

Wenn wir beide Encodersignale nutzen, erreichen wir also die doppelte Auflösung. Allerdings sind dafür dann auch 2 separate Portanschlüsse notwendig. Im Auslieferungszustand ist nur ein Encoder belegt. Wenn Sie mit dieser Auflösung von 24 Schritten pro Umdrehung nicht auskommen und noch Anschlüsse zur Verfügung haben, dann können Sie einfach eine Steckverbindung von Anschluss B des Encoders auf den Controller legen. 

 

Zählen der Impulse mit Hilfe von Interrupts

 

Die Frage ist allerdings, wie wir die Impulse programmtechnisch verarbeiten. Wir wissen, dass ein einzelner Controller ein Programm nur Schritt für Schritt abarbeiten kann. Aus diesem Grund haben wir im vorigen Tutorial das PWM-Signal vom autarken PWM-Timer erzeugen lassen. Beim Encoder sieht die Problematik noch etwas anders aus. Wir können die Eingänge des Controllers nur zu diskreten Zeitpunkten abfragen, die Geschwindigkeit hängt dabei von der Taktfrequenz und der internen Verarbeitungsgeschwindigkeit ab. Hätten wir keine weiteren Aufgaben zu erledigen könnten wir in möglichst kurzen Abständen den Encoder-Ausgang kontinuierlich abfragen (Polling). Ein entsprechendes Arduino-Programm sähe folgendermaßen aus:

 

Polling1

 

Wir benötigen eine Reihe von Variablen für den alten (enc_old) und neuen Zustand (enc_new) des Sensors und zum Speichern der Zählimpulse (pos_count). Das digitale Signal des Encoders ist entweder 0 (Zahnlücke) oder 1 (Zahnflanke). Wir konfigurieren den entsprechenden Encoder-Anschluss (ENC_R) als Eingang. In der loop()-Schleife fragen wir nun permanent den Zustand des Encoder-Pins ab und speichern ihn in der Variablen enc_new. So lange beide Werte gleich bleiben passiert nichts. Ändert sich der Zustand des Encoder-Pins aber von 0 auf 1, sind beide Werte ungleich und die for-Schleife wird durchlaufen. Dabei wird der Zähler um 1 erhöht und der alte Wert mit dem neuen geladen. Selbst wenn der Roboter mit voller Geschwindigkeit fährt, wird mit Sicherheit kein einziger Impuls verloren gehen. Anders sieht das aber aus, wenn wir noch andere aufwendige Aufgaben erledigen müssen. Würde sich das Rad in diesem Fall so schnell drehen, dass der Controller z.. B. immer dann abfragt, wenn sich der Sensor gerade über einer Zahnflanke befindet, würden wir immer eine 1 erhalten und der Zähler würde nicht weiter laufen. Realistisch ist es aber so, dass der Controller mehrere Tausend Mal während einer Radflanke abfragt und ebenso oft während einer Zahnlücke. Das ist keine besonders effiziente Programmierung.

 

Aus diesem Grund gibt es bei Mikrocontrollern eine Technologie, die sich Interrupts nennt. Wechselt ein interrupfähiger Anschluss seinen Zustand, wird nur noch der aktuelle Befehl zu Ende ausgeführt und danach springt das Programm an eine ganz bestimmte Adresse im Programmspeicher. Dort befindet sich ein kleines Programm, die Interrupt-Service-Routine (ISR). Diese wird abgearbeitet und das Programm fährt danach an der Adresse fort, von der es den Sprung getätigt hat. 

 

Interrupt_1

 

Das Interrupt-Ereignis kann dabei noch genauer präzisiert werden, z. B. steigende Flanke, fallende Flanke, Signaländerung. Die Programmadresse des jeweiligen Interrupts ist in einer Sprungtabelle festgelegt. Der entsprechende bekommt dabei einen Namen, z. B. INT0 oder INT1. Diese Interrutps werden von einem konkreten Anschluss ausgelöst. (INT1 liegt auf Pin 2, INT0 auf Pin 3).

Auch bei den Interrupts nimmt uns das Arduino-System ein wenig Arbeit ab. Würden wir ausschließlich mit einem C-Compiler ohne Bibliotheken arbeiten, müssten wir manuell einige Register konfigurieren und die entprechenden Interrupts freigeben. Auch keine große Sache, aber beim Arduino ist es eben noch einfacher. Dazu müssen wir lediglich 2 Dinge erledigen:

 

1. Wir konfigurieren den jeweiligen Interrupt über den Befehl: "attachInterrupt(INT#, isrRoutine, MODUS)"

Für INT# wird einfach nur die Nummer des jeweiligen Interrupts angegeben (der rechte Rad-Encoder des RoverROM-B3000 liegt auf Pin 3 und damit INT0, der linke auf Pin 2/INT1). 

isrRoutine ist eine Funktion innerhalb des Programms, die im Falle des Interrupts angesprungen wird.

Mit MODUS geben wir an, ob wir bei einer steigenden (RISING), einer fallenden (FALLING) oder wechselndem Pegel (CHANGE) triggern wollen. 

 

2. Wir schreiben unsere Funktion "isrRoutine", die einen beliebigen Namen haben kann. Es ist aber sinnvoll, diese durch eine bestimmte Schreibweise zu markieren, z. B. vorangehendem "isr_"

 

Hinweis: Prinzipiell befinden Sie sich bei einem Interrupt in einem Ausnahmezustand. Gleiche Interrupts sind z. B. in dieser Zeit gesperrt, weil es sonst zu Endlosschleifen kommen könnte, bzw. die Funktion nicht abschließend durchlaufen wird. Vor dem Absprung müssen alle Register gesichert werden, bei kaskadierbaren Interrupts kann dies schnell zu einem Überlauf diese speziellen Speichers (Stack) kommen. der als Stack Overflow bezeichnet wird. Halten Sie deshalb die ISR so kurz wie möglich. 

Zum Testen schreiben wir jetzt ein kleines Programm, das nur die Impulse der Rad-Encoder aufaddiert und bei jeder Erhöung über die serielle Schnittstelle ausgibt:

 

SimpleCount1

 

HInweis: Damit es sich nicht ständig wiederholt, werde ich nur noch die Dinge erläutern, die wir in den vorherigen Tutorials noch nicht besprochen haben. Falls mal etwas unklar ist, schicken Sie mir einfach eine Mail an support@boecker-systemelektronik.de. 

In der setup()-Funktion initialisieren wir neben den Encoder-Pins und der seriellen Schnittstelle auch die beiden Interrupts. Die Felgen unserer Reifen weisen 12 Zähne auf, da wir bei steigender und fallender Flanke einen Interrupt auslösen (CHANGE), erreichen wir 24 Zählimpulse pro Umdrehung. Unsere Interrupt-Service-Routinen heißen: isr_int0 für das rechte und isr_int1 für das linke Rad. Wir beschränken uns dort auf 2 Befehle: wir erhöhen den Zähler und wir setzen ein Flag. Häufig wird empfohlen, in der ISR einfach nur ein Flag zu setzen und den Rest im Hauptprogramm zu erledigen. So allgemein kann man das aber nicht sagen, denn dann würden wir exakt auf die gleiche Funktionalität wie beim Polling zurückfallen. Es könnten Ereignisse verschluckt werden. In unserem Fall könnte es zwar passieren, dass wir im schlimmsten Fall eine Ausgabe verschlucken, der Zähler hat aber zu jeder Zeit den korrekten Wert. Warum brauchen wir dann noch ein Flag? Ganz einfach, damit wir erkennen, ob eine Änderung erfasst wurde. Nur dann lösen wir eine Ausgabe aus. Das könnten wir auch in einem Vergleich im Hauptprogramm erledigen, aber so ist es einfacher.

Nun zum Programmkern in der loop()-Funktion:

 

SimpleCount2

 

In der Loop()-Funktion fragen wir den Zustand der Flags ab und geben nur eine Meldung aus, wenn etwas passiert ist. Die Methode "print" des Objektes "Serial" gibt den Text "Interupt rechts: " aus, danach folgt die "println"-Methode, die den Zählerstand ausgibt und mit einem Zeilenrücklauf (CR) endet. Wenn Sie jetzt das rechte Rad genau eine Umdrehung mit der Hand drehen (vor oder zurück  ist dabei egal), erhalten Sie die folgende Ausgabe (analog gilt das Gleiche für das linke Rad):

 

EncoderProg3

 

So viel zur Programmierung und dem Auslesen der Encodersignale. In den nächsten Tutorials werden wir praktische Umsetzungen (Messen von Streckenlängen und Bremswegen, Fahren von exakten Kurvenbögen, Geradeausfahrt) erarbeiten. 

 

Für weitere Informationen und Bestellungen des

RoverROM-B3000

einfach auf nachfolgenden Link klicken: Böcker RoverROM-B3000 Version 2

oder als Bausatz: Böcker RoverROM-B3000 Version 2 Bausatz

 

Sie wollen Arduino-Profi werden? Dann ist unser Video Mikrocontroller-Lehrgang MC1 genau das Richtige für Sie!

 

Falls Sie Fragen zum Thema haben, wenden Sie sich einfach an uns.

Wir helfen Ihnen gern weiter!

 

Ihr Team von Böcker Systemelektronik

 

Hinweis: Unser Tutorialangebot wird in unregelmäßigen Abständen erweitert. Wenn Sie sofort über eine Neuerscheinung informiert werden möchten, tragen Sie sich bitte hier in unsere Benachrichtigungsliste "Tutorials" ein. Sie können diesen unverbindlichen Service jederzeit in Ihrem Kundenkonto oder per E-Mail wieder abbestellen.  

 

 

Copyright © Böcker Systemelektronik