Motion Control mit Arduino: Motorisierung eines Kamera-Sliders
() translation by (you can also view the original English article)
Die Verfügbarkeit von billigen Schrittmotoren und Treibern in diesen Tagen bietet reichlich Gelegenheit zum Experimentieren außerhalb der teureren und komplizierteren 2D / 3D-Schneid- und Druckprojekte.
Für dieses Projekt nehme ich den OpenBuilds-Kamera-Slider (siehe Build-Video unter Erstellen eines Basic-Video-Sliders mit Open-Source-CNC-Teilen) und motorisiere ihn. Ich werde auch ein in sich geschlossenes System zur Steuerung des Motors erstellen.
Dieses Tutorial behandelt speziell das Zusammenstellen der Hardware, aber hauptsächlich den Aufbau einer rudimentären 16x2 LCD GUI unter Verwendung der LiquidCrystal Bibliothek und eines einfachen Menüsystems, gefolgt von der Funktionsweise des A4988 Stepper Treibers und wie man ihn mit Arduino steuert.
Dieses Projekt hat viele Schleifen und Schritte, und obwohl das Projekt insgesamt eher intermediär ist, habe ich versucht, es so zu erklären, dass Anfänger relativ schnell einsatzbereit sind.
Ausrüstungsliste
Komponenten
- Arduino Uno
- LCD Keypad Shield oder separate 16x2 LCD und Tasten, wenn Sie wissen, wie man das macht
- Pololu A4988 [Black Edition] Schrittmotortreiber
- Kleiner selbstklebender Kühlkörper aus Aluminium
- Steckbrücken, männliche und weibliche Überbrückungskabel usw
- 220-330 Ohm Widerstand (1 / 4W wird wahrscheinlich tun), Standard-NPN-Transistor (ich verwendete eine BC109)
- 3,5-mm-Stereo-TRS-Buchse
- 3,5 mm bis 2,5 mm Stereo TRS-Adapterkabel
- 3,5 mm Verlängerungskabel, wie für die Länge des Schiebers erforderlich
- 9V Fass Buchse Netzteil, wenn Sie das Arduino von der USB-Stromversorgung des Computers nehmen wollen
- 12V 2A Netzteil zum Betrieb des Schrittmotors
- NEMA 17 Schrittmotor
Teile
- GT2 5 mm breiter Zahnriemen mit 2 mm Zahnteilung: Doppelte Schieberlänge plus Fuß zur Sicherheit (11 Fuß für mich)
- Glattes Spannrollen-Kit
- Torsionsfeder mit Riemenspannung, wenn Sie Schwierigkeiten haben, die Riemenspannung über einen langen Zeitraum aufrechtzuerhalten
- 2x Belt Crimp Clamp (kann durch kleine Zipties ersetzt werden)
- GT2 7 mm breite, 20-Zahn-Aluminium-Riemenscheibe mit der gleichen Bohrung wie die Motorwelle
- 4x 30mm M3-0.5 Zylinderkopf Maschinenschrauben
Werkzeuge
- Computer mit Arduino IDE (Ich benutze Win7, Arduino 1.0.5 r2)
- Lötkolben mit kleiner Meißelspitze, Lot usw
- 2,5 mm Inbusschlüssel für M5 Schrauben
- 2mm Inbusschlüssel für M3 Schrauben
- 1,5 mm Inbusschlüssel für Stellschrauben in der GT2-Riemenscheibe
- Multimeter zur Fehlersuche und zur aktuellen Anpassung
- Schmale Zange zum Festziehen in kleinen Räumen
Funktionsübersicht
Ich beziehe den Motor und die Riemenscheiben auf den Schieber, fädle den Riemen herum und befestige ihn. Es ist eine einfache Modifikation.
Dann beschreibe ich, wie man ein Pololu A4988 Black Edition Kit zusammenstellt und wie man es zusammen mit allen anderen externen Boards auf ein Steckbrett aufbaut, sowie ein einfaches Sperrholzgehäuse, das ich in wenigen Minuten für meine 12V-Stromversorgung zusammengefügt habe Versorgung (oben aufgeführt), um Stöße zu vermeiden, da die Kabelklemmen freiliegen.



Das Menü ermöglicht die Eingabe der Wegstrecke, der Fahrzeit, der Anzahl der Fahrstufen und der Fahrtrichtung. Am Ende jedes Schritts hält der Schieberegler an, während die Kamera ausgelöst wird.
Ändern des Schiebereglers
Schritt 1: Motormontage
Die OpenBuilds V-Nut-Aktuator-Endhalterung hat NEMA 17-Dimensionslöcher, so dass vier 30-mm-M3-Zylinderkopfschrauben erforderlich sind, um den Motor daran zu befestigen.



