Schnellstart mit dem NanoESP – Teil2

In diesem Artikel finden Sie Auszüge aus einem meiner ersten Bücher „Schnellstart mit dem Arduino Micro“. Ich habe den Text etwas für das NanoESP-Board angepasst. Screenshots etc. können aber immer noch Text für einen anderen Arduino enthalten. Gedacht ist dieser Artikel vor allem für diejenigen, die sich für das NanoESP-Board (Pretzelboard) interessieren, aber noch nie mit Arduino gearbeitet haben. 

Vorbereitung

Nach dem theoretischen Einstieg in die Arduino-Welt ist es nun an der Zeit, sich mit den praktischen Grundlagen zu beschäftigen. Als ersten Schritt sollte man sich ein praktikables System für die Hardwareaufbauten aneignen. Für den NanoESP bietet sich ein mittleres bis großes Steckboard an.

Der NanoESP auf dem Steckboard

Der NanoESP auf dem Steckboard

Um meine Aufbauten übersichtlicher zu gestalten habe ich mir den Masse Pegel auf die obere, auf der ganzen Länge durchkontaktierte Leitung gelegt. Die untere Leitung hat dagegen 5 V. Dadurch ist der Zugang zu diesen wichtigen Versorgungsleisten erleichtert.

Es gibt natürlich auch andere Möglichkeiten um sich eine handliche Entwicklungsumgebung zu kreieren. Ein größeres Board schafft z.B. mehr Raum für größere Projekte. Wichtig ist nur, dass Sie den Überblick über Ihr System nicht verlieren und schnell und unkompliziert Bauteile austauschen oder Schaltungen erweitern können. Dadurch bleibt der Stress minimal und der Spaßfaktor erhalten.

Alle folgenden Programme können Sie hier herunterladen: Programme_NanoESP

Die erste LED

Genug vorbereitet, jetzt geht es endlich los mit dem ersten eigenen Programm. Zuerst schließen Sie eine LED an Pin0 an. Vor die LED gehört natürlich immer ein Vorwiderstand um den Strom zu begrenzen. Der Pin hat, wenn er HIGH geschaltet ist, einen Pegel von +5 V. Außerdem liefert er bis zu 40 mA. Also berechnet Sie den Widerstand wie folgt:

Spannung am Widerstand: 5 V – 1,7 V (Spannungsabfall an der LED) = 3,3 V
Strom am Widerstand = erlaubter Strom an der LED = 20 mA
Widerstand R = U / I à R = 3,3 V / 20 mA = 165 Ohm.

Das bedeutet also, dass Sie mindestens einen Widerstand von 165 Ohm einsetzen sollten, um die LED nicht zu überlasten. In meinem Fall habe ich 1,0 kOhm (Farbringe: Braun, Schwarz, Schwarz)  benutzt. Das bedeutet zwar, dass durch die LED nur 5 mA fließen, das ist aber immer noch ausreichend um sie hell leuchten zu lassen.  Auf dem Bild sehen Sie, wie die LED auf einem Steckbrett angeschlossen werden könnte.

Die LED an Pin 9

Die LED an Pin 9

Achten Sie darauf, dass der negative Pol  der LED (die Kathode) an Masse liegt. Sie erkennen die Kathode in den meisten Fällen an folgenden Merkmalen:

  • Kürzeres Kontaktbeinchen
  • Abflachung am Gehäuserand
  • Größere Kontaktfläche im inneren der LED

In der Programmierumgebung müssen Sie jetzt schrittweise folgendes tun:

  1. Grundlegende Programmstruktur anlegen
  2. Den Pin der LED als Ausgang definieren
  3. Den besagten Pin auf HIGH schalten

Heraus kommt folgendes Programm:

void setup() {

  pinMode (9, OUTPUT); //Pin9 als Ausgang definieren

}

void loop() {

  digitalWrite(9,HIGH); //Pin9 auf HIGH setzen

}

Kompilieren und uploaden Sie das Programm. Die LED sollte jetzt leuchten. Ist dies nicht der Fall überprüfen Sie folgendes:

  • Haben Sie den richtigen Pin getroffen
  • Haben Sie die LED richtig herum angeschlossen
  • Haben Sie die LED gegen Masse geschaltet
  • Wurde eine Fehlermeldung angezeigt
  • Haben Sie die Groß- und Kleinschreibung im Programm beachtet

