Advertisement
  1. Code
  2. Coding Fundamentals

Motion Control mit Arduino: Motorisierung eines Kamera-Sliders

Scroll to top
Read Time: 23 min

() 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

Teile

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.

Seitlich bleibt es so groß, dass die Pin-Nummern noch sichtbar sind!

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.

The OpenBuilds V-Slot Actuator End MountThe OpenBuilds V-Slot Actuator End MountThe OpenBuilds V-Slot Actuator End Mount
Die OpenBuilds V-Slot-Aktuator-Endhalterung

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.

Der Umlenkrollensatz

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.

Feeding the beltFeeding the beltFeeding the belt
Die Zähne greifen ineinander.

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.

Putting together the Stepper DriverPutting together the Stepper DriverPutting together the Stepper Driver
Zusammensetzen des Stepper-Treibers

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.

Cut off the corner of a small self-stick heatsinkCut off the corner of a small self-stick heatsinkCut off the corner of a small self-stick heatsink
Schneiden Sie die Ecke eines kleinen selbstklebenden Kühlkörpers ab

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.

Wiring Everything TogetherWiring Everything TogetherWiring Everything Together
Alles zusammen verdrahten

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.

When youre done it should look something like thisWhen youre done it should look something like thisWhen youre done it should look something like this
Wenn du fertig bist, sollte es ungefähr so aussehen.

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:

Like the button reader the test rotation sketch is included in the zip up topLike the button reader the test rotation sketch is included in the zip up topLike the button reader the test rotation sketch is included in the zip up top
Wie beim Button-Reader ist die Test-Rotationsskizze im Zip-Top enthalten.

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!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.