Thema
Verarbeitung von Sensoreingaben in Echtzeit mit dem Ziel 1. simultaner Steuerung von Aktoren und 2. Aufnahme und Verarbeitung von Signalen in weicher Echtzeit.
Positioniereinrichtungen in 3D-Druckern, xyz-Frästischen, Pipettierautomaten, Plotter, Lasercutter stellen nur einige Beispiele für Maschinen dar, deren Aufgaben Echtzeitfähigkeit für die Positionierung aber auch Überwachung von Bewegung erfordern. Mechanische oder optische Endschalter definieren zumindest im Zuge der Initialisierung der Maschinen Verfahrwege. Schrittfehler und somit Schrittverluste in der Folge hoher mechanischer Widerstände können mittels im Fahrweg befindlicher Sensoren erkannt werden. Moderne CNC-Maschinen erfassen ihre aktuelle Position zum Teil in Echtzeit, während sie zugleich Bewegungen und andere Aktionen ausführen.
Ziele
Während der Mikrocontroller einen Schrittmotor steuert, soll er zugleich die Anzahl der Tastendrücke auf einen Mikrotaster korrekt mitzählen und auf dem Serial Monitor ausgeben. Der Controller hat somit 3 Aufgaben parallel zu lösen. Bitmuster im korrekten Timing an den Schrittmotortreiber ULN2003 senden, Tasterabfrage, Kommunikation (Senden und Empfangen) mit dem PC.
Planung
Die Gesamtaufgabe ist schrittweise einer Lösung zuzuführen.
- Ausgabe von Zahlen auf dem Serial Monitor
- Erkennung von Betätigungen des Taster
- Steuerung eines Schrittmotors
Material
Das benötigte Material ist in den Starterkits wie „Elegoo UNO R3 The Most Complete Starter Kit“ oder „Funduino – XXL-UNO-Lernset Kit für Arduino“, aber auch anderen Kits enthalten.
- Arduino UNO R3 oder Arduino NANO
- Diverse Steckbrücken z. T. mit einseiter Buchse (male-female Art)
- Mikrotaster / Drucktaster / Button
- 10 kΩ Widerstand, ggf. weitere Widerstände nach Bedarf
- Breadboard
- Schrittmotorset
Implementation
1 Ausgabe von Zahlen auf dem Serial Monitor
Jedes Element der in Echtzeit zu verfolgenden Aktionen soll belegt / verifiziert werden können. Tastendruck oder nicht? Die erfolgte Handlung ist eine erfolgreiche Handlung, wenn der Mikrocontroller sich in der beabsichtigten Form verhält. Die Anzahl der durchgeführten Betätigungen eines Taster oder der mit einem zu Testzwecken vorgegebenen Zeitregime erfolgten Betätigungen eines Tasters sollen visuell belegbar und schlussendlich in einer Abnahmeprüfung dokumentierbar gemacht werden. Damit dies über ein möglichst einfaches und im System vorhandenes Mittel erfolgt, kann eine einfache Zählroutine, die ihr Ergebnis über den seriellen Monitor ausgibt, hilfreich sein.
1.1 Umsetzung
Vorschlag: Anpassen eines Demo-Programms der IDE
Datei -> Beispiele -> 01.Basics -> Blink
Das Blinken der LED kann der zusätzlichen, visuellen Kontrolle dienen.
Zunächst soll versuchsweise eine Ganzzahlvariable schrittweise inkrementiert und ausgegeben werden. Dazu benötigt man::
- die Variable,
- die serielle Kommunikation sowie
- eine Funktion für die Ausgabe der Variablen auf die verwendete Schnittstelle.
Von den vielen Möglichkeiten, eine Variable in einem C-Programm zu definieren (explizit, implizit, lokal, global) ist eine explizite Definition als Variable vom Typ Integer zu Beginn des Programms zielführend. Jede Variable sollte geeignet vorbelegt / initialisiert werden. Hierzu gibt es mehrere Möglichkeiten. Ein Weg führt über die Intialisierung im Zuge der Definition in der Art:
int laufvariable = 0;
Ein anderer Weg sieht eine Initialisierung im Zuge der Initialisierung aller Prozeduren, Objekte und Variablen innerhalb der setup()-Routine vor.
In der Funktion „setup()“ ist die Initialisierung der Schnittstelle zum Seriellen Monitor vorzusehen. Eine brauchbare Kommunikationsgeschwindigkeit (Baudrate) ist mit 9600 gegeben.
Serial.begin(9600);
Die Verwendungsart des Digital-Ein/Ausgangs ist zu definieren.
Die Ausgabe und Inkrementierung der Variablen sind in der Hauptroutine (loop()“) vorzunehmen.
Die Ausgabe auf den seriellen Monitor veranlasst ein:
Serial.println(laufvariable);
ein
laufvariable++;
sorgt für die Inkrementierung. Die Reihenfolge von Inkrementierung und Ausgabe legt fest, ob die Ausgabe mit einer „0“ oder einer „1“ beginnt.
1.2 Verifikation
Nach dem Compilieren und Hochladen des Programms auf den Controller wird die Funktion durch Anzeige der Ergebnisse auf dem seriellen Monitor überprüft und bestätigt.
Welche Zahl zeigt der serielle Monitor nach Erreichen einer angezeigten Zahl zwischen 10 und 20, wenn er geschlossen und anschließend erneut geöffnet wird?
Erweiterungsvorschlag: Für die Dokumentation und die Qualifizierung der Echtzeitfähigkeit sind möglichst präzise Zeitmessungen erforderlich. Je nach Design des Programms kann ein identisches Ergebnis eines Programm auf sehr unterschiedlich zeitbedürftige Weise erreicht werden. Die Funktion „micros()“ kann hier wertvolle Unterstützung leisten. Wie könnte die Implementierung einer Zeitmessung in Mikrosekunden inklusive der Anzeige der Ergebnisses auf dem seriellen Monitor aussehen.
1.3 Quellen
https://www.arduino.cc/en/Tutorial/Blink
2 Erkennung von Betätigungen eines Tasters
Eine funktionierende Zählroutine mit geeigneter Anzeige- und Dokumentationsmöglichkeit vorausgesetzt, sollen sensorisch erfasste Ereignisse – im einfachsten Fall Betätigungen eines mechanischen Tasters – durch den Mikrocontroller gezählt und angezeigt werden. Quelle entsprechender, binärer Signale von mehr oder weniger langer Dauer können auch Überfahrtaster und Lichtschranken etc. sein. Die Dauer des zum Zweck der Zählung anliegenden Signals kann sich somit auf einen sehr kurzen Zeitraum beschränken. Im Gegensatz zu Lichtschranken entstehen bei einer Vielzahl mechanischer Tastern zudem Probleme wie das Prellen. Diese sollen hier zunächst nicht berücksichtigt werden. Sie führen jedoch dazu, dass die Zahl der gezählten Tastendrücke oft weit über der Zahl der ausgeübten Tastendrücke liegt. Der Berücksichtigung derartiger Komplikationen ist das eigene Kapitel „Debouncing“ gewidmet, das sowohl Soft- als auch Hardware-Lösungen anbietet.
2.1 Umsetzung
Vorschlag: Anpassen eines der Demo-Programme der Arduino IDE [1, 2] oder einer anderen Anleitung zur Behandlung der Taster-Funktion [3, 4].
Zur Visualisierung des Erfolgs soll – wie oben – die Anzeige der Anzahl erfolgter Tastendrücke auf dem seriellen Monitor dienen, die mit jedem erkannten Tastendruck inkrementiert wird.
Das Studium und Nachvollziehen der Quellen [1 .. 4] gibt Aufschluss über mögliche Probleme, die durch unsachgemäße Verwendung / Einbau eines Tasters entstehen können.
- Durch Erzeugen eines Kurzschlusses, weil jeweils zwei der Beinchen des Tasters dauerhaft leitend miteinander verbunden sind.
- Der ohne Widerstand leitende Kontakt zwischen einer Spannungsquelle oder einem auf „high“ liegenden Digitalausgang und der Masse (GND) zu hohen Strömen und damit Kurzschluss und Zerstörung führen kann.
- Ein „frei“ liegendes Potenzial einen beliebigen Wert annehmen kann (Hinweis: Pull-up- oder Pull-down-Widerstand, intern oder extern)
Traue nie einem Eingangspegel, der nicht elektrisch festgelegt wurde. Auch die Funktion INPUT_PULLUP trifft eine derartige elektrische Festlegung. Nicht jeder Mikrocontroller verfügt über eine derartige interne Funktion. Der ESP32 verfügt zusätzlich zum internen pinMode(pinNummer, INPUT_PULLUP) auch über die Funktionalität pinMode(pinNummer, INPUT_PULLDOWN).
In Abhängigkeit von dem unter „Werkzeuge“ -> „Board“ ausgewählten Entwicklungssystem (Mikrocontroller-Board) lässt Arduino-IDE nur bestimmte Befehle und Variablen zu. Dies ist am Farbwechsel der eingegebenen Code-Bezeichnungen zu erkennen. Ein Farbwechsel findet nicht statt, wenn keine Vorbelegung der Bezeichnung gefunden wird.
Wenn das Beispiel aus Quelle [5] implementiert werden soll, ist wie im vorangegangenen Beispiel:
- eine Integer-Variable für die Anzahl der gezählten Tastendrücke zu definieren und zu initialisieren,
- der serielle Monitor in „Setup()“ zu initialisieren,
- die Anweisung zur Inkrementierung der Laufzeitvariablen an geeigneter Stelle einzufügen
- und die Schreibanweisung für den Wert der Laufzeitvariablen an eine geeignete Position des Codes einzufügen.
2.2 Verifikation
Welches Ergebnis wird für die Laufzeitvariable vom seriellen Monitor initial angezeigt?
Welche Position im Code bewirkt, dass die Variable nur einfach und unmittelbar nach der Betätigung des Tasters angezeigt wird?
Wie steigt die angezeigte Anzahl bei länger anhaltendem Drücken des Tasters?
Wie werden Tastendrücke gezählt, wenn sie kurz nacheinander erfolgen?
Erweiterungsvorschlag: Wie lassen sich die Verifikationsschritte als Probleme der Echtzeitfähigkeit beschreiben? Welche Einflüsse sind am größten und wodurch lassen sie sich minimieren? Wie kann eine Umsetzung aussehen (in Code)?
2.3 Quellen
[1] https://www.arduino.cc/en/Tutorial/Button
[2] https://www.arduino.cc/en/Tutorial/InputPullupSerial
[3] https://funduino.de/nr-5-taster-am-arduino
[4] Lektion 5: Digitale Eingänge des alten Handbuchs „The Most Complete Starter Kit for UNO V1.0“ oder die entsprechende Erklärung im englischsprachigen Handbuch „The Most Complete Starter Kit for UNO V2.0“ auf der Download-Seite der Firma Elegoo.
3a Steuerung eines Schrittmotors
Der Betrieb eines Schrittmotors verlangt die Aktivierung digitaler Ausgänge eines Mikrocontrollers in definierten Mustern. Die zeitlichen Abstände zwischen den Änderungen definieren die Bewegungsgeschwindigkeit des Motors ebenso wie die Feinabstimmung der Muster (Vollschritt- versus Halb- oder Mikroschrittverfahren). In jedem Fall bedingen Masseträgheit des Rotors und die Induktivität der Spulen einen mindesten Zeitbedarf für Schrittfolge.
Weil die digitalen Ausgänge den nötigen Strom für die Spulen eines Schrittmotors nicht aufbringen können, benötigen die Signale der digitalen Ausgänge zunächst eine Verstärkung. Oft werden Schrittmotoren bei anderen Spannungen betrieben als die sie steuernden Mikrocontroller. Übliche, maximale Strombelastbarkeiten digitaler Ausgänge betragen zwischen 2 und 20 mA. Entsprechend hoch sind die Verstärkungsfaktoren der Schrittmotortreiber dimensionieren. Der ULN2003A ist ein Darlington-Array [1], dessen Strombelastbarkeit pro Ausgangskanal 500 mA beträgt und eine Spannung von bis zu 50 V verträgt.
3.1a Umsetzung
Eine ausführliche auch visuelle Erklärung der Funktion, Steuerung und des inneren Aufbaus des verwendeten Schrittmotors 28BYJ-48 und Treiber ULN2003 gibt ein englischsprachiges Youtube-Video [2]. Eine knappere ebenfalls englischsprachige Einführung bietet Nikodem Bartnik in seinem Video [3].
Die in den Begleitheften zu den Starterkits verwendeten Beispiele [4, 5] verwenden jeweils Bibliotheken (Stepper.h). Da dies an dieser Stelle den geraden Weg zum Ziel verlässt sollte der Programmcode aus dem Video verwendet werden [6], der dem Code „S01_BruteForceWaveDrive.ino“ aus Video [2] im Ansatz entspricht.
Der vorgegebene Code ist um die oben beschriebenen Elemente zum Auslesen und Anzeigen von Taster-Betätigungen zu ergänzen.
3.2a Verifikation
Eine auf die Motorachse gesetzte Klammer oder ein angeklebtes Fähnchen aus Klebefilm hilft bei der Verifikation der Implementationsstufe.
Wie bewegt sich die Motorachse nach Start des Programms, wenn der Taster nicht betätigt wird? (Geschwindigkeit der Rotation in Umdrehungen pro Minute, wie und in welchem Ausmaß erfolgen Richtungswechsel, sind Stockungen in der gleichförmigen Bewegung zu beobachten?
Wie verändert sich die beobachtete Dynamik, wenn Tastendrücke erfolgen?
Wie präzise erfolgt die Zählung der Tastendrücke?
In welcher Form verändert sich die Perfomance, wenn die Geschwindigkeit der Rotation durch Vergrößerung des Wertes in der Funktion delay()?
3.3a Quellen
[1] https://en.wikipedia.org/wiki/ULN2003A
[2] 28BYJ-48 Stepper Motor and ULN2003 Driver Intro
[3] 28BYJ-48 stepper motor and ULN2003 Arduino (Quick tutorial for beginners)
[4] Lektion 31: Schrittmotor des alten Handbuchs „The Most Complete Starter Kit for UNO V1.0“ oder die entsprechende Erklärung im englischsprachigen Handbuch „The Most Complete Starter Kit for UNO V2.0“ auf der Download-Seite der Firma Elegoo.
[5] https://funduino.de/nr-16-schrittmotor
[6] https://github.com/NikodemBartnik/ArduinoTutorials/blob/master/28BYJ-48/28BYJ-48.ino
3b Steuerung eines Schrittmotors ohne delay()
Offensichtlich zeigt die Leistungsfähigkeit einer Steuerung bei zugleich zu erfassenden, externen Signalen mit Blick auf die Echtzeitfähigkeit deutliche Beschränkungen. Das in „1 Ausgabe von Zahlen auf dem seriellen Monitor“ als Grundlage gewählte Beispiel-Programm „Blink“ steht in einer für das Zeitmanagement von Mikrocontrollern erweitertern Funktionalität auch als „BlinkWithOutDelay“ unter „Datei“ -> „Beispiele“ -> „02 Digital“ -> „BlinkWithOutDelay“ zur Verfügung [1]. Wie leicht gezeigt werden kann hilft es nämlich nicht. Die Funktion „delay()“ durch die Funktion „delayMicroseconds()“ in den obigen Programmen zu ersetzen, denn das Problem ist prinzipiell und kann nur durch eine ebenso grundsätzliche Änderung behoben werden. Die grundsätzliche Änderung besteht in der Nutzung der verstrichenen Absolutzeit seit dem Programmstart und der Bildung von Zeitdifferenzen von Absolutzeiten. Die Absolutzeit kann mittels der Funktionen „millis()“ bzw. „micros()“ in Millisekunden oder Microsekunden ermittelt werden [2, 3].
3.1b Umsetzung
Ein Blick in das Demoprogramm „BlinkWithoutDelay“ zeigt, die Vermeidung der Funktion „delay()“ erfordert die zusätzliche Definition globaler Variablen vom Typ „unsigned long“. Dies sind Variable vom Typ Integer, die zwar nicht negativ werden können/dürfen, dafür aber einen stark erweiterten Wertebereich umfassen. Auch dieser Wertebereich ist jedoch endlich. Dadurch treten zwangsläufig Probleme beim Überschreiten der Wertebereichsgrenze auf, die im Programm erfasst und behandelt werden müssen. Die für diese Erfassung und Handhabung erforderliche, zusätzliche Zeit ist spätestens bei der Charakterisierung und Dokumentation der Echtzeitfähigkeit zu würdigen. Gegebenenfalls kann es auch zu zeitlichen Inhomogenitäten im Verlauf der Steuerung kommen (stockender Motor). Die Maximum bis zum Überlauf der Variablen vom Typ „unsigned long“ beträgt 4294967295. Bei Verwendung von Millisekunden also 4294967,295 Sekunden oder etwa 49 Tage. Bei Verwendung von „micros()“ ist diese Maximalzeit schon nach etwa 70 Minuten (auf dem Arduino UNO) erreicht.
Die Projektdokumentation von Mohannad Rawashdeh [4] gibt ein Codebeispiel, das die unter 3a zitierten Codebeispiele in eine Programmsequenz und -Struktur gibt, die auf die Nutzung der Delay-Funktion verzichtet.
Empfehlenswert ist eine stufenweise Umsetzung der Anpassung indem zunächst aufbauend auf den Code in [4] die Anzeigefunktion für den seriellen Monitor implementiert und verifiziert wird. Anschließend ist die Abfragefunktion zu implementieren.
3.2b Verifikation
Eine auf die Motorachse gesetzte Klammer oder ein angeklebtes Fähnchen aus Klebefilm hilft bei der Verifikation der Implementationsstufe.
Wie bewegt sich die Motorachse nach Start des Programms, wenn der Taster nicht betätigt wird? (Geschwindigkeit der Rotation in Umdrehungen pro Minute, wie und in welchem Ausmaß erfolgen Richtungswechsel, sind Stockungen in der gleichförmigen Bewegung zu beobachten?
Wie verändert sich die beobachtete Dynamik, wenn Tastendrücke erfolgen?
Wie präzise erfolgt die Zählung der Tastendrücke?
Erweiterung: Durch Vergleich der Taktung der Schrittfrequenz der Programmierweise entsprechend 3a und 3b ergeben sich deutliche Unterschiede in der Leistungsfähgigkeit für die Erkennung von Betätigungen des Tasters. Wie könnte eine Dokumentation und Verifikation gestaltet werden? Welche Ergebnisse sind mit Blick auf die Echtzeitfähigkeit zu diskutieren?
Optionales: Die in der 3a genannten Quellen [4, 5] verwenden die einfache Bibliothek stepper.h. Wie vergleicht sich deren Performance zur unter 3b implementierten Lösung? Benne de Bakker geht in seinem Tutorial [5] über die gezeigten Möglichkeiten hinaus. Sanftanlauf ist ebenso wie Sanftbremsung sind gerade bei Schrittmotoren ein wichtige Funktionalität, denn bei Bewegen größerer Massen führt deren hohe Trägheit entweder zu Schrittverlusten oder zu überdimensionierten Schrittmotoren mit entsprechend groß ausgelegten Schrittmotortreibern. Hier kommt mit AccelStepper [6] zudem eine breit eingesetzte Bibliothek für Schrittmotoren zum Einsatz.
Die Definition und Erkennung einer Ausgangsposition (Home-Positon) definiert für die meisten Standardanwengungen (3D-Drucker, CNC-Maschinen …) eine Marke zur Prüfung auf Schrittverluste, die innerhalb von Programmabläufen wiederholt angefahren werden kann, oder schlicht den Bezugspunkt für alle maschinelle Tätigkeit. Unter Verwendung eines entsprechenden Tasters, einer Gabellichtschranke, eines Abstandssensors …. lässt sich die Bestimmung einer Bezugsposition auch mit einem einfachen Schrittmotor nachvollziehen, wenn dessen Achse entsprechend dem verwendeten Sensor ergänzt wurde [7]. Sein volles Potenzial erreicht der Aufbau jedoch erst, wenn die Bestimmung der Grundposition (Home) mit den Fähigkeiten zu kontrollierten Beschleunigungs- und Bremsvorgängen kombiniert wird [8]. Die Umsetzung der hier in „Optionales“ genannten Erweiterungen bleiben einem eigenen Tutorial vorbehalten und dienen hier als Ausblick. Hierzu zählt ebenso die Verwendung und Charakterisierung von Schrittmotortreiberbausteinen aus dem 3D-Druck-Bereich, die viele der in diesem Text beschriebenen Schrittmotorsteuerungsvorgänge und damit vor allem auch deren Programmierung drastisch vereinfachen. Hinzugerechnet werden sollten ebenso Features wie einstellbare Begrenzung des Maximalstrom, Stromabsenkung, einfacher Wechsel zwischen Voll und Mikroschrittmodi, Bewegungsumkehr und weiterer Funktionalitäten dieser Treiberplatinen. Sie gehen weit über die Fähigkeiten des im obigen Beispiel verwendeten ULN2003 hinaus.
3.3b Quellen
[1] https://www.arduino.cc/en/tutorial/BlinkWithoutDelay
[2] https://www.arduino.cc/reference/en/language/functions/time/millis/
[3] https://www.arduino.cc/reference/en/language/functions/time/micros/
[4] https://www.instructables.com/id/BYJ48-Stepper-Motor/
[5] https://www.makerguides.com/28byj-48-stepper-motor-arduino-tutorial/
[6] https://www.airspayce.com/mikem/arduino/AccelStepper/index.html
[7] https://www.brainy-bits.com/homing-stepper-motor-at-startup/
[8] https://www.brainy-bits.com/post/homing-stepper-motors-using-the-accelstepper-library