Stellen Sie sicher, dass sich die 20-Zahn-GT2-Riemenscheibe in der Halterung befindet, bevor Sie die Motorwelle einschieben, da die Halterung nicht breit genug ist, um sie später wieder anzubringen. Sobald der Motor an der Unterseite angeschraubt ist, ziehen Sie die Stellschrauben mit einer gegen den flachen Teil der Motorwelle an und stellen Sie sicher, dass die Zähne direkt in der Mitte der gesamten Extrusionseinheit liegen.
Schritt 2: Umlenkrollensatz
Das Umlenkrollen-Kit passt genau wie ein Laufradsatz in das gegenüberliegende Ende.



Schritt 3: Belügen
Führen Sie den Riemen in der Mitte der V-Nut mit den Riemenscheiben zusammen und achten Sie dabei darauf, dass die Zähne nach oben zeigen.
Dann füttern Sie es und über die beiden Riemenscheiben und bringen Sie es in die Mitte der Dolly Build Platte.



Hier wickeln Sie eine Seite durch den Gurtschlitz und klemmen oder zippen daran, dann ziehen Sie den gesamten Gurt durch das ganze System, bevor Sie die andere Seite verbinden. Nicht zu eng für den Motor, aber nicht locker genug, um Zähne an der Antriebsscheibe zu überspringen!
Montage der Elektronik
Schritt 1: Montieren Sie den Stepper Driver
Der Pololu A4988 Black Edition Schrittmotortreiber (technisch das A4988 Trägerboard - der A4988 ist der Chip selbst) kommt typischerweise in Kit-Form, was einfach bedeutet, dass die Header gelötet werden müssen. Da es sich um eine Leistungskomponente handelt, ist es eine gute Idee, einen Kühlkörper hinzuzufügen, um die Lebensdauer zu erhöhen, auch wenn das Gerät nicht mit maximaler Kapazität betrieben wird.
Brechen Sie die Kopfzeile in zwei Hälften, um zwei acht Zeilen zu erhalten. Stecken Sie diese in die durchkontaktierten Löcher in der Platine und stecken Sie diese dann vorsichtig in das Steckbrett. Löten Sie die Stifte an Ort und Stelle, während das Steckbrett alles schön und senkrecht hält.



Sobald dies abgeschlossen ist, schneiden Sie die Ecke eines kleinen selbstklebenden Kühlkörpers mit einer Metallsäge oder Scrollsaw (vorsichtig, in einer Klemme!), Um den A4988 IC zu montieren.



Schritt 2: Breadboard-Mount die Komponenten
Jetzt muss alles an Steckbrettern montiert werden, damit es zu einem funktionierenden Kabel zusammengeschaltet werden kann. Ich verwende separate Boards für jedes Teil aus Gründen der Klarheit in den Bildern, aber fühlen Sie sich frei, um alles in ein einzelnes Board zu passen, wenn Sie es wünschen.
Die LCD-Tastaturabdeckung kann nicht auf einer Platine montiert werden, da Arduino die seltsame Entscheidung trifft, sich an einen Konstruktionsfehler zu halten, anstatt Standards zu erfüllen. Dies wird getrennt gehalten, obwohl das Verschrauben mit einem Stück Holz oder etwas zum Schutz der Stifte keine schlechte Idee ist.



Die Kamera-Trigger-Schaltung besteht aus einem Widerstand, einem Transistor und einem 2,5-mm-TRS-Sub-Mini-Stecker. Ich habe eine LED hinzugefügt, die blinkt, wenn der Auslösestift hoch ist, und eine 3,5-mm-TRS-Minibuchse, um Flexibilität zu ermöglichen.
Wenn Sie Komponenten für diesen Build kaufen, wäre eine 3,5-mm-Buchse, die für 0,1-Zoll-Pitch-Boards entwickelt wurde, eine gute Idee, aber meine ist von dem gespülten Haufen, also habe ich stattdessen einen Stecker gelötet.
Legen Sie alles aus, bereit, alles zu verkabeln.
Schritt 3: Alles zusammen verdrahten
Zeit, alle Überbrückungskabel zu greifen. Wenn Sie genug Farbe haben, um die Farben zu erhalten, wird das Leben bei der Fehlersuche einfacher. Sehen Sie im Schaltplan oben nach, wenn Sie die folgende Beschreibung an irgendeiner Stelle verwirrt.