Wenn alles funktioniert, können Sie noch etwas tun, um das Programm übersichtlicher zu gestallten. Es ist nämlich möglich, Ihren Pins Namen zu geben. Das kann später bei komplexeren Programmen sehr hilfreich sein.

Dies funktioniert, indem Sie eine Variable definieren. Globale Variablen (die man in allen Prozeduren benutzen kann) definiert man grundsätzlich noch vor der Setup-Prozedur. Möchten Sie eine Variable lokal (nur in genau dieser Prozedur) benutzen, müssen Sie diese zu Beginn der Prozedur deklarieren.

Diese Variable ist aber global und steht noch vor der Setup-Prozedur. Die Zeile, die Sie dort einfügen müssen lautet:

int led = 9;

int steht für Integer, einem Variablentypen, der für Ganzzahlen steht. led ist der Variablenname und nach dem Gleichheitszeichen steht der Wert, den Sie dieser Variable beim Start zuweisen. Der Compiler sorgt nun beim Übersetzen des Programms dafür, dass alle led-Variablen durch den entsprechenden Wert ersetzt werden. Das neue Programm sieht nun so aus:

int led = 9;             //Ab nun bedeutet led = 9

void setup() {

  pinMode (led, OUTPUT); //Pin0 als Ausgang definieren

}

 void loop() {

  digitalWrite(led,HIGH); //Pin0 auf HIGH setzen

}

Dieses Programm lässt sich schon leichter lesen.

Schalter

Beim Anschließen eines Schalters muss man folgendes beachten: der zu lesende Zustand sollte eindeutig High (hier 5 V) oder Low (0 V = GND) sein. Um das garantieren zu können gibt es zwei Möglichkeiten: Externer Pulldown und interner Pullup.

Pulldown bedeutet, dass der Pin von außen mit einem Widerstand auf das Null-Niveau von GND heruntergezogen wird. Wenn der Pin dann gelesen wird, ist er im Normalfall eindeutig LOW. Der Schalter wird dann so angeschlossen, dass er beim Betätigen den Port auf +5 V hochzieht, und somit auf eindeutig HIGH.

Die zweite Möglichkeit einen Schalter anzuschließen ist, den internen Pullup zu benutzen. Der Controller hat intern zuschaltbare Widerstände in der Größenordnung von etwa 20 kOhm, die einen zu hohen Kurzschlussstrom bei einer direkten Verbindung mit dem Masse-Pin verhindern.

Um einen Pullup einzuschalten müssen Sie den Eingangs-Pin im Programm auf HIGH setzen und über einen Taster an Masse anschließen. Drücken Sie nun auf den Button, wird der Port über den Schalter eindeutig auf GND-Niveau heruntergezogen. Der Vorteil an dieser Methode ist, dass Sie auf der Hardwareseite den Widerstand sparen. Der Nachteil: Die Funktion digitalRead() ist LOW wenn der Taster gedrückt wird, also Intuitiv vertauscht.

Ein Schalter an D6

Ein Schalter an D6

Das Programm sieht so aus :

int led = 9; //Ab nun bedeutet led = 9

int schalter = 6; //Ab nun bedeutet schalter = 6

 

void setup() {

  pinMode (led, OUTPUT); //Pin9 als Ausgang definieren

  pinMode (schalter, INPUT);  //Pin6 als Eingang definieren

  digitalWrite (schalter, HIGH); //Pin6 High setzen für internen Pullup

}

 

void loop() {

  digitalWrite(led,digitalRead(schalter)); //Pin0 auf den Wert von Pin6 setzen

}

Der Unterschied zum ersten Programm liegt darin, dass Sie per Tastendruck die LED ausschalten.

Es gibt noch eine weite Möglichkeit sich Zeilen zu sparen und das Programm übersichtlicher zu machen. Sie können bei der Angabe des Pin-Modus in  Zeile 6 einfach folgendes schreiben:

   pinMode (schalter, INPUT_PULLUP);  //Pin6 als Eingang mit internen Pullup definieren

