• Herzlich Willkommen auf Lightpainting Helpdesk!
  • Forum für und von Lightpainter.
Hallo, Gast! Anmelden Registrieren


Themabewertung:
  • 0 Bewertung(en) - 0 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
Arduino Tutorial - Teil 2: Es werde Licht
#1
Programmier Grundlagen für den Arduino

Der Arduino wird in einer Art con C/C++ programmiert. Wer noch nie etwas mit Programmierung zu tun hatte muss jetzt aber nicht gleich zurück schrecken, denn es ist eigentlich sehr viel einfacher als es eigentlich klingt. Für die Anwendung in LightPainting Tools benötigt man meistens auch keine sonderlich komplizierten Programme. Wer sich in das Thema tiefer einarbeiten will kann sich gerne die offiziellen Tutorials anschauen. Diese sind zwar auf Englisch, aber sehr ausführlich und leicht verständlich. Ich möchte hier nur auf die groben Grundlagen eingehen, mir denen sich schon viele Tools umsetzen lassen. Kommentare im Quellcode werden übrigens mit zwei Schrägstrichen ( // ) begonnen, alles was hinter diesen kommt wird vom Programm nicht beachtet, und dient nur der Erklärung oder für Notizen.

Datentypen:
Die meist benutzen Datentypen sind:
Code:
int // ein ganzzahliger Zahlenwert
long //ein großer ganzzahliger  Zahlenwert
float // eine Gleitkomma-Zahl, also alle Zahlen mit Wertem nach dem Komma
double // wie float, nur genauer und belegt mehr Speicher
boolean // ein "Wahr" oder "Falsch" Wert
char // ein Zeichen, also Buchstaben, Zahlen, oder alles was ihr Tippen könnt
Mit diesen Datentypen kommt man eigentlich aus. Wenn man mal einen String, also einen Text benötigt, wird dies meistens in einem Array, also einer Ansammlung von chars gemacht. Dies wird aber nur sehr selten hier benötigt.

Variablen / Konstanten:
Damit im Programm mit Werten gearbeitet werden kann, werden die Werte meistens in benannte Variablen oder Konstanten gespeichert. Der Unterschied ist das Variablen zur Laufzeit verändert werden können, und Konstanten immer den bestimmten Wert haben. Der Name einer Variable lässt sich frei wählen, in C/C++ war es meistens üblich die Variablennamen kurz zu halten, ist aber eigentlich nicht nötig. Heutzutage ist es üblich Variablennamen in Camelcase zu schreiben, das heißt Wörter ohne Lehrzeichen aneinander reihen, und jedes Wort mit einem Großbuchstaben beginnen. Man sollte sich eine Schreibweise angewöhnen und dabei bleiben, so lässt sich der Code später besser lesen. Am besten schaut man sich Beispielcode an um die gängigen Schreibweisen zu übernehmen, das erleichtert es Beispiele zu verstehen.
Variablen und Konstanten können auf verschiedene Weise definiert werden. Hier kommt es auch darauf an, ob diese innerhalb oder außerhalb einer Funktion definiert werden. Innerhalb einer Funktion definierte Variablen existieren auch nur innerhalb dieser Funktion. 
Hier mal ein paar Beispiele:
Code:
// Konstanten
#DEFINE LED_COUNT 5 // ein Define ist eine Programm-übergreifende Konstante. Diese kann von überall im Programm benutzt werden, egal in welcher Funktion oder Klasse

const int ledPin = 6; // mit const wird eine Konstante definiert. Diese ist hier vom Typ int, also eine ganze Zahl und hat den Wert 6
// Zu beachten ist das bei DEFINEs kein ; am Ende der Zeile benötigt wird, überall sonst schon.

// Variablen
bool lightOn = false; // ein Wahr/Falsch Wert aks Variable vorbelegt mit false also Falsch

void loop() {
 float sensorValue = 0; //  eine Variable innerhalb einer Funktion. Diese Variable ist außerhalb der Loop-Funktion weder sichtbar noch benutzbar.
}
Ob man eine Variable innerhalb einer Funktion definiert oder außerhalb hat schon tiefgreifende technische Hintergründe, allerdings reicht es hier sich einfach mal zu merken das Variablen die man eh nur innerhalb der Funktion benötigt ruhig auch dort definiert werden können, will man aber aus anderen Funktionen darauf zugreifen, oder braucht man den Wert z.B. im nächsten Durchlauf vom loop, so sollte es außerhalb definiert werden.
Üblicherweise definiert man Konstanten und Variablen ganz oben im Quellcode, also globale oberhalb der Setup Funktion und Variablen innerhalb von Funktionen direkt am Anfang der Funktion.

Hier mal ein Beispiel dazu:
Code:
int ledPin; // hier wird nur eine Variable vom Typ int angelegt, aber nicht mit einem Wert belegt

void setup() {
 ledPin = 6; // im Setup belegen wir nun die Variable mit einem Wert
}

void loop() {
 digitalWrite(ledPin, HIGH); // hier benutzen wir den Wert der Variablen um den entsprechenden Pin am Arduino mit Strom bzw. einem Ein-Dignal zu belegen
}
Methoden, Funktionen und Klassen:
Um ein Programm zu Ordnen wird es üblicherweise in Klassen, Methoden und Funktionen aufgeteilt. Technisch hat das auch noch mehr Hintergründe, für uns ist das aber nicht so wichtig, da die Programmieroberfläche uns die ganze Arbeit abnimmt. Für uns reicht es zu wissen, das Klassen in der Arduino Programmierung meistens gleichbedeutend mit Dateien sind, die dann Methoden und Funktionen enthalten. In den allermeisten Fällen arbeitet man bei den einfachen Programmierungen die wir hier machen auch gar nicht mit Klassen, daher können wir dies eigentlich auch ignorieren. Wer sich etwas in das Thema vertiefen will, findet dazu auf der offiziellen Arduino Seite auch viele Informationen.
Methoden und Funktionen sind weitestgehend das gleiche, als Funktion wird ein Programmabschnitt bezeichnet, der einen Wert zurück gibt, als Methode einer, der einfach nur abgearbeitet wird und "nichts" zurück gibt. Der Name von Methoden und Funktionen ist ebenfalls frei Wählbar, meistens wird durch den Namen aber beschrieben was die Funktion macht, und dann aus einem Verb und einem Subjekt zusammengesetzt.
Der Einfachheit halber werde ich von nun an sowohl Methoden als auch Funktionen als Funktionen bezeichnen.
Ein Beispiel:
Code:
void switchLedOn() {
 // dies ist eine Methode, das "void" bedeutet, das hier "nichts" zurück gegeben wird.
}

float readSensorData() {
 // dies ist eine Funktion, vor dem Namen der Funktion steht der Datentyp der zurück gegeben wird.

 return 0.5; // mit return wird angegeben was die Funktion zurück geben soll, hier einfach ein fester Wert, was eigentlich unüblich ist, der Name der Funktion lässt eher vermuten das hier eine Messung durchgeführt und der gemessene Wert zurück gegeben wird. Zu beachten ist, das eine Funktion bei einem return beendet wird. Schriebt man also in die Zeilen darunter noch etwas, wird dies niemals bearbeitet.
}
Funktionen kann man auch Werte übergeben. Diese werden innerhalb der runden Klammer einer Funktion angegeben. Wird diese Funktion dann aufgerufen, so muss auf jeden Fall ein Wert übergeben werden.
Code:
void switchLedOn(int ledPin) {
 // hier wird ein int, also eine ganze Zahl erwartet, und innerhalb der Funktion kann man auf diesen Wert mit dem dahinter stehenden Namen zugreifen. Es wird also direkt eine Variable angelegt. Braucht man mehrere Werte die übergeben werden sollen, so werden diese mit einem , getrennt in der runden Klammer hintereinander geschrieben.
}

Rechnen, auswerten, kontrollieren:
Mit den Variablen und Funktionen kann man nun Programmabläufe erstellen. Dies ist eine Schritt für Schritt Anweisung, was der Arduino tun soll. Dazu kann man Rechnen, Abfragen durchführen und die Hardware kontrollieren. In C/C++ gibt es eine Menge verschiedener Möglichkeiten Vergleiche oder Rechnungen durchzuführen, eine Übersicht bietet hier auf die offizielle Arduino Seite. Wir können uns aber merken, das alles was wir früher im Mathe-unterricht gemacht haben hier auch möglich ist.
Code:
int oneValue = 2;
int otherValue = 5;

int thisValue = oneValue + otherValue;
Es kann also einfach gerechnet werden. Alle gängigen Rechenarten werden unterstützt.

Abfragen oder wiederkehrenden Aufgaben werden in C/C++ als If-Anweisungen und Loops bezeichnet. Hier schreibt man quasi einen englischen Satz aus Stichworten und Rechenzeichen.
Code:
int valueA = 2;
int valueB = 10;

if (valueA > valueB) {
 // wenn die Bedingung in der Klammer hinter dem If zutrifft, dann wird dieser Block behandelt. Hier kann es nicht zutreffen, aber es ist ja auch nur ein Beispiel
} else {
 // trifft die Bedingung in der Klammer hinter den If nicht zu, so wird der Block hinter dem else durchlaufen. Der else Block ist optional, wenn man also nur auf eine Bedingung reagieren will kann man diesen auch weg lassen
}

while (valueA < 10) {
 // ein while ist eine Schleife, die so lange durchlaufen wird, bis die Bedingung in der Klammer zutrifft.
 valueA = valueA + 1; // wenn wir zu der Variablen, die in der Klammer abgefragt wird immer 1 addieren, wird sich die Bedingung irgendwann erfüllen, und die Schleife wird verlassen.
}
Funktionen die man definiert hat können einfach aufgerufen werden in dem man den Namen der Funktion schreibt. Selbst wenn die Funktion keine Werte übergeben bekommt muss man allerdings immer runde Klammern dahinter setzen.
Code:
void setuo() {
}

void myCoolFunction() {
 // von mir selbst geschriebene Funktion
}

int myOtherFunction(int inValue) {
 int outValue = inValue + 10; // hier wird der übergebene Wert benutzt um eine Rechnung durchzuführen

return outValue; // der errechnete Wert wird als Rückgabewert zurück gegeben
}

void loop() {
 myCoolFunction(); // um die Funktion aufzurufen, wird einfach der Name geschrieben. das ; nicht vergessen!

int someValue = myOtherFunction(50); // wir rufen eine Funktion mit einem übergebenen Wert auf, und merken uns den Rückgabewert direkt auf einer Variablen.
}


Mit diesen einfachen Mitteln lässt sich schon eine ganze Menge anstellen. Intern hat der Arduino schon eine ganze Reihe vorgefertigter Funktionen, mit denen man z.B. die Pins des Arduinos steuern kann. Eine ganze Liste kann auf der offiziellen Arduino Seite eingesehen werden, hier im Tutorial wäre es etwas viel alle aufzuzählen, ich werde sie erklären, sobald welche davon gebraucht werden.

Die erste Schaltung

Nur mit einem Arduino, selbst wenn man diesen jetzt programmieren kann, lässt sich noch nicht viel machen. Sein Potential entfaltet er erst, wenn man ihn mit ein wenig Elektronik verbindet! Hier lässt sich alles was man evtl. aus dem Technik-Unterricht kennt auch umsetzen. Es gibt mittlerweile eine riesige Anzahl an Sensoren und Module, die direkt auf den Arduino zugeschnitten sind. Wer sich hier mal eine Übersicht verschaffen will, kann gerne mal bei gängigen Online-Händlern den Suchbegriff "Arduino Module" eingeben. 

Für uns besonders interessant sind alle Module und elektronische Bauteile, die Licht erzeugen können. Sehr beliebt sind also z.B LEDs und Laser. Mit ein kleinen wenig Elektronik dazwischen lassen sich aber auch z.B. EL-Wires oder Plasmabälle steuern.

Hinzu kommen noch eine Vielzahl an Schaltern und Sensoren, die man in seine Tools einbauen kann. So z.B. Knöpfe, Schalter, Drehknöpfe oder sogar Gyroskop-Sensoren (wie im bekannten Wii-Chuck Kontroller). Hier setzt einen eigentlich nur die Fantasie eine Grenze, und wie viele Ideen einen so kommen. Wie wäre es mit einem LED Stab, bei dem die Farbe und Helligkeit mittels Drehknöpfe eingestellt werden können, und wenn ein Schalter umgelegt wird sich die Farbe mit Hilfe eines Gyroskops dann verändert, je nachdem in welche Richtung man damit zeigt?

Aber fangen wir erst ein mal klein an, und schauen wie wir Schalter und Drehknöpfe (Potentiometer) dazu benutzen können ein paar LEDs zum leuchten zu bringen.

Wir brauchen dafür also ein Arduino (ich benutze gerne den Nano), ein paar LEDs, Knöpfe, Potentiometer, Widerstände und eine Möglichkeit das alles zu verbinden. Dazu wird meistens ein Steckbrett verwendet, da hier eine Schaltung einfach zusammen gesteckt werden kann, ohne das man alles verlöten muss. Hat man alles so zusammen gestellt wie man möchte, kann man das ganze dann korrekt verlöten, so lange ist es aber ein riesen Vorteil mal eben ein paar Kabel umstecken zu können. Diese Steckbretter sind so konzipiert, das alle Löcher in einer Reihe elektrisch miteinander verbunden sind (zu erkennen meistens an den Zahlen am Rand).

   

Eigentlich sollte man nie einen Verbraucher, also eine LED direkt an die Pins vom Arduino anschließen, für dieses kleine Beispiel und die verwendeten LEDs ist dies jedoch kein Problem, weil die LEDs weniger Strom verbrauchen als der Arduino maximal aushält.

Die meisten Arduinos laufen mit einer Spannung von 5V, die hier verwendeten LEDs benötigen allerdings eine Spannung von ca. 3V. Ein ganz einfaches Mittel die Spannung von 3V auf 5V zu reduzieren ist ein Widerstand zu verwenden. Nach dem Ohmschen Gesetz können wir uns ausrechnen, wie groß der Widerstand sein muss, damit er die Differenz zwischen den 5V und den 3V der LED ausgleicht. Dazu benötigt man noch die Stromstärke, die bei den verwendeten LEDs meist 20mA beträgt. Es gibt auch sehr viele gute Widerstands-Rechner im Netz, mit denen sich schnell und einfach diese Widerstände ausrechnen lassen. Die LEDs werden dann in Reihe mit den Widerständen an die Pins des Arduino gehängt, und mit dem anderen Bein an den - Pol bzw Masse. Wenn man nun ein Signal auf diesen Pin aktiviert schaltet man damit die LED ein. 

   

Um die LEDs zu steuern verwenden wir hier einfach mal einen einfachen Schalter und einen Potentiometer. Ein Schalter unterbricht oder verbindet einen Stromkreis. Ein Potentiometer ist ein Drehknopf, der so funktioniert, das man durch die Drehung den Widerstand zwischen den Pins davon verändert. Die eine Seite ist die höchste Spannung, die andere Seite die niedrigste. Dreht man nun, so wird am mittleren Pin eine Spannung entsprechend des Widerstandes intern ausgegeben. Dies kann der Arduino nun messen, um so einen einstellbaren Wert zu bekommen (z.B. für Helligkeit, Farbe, Zeitintervalle, etc).

   

In diesem Tutorial möchte ich diese einfachen Bauteile dazu nutzen, einen Larson Scanner zu bauen. Was ist das? Kennt ihr noch Kampfstern Galactica und erinnert euch an die "Augen" der Zylonen? Oder habt ihr Knight Rider gesehen und erinnert euch an die laufenden Lichter vorne unter der Motorhaube von KIT? Genau das ist ein Larson Scanner Wink

Dazu benutzen wir ein paar LEDs um die laufenden Lichter zu erzeugen, einen Schalter um die LEDs an und aus zu schalten, und einen Potentiometer um die Geschwindigkeit mit der das Licht hin und her läuft zu kontrollieren. Dann zeige ich euch noch wie man damit tolle LightPainting Bilder machen kann.

Fangen wir also mit der Schaltung an:

   

Die Komponenten sind wie schon beschrieben sehr einfach. Ein paar LEDs, passende Widerstände, ein Potentiometer, ein Schalter, ein Arduino Nano und eine Stromquelle. Ich benutze hier eine 9V Batterie. Der Arduino hat einen Anschluss, an dem er mit 5-12V versorgt werden kann, und regelt selbstständig dann diese Spannung auf deine benötigten 5V runter, daher ist eine 9V Batterie perfekt für solche Anwendungen.

   

Die LEDs habe ich hier jeweils an eins der digitalen Pins des Arduino gehängt (langes Bein ist +, kurzes -). Ich habe hier einen kleinen Trick benutzt, um Widerstände zu sparen. Da ich den Arduino programmiere weiß ich, das immer nur maximal 2 der LEDs eingeschaltet sind, und das auch nur für einen Bruchteil einer Sekunde. Daher kann man den Widerstand so wählen, das er die Spannung für ca. anderthalb LEDs regelt. So kann man dann alle - Beinchen der LEDs mit diesem Widerstand verbinden.

   

Der Potentiometer wird mit seinen äußeren Beinen an den 5V Ausgang und an den GND Pin des Arduino verbunden, so das am mittleren Bein dann die eingestellte Spannung herrschen kann. Dies wird mit dem A0 Eingang des Arduino verbunden. Der Schalter wird mit GND und dem D12 Pin verbunden.

   

Zusammengebaut sieht die Schaltung durch die vielen Drähte zwar schon etwas größer aus, ist aber eigentlich gar nicht so kompliziert, wenn man mal genauer hin schaut. Die Pins sind so gewählt, das die D-Pins also Digitalen Pins des Arduino benutzt werden. Das sind die Pins, an denen der Arduino ein Ein/Aus Signal senden oder empfangen kann. Da die LEDs hier nur ein oder aus geschaltet werden müssen reicht uns das vollkommen. Die Beinchen die mit Ax markiert sind, werden vom Arduino als Analoge Eingänge benutzt, also die Beinchen an denen der Arduino eine Spannung auslesen kann.

Der Arduino hat ein paar eigene LEDs, mit denen z.B. angezeigt wird das er eingeschaltet ist. Bei Tools kann dies schon mal stören, aber die LEDs lassen sich einfach mit Klebeband abkleben, oder für eine etwas permanentere Lösung einfach mit einem Edding (mehrfach) übermalen.

Und nun die Programmierung:
 
Will man andere Pins benutzen, oder weniger LEDs (für mehr ist am Arduino Nano kein Platz), so kann man die Konstanten im oberen Bereich des Codes anpassen. Der Zeitintervall der durch den Potentiometer eingestellt werden kann ist so gewähl, das man zwischen schneller als das Auge es sehen kann bis ganz langsam beliebig die Geschwindigkeit einstellen kann. Dies dient als Beispiel, was alles möglich ist.
Code:
const int sensorPin = A0; // Pin an dem der Potentiometer ist
const int onOffPin = 12;
const int ledPins[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; // Pins für die LEDs
const int numLEDs (sizeof(ledPins) / sizeof(int)); //Anzahl der LEDs

int sensorValue = 0;  // Variable um den Wert vom Potentiometer zu lesen
int lastLED = 0;
int currentLED = 0;
boolean up = true;

void setup() {
  // LED Pins werden als Ausgang definiert:
  for (int index = 0; index < numLEDs; index++)  {
    pinMode(ledPins[index], OUTPUT);
  }
  // Pin für den Schalter wirs als Eingabe definiert, und auf "Pullup" gesetzt.
  // Das bedeutet, das intern der Pin mit Strum versorgt wird, also ohne äußeren
  // Einfluss immer ein HIGH zurück gibt. Wenn wir diesen mit GND verbinden gibt
  // der Pin LOW zurück. Also müssen wir den Schalter zwischen dem Pin und GND legen
  pinMode(onOffPin, INPUT_PULLUP);
}

void loop() {
  // zunächst lesen wir den Wert vom Schalter-Pin, denn nur wenn der Schalter ein
  // geschaltet ist wollen wir die LEDs leuchten lassen
  if (digitalRead(onOffPin) == LOW) {
    // Der Schalter ist An wenn ein LOW gelesen wird!
    // Wert vom Potentiometer lesen, Die Analogen Pins können nicht nur an oder aus
    // auslesen, sondern auch die Spannung in einem Bereich von 0 bis 5V
    // ein Potentiometer regelt je nach Drehung die Spannung in den Bereichen seiner
    // äußeren Pins, wenn wir dort also 0V und 5V anlegen können wir damit diesen Wert
    // festlegen
    sensorValue = analogRead(sensorPin);
    // map eine Funktion, die einen Wertebereich in einen anderen überträgt.
    // Man gibt einen Wert an, und dessen Minimum und Maximum, dann die neuen Grenzen.
    // Wenn man also 50 im Bereich 0 bis 100 auf den Bereich 0 bis 200 mappt, ist das 100
    // weil 50 vorher die Hälfte vom Maximumm war.
    int delayTime = sensorValue = map(sensorValue, 0, 1023, 0, 800);
    // lasse aktuelle LED leuchten
    digitalWrite(ledPins[currentLED], HIGH);
    // um einen seltsam aussehenden Anfang zu verhinden prüfen wir ob
    // die aktuelle gleich der letzten LED ist.
    if (lastLED != currentLED) {
      // kurz waren bis vorherige LED aus geschaltet wird, das verstärkt
      // den Effekt.
      delay(min(10, delayTime / 100));
      // vorherige LED ausschalten
      digitalWrite(ledPins[lastLED], LOW);
    }
    // Vom Potentiometer eingestellte Zeit warten
    delay(delayTime);
    // Aktuelle LED merken, damit es beim nächsten mal dann die vorherige ist
    lastLED = currentLED;
    // Prüfen in welche Richtung sich das Licht bewegt
    if (up) {
      // wenn das Licht sich "aufwärts" bewegt, den index erhöhen
      currentLED++;
      // wenn der Index höher wird als LEDs da sind, haben wir das Ende erreicht
      if (currentLED >= numLEDs) {
        // dann drehen wir die Richtung um und korrigieren den index
        currentLED = numLEDs - 2;
        up = !up;
      }
    } else {
      // wenn das Licht sich "abwärts" bewegt, den Index verringern
      currentLED--;
      // Wenn der Index kleiner als 0 wird is das Ende erreicht
      if (currentLED < 0) {
        // dann die Richtung wieder umdrehen und Index korrigieren
        currentLED = 1;
        up = !up;
      }
  } else {
    // wenn der Schalter aus geschaltet wird auch die LEDs aus schalten!
    digitalWrite(ledPins[lastLED], LOW);
    digitalWrite(ledPins[currentLED], LOW);
  }
}

Liefert man nun Strom, legt der Arduino mit deinem Job los. Betätigen wir den Schalter, dann wird der Stand des Potentiometers ermittelt, und damit die Geschwindigkeit der Lauflichter gesetzt. Die LEDs werden nacheinander ein und aus geschaltet, so das das Licht immer hin und her wandert. Dabei schaltet sich die vorherige LED ein wenig Zeit-erzögert aus, um den Effekt noch zu verstärken. Dies können wir nun einfach in einer Langzeitbelichtung durch das Bild bewegen um tolle Effekte zu erzeugen:

   

Oder schneller:
   

Dreht man das ganze, hat man schon ein kleines Tool für variable Scheiben:
   

(Hier nur auf eine Spule geklebt und kurz gedreht, aber ich denke es reicht als Beispiel um Ideen anzuregen Wink  )
 
Reply }
Thanks given by:
  


Möglicherweise verwandte Themen...
Thema Verfasser Antworten Ansichten Letzter Beitrag
  Arduino Tutorial - Teil 1: Grundlagen RyusLightworks 0 794 14.07.2016, 20:59
Letzter Beitrag: RyusLightworks

Gehe zu:


Browsing: 1 Gast/Gäste