Zuerst verdrahten Sie das LCD. Nimm 10 weibliche Jumper und verbinde sie mit den folgenden Shield Pins: digitale Pins 4-9, Power Bus Pins zurückgesetzt (wenn du den LCD Reset Knopf benutzen willst), 5V und eine der GNDs.
Wenn Sie von Frau zu männliche Jumper haben, können Sie es dort lassen. Verbinden Sie andernfalls die männlichen Jumper mit dem anderen Ende der Buchsen, um sie in die entsprechenden Arduino-Header-Buchsen zu stecken. Wenn Sie eine LCD-Tastaturabdeckung haben, auf der Kopfbuchsen installiert sind, können Sie diesen Schritt überspringen, da Ihr Schild nichts blockiert.
Als nächstes das Pololu A4988 Board. Dies erfordert acht Jumper auf einer Seite, ich habe schwarz und rot für Logik / Motorleistung am oberen Ende verwendet, und rot / grün / blau / gelb in der Mitte vier, um mit den Servoleitungen des Schrittmotors übereinzustimmen.
Der Logik-Power-Pin geht auf 3,3 V am Arduino, da das LCD oben den 5-V-Pin verwendet. Die Motorstromkabel gehen zu Ihrer 12V-Stromversorgung. Auf der anderen Seite, in der Nähe des A4988-Chips, verwende ich Blau und Orange für STP bzw. DIR, um mit den relativ einheitlichen Farben überall anders zu kontrastieren. Sie gehen zu Arduino Pins 11 und 12, sofern Sie den Code nicht ändern. Dann kurz RST und SLP zusammen, um das Board aktiviert zu halten; Ich habe hier die weiße Leitung benutzt.



Schließlich verdrahten Sie die Schaltung des Kameraauslöseschalters. Hier sind die schwarzen Drähte geerdet - die A-Leitung zu Arduino, die C-Leitung zur 3,5-mm-Buchse. Das gelbe geht zu Arduino Pin 13 (also gibt es eine LED-Anzeige auf der Platine sowie am Schalter!), Und das rote Kabel geht auf die andere Seite der 3,5-mm-Buchse (oder 2,5-mm-Stecker führen, wenn Sie gehen diese Route).
Stecken Sie den Schrittmotor gemäß dem A4988 Board-Diagramm und dem Datenblatt Ihres Steppers in die farbigen Drähte. Für mich war das so:



Achtung: Denken Sie daran, dass die Kabel, die den Motor mit Strom versorgen, wahrscheinlich 1-2A bei der von Ihnen gewählten Spannung ziehen werden. Stellen Sie daher sicher, dass die verwendeten Drähte dafür ausgelegt sind. Der A4988 Chip und die Platine können heiß werden! Das in der Platine integrierte Potentiometer bietet eine Strombegrenzung, um sowohl den A4988 als auch den Motor zu schützen. Stellen Sie daher sicher, dass Sie es vor der Verwendung mit einem Multimeter richtig eingestellt haben.
Einrichten des Programms
Sobald die Komponenten zusammengebaut sind, können Sie zur Codierung wechseln. Laden Sie die in diesem Lernprogramm enthaltene Zip-Datei herunter, oder überprüfen Sie dieses GitHub-Repository, wenn Sie dies bevorzugen. Ich werde beschreiben, wie ich es zusammensetze, damit Sie den allgemeinen Programmablauf und die Zusammenarbeit der Module verstehen können.
Schritt 1: Enthält und grundlegende Definitionen
Die einzige dazu notwendige Komponente war die LCD-Schreibbibliothek LiquidCrystal.h
. Dies ermöglicht den Zugriff auf die Funktionen von lcd.xxxx()
. Es gibt ein pow()
im Programm, und ich fand, dass die Einbeziehung der C++ Bibliothek math.h
nicht notwendig ist, da einige der nützlichsten Funktionen in der Arduino-Standardumgebung enthalten sind, einschließlich pow()
.
1 |
#include <LiquidCrystal.h> |
2 |
|
3 |
LiquidCrystal lcd(8, 9, 4, 5, 6, 7); //set LCD output pins |
4 |
|
5 |
//define stepper driver pins
|
6 |
const int stp = 11; //can't use pin 10 with the SS LCD as it's the backlight control. |
7 |
//if it goes low, backlight turns off!
|
8 |
const int dir = 12; |
9 |
|
10 |
//define trigger pin
|
11 |
const int trig = 13; |
12 |
|
13 |
//BUTTONS
|
14 |
//define button values
|
15 |
const int btnUp = 0; |
16 |
const int btnDn = 1; |
17 |
const int btnL = 2; |
18 |
const int btnR = 3; |
19 |
const int btnSel = 4; |
20 |
const int btnNone = 5; |
21 |
|
22 |
//define button-reading variables
|
23 |
int btnVal = 5; |
24 |
int adcIn = 0; |
Ich habe die LCD-Ausgangspins, die Ausgangspins des Stepper-Treibers und den Kamera-Trigger-Ausgangspin eingestellt. Sobald die tatsächliche Hardware-Schnittstelle eingerichtet wurde, fügte ich Variablen für Schaltflächenereignisse hinzu, gefolgt von der Schaltflächenlesefunktion, die ich aus dem DFRobot-Wiki auf ihrem identischen LCD-Tastaturschild angepasst habe. Beachten Sie, dass SainSmart keine Dokumentation zur Verfügung stellt.
Schritt 2: Setup () Schleife
Das ist super einfach. Initialisieren Sie die LCD-Anzeige und die entsprechenden Ausgangsstifte, gefolgt von einem Begrüßungsbildschirm, und wechseln Sie dann in den Startbildschirm: Menüoption 1 mit Nullwerten.
1 |
void setup() { |
2 |
lcd.begin(16, 2); // initialise LCD lib full-screen |
3 |
lcd.setCursor(0,0); // set cursor position |
4 |
|
5 |
pinMode(stp, OUTPUT); //initialise stepper pins |
6 |
pinMode(dir, OUTPUT); |
7 |
|
8 |
pinMode(trig, OUTPUT); //initialise trigger pin |
9 |
digitalWrite(trig, LOW); //ensure trigger is turned off |
10 |
|
11 |
lcd.print("Welcome to"); //welcome screen |
12 |
lcd.setCursor(0,1); |
13 |
lcd.print("SliderCam v0.2!"); |
14 |
delay(1000); |
15 |
lcd.clear(); |
16 |
lcd.print(menuItemsTop[0]); |
17 |
delay(100); |
18 |
lcd.setCursor(0,1); |
19 |
for (int i = 0; i < 4; i++) { |
20 |
lcd.setCursor(i, 1); |
21 |
lcd.print(currentDistance[i]); |
22 |
}
|
23 |
lcd.setCursor(4,1); |
24 |
lcd.print("mm(max 1300)"); |
25 |
}
|
Schritt 3: Monitor-Tasten
Der Vorteil hierbei ist, dass das Gerät ohne Benutzereingaben gar nichts tun muss. Was bedeutet, dass die erste Sache einfach eine ewige Button-Poll-Schleife sein kann. Wenn Sie die Funktion readLcdButtons()
immer wieder aufrufen, bis sich ihr Wert ändert, wirkt sich dies nicht negativ auf die Programmleistung aus, und Sie müssen sich keine Sorgen darüber machen, ob die Interrupt-Pins verfügbar bleiben.
1 |
void loop() { |
2 |
do { |
3 |
btnVal = readLcdButtons(); //continually read the buttons... |
4 |
}
|
5 |
while (btnVal==5); //...until something is pressed |
1 |
//declare button poll function
|
2 |
int readLcdButtons() { |
3 |
delay(90); //debounce delay, tuned experimentally. delay is fine as program shouldn't be doing anything else |
4 |
//at this point anyway
|
5 |
adcIn = analogRead(0); //read value from pin A0 |
6 |
|
7 |
/*threshold values confirmed by experimentation with button calibration sketch returning the following ADC read values:
|
8 |
right: 0
|
9 |
up: 143
|
10 |
down: 328
|
11 |
left: 504
|
12 |
select: 741
|
13 |
*/
|
14 |
|
15 |
if (adcIn > 1000) return btnNone; |
16 |
if (adcIn < 50) return btnR; |
17 |
if (adcIn < 250) return btnUp; |
18 |
if (adcIn < 450) return btnDn; |
19 |
if (adcIn < 650) return btnL; |
20 |
if (adcIn < 850) return btnSel; |
21 |
|
22 |
return btnNone; //if it can't detect anything, return no button pressed |
23 |
}
|
ReadLcdButtons()
hat eine Verzögerung von 90ms, um die Tasten zu entprellen. In Wirklichkeit ist dies keine Entprellung, da es die ADC-Messung nicht nach einer bestimmten Zeit wieder aufnimmt, sondern die Schaltflächen selten abfragt, um selten mehr als einen einzelnen Klick zu registrieren.
Dies wird durch eine praktische UX-Ansicht erreicht. Es handelt sich eher um eine 90-Minuten-Umfrage als um eine ständige Abfrage, weshalb die Verwendung von delay()
im Allgemeinen nicht als gute Praxis für Entprellzwecke gilt, aber das Problem wurde behoben (nur jedes Ende der Menüs war zugänglich).
Schritt 4: Bildschirm aktualisieren
Sobald das Gerät auf Eingaben reagieren kann, muss es möglich sein, diese Reaktionen anzuzeigen.
Nach dem Versuch, Aktualisierungen sofort zu machen, stellte ich fest, dass eine konsistente Bildschirmaktualisierung wie bei einem echten Betriebssystem bei meinen Versuchen mit einer modular erweiterbaren Struktur einfacher zu verwalten war. Dies ist so einfach wie das Löschen des Bildschirms und das erneute Erstellen basierend auf bekannten aktuellen Parametern.
Das hört sich kompliziert an, erleichtert aber in der Praxis das Leben erheblich. Es entfernt eine große Anzahl von LCD-Befehlen von einer anderen Stelle im Programm und erzeugt eine variable-typ-agnostische Zone, die durch von ihr ausgehende Programmaktualisierungen minimal beeinflußt wird.
Der eigentliche Aktualisierungsteil besteht aus vier verschiedenen Schritten:
Parameter zurücksetzen ...
1 |
//PRINT NEW SCREEN VALUES
|
2 |
btnVal=btnNone; |
3 |
lcd.clear(); |
... drucke die obere Zeile ...
1 |
lcd.setCursor(0, 0); |
2 |
lcd.print(menuItemsTop[currentMenuItem]); //print top level menu item |
... drucke die untere Zeile, die ich später erklären werde ...
1 |
lcd.setCursor(0,1); |
2 |
switch (currentMenuItem) { |
3 |
case 0: |
4 |
{
|
5 |
for (int i = 0; i < 4; i++) { |
6 |
lcd.setCursor(i, 1); |
7 |
lcd.print(currentDistance[i]); |
8 |
}
|
9 |
break; |
10 |
}
|
11 |
|
12 |
case 1: |
13 |
{
|
14 |
for (int i = 0; i < 6; i++) { |
15 |
lcd.setCursor(i, 1); |
16 |
lcd.print(currentDuration[i]); |
17 |
}
|
18 |
break; |
19 |
}
|
20 |
|
21 |
case 2: |
22 |
{
|
23 |
for (int i = 0; i < 4; i++) { |
24 |
lcd.setCursor(i, 1); |
25 |
lcd.print(currentSteps[i]); |
26 |
}
|
27 |
break; |
28 |
}
|
29 |
|
30 |
case 3: |
31 |
{
|
32 |
if (travelDir == 0) lcd.print("From Motor"); |
33 |
else lcd.print("To Motor"); |
34 |
break; |
35 |
}
|
36 |
|
37 |
case 4: |
38 |
{
|
39 |
lcd.print("Stop!"); |
40 |
break; |
41 |
}
|
42 |
} //end switch |
... und fügen Sie bildschirmspezifische Befehle über den bereits gedruckten Inhalt hinzu.
1 |
if (currentMenuItem==0){ |
2 |
lcd.setCursor(4,1); |
3 |
lcd.print("mm(max 1300)"); //insert max carriage travel on slider used |
4 |
} |
5 |
if (currentMenuItem==1){ |
6 |
lcd.setCursor(6,1); |
7 |
lcd.print("s(3600/hr)"); |
8 |
} |
9 |
if (currentMenuLevel == 1) { |
10 |
lcd.setCursor(currentCursorPos, 1); |
11 |
lcd.blink(); |
12 |
} |
13 |
else lcd.noBlink(); |
Gebäude A Menü: Hauptüberschriften
Natürlich schreibt sich dieser genaue Bildschirmauffrischungsabschnitt nicht selbst, und wir müssen das Menü kennen, auf das geschrieben wird, bevor es vervollständigt werden kann. Die Hauptüberschriften sind einfach, da sie sich je nach Benutzereingabe nicht ändern. Dies bedeutet, dass es sich einfach um ein String-Array handeln kann - technisch gesehen um ein Char-Pointer-Array oder ein Array von Arrays:
1 |
//MENU GUI
|
2 |
//define top-level menu item strings for numerical navigation
|
3 |
char* menuItemsTop[] = { |
4 |
" 01 Distance >", "< 02 Duration >", "< 03 Steps > ", "< 04 Direction >", "< 05 Go!"}; |
5 |
|
6 |
int currentMenuLevel = 0; //top menu or submenu |
7 |
int currentMenuItem = 0; //x-axis position of menu selection |
8 |
int currentCursorPos = 0; //current lcd cursor position |
9 |
int currentDistance[4] = { |
10 |
0, 0, 0, 0}; |
11 |
int currentDuration[6] = { |
12 |
0, 0, 0, 0, 0, 0}; |
13 |
int currentSteps[4] = { |
14 |
0, 0, 0, 1}; |
Dies bedeutet, dass dieses menuItemsTop
-Array navigiert werden kann, indem einfach die Zahl innerhalb der eckigen Klammern zur Bildschirmaktualisierungszeit geändert wird. Was eben passiert, da alles nullindiziert ist, um mit der Ganzzahl currentMenuItem
identisch zu tracken.
Das Manipulieren des currentMenuItem
bei Schaltflächenereignissen ermöglicht uns die eindimensionale Navigation. Wenn Sie also menuItemsTop[currentMenuItem]
sehen, ist dies offensichtlich die aktuelle Menüüberschrift.
1 |
if (currentMenuLevel==0) { |
2 |
switch (btnVal){ |
3 |
case btnL: |
4 |
{
|
5 |
if (currentMenuItem == 0) break; //can't go left from here |
6 |
else currentMenuItem--; |
7 |
break; |
8 |
}
|
9 |
|
10 |
case btnR: |
11 |
{
|
12 |
if (currentMenuItem == 4) break; //can't go right from here |
13 |
else currentMenuItem++; |
14 |
break; |
15 |
}
|
16 |
|
17 |
case btnSel: |
18 |
{
|
19 |
currentMenuLevel++; |
20 |
if (currentCursorPos > 3 && (currentMenuItem == 0 || currentMenuItem == 2)) currentCursorPos = 3; //don't go off the end of the numbers for the 4-digit numbers |
21 |
if (currentCursorPos > 0 && (currentMenuItem > 2)) currentCursorPos = 0; // set blinking cursor to left for text-based options |
22 |
if (currentMenuItem == 4) { |
23 |
motion = 1; |
24 |
motionControl(); |
25 |
break; |
26 |
}
|
27 |
}
|
28 |
} //end of switch |
29 |
} //end of level 0 |
So können Sie sich nach links und rechts bewegen und in ein Menü gehen, oder im Fall von Go! dann ist die Bewegungssteuerung aktiviert. Was ist das alles hier?
Gebäude A Menü: Untermenü
Das Untermenü-System hat dank seiner internen Komplexität etwas mehr getan. Die ersten drei Einträge, Distance, Duration und Steps, bestehen technisch aus einem Sub-Sub-Menü, die jeweils die Navigation des mehrstelligen Wertes sowie jedes einzelnen Charakters ermöglichen.
Dies wird dadurch abgedeckt, dass jeder Untermenüeintrag selbst ein geschaltetes Steuersystem wird. Obwohl dies ein langer Weg war, ist es eine einfache und konsistente Methode, um solche Low-Level-Navigation zu ermöglichen. Da ich gerade das Untermenü Entfernung herausgefunden und dann für die anderen Untermenüs kopiert habe, hier ein Blick auf dieses.
1 |
else { // i.e. "else if currentMenuLevel = 1" |
2 |
if (currentMenuItem == 0) { //01 DISTANCE |
3 |
|
4 |
switch (btnVal) { |
5 |
case btnUp: |
6 |
{
|
7 |
currentChar = currentDistance[currentCursorPos]; |
8 |
adjustDigit(currentChar, 1); |
9 |
currentDistance[currentCursorPos] = currentChar; |
10 |
break; |
11 |
}
|
12 |
|
13 |
case btnDn: |
14 |
{
|
15 |
currentChar = currentDistance[currentCursorPos]; |
16 |
adjustDigit(currentChar, 0); |
17 |
currentDistance[currentCursorPos] = currentChar; |
18 |
break; |
19 |
}
|
20 |
|
21 |
case btnL: |
22 |
{
|
23 |
if (currentCursorPos == 0) break; //can't go left from here |
24 |
else currentCursorPos--; |
25 |
break; |
26 |
}
|
27 |
|
28 |
case btnR: |
29 |
{
|
30 |
if (currentCursorPos == 3) break; //can't go left from here |
31 |
else currentCursorPos++; |
32 |
break; |
33 |
}
|
34 |
|
35 |
case btnSel: |
36 |
{
|
37 |
parseArrayDistance(); |
38 |
currentMenuLevel--; |
39 |
}
|
40 |
} //end switch |
41 |
} //end DISTANCE |
Links und rechts sind im Wesentlichen die gleichen wie das Top-Level-Menü, einfach auf die gleiche Weise vor und zurück entlang der Zahl, indem die Zahl tatsächlich eine Reihe von Ziffern in einem int-Array und die aktuelle Position in einem int namens currentCursorPos
, das Blinken ermöglicht, wie oben im Bildschirm-Aktualisierungsmodul angezeigt.
Das Drucken dieser Arrays entlang der unteren LCD-Zeile ist das, was die for-Schleifen in dem Bildschirmauffrischungsabschnitt waren; i
von 0 bis 3, LCD-Spalte
von 0 bis 3, currentDistance[]
von 0 bis 3.
1 |
int adjustDigit(int x, int dir){ //digit adjust function |
2 |
if (dir == 0 && x > 0) x--; //subtract from digit on btnDn |
3 |
if (dir == 1 && x < 9) x++; // add to digit on btnUp |
4 |
lcd.setCursor(currentCursorPos, 1); |
5 |
lcd.print(x); |
6 |
currentChar = x; |
7 |
return currentChar; //return new digit |
8 |
}
|
Das Erhöhen und Verringern der Anzahl erfolgt durch Speichern der aktuellen Ziffer in der Variablen currentChar
, die dann zusammen mit einem booleschen Wert, der die Richtung angibt, an die Funktion adjustDigit()
übergeben wird. um den Motor zu erhöhen oder zu verringern.
Dies passt einfach die Ziffer entsprechend dem booleschen Wert an und speichert das Ergebnis, woraufhin der Fluss zur Hauptschleife zurückkehrt, wo der currentChar-Wert in die korrekte Position des ursprünglichen currentDistance[]
-Arrays gespeichert wird und die neu eingestellte Ziffer auf dem Bildschirm gedruckt wird Aktualisierung.
Parsing von Display-Array-Werten
Wenn Select von einem der Array-Untermenüs angeklickt wird, löst es die relevante Parsing-Funktion aus - in diesem Fall parseArrayDistance()
. Sie müssen das Array, das zum Anzeigen und Bearbeiten geeignet ist, in eine Ganzzahl zerlegen, die für tatsächliche Bewegungsberechnungen nützlich ist. Ich entschied mich, dies jetzt zu tun, anstatt auf Go! UX fühlt sich bissig.
1 |
int adjustDigit(int x, int dir){ //digit adjust function |
2 |
if (dir == 0 && x > 0) x--; //subtract from digit on btnDn |
3 |
if (dir == 1 && x < 9) x++; // add to digit on btnUp |
4 |
lcd.setCursor(currentCursorPos, 1); |
5 |
lcd.print(x); |
6 |
currentChar = x; |
7 |
return currentChar; //return new digit |
8 |
}
|
Ich kam mit dieser Funktion aus dem einen nützlichen Kommentar, den ich gefunden hatte, nachdem ich erschöpfte, dass Google nach standardmäßigen Array-to-Int-Funktionen suchte, die leer waren und das Chaos von Array-zu-Char-to-Int-Funktionen beseitigten eine unwirksame Problemumgehung. Es scheint ziemlich kurz und leicht zu sein, wenn man bedenkt, dass es buchstäblich auf der Grundlage der Dezimalkalkulation basiert, aber wenn Sie eine bessere Methode kennen, bin ich ganz Ohr.
Bewegungssteuerung und Kameraauslösung
Alle Werte sind festgelegt und Sie drücken Go! Was passiert als nächstes? Sie müssen genau berechnen, was die angegebenen Zahlen tun sollen, um die letzte Bewegung auszuführen. Dieser Teil ist funktional, aber in Arbeit; Ich denke, dass es mehr Optionen für verschiedene Arten von Bewegung geben muss.
1 |
int motionControl() { |
2 |
totalMotorSteps = currentDistanceInt * 5; //calculate total steps (0.2mm = 20-tooth gear on 2mm pitch belt; 40mm per rev, 200 steps per rev, ergo 1/5th mm per step) |
3 |
pulseDelay = (1000L * (currentDurationInt - (currentStepsInt * shutterDuration))) / totalMotorSteps; //how long to pause in ms between STP pulses to the motor driver |
4 |
intervalDistance = totalMotorSteps / currentStepsInt; |
Was in dieser Funktion passiert, ist ziemlich klar aus der Kommentierung, denke ich. Ich habe eine shutterDuration
von 2 Sekunden in die Software gelegt, hauptsächlich um die Tests ziemlich schnell zu machen. Wenn Sie in der Nacht bei niedrigeren ISO-Werten fotografieren, müssen Sie je nach Verschlusszeit möglicherweise mehr zwischen 25 und 35 Sekunden wählen.
Die pulseDelay
wird am Ende mit 1000
multipliziert, um natürlich von Sekunden zu Millisekunden zu konvertieren. Das L
, um das konstante int in ein long zu konvertieren, irrte mich mehr auf der Seite der Vorsicht, als tatsächlich notwendig zu sein. Da es sich um eine relativ kleine Skizze handelt, mache ich mir keine großen Gedanken über die variable Speichernutzung.
Diese Berechnungen setzen voraus, dass die Schleife selbst eine vernachlässigbare Menge an Zeit benötigt, um im Vergleich zu der pulseDelay
zeit zu laufen, die, wenn ich den Knopfabruf herausgenommen habe, wahr zu sein scheint.
1 |
//once per overall run
|
2 |
if (travelDir == 0) digitalWrite(dir, LOW); |
3 |
else if (travelDir == 1) digitalWrite(dir, HIGH); |
4 |
//Serial.begin(9600);
|
5 |
//Serial.println(pulseDelay);
|
6 |
|
7 |
//step loop
|
8 |
do { |
9 |
digitalWrite(stp, HIGH); //fire motor driver step |
10 |
delay(pulseDelay); |
11 |
digitalWrite(stp, LOW); //reset driver |
12 |
//btnVal = readLcdButtons(); //check there's no stoppage - this takes too long and significantly slows motor; use reset for stop!
|
13 |
currentStep++; |
14 |
|
15 |
//at end of each step
|
16 |
if (currentStep % intervalDistance == 0) { //if current number of motor steps is divisible by the number of motor steps in a camera step, fire the camera |
17 |
digitalWrite(trig, HIGH); //trigger camera shutter |
18 |
delay(80); |
19 |
digitalWrite(trig, LOW); //reset trigger pin |
20 |
delay((shutterDuration * 1000)-80); //delay needs changing to timer so stop button can be polled |
21 |
}
|
22 |
|
23 |
}
|
24 |
while (currentStep < totalMotorSteps); |
25 |
|
26 |
} //end motion control |
Beachten Sie schließlich den currentSteps
-Wert, der auf 1 gesetzt ist. Ich habe dafür keine Fehlerüberprüfungsfunktion erstellt, aber der einfache gesunde Menschenverstand sagt, dass stepSize
unendlich wird, wenn currentStepsInt == 0
ist, also ist es am besten, es bei eins zu halten, wenn eine kontinuierliche Bewegung gewünscht wird. Ich habe bereits einen Verbesserungseintrag dafür hinzugefügt.
Das Endprodukt laufen lassen
Für etwas, das auf Code läuft, der in zwei Tagen mehr oder weniger von Grund auf neu geschrieben wurde und über zwei weitere Bugfixed ist, funktioniert es wie ein Traum! Der Beweis ist jedoch im Pudding. Wird es wirklich lohnenswertes Timelapse-Material, und funktioniert das Steuergerät wirklich gut im Feld?
In meinen Tests scheint die Antwort ein Ja von ganzem Herzen zu sein. Unten ist ein zweistündiger Zeitraffer mit 650 Bildern, der allererste Test. Der Slider hat auch einen 9 Stunden 720 Frame Test fehlerfrei absolviert, aber leider hat der Kamera Akku nach 2 Stunden nicht so gut abgeschnitten ... was ich bis zur 8.5 Stunden Marke natürlich nicht herausgefunden habe.
Wenn ich die Zeit und die Schritte entsprechend einstelle, kann die Bewegung für langsame Dolly-Moves im Live-Action-Video kontinuierlich sein, obwohl die ruckartigen Enden editiert oder beschleunigt werden müssen.
Ton kann ein Problem sein, es sei denn, Ihr Stepper ist sehr leise, aber um den Produktionswert für Selbstaufnahmen zu erhöhen, ist dies eine Option.
Verbesserungen
Wie bei allem gibt es mögliche Verbesserungen. Ich habe diese oben in der .ino
-Datei aufgelistet, obwohl zugegebenermaßen ohne besondere Sorgfalt für die Machbarkeit, aber auch nicht von irgendwelcher Wichtigkeit.
Einige davon habe ich vor der Veröffentlichung dieses Tutorials mit v0.2 in Betracht gezogen, aber ich denke, dass sie selbst eine Lernerfahrung sind, um die Benutzerfreundlichkeit eines Programms mental zu reduzieren.
1 |
IMPROVEMENTS AND CONSIDERATIONS TOWARDS V1.0: |
2 |
1) Efficiency of submenu button response code for first three menu headers |
3 |
2) Use of bulb shutter time as an extra menu option, passed to shutterDuration int |
4 |
3) shutter duration should be timed pause, not delay() - can't poll stop button! |
5 |
4) Use EEPROM library functions to save quantities, can thus simplify the motion control section and use Reset as "stop" |
6 |
5) Remove switch from "Go" submenu, replace with more appropriate logic statement |
7 |
6) Would it be better to time camera steps rather than total travel? "duration" being more like 15 sec or 2 min than 30 min or 4hrs? |
8 |
7) Any const ints that would be better as #define or ints better as boolean? Hardly running against the limits of SRAM space at 8kB, though. |
9 |
8) Tweening/easing for acceleration curves, particularly for video use |
10 |
9) Error check for zero step size, or simply add one to intervalDistance if value is zero before calculations- other end of Distance is still 1 step |
11 |
10) Would sub-16ms delay()s be better as delayMicroseconds()? How much do interrupts throw off timing? |
12 |
11) Use of sleep on A4988 to reduce power consumption in the field? |
13 |
12) Error check for currentDurationInt <= currentStepsInt*shutterDuration, allowing no time for movement or even negative pulseDelay! |
14 |
*/
|
Dies sind nur die Verbesserungen, an die ich bisher gedacht habe, um die Codebase von einer rudimentären, aber funktionalen v0.2 zu einer optimierten und leistungsfähigeren Version v1.0 zu führen. Vielleicht bemerken Sie mehr. Fühlen Sie sich frei, sie in den Kommentaren unten oder auf GitHub zu hinterlassen.
Aufwickeln
Wenn Sie von Anfang bis Ende gefolgt sind, einschließlich der Photography Tuts+ Teile des Builds, sind Sie jetzt stolzer Besitzer eines hochwertigen motorisierten Kamera-Sliders, der Timelapse-Aufnahmen und subtile Dolly-Moves erzeugen kann. Wenn Sie den Code für ein anderes Projekt verwenden, würde ich es gerne sehen.
In diesem Tutorial habe ich verschiedene Formen von Schleifen-basierten Flusskontrolle untersucht, eine rudimentäre GUI erstellt und ein LCD basierend auf Benutzereingaben aktualisiert. Ich betrachtete auch die gleichzeitige Kontrolle mehrerer externer mechanischer Geräte über Breakout Boards.
Sie haben den Fluss und die Leichtigkeit des Programmierens von modularem Code gesehen und sehen Ideen, wie Sie Code verbessern können, der zwar funktional, aber nicht optimiert ist, sowohl von UX als auch von Prozessor-Effizienzstandpunkten. Diese Tools sollen Ihnen in Zukunft für eine Vielzahl von kommunikations- und interaktionsbasierten Projekten nützlich sein.
Bitte hinterlassen Sie Fragen oder Kommentare die Kommentare unten!