Es passiert im Grunde das gleiche wie davor: der interne Pullup-Widerstand zieht den Pin hoch. Allerdings ist dieser Befehl um einiges praktischer und Sie können sich die Zeile 7 komplett sparen.

Kleiner Tipp: Wenn Sie die Ihre Programms wieder Intuitiv gestalten wollen, können Sie  vor digitalRead() ein Ausrufezeichen schreiben. Dadurch wird der Zustand negiert: aus HIGH wird LOW und umgekehrt. Die LED leuchtet nur, wenn Sie auf den Button drücken.

digitalWrite(led,!digitalRead(schalter)); //Pin9 auf den Wert von Pin5 setzen

PWM

Mit dem PWM-Verfahren kann man die Helligkeit einer LED steuern oder auch die Geschwindigkeit eines Motors. Das geht aber nicht mit jedem beliebigen Anschluss sondern nur mit den Anschlüssen D3,D5,D6, D9, D10 und D11 des NanoESP. In dem nächsten Beispiel wird nicht mehr Pin0 sondern Pin3 benutzt, da sich Pin0 nicht mit PWM ansteuern lässt:

D6 ist auch ein PWM Ausgang

D6 ist auch ein PWM Ausgang

Wie Sie sehen ist der Schalter aus dem vorherigen Beispiel noch angeschlossen. In Verbindung mit einer Entscheidungsabfrage (IF-Abfrage) ist es deswegen möglich, die Helligkeit zu steuern. Trotzdem wird das Programm nicht viel komplizierter und Sie können etwas über IF-Abfragen lernen.  Aber zuerst der Quelltext:

int led = 9;                            //Ab nun bedeutet led = 9

int schalter = 6;                       //Ab nun bedeutet schalter = 6

byte helligkeit = 0;                    //Zustandsvariable der Helligkeit

 

void setup() {

  pinMode (led, OUTPUT);                           //Pin0 als Ausgang definieren

  pinMode (schalter, INPUT_PULLUP);      //Pin6 als Eingang definieren

 }

 

void loop() {

  if (digitalRead (schalter)  == LOW) {    //WENN Schalter gedrückt DANN {…}

      helligkeit = helligkeit +1;                        //Helligkeitsvariable um 1 erhöhen

      analogWrite(led, helligkeit);                //Helligkeit an LED ausgeben

      delay(5);                                                      //Wartezeit

     }

}

 

Die wichtigsten Befehle zum Verständnis:

  if (digitalRead (schalter)  == LOW) {   }                                //WENN Schalter gedrückt DANN {…}

Dies ist die IF-Abfrage. In den runden Klammern steht die Bedingung (hier Schalter gleich LOW, denn Sie haben den internen Pullup benutzt). Beachten Sie, dass Vergleiche immer mit Doppel-Gleichheitszeichen geschrieben werden müssen und nicht wie bei Zuweisungen mit einem einzelnen Gleichheitszeichen. In den geschweiften Klammern steht der Programmteil, der bei erfüllter Bedingung ausgeführt wird. Sie können an Ihre IF-Abfrage noch mit ELSE { } einen Programmteil anhängen, der ausgeführt wird, wenn die IF-Bedingung nicht erfüllt ist. Die genaue Schreibweise finden Sie im Anhang.

            helligkeit = helligkeit +1;                                         //Helligkeitsvariable um 1 erhöhen     

Dieser Teil steht zwischen den geschweiften Klammern der If-Abfrage und wird deshalb immer nur ausgeführt, wenn der Button gedrückt ist. Die Variable helligkeit wird immer um eins erhöht. Das gilt, bis der Maximalwert 255 erreicht ist. Wenn die Taste dann immer noch gedrückt wird passiert ein sogenannter Overflow (Überlauf). Das bedeutet, dass die Variable wieder bei 0 beginnt, da ein Byte keine Werte über 255 annehmen kann.

                  analogWrite(led, helligkeit);                                   //Helligkeit an LED ausgeben

Bei diesem Befehl handelt es sich um den eigentlichen PWM-Befehl. Der Name des Befehls ließe vermuten, dass hier einfach die Spannung an dem Pin verändert würde. Tatsächlich passiert bei PWM aber folgendes:

PWM steht für Pulsweitenmodulation und bedeutet, dass ein Pin sehr schnell An und Aus geschaltet wird. Dabei kann man das Verhältnis von An- und Auszeit variieren, sodass es aussieht als würde eine an diesem Pin angeschlossene LED ihre Helligkeit verändern. Im Fall der Arduino sind in der Regel 8-Bit-Timer für die Steuerung der An/Auszeiten zuständig. Das ist auch der Grund warum der Maximalwert des PWM bei 255 liegt (8 bit = 1 Byte = Werte von 0 bis 255). Es gibt aber auch 10-Bit oder 16-Bit PWM-Timer. Durch die höhere Auflösung kann man z.B. das Flackern einer LED verringern und die Helligkeit kleinschrittiger Steuern.

                  delay(5);                         //Wartezeit             

Bei dem Befehl delay() handelt es sich um einen Wartebefehl der das Programm kurzzeitig stoppt. Die Zahl in den Klammern steht für die Wartezeit in Millisekunden (ms). Diese Wartezeit wird hier benötigt da sonst die Helligkeitsvariable zu schnell durchlaufen würde und man von einer Veränderung nichts mitbekommen würde.

Senden über die serielle Schnittstelle

Mit einer seriellen Schnittstelle kann man Texte oder Zahlenwerte senden und empfangen. Das ist z.B. nützlich um ein Programm zu debuggen, also  Bugs (Fehler) zu finden. Es ist oft schwierig nachzuvollziehen, warum ein Programm nicht funktioniert da man nicht in den Controller reingucken kann.  Aber dank der seriellen Schnittstelle kann man Informationen ans Terminal am PC (hier Serial Monitor genannt) senden lassen. Dadurch ist es möglich, Zwischenwerte zu überprüfen. In diesem Beispiel ändern Sie nichts am Hardwareaufbau sondern nur am Programm selbst:

int led = 9;           //Ab nun bedeutet led = 3

int schalter = 6;      //Ab nun bedeutet schalter = 6

byte helligkeit = 0;   //Zustandsvariable der Helligkeit

 

void setup() {

   pinMode (led, OUTPUT);         //Pin0 als Ausgang definieren

   pinMode (schalter, INPUT_PULLUP);     //Pin6 als Eingang definieren

   Serial.begin (9600);           //Serielle Schnittstelle initialisieren

}

 

void loop() {

   if (digitalRead (schalter)  == LOW) {           //WENN Schalter gedrückt DANN {…}

      helligkeit = helligkeit +1;                  //Helligkeitsvariable um 1 erhöhen

     Serial.print („Helligkeit: “ );              //Helligkeit ausgeben (Text)

     Serial.println (helligkeit);                 //Helligkeit ausgeben (Wert)

     analogWrite(led, helligkeit);                 //Helligkeit an LED ausgeben

     delay(5);                                   //Wartezeit

  }

}

In Zeile 8 wird die serielle Schnittstelle initialisiert und die Baudrate einstellt. Die Baudrate (hier 9600 bit/s) ist die Übertragungsgeschwindigkeit. Es ist wichtig, dass das Programm und das Terminal auf dieselbe Geschwindigkeit eingestellt sind.

In Zeile 14 steht zum ersten Mal eine Ausgabe. Der Befehl Serial.print (); sendet, was zwischen den Klammern steht. Dabei kann es sich, wie in diesem Fall, um einen Text in Anführungszeichen handeln, oder um eine Variable, wie in der nächsten Zeile. Hier steht allerdings nicht  Serial.print() , sondern Serial.println (); . Das ln bedeutet, dass eine neue Zeile (ln für engl. line) angefangen wird. Würde hier wieder nur print stehen, käme alles in eine Zeile.

Übertagen Sie das Programm in ihren Controller und starten Sie das Terminal mit Klick auf das Symbol in der rechten, oberen Ecke. Achten Sie darauf, dass im Terminal unten rechts „9600 baud“ ausgewählt ist.  Drücken Sie dann den Taster auf Ihrem Board. Im Terminal können Sie nach jedem Tastendruck die aktuelle Helligkeit ablesen.

Der Serielle Monitor

Der Serielle Monitor

Analog einlesen

