Programmierung Kamera Slider mit dem Arduino
Im Dritten Teil des Kamera Sliders auf Basis eines Arduino geht es nun endlich um die Programmierung. Mein Quelltext nicht sonderlich lang (nur knapp 500 Zeilen), dafür erfüllt es eigentlich jeden Wunsch :)
Navigation Arduino Kamera Slider
- Teil 1: Bau des Sliders
- Teil 2: Bau des Steuerung
- Teil 3: Programmierung der Steuerung
Fertiges Programm kaufen
Da die meisten nicht die Zeit und Motivation haben, sich in die Programmierung einzuarbeiten und es anfangs sehr frustrierend ist bis man ein lauffähiges Programm geschrieben hat, biete ich den Arduino Sketch zum Download an. Den Link maile ich euch zu. Bitte vorab einen Betrag eurer Wahl auf mein Paypal Konto überweisen: https://www.paypal.me/dahansi
In Dieser Anleitung gebe ich nur wieder Denkanstöße und verweise auch Teilweise auf die Programmierung der Brausteuerung, da vieles analog läuft.
Die einzelnen Bestandteile
Die Slider-Steuerung besteht aus nur 5 Teile:
- Display
- Steuerung des Rotary-Encoders (=Dreh-Druck-Knopf) & Potentiometer
- Menü
- Auslösen der Kamera
- Ansteuern des Motors
Display
Um überhaupt etwas sehen zu können, müssen wir das Display ansteuern. Das geht genauso wie auch bei der Brausteuerung: Arduino LCD-Display ansteuern
Steuerung des Rotary-Encoders und Potentiometers
Mit dem Encoder möchten wir rauf / runter und bestätigen. Mit dem Potentiometer wollen wir die Größe der Schritte bestimmen (oder wollt ihr 10.000 Schritte mit dem Encoder drehen ;) ).
Rotary Encoder
Zu erst fangen wir mit der PIN-Belegung an und setzen ein paar Variablen:
// Drehknopf #define DIR_CCW 0x10 #define DIR_CW 0x20 int Rotary1 = 10; // Rotary Drehknopf PIN int Rotary2 = 11; // Rotary Drehknopf PIN int RotaryButton = 12; // Rotary Button PIN int rAlt = 0; // Rotary PressStatus Alt int rWert = 0; // Rotary DrehWert //in die void setup() Funktion: void setup(){ // Rotary Encoder: pinMode(Rotary1, INPUT); // Drehknopf initialisieren pinMode(Rotary2, INPUT); // Drehknopf initialisieren digitalWrite(Rotary1, HIGH); digitalWrite(Rotary2, HIGH); pinMode(RotaryButton, INPUT); // Button initialisieren digitalWrite(RotaryButton, HIGH); // Button aktivieren }
Falls der Knopf später "falschrum" dreht, tauscht einfach die PIN-Belegung 10 und 11. Falsch gar nichts geht, habt ihr vermutlich die ganze PIN-Belegung verhauen, prüft daher nochmal genau, wo Button und die Dreh-Impuls Kabel hingehen.
Dann prüfen wir mit dem folgender Funktion, ob er gedrückt wurde. Wir machen später immer eine While-Schleife um diese Funktion im Menü. Solange diese "false" ist wird sie ausgeführt, wird der Button gedrückt, gibt die Funktion "true" zurück und die Schleife bricht ab:
/* * Knopfdruck abfragen */ boolean pushed(){ int rStatus = digitalRead(RotaryButton); if (rStatus != rAlt) { rAlt = rStatus; if(rStatus == HIGH) return true; } return false; }
Der Code für die Drehrichtung ist etwas komplizierter, sollte aber 1:1 übernommen werden können. Gibt die Funktion 1 zurück, wurde nach rechts, bei -1 nach links gedreht. Wird "false" zurückgegeben, wurde gar nicht gedreht:
// Drehimpuls abfragen const unsigned char ttable[7][4] = { {0x0, 0x2, 0x4, 0x0}, {0x3, 0x0, 0x1, 0x10}, {0x3, 0x2, 0x0, 0x0}, {0x3, 0x2, 0x1, 0x0}, {0x6, 0x0, 0x4, 0x0}, {0x6, 0x5, 0x0, 0x20}, {0x6, 0x5, 0x4, 0x0}, }; volatile unsigned char stateR = 0; int getRotary() { unsigned char pinstate = (digitalRead(Rotary2) << 1) | digitalRead(Rotary1); stateR = ttable[stateR & 0xf][pinstate]; unsigned char result = (stateR & 0x30); if(result) return (result == DIR_CCW ? -1 : 1); return false; }
Potentiometer
Das Potentiometer ist recht simpel. Hier wird einfach ein Wert zwischen 0 und 1023 zurückgeben - je nach dem wie weit er gedreht wurde. Anhand dessen kann man deutlich schneller zu hohen Zahlen kommen.
// Poti int potPin = 2; int stepSize = 0; // zum Abfragen des Wertes einfach diesen Befehl aufrufen: stepSize = analogRead(potPin);
stepSize wäre z.B. der Wert 23 und dann zählt er immer 23 auf die aufzunehmenden Bilder dazu: 23, 64, 87, usw.
Die Menüsteuerung eines Arduinos
Es gibt viele Methoden ein Menü zu erstellen. Ich habe mir das ganze so überlegt:
Zu erst lade ich das Hauptmenü, dort gibt es die Auswahl zwischen "Setup Starten" und "Manueller Modus". Im Manuellen Modus kann man einzelne Bilder aufnehmen und den Slider bewegen. Im Setup kann man die Zeitrafferaufnahme starten und alles Schritt für Schritt einstellen (Anzahl Bilder, Zeit Interval, Richtung, usw).
Die Grundfunktion
Fangen wir mit der Funktion an, die die einzelnen Menüs lädt. Das kommt in die Void-Loop() Funktion:
//Die 2 Variablen werden zu Beginn festgelegt: /* * 0 = Hauptmenü * 1 = Setup * 2 = Manuell */ int state = 0; // Aktueller Programmteil int stateAlt = -1; // Alter Status // unter die setup-Funktion: int menuAkt = 0; // Aktueller MenüPunkt int menu[] = {0,1,2}; // Mögliche Menüs int menuSize = sizeof(menu) / sizeof(int); void loop() { if(state != stateAlt){ // Falls sich etwas ändert stateAlt = state; switch(state){ case 1: state = loadSetup(); break; // Setup Starten case 2: state = loadManuell(); break; // Manueller Modus default: state = loadMenu(); break; // Hauptmenü } } delay(1000); }
Das Hauptmenü
Im Prinzip sind alles einzelne Endlosschleifen, die mit dem Klick eines Buttons ausgeführt werden. Die Funktion für das Hauptmenü ist etwas komplizierter, da wir hier einen Pfeil rauf und runter bewegen der zeigt welchen Menüpunkt wir ausgewählt haben Die Funktion loadMenu() wird im Schritt zuvor aufgerufen:
boolean menuTrigger = false; int loadMenu(){ // 0: Setup starten // 1: Manueller Modus int menuAlt = 0; changeMenu(0); // Updated das Menü und zeigt die neue Pfeil Position an while(1){ int rotary = getRotary(); // Ruft ab ob der Encoder bewegt wurde if(rotary){ // Falls ja, Menü neuladen -> Pfeilposition ändern changeMenu(rotary); } if(pushed() && menuTrigger){ // Falls Button gedrückt wurde menuAkt++; return menuAkt; //Gibt die ID des neuen Menüs zurück } menuTrigger = true; } return 0; } void changeMenu(int s){ if(!s) showMenu(0); // Einfach nur Menü anzeigen -> beim ersten Aufruf else{ if(s < 0) menuAkt--; // Linksrum gedreht else menuAkt++; // Rechtsrum gedreht if(menuAkt > menuSize) // Falls Pfeil mehr als Anzahl der Menüpunkte, fängt er wieder von vorne an menuAkt = 0; else if(menuAkt < 0) // Falls kleiner 0, fängt er beim letzten Menüpunkt an menuAkt = menuSize; showMenu(menuAkt); //Neues Menü laden } } /* Pfeil */ byte arrow[8] = { B00000, B11000, B01100, B00110, B01100, B11000, B00000, }; void showMenu(int s){ lcd.createChar(0, arrow); // Pfeil anzeigen lcd.clear(); // LCD löschen lcd.setCursor(2,0); lcd.print("Setup starten"); lcd.setCursor(2,1); lcd.print("Manueller Modus"); switch(s){ case 1: lcd.setCursor(0,1); lcd.write(byte(0)); break; //Pfeil anzeigen default: lcd.setCursor(0,0); lcd.write(byte(0)); break; //Pfeil anzeigen } menuAkt = s; }
Das Manuelle Menü
Wählt man jetzt das Manuelle Menü, wird die Funktion loadManuell über die Grundfunktion aufgerufen. In loadManuell() soll man ein Foto auslösen und den Motor bewegen können. Mir fällt gerade auf, dass ich die Motorsteuerung und den Auslöser vielleicht hätte vorher zeigen sollen, aber dann kommt das halt danach ;).
boolean loadManuell(){ setRes(16); // Geschwindigkeit des Motors digitalWrite(motorEnable,LOW); // Motor aktivieren lcd.clear(); // LCD löschen myTxt = String("Manueller Modus"); lcd.print(myTxt); int stepSize = 10; // Schrittgröße des Motors while(1){ rotary = getRotary(); stepSize = analogRead(potPin); // Motorgeschwindigkeit über das Potentiometer auslesen if(stepSize < 5) // Kleiner 5 macht mein Motor nicht mit, daher mindestens 5 möglich stepSize = 5; if(rotary){ // Wenn Rotary Encoder gedreht wird, bewegt sich der Motor vor bzw. zurück if(rotary > 0){ digitalWrite(motorDir,HIGH); steps += stepSize; }else{ digitalWrite(motorDir,LOW); steps -= stepSize; } Move(stepSize); float prozent = ((float) steps / float(maxSteps)) * 100; myTxt = String("Distanz: ") + prozent + String("% "); // Zurückgelegt Entfernung lcd.setCursor(0,1); lcd.print(myTxt); } if(pushed()){ // Wenn gedrückt wird, macht er ein Foto Serial.println("Bild!"); delay(500); shutter(); delay(500); myTxt = String("# Fotos: ") + pictureCounter; // Anzahl der Fotos zählen lcd.setCursor(0,2); lcd.print(myTxt); } } digitalWrite(motorEnable,HIGH); // Motor deaktivieren return 0; }
Das war soweit der Code für das Menü. Die Funktion fürs Timelapse ist genauso aufgebaut. Ihr müsst lediglich in einzelnen Schritten die Anzahl der Bilder, das Zeitinterval und die Laufrichtung des Sliders abfragen. Ich habe noch eine "Kann es losgehen?"-Seite gemacht, damit nicht sofort gestartet wird.
Klickt man dann, lädt es die doTimelpase Funktion. Dort wird in einer while-Schleife geprüft ob die maxBilder Anzahl bereits erreicht ist. Solange das nicht ist, wird der Slider bewegt und ein Foto geschossen. Die Distanz die der Motor zurücklegt, müsst ihr natürlich immer berechnen. Also ob er 20 Bilder auf die ganze Distanz macht oder 200 Bilder, ist ja ein erheblicher Unterschied.
Das bekommt aber jeder selber hin, der bis jetzt noch durchblickt ;) Vorher schauen, wie viele Schritte der Motor braucht, um den ganzen Slider entlang zu fahren. Bei mir waren das 52.600 Stück. Sprich so viele Bilder könnte ich machen, und der Slider bewegt sich jedes mal dazwischen.
Auslösen einer Kamera über Arduino
Jetzt kommen wir zum coolsten Teil, das Auslösen der Kamera! Das ist super simpel und braucht nur ein paar Zeilen Code:
// Counter-Variable für die Bilder int pictureCounter = 0; short int shutterPin = 2; // PIN der Kamera //In die Setup-Funktion: void setup(){ pinMode (shutterPin, OUTPUT); } // Funktion zum fotografieren: void shutter(){ digitalWrite(shutterPin, HIGH); delay(50); digitalWrite(shutterPin, LOW); pictureCounter++; //Hochzählen des Counters }
Das wars es auch schon. Sobald ihr jetzt die Funktion shutter() aufruft, wird die Kamera ausgelöst.
Stepper-Motor steuern Arduino
Der letzte Teil ist die Steuerung des Motors. Das ist gar nicht so simpel und ich bin um ehrlich zu sein auch noch nicht zu 100% zufrieden. Aber der Zweck wird erfüllt und es funktioniert alles sehr gut.
Vorab ein bisschen Mathe: Schaut mal wie viel Grad pro Schritt euer Motor macht. Bei mir sind das 1,8. Jetzt rechnet ihr 360/1.8 = 200. Falls bei euch etwas anderes rauskommt, müsst ihr die 200 in meinem Code mit eurer Zahl ersetzen.
Wie gehabt, ein paar Variablen definieren und die Setup-Funktion:
const int resolution = 16; // bzw. 1/16tel const long int maxSteps = 52600; // von Links nach Rechts 17 ganze Schritt á 200 Einzel Steps int speed = 800; long int steps = 0; long int maxPics = 0; long int maxDelay = 0; short int motorEnable = 6; // HIGH = Motor deaktiviert | LOW = Motor aktiviert short int motorStep = 5; // Step short int motorDir = 4; // Dir | LOW hin zum Motor | High = Weg vom Motor boolean motorDirection = HIGH; //LOW hin zum Motor | High = Weg vom Motor short int motorM1 = 7; // \ short int motorM2 = 8; // -> Bestimmen die Revolution | M1,M2,M3 High = 1/16, M1,M2,M3 auf LOW = 1 short int motorM3 = 9; // / void setup{ // Motor pinMode(motorEnable,OUTPUT); // Enable digitalWrite(motorEnable,HIGH); // Motor deaktivieren pinMode(motorStep,OUTPUT); pinMode(motorDir,OUTPUT); pinMode(motorM1,OUTPUT); pinMode(motorM2,OUTPUT); pinMode(motorM3,OUTPUT); setRes(16); }
Resolution bestimmen
Hier wird bereits die erste Funktion, nämlich die setRes (für setResolution) aufgerufen. Die Resolution bestimmt das A4988-Board (also diese kleine Platine für den StepperMotor). Es gilt:
- MS1, MS2 und MS3 = LOW -> Full Step (also eine ganze Umdrehung)
- MS1 = HIGH, MS2 und MS3 = LOW -> Half Step (also eine halbeUmdrehung)
- MS1 = LOW, MS2 = HIGH, MS3 = LOW -> 1/4 Step (also eine viertel Umdrehung)
- MS1 & MS2 = HIGH und MS3 = LOW -> 1/8 Step (also eine achtel Umdrehung)
- MS1, MS2 und MS3 = HIGH -> Full Step (also eine 16tel Umdrehung)
MS1-MS3 sind die Pins auf dem A4988 Board. Wenn also alle 3 auf HIGH stehen, sind die schritte nochmal viel genauer, als wenn sie auf LOW stehen. Ich habe nur alle auf LOW oder alle auf HIGH eingebaut. LOW nutze ich nur, wenn der Motor schnell zur Ausgangsposition fahren soll. HIGH sobald der Timelapse gestartet wurde. Natürlich macht es hier Sinn auch die anderen Resolutionen einzubauen, allerdings ist das dann deutlich aufwendiger, da man eine Logik einbauen müsste, wann welche Resolution genommen werden soll ;)
Der Code schaut also aus:
void setRes(int i){ if(i == 16){ digitalWrite(motorM1,HIGH); digitalWrite(motorM2,HIGH); digitalWrite(motorM3,HIGH); }else{ digitalWrite(motorM1,LOW); digitalWrite(motorM2,LOW); digitalWrite(motorM3,LOW); } }
Die Move-Funktion
Die Bewegung des Motors ist wieder sehr simpel. Man übergibt die Anzahl der Schritt und der Motor bewegt sich mit Hilfe einer for-Schleife bis alle Schritte fertig sind:
void Move(int steps){ for(int i = 0;i < steps;i++){ digitalWrite(motorStep,HIGH); delayMicroseconds(speed); // 1/2ms warten digitalWrite(motorStep,LOW); delayMicroseconds(speed); // 1/2ms warten } }
Aufruf der Move-Funktion
Standardmäßig ist der Motor deaktiviert. In jeder Funktion, in der der Motor sich bewegen soll (loadManuell und loadSetup bzw. timelapse()), muss er erst aktiviert werden und die Resolution gesetzt werden:
loadManuell(){ .... setRes(16); digitalWrite(motorEnable,LOW); .... Move(stepSize); .... }
Wie gehts weiter
Ich denke, das gibt einen guten Überblick über die einzelnen Bestandteile des Programms und man sollte es gut schaffen die Steuerung selbst zu bauen. Gerne kann der Code auch bei mir erworben werden (s. oben).
Wer 2- oder 3- Achsen nutzen möchte, kann das ebenfalls in die Steuerung einbauen, das sollte genauso gut funktionieren. Cool wäre auch einen Start- und Endpunkt (+ ggf. Zwischenpunkte) festzulegen und die Steuerung errechnet sich darauf den ideal Pfad. Bisher ist es nämlich so, dass sie immer auf die ganze Länge des Sliders ausgelegt ist.
Timelapse aus Schottland (Ja, die Musik ist Geschmackssache...)
Ergebnis aus dem Garten - mit Bewegungsmeldern und so -.-
1 Kommentare
02.11.2017 - Endstops
von: Marvin
Tolles Tutorial . Werde mich die Tage mal dran versuchen. Wie siehts eigentlich mit Endstops aus ? Was passiert wen der Schlitten an das Ende kommt ? Grüße
Schritte zählen von Hansi - 03.11.2017
Hall Marvin,
freut mich, dass es Dir gefällt und Du den Eigenbau versuchst :)
Sobald der Slider am Ende ankommt, fährt er bei mir die gleichen Anzahl an Schritte zurück.
Sprich er geht einfach zurück auf Start. Das kannst Du aber so einbauen, wie Du möchtest.
Schöne Grüße
Johannes
Bewertung:
War dieser Kommentar hilfreich?