Die meisten Sensoren geben keine digitalen Signale sondern analoge Spannungswerte aus. Beispielsweise misst man bei Temperatur- und Lichtsensoren eine Spannung, und der ADC (Analog-Digital-Converter) macht daraus einen programmtauglichen Wert.

Ein Beispiel für einen Lichtsensor wäre z.B. ein LDR (Light Dependent Resistor = Lichtabhängiger Widerstand), dessen Widerstandswert geringer wird, je mehr Licht auf ihn fällt. Um die Lichtintensität messen zu können, würden Sie einen LDR nehmen  und mit einem Widerstand (günstig sind 10 kOhm: Braun, Schwarz, Orange) als Teil eines Spannungsteilers anschließen.

In diesem Fall simulieren wir allerdings nur einen analogen Sensor mithilfe eines Potentiometers. Dieser ist schon ein Spannungsteiler, mit Widerstandswerten zwischen  0 und 10 kOhm. Wenn beide äußeren Pins auf GND und 5V verbunden sind, kann man so auf dem mittleren Pin Spannungen von 0-5V einstellen. Der mittlere Pin wird nun an A0 angeschlossen.

Das Poti an A0

Das Poti an A0

Der hier verwendetet ADC hat eine Auflösung von 10 Bit. Das bedeutet einen Wertebereich von 0 (bei 0V = GND) bis 1023 (bei 5V, der Referenzspannung). Überprüfen lässt sich das mit dem Terminal. Das Programm überträgt permanent den gemessenen Wert an den PC.

Bei einer Lichtmessung müssen Sie beachten, dass die Intensität des Lichtes nicht linear empfunden wird. Liegt Ihr Arbeitsplatz gerade in der Sonne, wird ein sehr niedriger Wert angezeigt. Arbeiten Sie nachts, ist der Wert vermutlich sehr hoch und die LED leuchtet hell. Unser Auge stellt sich auf verschiedene Helligkeiten ein und deswegen täuschen wir uns leicht in der tatsächlichen Helligkeit. Dabei ist der Unterschied zwischen einem nachts gut beleuchteten Arbeitsplatz (Ca. 1000 Lux) und direkter Sonneinstrahlung (bis zu 100.000 Lux) enorm.

Der Quelltext:

int led = 9;           //Ab nun bedeutet led = 9

int LDR = A0;      //Ab nun bedeutet LDR = 0

byte helligkeit = 0;   //Zustandsvariable der Helligkeit

 

void setup() {

  pinMode (led, OUTPUT);              //Pin0 als Ausgang definieren

  Serial.begin (9600);                          //Serielle Schnittstelle initialisieren

}

 

void loop() {

  helligkeit = analogRead(LDR);                       //Messen

  Serial.println(helligkeit);              //Messergebnis an PC senden

  analogWrite(led, helligkeit/4);                  //angepasste Helligkeit an LED ausgeben

  delay(10);                                                         //Wartezeit

}

Um einen analogen Eingang zu messen benutzt man den Befehl aus Zeile 11: analogRead();. In Klammern steht der zu messende analoge Kanal (hier A0, zu unterscheiden vom digitalen Eingang/Ausgang 0).

Der gemessene Wert wird in die Varible helligkeit geschrieben und am den PC gesendet. Anschließend muss der Wert vor der PWM-Ausgabe an der LED durch 4 geteilt werden, damit aus einem 10-bit-Wert ein 8-bit-Wert wird (1023/4 =255, gerechnet in Ganzzahlen).

Eine alternative um den Sensorbereich an einen Ausgabebereich anzupassen bietet der map Befehl. Z.B. könnte man Zeile 13 auch wie folgt schreiben:

helligkeit = map(analogRead(LDR), 0, 1023, 0, 255);

Im Grund passiert in beiden Verfahren das gleiche aber wenn Sie später mal andere Sensoren benutzen wollen die einen ganz anderen Messebereich haben können Sie sich mit diesem Befehl eine Menge Rechnerei ersparen.

In Zeile 14 steht erneut ein Wartebefehl. Dieser ist nötig, damit das Programm den PC nicht unter Dauerfeuer mit Informationen überlastet. Der Serielle Monitor und das Programm würden abstürzen