Vorwort

Version 2016-01-09 

Diese Einführung basiert auf den Tutorials auf arduino.cc, der Einführung von .:oomlout:., Tipps aus der Literatur, dem arduino-Forum und den IC-Tutorials von doctronics. Eigentlich ist bei diesen hervorragenden Quellen kein eigenes, weiteres Skript mehr nötig. Bei meinen Schülern enstand jedoch der Wunsch nach einem an den Unterricht angelehnten, deutschsprachigen Skript. Diese Seite enthält nicht meinen Unterrichtsgang, jedoch viele im Unterricht verwendete Beispiele. 

Im Unterricht verwende ich sehr gerne Teile (Sensoren, Servos, Arduino-Shields) aus den Sortimenten von Sparkfun und Parallax, welche in Deutschland günstig bei Elmicro erhältlich sind. Standardteile sind z.B. bei Conrad, csd-electronics, Pollin, Reichelt, Segor und Watterott erhältlich.

Die Fotos/Grafiken enstammen den zitierten Datenblättern oder wurden selbst fotografiert oder mit fritzing erstellt oder enthalten Quellenangaben. Formeln wurden mit dem online-TEX-Formelgenerator von codecogs.com erzeugt.

Über Tipps und Hinweise freue ich mich! Bitte schicke eine Email an frerk@popovic.info.

Diese Seite steht unter der creative-commons-Lizenz CC BY-SA.

cc by-sa

Inhaltsverzeichnis

Basics und Input / Output

Allgemeine Programmstruktur

Unser arduino wird C-ähnlich programmiert.Jedes Programm besteht dabei aus zwei Blöcken: Dem setup()-Block, der genau einmal ausgeführt wird, und dem loop()-Block, der anschließend so lange wiederholt wird, bis das Gerät ausgeschaltet wird:

void setup() {
  // initialize serial communications at 9600 bps:
  Serial.begin(9600); }

void loop() {
  Serial.println("Hallo Welt!" );   
// wait 10 milliseconds before the next loop

  delay(10); }

In Beispiel wird die der Serial Monitor benutzt, um über ihn eine Nachricht auszugeben. Auch wenn du an dieser Stelle noch nicht verstanden hast, wie das gemacht wird, solltest du es dir merken, denn später werden wir diese Ausgaben oft zur Fehlersuche verwenden.

Achtung: Wenn du die serielle Schnittstelle benutzt, darfst du die Ports 0 und 1 nicht verwenden!

Kommentare dienen zur besseren Verständlichkeit des Quelltextes, sie werden beim Compilieren der Programme ignoriert. Du erkennst sie daran, dass sie in der Entwicklungsumgebung grau dargestellt sind. Kommentare kannst du mittels //... oder /* ... */ erzeugen.

Blinkende Leuchtdiode

blinkende_led

Der digitale Port 13 besitzt eine integrierte LED, d.h. du benötigst eigentlich keine separate. Falls du dennoch eine anschließen möchtest, achte auf einen passenden Vorwiderstand und darauf, dass die LED richtig herum sitzt (abgeflachte Seite Richtung GND).

/*
  Blink
  Eine Leuchtdiode wird eine Sekunde an, und dann wieder eine Sekunde ausgeschaltet. Dieser Vorgang wird endlos wiederholt.
 */
void setup() {   // Port 13 wird als Ausgang festgelegt
  pinMode(13, OUTPUT); }

void loop() {
  digitalWrite(13, HIGH); // Port 13 wird high geschaltet
  delay(1000); // eine Sekunde warten
  digitalWrite(13, LOW); // Port 13 wird low geschaltet
  delay(1000); // eine Sekunde warten
}

Aufgabe:

  1. Übertrage das Programm auf den Mikrocontroller und lasse so die eingebaute LED blinken.

Dimmen einer LED mittels PWM (Pulsweitenmodulation)

Eine LED lässt sich im Gegensatz zu einer herkömmlichen Glühbirne nicht dimmen, sondern lediglich ein- oder ausschalten. Wird eine LED jedoch sehr schnell abwechselnd ein- und wieder ausgeschaltet, so nehmen wir Menschen eine geringere Helligkeit wahr. 

pwm

Beachte: Nur die mit der "~" gekennzeichneten Ports können für die PWM genutzt werden.

Aufgaben:

  1. Schließe eine LED mit passendem Vorwiderstand an Port11 an und lasse Sie blinken. Verändere die Dauer der Pause nun so, dass die LED dunkler leuchtet. Ab wie vielen Millisekunden Pausendauer nimmt dein Auge ein Blinken wahr?
  2. Wie musst du die PWM anpassen, damit das Signal im Mittel 3 V beträgt?
  3. Die PWM kannst du auch mittels analogWrite() am digitalen (sic!) PWM-Port erzeugen.
  4. Für Schnelle: Siehe dir das Beispiel "Fade" an und benutze es, um eine LED kontinuierlich heller und wieder dunkler werden zu lassen.

Achtung: analogWrite() hat nichts mit analogen Ports zu tun. Du benutzt die (digitalen) mit ~ als PWM-Ports markierten Ports und brauchst sie vorher nicht einmal mittels pinMode() als Ausgang deklarieren.

Digitale Werte erfassen

Digitale Ports können wahlweise als Ein- oder Ausgang festgelegt werden. Benutze folgendes Programm, um einen Wert einzulesen:

/*
  DigitalReadSerial
  Die Daten an Port 2 werden eingelesen und auf dem Serial Monitor ausgegeben. 
 */
void setup() {
  Serial.begin(9600);
  pinMode(2, INPUT);
}

void loop() {
  int sensorValue = digitalRead(2);
  Serial.println(sensorValue, DEC);
}

Tipp: Wenn du einmal zu wenige digitale Ports hast, kannst du einfach die analogen Ports A0 bis A5 als digitale Ports verwenden. Sie erhalten dann die Nummern von 14 bis 19 . Alternativ kannst du ein Schieberegister oder einen Portexpander verwenden.

Aufgaben:

  1. Welche Werte nimmt die Variable sensorValue an? Teste, indem du ein Patchkabel mit Port2 und wahlweise +5V oder GND verbindest.
  2. Was passiert, wenn du das in Port2 eingesteckte Kabel in der Luft hängen lässt und dabei ggf. bewegst?
    Bemerkung: Um das auftrende, störende Verhalten zu beseitigen verwendet man pull-down-Widerstände.

Analoge Werte erfassen

Der Mikrocontroller hat einen Analog-Digitalwandler an Board. Werte, die du auf den analogen Ports (z.B. A0) einliest, werden in digitale Werte gewandelt. Die Auflösung beträgt dabei 10 bit, d.h. die Messwerte liegen zwischen 0 und 1023:

Beachte: Die analogen Ports werden nicht als Ein- bzw. Ausgang definiert!

Baue eine Spannungsteilerschaltung mittels Potentiometer (veränderbarer Widerstand) so auf, dass du das Potenzial an A0 variieren kannst.

const int analogInPin = A0; // Analog input pin
int sensorValue = 0; // value read

void setup() {
  // initialize serial communications at 9600 bps:
  Serial.begin(9600); }

void loop() {
  // Lese den analogen Wert ein
  sensorValue = analogRead(analogInPin);   
// Gib den Wert mittels Serial Monitor aus

  Serial.print("sensor = " );   Serial.print(sensorValue);   
// Warte 0,5 Sekunden

  delay(500); }


Aufgaben:

  1. Neu ist in diesem Beispiel der Vorspann: Hier werden Variablen deklariert (und initialisiert). Die Festlegung analogInPin = A0 könnte man sich sparen. Welchen Zweck erfüllt sie?
  2. Welcher Wert wird gemessen, wenn du das Sensorkabel mit GND bzw. 5 V verbindest? Was passiert, wenn du es in der Luft hängen lässt und dabei ggf. bewegst?

 Beispiel: Helligkeit mittels LDR messen

Ein LDR (light dependent resistor = Fotowiderstand) ist ein Bauteil, dessen Widerstand sich mit der Helligkeit ändert.

ldr

Aufgaben:

  1. Nimm den LDR in die Hand und messe seinen den Widerstand abgedunkelt und in direktem Licht. Notiere deine Messwerte.
  2. Baue nun eine Spannungsteilerschaltung auf, wobei du den zweiten Widerstand etwa gleichgroß wie den LDR-Widerstand wählst. Welche Werte misst du in A0? Benutze den Serial Monitor, um sie auszugeben.
  3. Für Schnelle: Benutze die Methode map(), um die gemessenen Werte in den Widerstand umzurechnen.

Beispiel: Temperatur mittels NTC messen

analogRead_ntc

Ein NTC (negative temperature coefficient thermistor = Heißleiter) ist ein Bauteil, dessen Widerstand sich mit zunehmender Temperatur kleiner wird.

Aufgaben:

  1. Nimm den NTC in die Hand und messe seinen Widerstand bei Raumtemperatur und in der warmen Hand. Wie ändert sich sein Widerstand mit der Temperatur? Bilde einen "Je ..., desto ..." Satz.
  2. Baue nun eine Spannungsteilerschaltung auf, wobei du den zweiten Widerstand etwa gleichgroß wie den NTC-Widerstand wählst. Welche Werte misst du in A0? Benutze den Serial Monitor, um sie auszugeben.
  3. Für Schnelle: Benutze die Methode map(), um die gemessenen Werte in die Temperatur umzurechnen. Wie könntest du dein Thermometer eichen?

Tipp: Später lernst du den Sensor LM35DZ kennen, der dir direkt die Temperatur in °Celsius liefert.

Töne mittels Lautsprecher ausgeben

Du kannst einen Lautsprecher ohne Transistor direkt am Port betreiben. In diesem Beispiel ändert sich die Tonhöhe mit dem  Lichteinfall:

lautsprecher

Code:

// Original von Tom Igoe, siehe http://arduino.cc/en/Tutorial/Tone2
const int ldrPin = A0;
const int lautsprecher = 9;
void setup() {
  Serial.begin(9600);
}
void loop() {
  int sensorReading = analogRead(ldrPin);
  Serial.println(sensorReading);
  // auf die Ausgabefrequenz zwischen 120 und 1500 Hz mappen
  int tonHoehe = map(sensorReading, 400, 1000, 120, 1500);
  tone(lautsprecher, tonHoehe, 10);
  delay(1);   // benutze "noTone(lautsprecher);", um die Tonausgabe zu unterbrechen
}

Bemerkung: Verwende eine Transistorschaltung (z.B. den BD437), um ein größere Lautstärke zu erzielen und ein Potentiometer, um die Lautstärke regeln zu können.

Lautsprecher mit Verstärkung

Background: Welche Arten von Speicher gibt es?

Die Arduinos besitzen drei Arten von Speicher:

Beachte: Die Daten im flash-memory und im eeprom bleiben auch ohne Batterie erhalten, die Daten im SRAM sind dagegen verloren. Wenn dir während der Laufzeit das SRAM zur Neige geht, kann es sein, dass das Programm sich unerwartet beendet. Abhilfe kannst du schaffen, indem du z.B. eher byte als int als Variablentyp wählst (du sparst damit ein Byte) oder indem du Daten ins flash-memory auslagerst, siehe PROGMEM.

Watch dog timer

Es gibt Situationen in denen sich der µC "aufhängt", z.B. wenn millis() nach 49 Tagen einen overflow produziert. Dafür gibt es den "Rettungshund" wdt: Kommt zu lange kein Lebenszeichen, so wird ein Interrupt ausgelöst, der einen Reset des Controllers bewirkt. Der watch-dog-timer muss jedoch explizit gestartet werden:

--->Lücke<---

Kontrollstrukturen

Entscheidungen mit if()

Schließe einen Push-Button ("Schließer") von 5 V aus an den digitalen Port 2 an und von dort mit einem 10 kΩ - pull-down-Widerstand an Masse. 

pull_down_digital

/*
  Quelle http://www.arduino.cc/en/Tutorial/Button
 */

const int buttonPin = 2; // Port, an den der Push-Button angeschlossen wird
const int ledPin = 13; // Port, an den die LED angeschlossen ist
int buttonState = 0; // Variable, die den Zustand des Push-Buttons speichert

void setup() {
  // Lege den lED-Port als Ausgang fest:
  pinMode(ledPin, OUTPUT);  
// der buttonPin-Port wird als Eingang festgelegt:

  pinMode(buttonPin, INPUT); }

void loop(){
  // Der Zustand des Schalters wird ausgelesen und gespeichert:
  buttonState = digitalRead(buttonPin);
  // Prüfe, ob der Schalter gedrückt ist.
  // Wenn ja, so ist buttonState high und
  if (buttonState == HIGH) {    
// die LED wird eingeschaltet:

    digitalWrite(ledPin, HIGH);   } 
  else {
    // ansonsten wird sie ausgeschaltet:
    digitalWrite(ledPin, LOW);   }
}

Aufgaben:

  1. Wie kannst du das Programm möglichst einfach (!) verändern, damit die LED leuchtet, wenn der Button nicht gedrückt ist?
  2. Was passiert, wenn du in der if-Abfrage anstelle von if(buttonState == HIGH) einfach nur if(true) eingibst?

Welches sind die wichtigsten Vergleichsoperatoren?

Überprüft man die Aussage "a = b" so kann die Antwort nur true oder false lauten. Bei den meisten Programmiersprachen muss man den Vergleichsoperator mit zwei Gleichheitszeichen schreiben:

a == b überprüft, ob a = b ist
a != b überprüft, ob a ungleich b ist.
a > b überprüft, ob a echt größer als b ist
a <= b überprüft, ob a kleiner oder gleich b ist

Aufgaben:

  1. Baue die Schaltung zur Temperaturmessung auf und ergänze eine rote und eine grüne LED (Vorwiderstand nicht vergessen!). Benutze die if/else-Entscheidung und einen passenden Operator, um folgende Temperaturüberwachung zu realisieren: Ist die Temperatur unter einer von dir festzulegenden Schwelle, soll die grüne LED leuchten, darüber die rote.
  2. Für Schnelle: Benutze eine Zweifarb-LED.

Wiederholungen mit while()

Bislang haben wir lediglich die Endloswiederholung mittels loop() benutzt. Nun wollen wir eine while()-Schleife benutzen, um eine  LED nach dem Einschalten genau 27 mal blinken lassen. Du brauchst keine neue Schaltung aufzubauen, da wir die integrierte LED an Port13 nutzen.


int i = 27;
int pausenZeit = 500;
void setup() {  
pinMode(13, OUTPUT);
  while (i>0){
    i--; // i wird um 1 erniedrigt, kurz für i = i-1;
    digitalWrite(13, HIGH);
    delay(pausenZeit);
    digitalWrite(13, LOW);
    delay(pausenZeit);
  }

void loop() { }

Aufgaben:

  1. Wie musst du das Programm verändern, damit du anstelle des Dekrements i-- das Inkrement i++ (entspricht i = i + 1) verwenden kannst?
  2. Benutze die for()-Schleife und schreibe damit ein Programm, um die LED ebenfalls genau 27 mal leuchten zu lassen.

Wiederholungen mit for()

Eine Leuchtdiode an Port 13 soll genau 27 mal blinken. Danach soll nichts mehr passieren.

const int ledPin = 13;
int pausenZeit = 500;
void setup() {
  pinMode(ledPin, OUTPUT);
  for (int i = 0; i < 27; i++) {
    digitalWrite(ledPin, HIGH);
    delay(pausenZeit);
    digitalWrite(ledPin, LOW);
    delay(pausenZeit);
  }
}
void loop() { }

Aufgabe:

  1. Wie ist der Code zu ändern, damit die Pause nach jedem Blinken um eine halbe Sekunde zunimmt?

Arrays (eindimensional)

Wenn du ein Lauflicht aus z.B. 5 LEDs basteln möchtest, so könntest du jede nacheinander ein- und wieder ausschalten. Bei vielen LEDs wird das aber sehr umständlich. Wir verwenden in diesem Beispiel Arrays, um das Problem elegant zu lösen.

led lauflicht fritzing

Ein array kannst du dir wie einen Schuladenkasten vorstellen. Die Schuladen sind mit der 0 beginnend nummeriert, hier fünf Schubladen von 0 - 4. Wie bei Variablen üblich musst du vorher festlegen, was in die Schublade reingesteckt werden darf, d.h. den Datentyp angeben (hier int).

int meineLEDs[5]; // ein Schubladenkasten der Länge fünf, also mit Schubladen von 0 bis 4 wird angelegen 
void setup() {
  // Das array füllen:
  for (int i = 0; i < 5; i++) {
    // Die 5 LEDs hängen an den Ports 2 bis 6:
    meineLEDs[i] = i + 2; // meineLEDs[0]=2, meineLEDs[1]=3, ..., meineLEDs[4]=6
  }
  Serial.begin(9600);
  // LED-Ports 2 bis 6 sind Ausgänge:
  for (int i = 0; i < 5; i++) {
    pinMode(meineLEDs[i], OUTPUT);
  }
}
void loop() {
  // LED-Lauflicht:
  for (int i = 0; i < 5; i++) {
    digitalWrite(meineLEDs[i], HIGH);
    delay(1000);
    Serial.print("Jetzt leuchtet die LED an Port Nr. "); Serial.println(meineLEDs[i]);
    digitalWrite(meineLEDs[i], LOW);
  }
}

Die Ausgabe am serial monitor:

array ausgabe serial port

Achtung: Anders als wenn du z.B. in Java programmierst bekommst du in C (und damit auch beim Arduino) keine Fehlermeldung des Compilers, wenn du versuchst auf eine "Schublade" zuzugreifen, die es gar nicht gibt. Versucht man im obigen Beispiel auf die nicht vorhandene "6. Schublade" zuzugreifen, so wird ein zufälliger Wert zurückgeliefert:

invalid array

Spezielle Bauteile und Bibliotheken

7-Segment-Anzeigen

werden z.B. im Fahrstuhl zur Anzeige des Stockwerkes benutzt. Sie bestehen aus 7 (bzw. 8) LED's. Es gibt Modelle mit gemeinsamer Anode, z.B. SA52-11 (Datenblatt von Kingbright), und Modelle mit gemeinsamer Kathode, z.B. SC52-11. Die Verdrahtung ist zum Glück bis auf die Vertauschung von +5V und Masse identisch.

sa52-11sa-52-pinbelegung

Alternative 1: Direkter Anschluss an 7 (bzw. 8) Ports

Die einfachste Variante zum Anschluss einer 7-Segment-Anzeige ist die direkte Verkabelung der einzelnen LED's. Geeignete Vorwiderstände müssen verwendet werden:

7-Segment-parallel

Alternative 2: Verwendung eines Schieberegisters (74HC595)

Besonders wenn mehrere 7-Segment-Anzeigen angeschlossen werden sollen, werden die Ports sehr schnell knapp. Abhilfe schafft ein "shift register": Das 74HC595 (Datenblatt von Philips) hat einen seriellen Eingang (DS), 8 parallele Ausgänge (Q0 bis Q7) und einen seriellen Ausgang (Q7'). Letzteren kannst du zur Kaskadierung von Anzeigen nutzen. 

Man schickt Bit für Bit Information vom µC an das Schieberegister, welches jedes Bit bei einem neu eintreffenden um eine Position weiter schiebt. Arduinos kennen zum Glück ShiftOut(), damit wird ein Byte an Information, z.B. 11001100, an den IC gesendet und man benötigt dazu lediglich drei Ports. Der 74HC595 gibt diese Information an seinen 8 parallelen Ausgängen aus, d.h. er kann 8 Ports steuern!

Achtung: In der Grafik wurde zwecks Übersichtlichkeit auf die passenden Vorwiderstände verzichtet!

schieberegister


Pinbelegung Ablauf
74HC595_pinbelegung
1 - 7
und
15
Datenausgang parallel
8
0 V
9 Datenausgang seriell
10 master-reset: dauerhaft auf HIGH setzen
11 clock-pin (Takteingang)
12 latch-pin (beim Datenempfang auf LOW setzen)
13 output-enable (auf LOW setzen)
14 serial data input (an einen digitalen Ausgang des Arduinos anschließen)
16 + 5V
  1. latch-pin 12 auf LOW setzen.
  2. Daten an Pin 14 senden (1 Byte).
  3. Taktsignal an Pin 11 togglen (HIGH und wieder LOW schalten).
  4. latch-pin 12 auf HIGH setzen.

Wir benutzen shiftOut(dataPin, clockPin, bitOrder, value), um ein Byte an Daten an den 74HC595 zu übertragen und den clock-pin zu togglen. Zur Syntax: Bei der bitOrder muss festgelegt werden, wie die Binärzahl codiert ist: Bei uns ist üblicherweise das unwichtigste Bit (20 = 1) rechts, d.h. wir müssen MSBFIRST (most significant bit first) als bitOrder wählen.

/*
  Schieberegister 74HC595
*/
const int clockPin = 12; // toggeln während der Datenübertragung
const int latchPin = 8; // vor dem Senden der Daten an den IC auf LOW ziehen, danach wieder HIGH
const int dataPin = 11; // an den seriellen Eingang des IC; jeweils 1 Byte übertragen
void setup() {
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  
}
void loop() {
  digitalWrite(latchPin, LOW);
  // Datenübertragung: das Byte wird von rechts 2^0 nach links 2^7 codiert (least significant bit first)
  shiftOut(dataPin, clockPin, MSBFIRST,255);
  digitalWrite(latchPin, HIGH);
}

Tipp: Diesen Baustein kannst du sehr gut zur Porterweiterung benutzen!

Bemerkung: Normalerweise sendet man immer ein Bit an Information an das Schieberegister und toggelt danach den clock-Pin. Die Methode shiftOut() macht im Prinzip genau das 8 mal hintereinander und überträgt damit insgesamt 1 Byte an Information.

Aufgaben:

  1. Schließe eine 7-Segment-Anzeige passend an die Datenausgänge des IC's an und teste das Programm.
  2. Erweitere das Programm so, dass du einen Countdown von 9 auf 0 darstellen kannst.
  3. Schaue in das ShiftOut-Tutorial, um zwei 7-Segment-Anzeigen kaskadiert zu betreiben.

Beispiel: Timer mit zwei Shiftregistern und einer 2x7-Segment-Anzeige

2x595<->7-segment

duo-7-seg_with_shift-register

Ein Timer (via Interrupt, siehe MsTimer2-Bibliothek) zählt die abgelaufene Zeit in Sekunden und wird durch Drücken des linken Schließers (= push button) gestartet und durch das Drücken des rechten Schließers gestoppt. Die Taster wurden mittels Bounce-Bibliothek entprellt (enprellen = debouncen). 

Der etwas umfangreichere Sketch zum Download: kaffee_timer_v3.

Alternative 3: Verwendung eines BCD-Decoders / 7-Segment-Drivers (74HC4511)

Für den Anschluss des 74HC4511 (Datenblatt von Philips) benötigst du vier Ports des µC: Binär codiert lassen sich alle Zahlen von 0 bis 9 durch 4 bit darstellen. Der Baustein selbst benötigt somit vier Eingänge für das halbe Byte an Information ("nibble") und 7 Ausgänge für die LED's a bis g (Benennung siehe oben):


BCD Eingang
Ausgang
Display
D
C
B
A
a
b
c
d
e
f
g
0
0
0
0
1
1
1
1
1
1
0
0
0
0
0
1
0
1
1
0
0
0
0
1
0
0
1
0
1
1
0
1
1
0
1
2
0
0
1
1
1
1
1
1
0
0
1
3
0
1
0
0
0
1
1
0
0
1
1
4
0
1
0
1
1
0
1
1
0
1
1
5
0
1
1
0
0
0
1
1
1
1
1
6
0
1
1
1
1
1
1
0
0
0
0
7
1
0
0
0
1
1
1
1
1
1
1
8
1
0
0
1
1
1
1
0
0
1
1
9



Pinbelegung Bemerkungen
4511_pinbelegung
1, 2
6, 7
BCD - Daten vom µC
3 Lamp Test (aktiv: LOW)
4 ripple Blanking Input (aktiv: LOW)
5 Latch Enable input (aktiv: LOW)
8
0 V
9 - 15
zur 7-Segment-Anzeige
16 +3 V bis +15 V
  • LT auf LOW ziehen, um alle Lampen einzuschalten. Wir setzen LT auf HIGH.
  • BI auf LOW ziehen, um alle Lampen auszuschalten. LT muss dazu HIGH sein. Wir setzen BI auf HIGH.
  • LE auf HIGH ziehen, um die Anzeige einzufrieren. Wir setzen LE auf LOW.

Danke an Mona und Nadine für den Aufbau der Schaltung (hier mit dem BS1 Project Board von Parallax):
Foto 4511 an 7Segment

Bemerkung: Du kannst einen Zählerbaustein, z.B. 4510, verwenden um weitere Ports des µC einzusparen.

Alternative 4: Verwendung des 7219 (Treiber für bis zu 8 7-Segment-Anzeigen)

Ohne Zweifel die eleganteste Methode einen Timer, einer Uhr oder ähnliches mittels 7-Segment-Anzeigen zu realisieren. Nachteil: der Chip kostet gut 10 € (z.B. bei Segor).

Alternative 5: Ansteuerung einer 4-fach-7-Segment-Anzeige (CL5642BH) mittels Multiplexing

Vorarbeit: Willst du eine vierstellige Zahl ausgeben, so musst du zunächst die einzelnen Stellen (Einer, Zehner, ...) extrahieren. Dies leistet der folgende Sketch, der lediglich die Einer, Zehner, Hunderter und Tausender einer vierstelligen Zahl auf dem serial monitor ausgibt.

/* Extrahiert die einzelnen Stellen einer vierstelligen Zahl*/
int einer;
int zehner;
int hunderter;
int tausender;
void setup() {
  Serial.begin(9600);
}
void loop() {
  for (int i = 0; i < 10000; i++) {
    Serial.println(i);
    einer = i % 10;
    zehner = (i / 10) % 10;
    hunderter = (i / 100) % 10;
    tausender = (i / 1000) % 10;
    Serial.print("Einer: ");
    Serial.print(einer);
    Serial.print(" Zehner: ");
    Serial.print(zehner);
    Serial.print(" Hunderter: ");
    Serial.print(hunderter);
    Serial.print(" Tausender: ");
    Serial.println(tausender);
    delay(50);
  }
}

 Das Ergebnis sieht dann so aus:

einer_zehner

Mit dieser Vorarbeit können wir nun die vier Stellen einer Zahl zwischen 0 und 9999 auf einem 4-fach-7-Segment-Display, z.B. dem CL5642BH, ausgeben. Um solch ein Display anzusteuern bräuchte man sehr viele Ports, nämlich 7 x 4 = 28 Stück, zzgl. 6 Ports für die Punkte.

Um Ports einzusparen steuern wir nun jede Stelle nacheinander an. Das geschieht so schnell, dass unser Auge dies nicht wahrnimmt.

7-seg-4-fach

Um z.B. die Zahl 83 auszugeben muss zuerst kurz DIG4 angesteuert werden, also 13 HIGH und 1, 2, 3, 4, 7 LOW (A, B, C, D und G) und 5, 6, 8 HIGH. Um die Zahl 8 an DIG3 auszugeben muss dann kurz 12 HIGH geschaltet werden und zeitgleich 1 - 7 LOW und 8 HIGH sein. Anschließend müssen jeweils DIG2 und DIG1 kurz angesteuert werden und zwar so, dass alle Ports 1 - 8 HIGH sind (die 7-Segment-Anzeigen haben eine gemeinsame Anode!).

7-seg-4fach-multiplexing

Bei einer Vorwärtsspannung von 2,1 V und einer maximalen Stromstärke von 20 mA brauchst du eigentlich einen Vorwiderstand von 145 Ohm pro LED. 

Lücke

Du kannst dir die Vorwiderstände sparen, indem du die LEDs via PWM ansteuerst, das erleichtert den Aufbau erheblich:

Lücke

Alternative 6: Verwendung eines Shields

--->Lücke<---

RS-Flipflop

Bei verschiedenen Sensoren ist es so, dass sie beim Eintreten eines Ereignisses (z.B. ein Klatschen) ein LOW-Signal kurz auf HIGH ziehen. Ist der µC gerade mit etwas anderem beschäftigt kann man das Signal zwischenspeichern indem man z.B. ein R-S-Flipflop verwendet (alternativ könnten man Interrupts verwenden). Hier benutzen wir das CD4043:

rs_flipfloprs_flipflop

Wir nutzen dabei nur ein Flipflop (der Baustein enthält 4 Stück), dessen Ausgang Q wir an Port 2 einlesen. Die Eingänge des Flipflops S für set und R für reset sind über einen Pull-Down-Widerstand auf low gezogen. Bei Drücken des jeweiligen Buttons werden R bzw. S high.

Beachte: Bei der Nutzung von CMOS-Bausteinen musst du alle nicht benutzten Eingänge auf GND legen!

Aufgaben:

  1. Schreibe ein Programm, welches den Zustand an Port 2 via Serial Monitor und über die eingebaute LED an Port13 anzeigt.
  2. Untersuche was passiert, wenn du zunächst einmal (oder mehrfach) den Button S drückst und anschließend den Button R.
  3. Ändere dein Programm nun so, dass du einen Klatschschalter (oder PIR, oder ...) anschließen kannst: Beim Auftreten eines Ereignisses soll Q high werden. Nach dem Einlesen dieses Signals soll das Flipflop vom µC resettet werden.

Lösung:

// Dank an Pascal und Julien!
const int Q = 2; // Ausgang Q des RS-Flipflops an den digitalen Port 2
const int ledPin = 13; // Port, an den die LED angeschlossen ist
void setup() {
  pinMode(ledPin, OUTPUT); // lED-Port ist Ausgang
  pinMode(Q, INPUT); //Port 2 ist Eingang
  Serial.begin(9600); // Serielle Schnittstelle bereitstellen
}
void loop(){
  if (digitalRead(Q) == HIGH) { // prüft, ob Q high ist
    Serial.println("Ich bin high"); // Ausgabe erfolgt am Serial Monitor
    digitalWrite(ledPin, HIGH);   }
  else {
    // ansonsten wird das ledPin ausgeschaltet:
    digitalWrite(ledPin, LOW);   }
}

I2C-Portexpander

In bestimmten Situationen reichen die vorhandenen Ports des µC nicht aus. Oben hast du die Möglichkeit kennen gelernt, mit einem Schieberegister Abhilfe zu schaffen. Hier verwenden wir den I2C-Bus, um den Port-Erweiterungs-IC PCF8574 (Datenblatt von ) zu nutzen:

--->Lücke<---

Anstelle dieses IC kannst du auch ein Mux-Shield (Multiplexer-Shield mit 48 I/O Ports) verwenden.

I2C-RTC (real time clock)

Zwar besitzen Arduinos die Möglichkeit Zeiten zu messen, z.B. mittels millis(). Es wird jedoch die Zeit gemessen, seit der µC eingeschaltet wurde. Ein RTC-Baustein hat den Vorteil, dass er sich auch bei ausgeschaltetem Board die Uhrzeit merken kann, da er eine separate Batterie (meist die Knopfzelle CR2032) besitzt.

Wir verwenden den Uhrenbaustein DS1307 von Maxim (pinkompatibel zum PCF8583, jedoch zusätzlich mit EEPROM und Alarmfunktion) auf einem Board von Sparkfun, um Uhrzeiten abfragen zu können. Vorweg kurz die Anschlüsse:

5 V werden zur Abfrage der Uhrzeit benötigt. Wenn hier keine 5 V anlegen verwendet der Baustein die Batterie im "Schlafzustand".
GND muss zwingend auf 0 V gesetzt werden.
SDA Daten-Pin des I2C-Bus  ist bei den meisten Arduinos mit dem analogen Pin 4, also A4 zu verbinden.
SCL Clock-Pin des I2C-Bus ist (meist) mit A5 zu verbinden.
SQW benötigen wir nicht. Der Pin kann verwendet werden um ein Rechtecksignal auszugeben.

Der Chip liefert immer 7 Byte an Information über den I2C-Bus, nämlich Datum und Uhrzeit - allerdings BCD-codiert. BCD bedeutet dabei binär codierte Dezimalzahl (Binary Coded Decimal). Was ist damit gemeint? Ganz einfach: Jede einzelne Ziffer wird binär codiert, damit wäre z.B. 27 = 27 = 0010.0111, also immer schön in Binärhäppchen :-). Beachte: 27 = 11011b wäre die Dezimalzahl, wenn man sie komplett binär codieren würde! 

Wir müssen, nachdem wir die Uhr einmal gestellt haben, die empfangenen Informationen noch passend decodieren. Glücklicherweise gibt es dazu Bibliotheken, z.B. von adafruit. Folgende Schritte sind notwendig:

  1. Lade die Library herunter.
  2. Entpacke sie in den Ordner ...\arduino\libraries.
  3. Benenne den Ordner in RTCLib um.
  4. Schließe die Uhr wie in der Tabelle beschrieben an und setze die Batterie ein.
  5. Lade den Code auf den Arduino hoch. Bemerkung: Die Uhr brauchst du nur 1x zu stellen, solange du die Batterie nicht herausnimmst.
// Wir verwenden die Uhr DS1307, angeschlossen über den I2C-Bus
// siehe http://learn.adafruit.com/ds1307-real-time-clock-breakout-board-kit/overview 
// Der Code von adafruit wurde dazu modifiziert
#include <Wire.h>
#include "RTClib.h"
RTC_DS1307 RTC;
void setup () {
  Serial.begin(57600);
  Wire.begin();
  RTC.begin();
  if (! RTC.isrunning()) {
    Serial.println("RTC is NOT running!");
    // following line sets the RTC to the date & time this sketch was compiled
    RTC.adjust(DateTime(__DATE__, __TIME__));
  }
}
void loop () {
  // Uhrzeit auslesen und in "now" speichern:
  DateTime now = RTC.now();
  // Datum und Uhrzeit auf dem serial monitor ausgeben:
  Serial.print(now.day(), DEC);
  Serial.print('.');   Serial.print(now.month(), DEC);
  Serial.print('.');
  Serial.print(now.year(), DEC);
  Serial.print(' ');
  Serial.print(now.hour(), DEC);
  Serial.print(':');
  Serial.print(now.minute(), DEC);
  Serial.print(':');
  Serial.print(now.second(), DEC);
  Serial.println();
  // Wecker 2 h in die Zukunft stellen:
  DateTime future (now.unixtime() + 2 * 3600L);
  // Bemerkung: siehe http://arduino.cc/en/Reference/IntegerConstants für das "L"
  // Weckzeit ausgeben:
  Serial.print(future.hour(), DEC);
  Serial.print(':');
  Serial.println(future.minute(), DEC);
  delay(3000);
}

Und so sieht das ganze dann aus:

LCD

Dank der LCD-Bibliothek ist es sehr einfach ein LCD mit dem Arduino zu verwenden: Die blauen Leitungen entsprechen den Datenleitungen  (4 im 4bit-Modus, sonst 8), rot/schwarz auf der linken Seite sind die Versorgungsspannung des Chips und rot/schwarz auf der rechten Seite dienen der LED-Hintergrundbeleuchtung. Das orangene Kabel dient zur Einstellung des Kontrastes, wobei das Potenzial am mittleren Pin eines 10 kΩ-Potis abgegriffen wird. Die beiden grünen Kabel (register select und enable) dienen der Kommunikation zwischen Arduino und LCD, das mittlere schwarze legt fest, dass wir das LCD nur zur Ausgabe ("Schreiben") benutzen wollen.

lcd

Tipp: Die Kontrasteinstellung soll eigentlich mit einem Potenzial zwischen 0 und 1,5 V erfolgen. Wenn man lediglich einen Poti verwendet, spielt sich die Kontrastveränderung in einem sehr kleinen Drehbereich ab. Zur Abhilfe kann ein großer Widerstand (z.B. 20 kΩ) zum Poti in Reihe geschaltet werden. Damit ist die Potenzialdifferenz am Poti selbst deutlich geringer.

Serielles LCD

Du kannst den Verkabelungsaufwand deutlich verringern, indem du ein serielles LCD verwendest: Verbinde den TX-Pin (t für transmit) des Arduinos mit dem RX-Pin (r für receive) des Displays. Beachte, dass manche Displays einen Jumper besitzen, den du ggf. noch setzen musst.

Für detailliertere Infos siehe http://playground.arduino.cc/Learning/SerialLCD, hier einfach ein Beispielcode, um Text darzustellen.

Code:

//  Copyleft 2006 by djmatic (verkürzt)
void setup(){
  Serial.begin(19200);
}

void
loop(){ 
  backlightOn(0);  // Hintergrundbeleuchtung dauerhaft aktivieren
  clearLCD();
  Serial.write("Herzlich");   cursorSet(0, 1);             
  Serial.write("Willkommen!");
  delay(1000);
}

// clear the LCD
void clearLCD(){
  Serial.write(12);
}

// Cursor setzen
void cursorSet(int xpos, int ypos){  
Serial
.write(254);
  Serial.write(71);   Serial.write(xpos); // Spalte
  Serial.write(ypos); // Zeile
}

// Kontrast einstellen
void setContrast(int contrast){
  Serial.write(254);  
Serial
.write(80);  
Serial
.write(contrast);
}

// Hintergrundbeleuchtung aktivieren
void backlightOn(int minutes){
  Serial.write(254);  
Serial
.write(66);  
Serial.write(minutes); // 0 für dauerhaft EIN
}

// Hintergrundbeleuchtung ausschalten
void backlightOff(){
  Serial.write(254);  
Serial.write(70);
}

Temperaturmessung mittels LM35DZ

Der Sensor LM35DZ liefert einen Messwert, den man sehr einfach in die Temperatur in °Celsius umrechnen kann.

lm35dz

Achtung: Im Datenblatt ist das Bauteil von unten gesehen (BOTTOM VIEW) abgebildet!

float tempMesswert;
float tempCelsius;
void setup()
{
  Serial.begin(9600); //opens serial port, sets data rate to 9600 bps
}
void loop()
{
  tempMesswert = analogRead(0);   
tempCelsius = (5.0 * tempMesswert * 100.0)/1024.0;  //Umrechnung in ° Celsius
  Serial.print((byte)tempCelsius);   
delay(1000);
}

Tipp zum Weiterschmökern: Messwerte mit Processing darstellen.

Lichtschranke mittels Reflexoptokoppler CNY70 (kurze Distanz)

Der Reflexoptokoppler CNY70 ist mit einer Infrarot-Sendediode (Wellenlänge 950 nm, d.h. dein Auge sieht das Licht nicht, wohl aber deine Handykamera) und einem Fototransistor als Empfänger ausgestattet. Dieses Bauteil kann man für viele verschiedene Dinge verwenden, z.B. als berührungsloser Sensor, ob eine Tür offen ist: Die Infrarotdiode sendet ein Licht aus und der Fototransistor detektiert, ob dieses reflektiert wurde. Man kann auch zwei von diesen Teilen verwenden, um einen Roboter eine Linie nachfahren zu lassen: siehe Liniensensor.

Hier wollen wir den CNY70 zweckentfremden und zwei Stück davon als Lichtschranke für kurze Distanzen (10 cm sind möglich) verwenden.

Vorweg Achtung: Je nach Hersteller sind die Anschlüsse verschieden: Vishay CNY70 bzw. Temic CNY70.

---> Lücke <---

Lichtschranke mittels TEMIC K153P (kurze Distanz)

Diese Lichtschranke besteht aus einer Infrarot-Sendediode (hellblau; Wellenlänge: 950 nm) und einem Fototransistor als Empfänger (schwarz). Der große Vorteil dieses Pärchens ist der Preis: bei Pollin kosten 10 Paare weniger als 1 €! Die Inbetriebnahme der Temic K153P ist einfach:

temic_k153p_schaltplan

Funktionsweise:

Die Infrarotdiode sendet ein Licht, das für unser Auge nicht sichtbar ist, aus. Solange kein Licht auf die Basis des Fototransistors trifft, sperrt dieser, d.h. am Port des µC liegt HIGH an.
Sobald jedoch Licht auf die Basis des Fototransistors trifft öffnet sich die C-E-Strecke und am Port liegt LOW an.

Nachteile: Der Fototranistor ist empfindlich für Streulicht. Somit sind nur sehr begrenzte Reichweiten möglich.

Aufgabe:

  1. Experimentiere mit verschiedenen Vorwiderständen (Poti verwenden) für den Fototransistor, um zu testen, welche Reichweite möglich ist.

Gabellichtschranke TCST2000 (CNY35)

Gabellichtschranke

Funktionsweise:

Die Infrarotdiode sendet ein Licht, das für unser Auge nicht sichtbar ist, aus. Mit dem Fotoapparat kannst du diese Licht jedoch sichtbar machen, siehe Abbildung oben. Solange kein Licht auf die Basis des Fototransistors trifft, sperrt dieser, d.h. am Port des µC liegt LOW an. Sobald jedoch Licht auf die Basis des Fototransistors trifft öffnet sich die C-E-Strecke und am Port liegt HIGH an.

Gabellichtschranke Abbildung    Gabellichtschranke Schema  

Schaltbild:

Gabellichtschranke Schaltbild

Achtung: Im Datenblatt des TCST2000 wird ein Pulldown-Widerstand von 100 Ohm am Transistor empfohlen. In der Praxis musste ich jedoch zwischen 500 Ohm und 10 kOhm verwenden.

Code:

int lichtSchranke = 2;

void setup() {
  Serial.begin(9600);
  pinMode(lichtSchranke, INPUT);
}

void loop() {
  Serial.println(digitalRead(lichtSchranke));
  delay(1);
}

Abstandssensor GP2 Y0A02 YK0F (mittlere Distanz)

Der Sharp GP2Y0A02YK0F ist z.B. bei Segor günstig erhältlich und deckt eine Reichweite von 20 cm - 150 cm ab. Alternativ gibt es noch einen deutlich teureren Sensor (Sharp GP2 Y0A710 K0F), der bis zu 550 cm Entfernungen misst.

Tipp: In jedem Fall die passenden JST-3-Pin-Stecker bzw. Verbindungskabel mitbestellen.

Der große Vorteil gegenüber den oben verwendeten Lichtschranken ist, dass bei diesem Modell das gesendete Infrarotlicht moduliert wird, d.h. es ist nicht störanfällig für Streustrahlung. Verwendet werden solche Sensoren z.B. bei Urinalen, in der Robotik, ...

Der Ausgang des Sensors liefert ein Potenzial zwischen 0 V und 5 V und wird an einen analogen Port angeschlossen. Am analogen Port haben wir eine 10 bit Auflösung, d.h. (210=1024 verschiedene) Messwerte von 0 bis 1023 sind möglich. Nun muss man den Messwert noch in die Entfernung zurückrechnen. Dazu muss kann man die Kurve im Datenblatt durch eine Hyperbel annähern (noch besser wäre evtl. eine Exponentialfunktion). Bei mir klappt es ganz gut mit folgender Formel:


const int irPin = 1; // analoger Eingang für den Abstandssensor
float distance = 0; // Objektentfernung
void setup() {
  Serial.begin(9600); // serielle Schnittstelle bereitstellen
}
void loop() { 
distance = 10650 * pow(analogRead(irPin),-0.935)-15; // Achtung: Sensor eichen!
  Serial.println(distance);
delay(500);
}

Wichtig: die Zahlen 10650 und -15 sollten dem Sensor angepasst werden, d.h. man stellt ein Hinderniss in 50 cm und 100 cm Entfernung und ändert die Zahl so lange, bis man eine bestmögliche Übereinstimmung hat. Klar sollte sein, dass der Sensor nur in den vorgesehenen Distanzen ein gutes Messergebnis liefert. Möchte man dennoch kleinere Entfernungen damit messen, so müsste man die Abstandsformel ändern oder besser einen für diesen Messbereich ausgelegten Sensor verwenden.

sharp infrared

Bemerkung: Abgebildet ist ein halbes "screw-shield". Diese Dinger sind sehr praktisch, da man Sensorkabel anschrauben und außerdem weitere Shields aufstecken kann.

Ultraschall-Entfernungsmesser (Parallax Ping: 2 cm bis 3m)

Eigentlich gibt es günstigere Ultraschall-Entfernungsmesser als den PING))) von Parallax (Datenblatt). Da wir dieses Bauteil jedoch ohnehin vorrätig haben hier ein Beispiel:
Die Entfernung zu einem Gegenstand (Segel auf Modellauto) wird gemessen. Wenn diese kleiner als 50 cm wird, soll  eine LED leuchten und ein Elektromagnet eine Kugel fallen lassen. Der Elektromagnet wird via Transistor (BD437) angeschlossen, da er eine hohe Stromstärke benötigt.

Warnung: Wird der PING-Sensor versehentlich mit 9 V verbunden geht er kaputt. Zu erkennen ist dies am verschmorten Aufkleber auf dem IC (Foto folgt).

Code:

// Ursprünglich Idee von David A. Mellis, Tom Igoe 
// siehe http://www.arduino.cc/en/Tutorial/Ping
#define magnetPin 2
#define pingPin 7
#define ledPin 13
long duration, cm;

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
  pinMode(magnetPin, OUTPUT); }

void loop(){   // der Port pingPin wird als Ausgang geschaltet und ein 5 ms HIGH-Impuls ausgegeben.
  pinMode(pingPin, OUTPUT);
  digitalWrite(pingPin, LOW);
  delayMicroseconds(2);
  digitalWrite(pingPin, HIGH);
  delayMicroseconds(5);
  digitalWrite(pingPin, LOW);
  // nun wird der pingPin Port als Eingang geschaltet und gemessen, wie lange es dauert, bis das Eche eintrifft.
  pinMode(pingPin, INPUT);
  duration = pulseIn(pingPin, HIGH);
  // Zeit in Entfernung umrechnen:
  cm = duration / 29 / 2; // Schallgeschwindigkeit 29 cm/s; halbe Strecke
  Serial.print(cm);
  Serial.println("cm");
  if(cm < 50){
    digitalWrite(ledPin, HIGH);
    digitalWrite(magnetPin, LOW);
  }
  else{
    digitalWrite(ledPin, LOW);
    digitalWrite(magnetPin , HIGH);
  }
  delay(100);
}

Ultraschall-Entfernungsmesser (HC-SR04: 2 cm bis 4 m)

Deutlich günstiger als der PING-Sensor von Parallax ist der HC-SR04 von Cytron Technologies. Er benötigt ein 10 µs langes Triggersignal, woraufhin er acht 40 kHz Impulse aussendet und misst, wie lange es bis zum Eintreffen des Echos benötigt (150 µS bis 25.000 µs). Bei fehlendem Echo werden 38.000 µs zurückgemeldet. Diese Zeit wird über den Echo-Pin ausgegeben und zwar in Form eines Impulses, dessen Weite gerade der Signallaufzeit (in µs und für hin & zurück) entspricht. Die Entfernung (in cm) berechnet sich aus der Signallaufzeit in µs durch:

s = 0.5 * t * v = 0.5 * 340 m/s * t = 0.017 * t [s in cm, t in µs]

Es gibt zwar die Ultrasonic-Bibliothek, aber der Code ist auch schnell selbst geschrieben:

Code:

# define echoPin 6
# define trigPin 7
long distance;

void setup(){
  pinMode(echoPin,INPUT);
  pinMode(trigPin,OUTPUT);
  Serial.begin(9600);
}

void loop(){
  digitalWrite(trigPin,HIGH);
  delayMicroseconds(1000);
  digitalWrite(trigPin,LOW);
  distance = pulseIn(echoPin,HIGH)*0.017;
  Serial.print(distance);
  Serial.println(" cm");
}

Getriebemotor mit dem L293D ansteuern

Einen Getriebemotor könntest du, wie einen Lautsprecher, über eine einfache Transistorschaltung ansteuern (du bräuchtest aber eine zusätzliche Diode, damit die Induktionsspannung beim Abschalten den Transistor nicht zerstört). Oft möchte man jedoch die Drehrichtung des Motors ändern und dann ist es ganz praktisch, wenn man einen speziellen Motortreiber-IC verwendet. Hier stelle ich euch den L293D vor, mit dem man zwei Motoren ansteuern kann. Man benötigt dann auch keine Schutzdiode mehr, denn diese ist bereits integriert.

L293D Pinbelegung
VCC1 Versorgungsspannung für den IC, d.h. die 5 V vom Arduino.
VCC2 Versorgungsspannung für den Motor an 1Y/2Y bzw. 3Y/4Y. Hier kannst du z.B. externe Akkus anschließen.
1,2EN Setze diesen PIN auf HIGH, damit der Treiberbaustein aktiv ist. LOW, um den Motor abzuschalten. Du kannst diesen Enable-Pin auch dauerhaft mit 5 V verbinden.
1A, 2A Setze 1A auf LOW und 2A auf HIGH, damit sich der Motor in eine Richtung dreht und umgekehrt, damit er sich in die andere Richtung dreht.
Beide auf LOW (oder HIGH), damit der Motor steht.
1Y, 2Y Hier wird jeweils das Signal von 1A bzw. 2A verstärkt ausgegeben.

An 3,4EN, 3A, 3Y, 4A, 4Y brauchst du nichts anschließen.

Code:

# define motorPin1 2
# define motorPin2 7

void setup() {
  pinMode(motorPin1, OUTPUT);
  pinMode(motorPin2, OUTPUT);
}
void loop() {
  digitalWrite(motorPin1, HIGH);
  digitalWrite(motorPin2, LOW);
  delay(1000);
  digitalWrite(motorPin1, LOW);
  digitalWrite(motorPin2, HIGH);
  delay(1000);
  digitalWrite(motorPin1, LOW);
  digitalWrite(motorPin2, LOW);
  delay(4000);
}
L293D

Achtung: Bei stärkeren Motoren ist eine externe Stromversorgung zu verwenden (wichtig: GND jeweils verbinden). Beträgt der Strom pro Motor 0,6 A oder mehr muss ein größerer Motortreiber verwendet werden, z.B. L298 (max. 2 A je Motor). Auf mikrocontroller.net gibt es eine schöne Übersicht von H-Brücken.

Geschwindigkeit eines Getriebemotors regulieren

Du kannst die Winkelgeschwindigkeit eines Getriebemotors elegant regeln, indem du den Enable-PIN der H-Brücke L293D an einen PWM-Port anschließt und damit den Motor (via PWM) immer ein- und ausschaltest. Das Ein- und Ausschalten des Motors erledigt der Arduino elegant mittels analogWrite (Pulsweitenmodulation):

Code:

const int motorPin1 = 2;
const int motorPin2 = 7;
const int enablePin = 9;
int speed = 127; // Geschwindigkeit 50%, max. 255

void setup() {
  pinMode(motorPin1, OUTPUT);
  pinMode(motorPin2, OUTPUT);
}
void loop() {
  analogWrite(enablePin, speed);  
digitalWrite
(motorPin1, HIGH); digitalWrite(motorPin2, LOW);
  delay(1000);
  digitalWrite(motorPin1, LOW); digitalWrite(motorPin2, HIGH);
  delay(1000);
  analogWrite(enablePin, 0); // Motor aus
  delay(4000);
}

Servomotor von Hand ansteuern

Servomotoren werden z.B. bei RC-Cars für die Lenkung eingesetzt. Sie bestehen aus der Steuerelektronik und einem Gleichstrommotor in einem Gehäuse. Über einen Poti misst die Elektronik den Drehwinkel des Motors und regelt ggf. nach. So schaut der Winzling geöffnet aus:

xxx Abb. geöffneter Servo xxx

Es gibt Standardservos (Drehbereich 0° - 180°) und kontinuierliche Servos (z.B. von Parallax). Beide haben drei Anschlüsse:

Foto Servo

An die Signalleitung gibt man nun zwischen einer Millisekunden und 2 ms HIGH aus, gefolgt von ca. 20 ms LOW. Gibt man für 1 ms HIGH und anschließend LOW aus, so dreht sich der Motor auf 0° (gegen den Uhrzeigersinn). Gibt man 2 ms HIGH und anschließend LOW aus, so dreht sich der Motor in die 180°-Position:

Servo pwm

Achtung: Du musst dem Motor etwas Zeit lassen, sich einzustellen. Möchte man zu schnell von der 180° Position zurück zu 0°, so schafft der Motor es unter Umständen nicht rechtzeitig.

Code:

const int servoPin = 10;
void setup() {
  pinMode(servoPin, OUTPUT);
}
void loop() {
  for(int winkel=0; winkel<180; winkel++){
    servoAngle(winkel);  
  }  
  for(int winkel=180; winkel>0; winkel--){
    servoAngle(winkel);  
  }  
}
void servoAngle(int angle){
  digitalWrite(servoPin, HIGH);
  angle = map(angle,0,180,500,2500); // Winkel in Zeit umrechnen, siehe Bemerkung!
  delayMicroseconds(angle);
  digitalWrite(servoPin, LOW);
  delayMicroseconds(15000);
}
Bemerkung: Eigentlich müsste das HIGH-Signal zwischen 1000 µs und 2000 µs lang sein. In meinen Tests ergab sich jedoch, dass das Signal zwischen 500 µs und 2.500 µs lang sein muss, damit die volle Winkelweite von 180° überstrichen wird. Die Ursache für dieses Verhalten ist mir unklar.

Servomotor unter Verwendung der Bibliothek ansteuern

Einfacher wird es, wenn du die vorhandene Servo-Bibliothek nutzt. Du musst für jeden Servo lediglich noch angeben, an welchem Port er angeschlossen ist.

Code:

#include <Servo.h> 
Servo
meinServo;

void setup(){  
meinServo.attach(10); // Servo an Pin 10
}

void loop() {  
for(int i = 0; i<180; i++){
    meinServo.write(i);    
delay(19);  
}
  for(int i = 180; i>0; i--){
    meinServo.write(i);    
delay
(19);  

}

Bipolaren Stepper mittels L293D ansteuern

Es gibt unterschiedliche Typen von Schrittmotoren, die z.B. in Festplatten, Druckern, Scannern ... eingesetzt werden. Wir betrachten hier lediglich den bipolaren Typ mit vier Anschlüssen. Zahlreiche Infos über weitere Typen findest du im Roboternetz unter Schrittmotoren.

Zunächst musst du mit einem Multimeter herausfinden, welche beiden Anschlüsse jeweils zu einer Spule gehören (der Widerstand ist unendlich, wenn du die falschen Kabel erwischst).

Im nächsten Schritt benutzt du den bereits oben verwendeten Motortreiberbaustein L293D um die Spulen des Schrittmotors anzusteuern. Der Baustein ist nötig, da die Stromstärke durch die Ports alleine zu gering wäre.

Schließe den Schrittmotor wie beschrieben und abgebildet an:

l293d_stepper
VCC1 Versorgungsspannung für den IC, d.h. die 5 V vom Arduino.
VCC2 Versorgungsspannung für den Motor (kann höher gewählt werden).
1,2EN
3,4EN
Setze diese PINs auf HIGH, damit der Treiberbaustein aktiv ist.
1A,2A
3A,4A
Vom Mikrocontroller.
1Y, 2Y
3Y, 4Y
Zu den Spulen des Schrittmotors

L293D Stepper

Wir benutzen ein Beispiel von Tom Igoe aus den offiziellen Arduino-Beispielen, um den Motor eine Drehung durchführen zu lassen. Dazu musst du noch herausfinden, wie viele Schritte einer Umdrehung entspricht. Oft sind es wie hier 200.

Code:

#include <Stepper.h>

const int stepsPerRevolution = 200; // Anzahl Schritte pro Umdrehung

// Motortreiber an die Pins 8-11 anschließen:
Stepper myStepper(stepsPerRevolution, 8, 9, 10, 11);

void setup() {
  myStepper.setSpeed(60); // 60 U/Min.
  Serial.begin(9600);
}

void loop() {
  Serial.println("clockwise"); // Drehrichtung: Uhrzeigersinn
  myStepper.step(stepsPerRevolution);
  delay(500);

  Serial.println("counterclockwise"); // Drehrichtung: gegen den Uhrzeigersinn
  myStepper.step(-stepsPerRevolution);
  delay(500);
}

Debouncen

Wenn man das Schließen eines Schalters detektieren möchte steht  man vor dem Problem, dass der Schließvorgang nicht ideal (rot gezeichnet) verläuft. Der Übergang von Masse auf 5 V (und umgekehrt) sieht im Gegenteil so aus, dass der Taster prellt (schwarz dargestellt):

bounce

Es gibt prinzipiell zwei Möglichkeiten, den gewünschten Übergang sauber zu detektieren:

Variante A: mittels Bounce-Bibliothek

Per Software sorgt man dafür, dass der Übergang nur dann detektiert wird, wenn mehrere Millisekunden durchgehend das neue Potenzial anliegt. Du kannst selbst das Debounce-Beispiel nachbasteln oder gleich die passende Bounce-Bibliothek verwenden:

--->Lücke<---

Ein umfangreicheres Beispiel unter Verwendung dieser Bibliothek findest du beim Bau des Kaffee-Timers (Schieberegister mit Push-Buttons).

Variante B: Verwendung eines RC-Gliedes

Möchte man einen Taster, der einen Interrupt auslöst, entprellen, ist es ratsam per Hardware zu debouncen. Dies wird mit einem sogenannten RC-Glied (weil es aus einem Widerstand und einem Kondensator besteht) erledigt:

rc-glied

Hier steckt etwas Physik dahinter: Wenn der Schalter länger geöffnet war, wurde der Kondensator C zunächst über die Widerstände R1 und R2 geladen und beim Eingang des Schmitt-Triggers (z.B. NAND-Gatter CD4093) liegt damit "high" an, da die obere Platte des Kondensators auf 5 V liegt. Da der invertierende Schmitt-Trigger das Signal umdreht detektiert der µC "low".
Schließt man nun den Schalter, entlädt sich nun der Kondensator - wie schnell das geht, hängt neben dem Kondensator selbst vom Widerstand R2 ab. Man muss diese nun so wählen, dass der Lade- bzw. Entladevorgang deutlich länger als der Prellvorgang (ca. 10 ms) dauert. Wähle z.B. R1 = 10 kΩ, R2 = 22 kΩ und C = 1 µF.


Bemerkungen: 

  1. Aufgrund der Invertierung muss ein pull-up-Widerstand anstelle des pull-down-Widerstandes verwendet werden.
  2. Du kannst auch auf R2 verzichten und dann R1 = 10 kΩ und C = xx µF wählen.

Link-Tipp: Auf mikrocontroller.net/articles/Entprellung ist das obige Beispiel detaillierter erklärt. Außerdem kannst du nachlesen, wie man mittels RS-Flipflop entprellen kann.

Exkurs: Der Schmitt-Trigger

hat einen Eingang und einen Ausgang. Du kannst ihn dir als elektrischen Schalter vorstellen: Bei Überschreiten einer bestimmten Schwelle Uhigh wird sein Ausgang so lange auf high gezogen, bis eine anderen Schwelle Ulow unterschritten wird. Erst dann wird der Ausgang so lange auf low gezogen, bis wieder die obere Schwelle Uhigh überschritten wird. Der Schmitt-Trigger hat bewusst diese Hysterese. In der Abbildung ist das Schaltverhalten des Schmitt-Triggers grün dargestellt.

schmitt-trigger

Aufgabe:

  1. Beschreibe das Schaltverhalten des organge abgebildeten Komparators im Vergleich zum Schmitt-Trigger.

Interrupts

Bei einem Interrupt wird durch ein Ereignis (z.B. Timer, Signal von außen, ...) das Hauptprogramm loop() unterbrochen und ein vorab festgelegtes Unterprogramm ausgeführt. Nach Abarbeitung des Unterprogrammes wird das Hauptprogramm an der ursprünglichen Stelle weiter ausgeführt.
Unser Arduino-Board kann zwei Interrupts erfassen: Interrupt0 am digitalen Port2 und Interrupt1 an Port D3. Man muss dem µC allerdings zunächst mitteilen, dass er auf Interrupts reagieren soll und welches Unterprogramm er beim Auftreten eines Interrupts ausführen soll. Dies erledigt man im setup() mit der Methode attachInterrupt(interrupt, function, mode).

int klatschSensor = 2; // Klatschschalter, z.B. von Parallax, an Port D2 anschließen
int ledPin = 13;

volatile int zustand = LOW; /* Bemerkung: Das keyword "volatile" ist eine Anweisung für den Compiler:
Wird eine Variable so deklariert, wird sie aus dem RAM und nicht aus Registern zu laden. */

void setup()
{
  pinMode(klatschSensor, INPUT);
  pinMode(ledPin, OUTPUT);
  /*
  Hier wird der Interrupt aktiviert: Ändert sich das Signal am Pin2 (="CHANGE"),
   so wird das Hauptprogramm "loop()" unterbrochen und zustandAendern ausgeführt  
   */
  attachInterrupt(0, zustandAendern, CHANGE);
}
void loop()
{
  digitalWrite(ledPin, zustand);
}
void zustandAendern()
{
  zustand = !zustand; // logische Negation
}

Neben "CHANGE" sind folgende Modi möglich:

LOW Wenn D2 low ist wird ausgelöst.
CHANGE Ändert sich der Zustand von D2 wird ausgelöst.
RISING Bei steigender Flanke, d.h. einem Übergang von HIGH zu LOW von D2 wird ausgelöst
FALLING Bei fallender Flanke an D2 wird ausgelöst.

Mit detachInterrupt(0) kann Interrupt0 deaktiviert werden. Soll ein bestimmter (evtl. zeitsensibler) Code innerhalb von loop() nicht unterbrochen werden, so kann man die Annahme von Interrupts mit noInterrupts() vorübergehend verweigern und anschließend mit interrupts() wieder zulassen.

Aufgaben:

  1. Benutze einen Sensor deiner Wahl an Port D2, der einen Interrupt auslöst.
  2. Welchen Vorteil bietet die Nutzung eines Interrupts im Vergleich zum "Polling"?
  3. Informiere dich, welche Aufgabe der "watch dog timer" bei Mikrocontrollern hat.

Die serielle Schnittstelle

kann prima verwendet werden, um Daten mit anderen Geräten auszutauschen, oder um Debug-Infos an den "Serial Monitor" zu liefern. Das Uno-Board hat lediglich eine serielle Schnittstelle; weitere können jedoch per Software emuliert werden. Das Mega-Board hat vier serielle Schnittstellen.

Wie funktioniert die serielle Datenübertragung?

Die Daten werden binär kodiert byteweise (= 8 bitweise) versendet. Folgende Grafik stammt von Gerald Deppe und erklärt das ganz schön, siehe auch UART bei Wikipedia

serielle_kommunikation

Der Austausch von Daten zwischen zwei Geräten kann gleichzeitig erfolgen, denn es gibt jeweils zwei Datenleitungen: TX ("transmit") und RX ("receive"). Möchte man zwei Unos seriell verbinden, muss man die Verkabelung kreuzen. Wir benutzen die serielle Schnittestelle z.B. zur drahtlosen Übertragung von Information mittels XBee.

Beispiel zur Kodierung:

Die Dezimalzahl 76 kann unterschiedlich kodiert ausgegeben werden, siehe Serial Monitor:

seriel_kodierung

Shields

Unter Shields versteht Platinen, die so gebaut wurden, dass sie gerade auf das Arduino-Board aufgesteckt werden können. Mittlerweile gibt es eine riesige Anzahl an Shields, eine Übersicht gibt es unter shieldlist.org/.

LCD-Keypad-Shield (mit RTC)

Wenn man ein LCD und einige Buttons benötigt ist das LCD-keypad-shield von DFRobot hervorragend geeignet. Leider ist mein Nachbau-Exemplar nicht empfehlenswert, da die DOWN-Taste manchmal streikt und zudem die digitalen Ports D2 und D3 nicht korrekt nach außen geführt sind, was mir eine Stunde Fehlersuche bescherte :-(.

In diesem Beispiel habe ich eine Uhr (ohne weitere Funktionalität) aufgebaut. Sie kann z.B. problemlos zu einem Wecker erweitert werden (Achtung: Weckzeit im EEPROM speichern lassen, sonst ist diese bei Stromausfall weg!). [Bemerkung: Foto ohne RTC]

keypad-shield

In meinen Augen ist dieses Beispiel dennoch sehr interessant, denn du kannst etwas über das Debouncen (= Entprellen von Tastern) lernen. Hintergrund: Bei diesem Shield werden die fünf Taster (select, left, right, up, down) über einen (sic!) analogen Port eingelesen. Man kann also die üblichen Debounce-Bibliotheken und -Beispiele nicht verwenden.

Logik für das Debouncen:

debouncen_nsd

Code:

#include <LiquidCrystal.h>
#include <DateTime.h>
#include <DateTimeStrings.h>
#include <Wire.h>
#include "RTClib.h"
// Zum Debuggen über den Serial Monitor hier eine "1" eintragen, sonst "0"
#define debug 1 
RTC_DS1307 RTC;
// Belegung Keypad-Shield:
// D4 4, D5 5, D6 6, D7 7, RS 8, Enable 9
LiquidCrystal lcd(8,9,4,5,6,7);
// Tastenbelegung:
// 1: select, 2: left, 3: up, 4: down, 5: right, 6: reset
int buttonValue = 0;
int selected = 0;
int lastButtonState = 0;
long lastDebounceTime = 0; long debounceDelay = 110;

void
setup(){
  lcd.begin(16, 2);
  Serial.begin(9600);
  Wire.begin();
  RTC.begin();
  delay(500);
  lcd.setCursor(0, 0); // 0. Spalte, 1. Zeile
  lcd.print("RTC mit dem ");
  lcd.setCursor(0,1);
  lcd.print("Keypad-Shield");
  delay(500);   lcd.clear();   // Auskommentieren, falls die Uhr neu gestellt werden muss:
  //RTC.adjust(DateTime(__DATE__, __TIME__));
}

void loop() {
  uhrZeitAnzeigen();
  selected = buttonAbfrage();
  if(debug){
    if(selected != 0) Serial.println(selected);
  }
  selectedAnzeigen();
}

int buttonAbfrage(){
  buttonValue = analogRead(0);
  if(900<buttonValue) selected = 0;  
if(600<buttonValue && buttonValue <901) selected = 1;
  if(400<buttonValue && buttonValue <601) selected = 2;
  if(80<buttonValue && buttonValue <201) selected = 3;  
if
(250<buttonValue && buttonValue <350) selected = 4;  
if
(buttonValue==0) selected = 5;
  // Logik für's Debouncen (=Entprellen):
  if(selected != lastButtonState) {
    lastDebounceTime = millis();
    lastButtonState = selected;
  }
  if ((millis() - lastDebounceTime) > debounceDelay) {
    if(selected == lastButtonState) {
      lastDebounceTime = millis();
      return selected;
    }
  }
  return 0; // Default-Wert zurückgeben
}

void
uhrZeitAnzeigen(){
  DateTime now = RTC.now();
  lcd.setCursor(0, 0);
  if(now.hour()<10) lcd.print("0"); // ggf. "0" ergänzen  
lcd.print(now.hour(), DEC);
  lcd.print(":");
  if(now.minute()<10) lcd.print("0");  
lcd.print(now.minute(), DEC);
  lcd.setCursor(0,1);
  lcd.print("Uhrzeit");
}

void selectedAnzeigen(){
  lcd.setCursor(15,1);
  if(selected != 0) lcd.print(selected);
}

Network-Shield

In diesem Beispiel wird die Temperatur mittels LM35DZ am analogen Port 0 gemessen und der Messwert auf einer Webseite ausgegeben, d.h. auf dem Arduino läuft ein WebServer.

network shield mac adress

Code:

/*
 * Web Server im Original von David A. Mellis, Tom Igoe
 * Belegte Pins:
 * Ethernet shield attached to pins 10, 11, 12, 13
 * LM35DZ am analogen Port 0
 */
#include <SPI.h>
#include <Ethernet.h>
float tempCelsius;
// MAC-Adresse von der Unterseite des Ethernetshield abschreiben:
byte mac[] = {0x90, 0xA2, 0xDA, 0x0D, 0x44, 0x6F};
//IP-Adresse passend zum Heimnetz
IPAddress ip(192,168,178, 21);
EthernetServer server(80); // Port 80

void setup() {
  Serial.begin(9600);
  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());
}

void loop() {
  //Temperaturmesswert einlesen und umrechnen:
  tempCelsius = (5.0 * analogRead(0) * 100.0)/1024.0;   Serial.print((byte)tempCelsius);   // listen for incoming clients
  EthernetClient client = server.available();
  if (client) {
    Serial.println("new client");
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        Serial.write(c);
        // if you've gotten to the end of the line (received a newline
        // character) and the line is blank, the http request has ended,
        // so you can send a reply
        if (c == '\n' && currentLineIsBlank) {
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          // add a meta refresh tag, so the browser pulls again every 5 seconds:
          client.println("<meta http-equiv=\"refresh\" content=\"5\">");
          client.print("Die Temperatur betraegt ");
          client.print(tempCelsius);
          client.print(" Grad Celsius <br />");          
client.println("</html>");
          break;
        }
        if (c == '\n') {
          // you're starting a new line
          currentLineIsBlank = true;
        } 
        else if (c != '\r') {
          // you've gotten a character on the current line
          currentLineIsBlank = false;
        }
      }
    }
    // give the web browser time to receive the data
    delay(1);
    // close the connection:
    client.stop();
    Serial.println("client disonnected");
  }
}

Network-Shield: Tweets versenden

Etwas aufwändiger als die Verwendung des µC als Twitter-client ist es, selbst Nachrichten zu versenden (twittern). Z.B. wenn ein Einbrecher das Haus betritt oder die Temperatur im Serverschrank zu hoch ist oder ...

tweet_gesendet

Vorbereitungen:

  1. Twitter account erstellen.
  2. Security-Token für den Twitteraccount generieren.
  3. Twitter library herunterladen, entpacken und in den libraries-Ordner kopieren. Achtung: den Ordner Twitter aus dem entpackten Ordner heraus kopieren und die Entwicklungsumgebung neu starten.
  4. MAC-Adresse von der Rückseite des network-shield abschreiben und in den sketch notieren.
  5. Security Token im Sketch notieren.
  6. Sketch uploaden, abwarten und freuen.

Der Code:

#include <SPI.h> 
#include <Ethernet.h>
#include <Twitter.h>

// MAC-Adresse von der Unterseite des Ethernetshield abschreiben:
byte mac[] = {0x90, 0xA2, 0xDA, 0x0D, 0x44, 0x6F};

// wenn man eine IP-Adresse angeben möchte (anstelle DHCP)
// byte ip[] = { 192, 168, 178, 21 };

//Security token erzeugen und eintragen: http://arduino-tweet.appspot.com/oauth/twitter/login
Twitter twitter("Twitter-SECURITY-TOKEN hier eintragen");
// Twitter-Nachricht eintragen:
char msg[] = "Testo rockt!";
void setup()
{
  delay(2000);
  //Ethernet.begin(mac, ip); falls die IP-Adresse genutzt wird
  Ethernet.begin(mac);
  Serial.begin(9600);  
Serial
.println("connecting ...");
  if (twitter.post(msg)) {
    // Statusabfrage:
    int status = twitter.wait(&Serial);
    if (status == 200) {
      Serial.println("OK.");
    } 
    else {
      Serial.print("failed : code ");
      Serial.println(status);
    }
  } 
  else {
    Serial.println("connection failed.");
  }
}
void loop()
{
}

Achtung:

Die getwitterte Nachricht muss sich (von Zeit zu Zeit?) ändern, sonst wird die Annahme verweigert (Code 403).

Tipp:

Man kann die Netzwerkeinstellungen für den Arduino entweder händisch eintragen oder via DHCP vom Router bekommen. Ich bevorzuge letztere Variante. Bei vielen Routern kann man einstellen, dass bestimmten Geräten immer die gleiche IP-Adresse zugewiesen wird. In der Geräteübersicht kann man dann den Arduino einfacher identifizieren. Hier am Beispiel einer FritzBox:

gleiche_ip

WiFi-Shield

Mit dem WiFi-Shield kannst du dich mit einem (verschlüsselten) WLAN verbinden. Das Shield kannst du mit dem Arduino Uno oder Mega verwenden. Es enthält zudem einen Micro-SD-Karten-Slot.

Achtung1: Der Arduino kommuinziert via SPI (serielle periphere Schnittstelle) mit dem WiFi-Shield und mit der SD-Karte. Deshalb sind beim Uno die Pins 11, 12 und 13 nicht zu benutzen (beim Mega 50, 51 und 52). Auf beiden Boards Uno und Mega sind die Ports 10 für WiFi und 4 für die SD-Karte reserviert. Beim Mega darf zusätzlich 53 nicht verwendet werden, bzw. muss als OUTPUT geschaltet sein.

Achtung2: Da sowohl WiFi als auch die SD-Karte via SPI-Bus angesteuert werden, können nicht beide zeitgleich angesprochen werden.

Funktionstest

Benutze das Beispiel ConnectWithWPA aus der Arduino-IDE , um das Shield zu testen. Du musst lediglich die SSID deines WLANs und dein WPA2-Passwort eingeben. Du kannst z.B. dein Smartphone verwenden, um ein WLAN zur Verfügung zu stellen (tethering). Die Ausgabe auf dem Serial Monitor sollte so aussehen::

wifi check

Bemerkung: Falls dein WiFi-Shield ein Firmware-Upgrade benötigt kannst du dieses wie bei dfrobot beschrieben über den Mini-USB-Anschluss einspielen. Es klappte bei mir nicht, das Update wie auf arduino.cc beschrieben einzuspielen (es fehlte die msvcr100.dll).

Web-Server

Benutze das Beispiel WiFiWebServer aus der Arduino IDE, um in deinem WLAN einen WebServer zu implementieren. Die IP-Nummer, die zum Aufruf im Browser benötigst, erfährst du nach dem Upload des Sketches vom Serial Monitor:

wifi ip

XBee-Sockel + WLAN Modul (Roving RN-171)

Das XBee-Shield kann man auch für WLAN, Bluetooth und RFID verwenden. In diesem Abschnitt wollen wir unseren Arduino ins WPA-gesicherte WLAN einbinden. Wir verwenden das RN-XV Board Roving RN-171 von Sparkfun, das ich über Elmicro bezogen habe.

wifi

Die Teile sind zwar schnell zusammengestöpselt ...

wifi-aufgebaut

... müssen dann aber noch konfiguriert werden. Die Befehle dazu sind der Bedienungsanleitung entnommen. Hier die notwendigen Schritte:

kitty_config
wlan_config
fritzbox_wlan
wifi_telnet_config

wifi_telnet_answer

Bemerkung:

XBee-Shield

XBee kann verwendet werden, um drahtlos zwischen zwei Arduinos oder dem Arduino und einem PC zu kommunizieren. Es ist sogar möglich, einen analogen Wert mit einem XBee (ohne weitere Hardware!) zu messen und zu einem anderen XBee (z.B. an einem Arduino) zu übertragen (siehe Arduino Cookbook von M. Margolis).

Einkaufsliste für die Kommunikation zwischen zwei Arduinos:

Konfiguration mit X-CTU

  1. Zunächst musst du die Software X-CTU von digi.com (unter Utilities) herunterladen und installieren.
  2. Bereite die Arduinos wie abgebildet vor:
  3. Öffne die Software X-CTU zwei mal und wähle für jedes Modul den passenden COM-Port (ggf. im Gerätemanager nachsehen).
  4. Führe jeweils einen Test durch, ob du die Module erreichen kannst:

    xbeetest

  5. xbee_konfig
  6. Nun kannst du einen Range-Test durchführen ...

    xbeerangetest
  7. ... oder Nachrichten auf dem Terminal hin und her senden:

    xbeeterminaltest

Alternative Konfiguration mit putty

Benutze putty (oder kitty, eine sinnvolle Erweiterung), um die XBees zu konfigurieren. Bereite dazu die Arduinos wie oben beschrieben vor.

Die putty/KiTTY-Konfiguration:

Die XBee-Konfiguration:

Sender Empfänger Erklärung
+++ +++ (ohne Entertaste!) Kommunikation starten, Antwort "OK" xbee_config
TWRE↵ TWRE↵ Auf Herstellereinstellungen resetten
ATMY1234↵ ATMY5678↵ XBee ID festlegen
ATDL5678↵ ATDL1234↵ Ziel-XBee festlegen (low byte)
ATDH0↵ ATDH0↵ Ziel-XBee (high byte)
ATID0↵ ATID0↵ Netzwerk-ID festlegen
ATWR↵ ATWR↵ Daten dauerhaft auf dem XBee speichern.

Beispiel: Test des Senders

Dieses Beispiel kann man zur Fehlersuche benutzen wenn nicht klar ist, ob der Sender überhaupt sendet:

Transmitter Receiver
Arduino ohne Drahtbrücke mit Xbee-Shield (Jumper auf Xbee):
Arduino sendet im Wechsel H und L
Arduino im Resetmodus (Drahtbrücke!) mit XBee-Shield (Jumper auf USB)
Terminal der Software X-CTU
xbee-test-sender

Beispiel: Zwei Arduinos kommunizieren

Vorbereitung:

Die Programme:

Sender Empfänger


xbee2ard

Der Sender sendet im Wechsel ein H und L (beide binär kodiert). Der Empfänger schaltet je nach Zustand die LED an Port 13 an bzw. aus.

Assembler

--->Lücke<---

Processing

kann wunderbar benutzt werden, um eine grafische Ausgabe am PC-Monitor mittels arduino zu erzeugen. Lade dazu bitte zunächst processing für dein Betriebssystem von processing.org runter. Unter Windows benötigst du keine Installation, das Programm ist direkt ausführbar (ich gehe mal davon aus, dass Java (JRE reicht aus) auf deinem Rechner installiert hast).

Verbindung Arduino <-> PC

Das arduino-uno-Board hat lediglich eine serielle Schnittstelle: Die digitalen Ports 0 (RX) und 1 (TX) sind für die Kommunikation mit dem PC vorgesehen und sollten wenn möglich nicht anderweitig genutzt werden.

Atmega 328 mit und ohne Arduino Board ... 

Das Arduino Uno Board hat gegenüber den anderen (wie z.B. Leonardo, ...) den großen Vorteil, dass man den µC aus seiner Fassung nehmen und für Projekte auf ein Breadboard stecken oder eine Platine löten kann. Zunächst wird kurz gezeigt, wie man einen im Arduino-Board programmierten Atmega standalone betreibt. Im zweiten Schritt werden wir einen Arduino Bootloader auf einen nackten Atmega 328 brennen.

Uno im Arduino-Board programmieren und umpflanzen

  1. Ihr schreibt euer Programm wie gewohnt in der Arduino IDE (in diesem Beispiel eine blinkende LED an D13).
  2. Ihr entnehmt den µC vorsichtig und baut ihn in folgende Schaltung ein.

Benötigte Teile:

Optionale Teile:

Atmega 328 naked

atmega 328p naked

Optionale Stromversorgung:

Bootloader mittels Uno-Board auf einen nackten Atmega 328 brennen

Ihr möchtet einen defekten Atmega 328 auf einem Arduinoboard ersetzen oder plant ein Projekt und möchtet einen fabrikneuen Atmega mit dem Arduino Bootloader versehen? Hier lernt ihr, wie ihr den Bootloader mit einem Uno-Board brennt.

[Achtung: bin noch dabei das Prozedere zu testen!]

Benötigte Teile:

Optionale Teile:

ToDo:

  1. Schaltung aufbauen, Uno-Board via USB an den PC anschließen.
  2. In der Arduino IDE das Board (Uno) und den seriellen Port (z.B. 11) auswählen.
  3. Unter Datei->Beispiele Arduino ISP wählen und uploaden.
  4. Funktionstest durchführen (frisch gebrannten Atmega ins Uno Board einsetzen und ein Blink-Programm uploaden).

Portbelegung auf dem Uno-Board:

arduino as isp

Anhang: Ein klein wenig Physik

Vorwiderstand berechnen

Wenn du eine LED direkt an 5 V anschließt geht sie wahrscheinlich kaputt!

Du musst erst herausfinden, welche Vorwärtsspannung deine LED hat - meist sind dies etwa 1,5 Volt. Damit kannst du den Wert eines geeigneten Vorwiderstands berechnen, das geht ganz einfach:

vorwiderstand_berechnen

Die Spannungsteilerschaltung

Wenn du einen Sensor, z.B. einen Temperaturfühler, mit dem Arduino auslesen willst, benötigst du eine Spannungsteilerschaltung. Die Schaltung heißt so, weil sich die Spannung in zwei Teile aufteilt: je größer ein Widerstand, desto größer ist die Spannung, die an ihm abfällt. Im gezeigten Beispiel ist der untere Widerstand  9x so groß wie der obere, d.h. am Eingangsport würden 4,5 V anliegen.

Liest du den Eingangsport nun mit digitalRead(10) aus, so wird high eingelesen. 

Wählst du statt dessen einen analogen Eingangsport, z.B. A0, so wäre der Wert etwa 921.
Grund: Die Auflösung beträgt 10 bit, d.h. der kleinste erfassbare Wert ist 0 und der höchste 1023 (= 2^10-1). Das Potenzial 4,5 V entspricht 9/10 des Gesamtpotenzials. Nimmst du nun 90% von 1023, erhältst du etwa 921.

spannungsteilerschaltung

Pull-up und pull-down-Widerstände

a) Schließe einen Push-Button von 5 V aus an den analogen Port A2 an und logge die Messwerte über den Serial Monitor (Taster offen / Taster zu). 

pull_down_ohne_R

b) Verbinde nun den Taster zusätzlich von A2 aus über einen 10 kΩ - Widerstand mit GND und logge erneut die Messwerte in beiden Tasterstellungen.

pull_down_analogpull_down

Aufgaben:

  1. Zu Teil a): Welche Beobachtung machst du, wenn der Taster offen ist?
  2. Zu Teil b): Warum nennt sich der eingebaute Widerstand "pull-down"-Widerstand?
  3. Der schlaue Peter behauptet, man könne anstelle des pull-down-Widerstandes einfach ein Kabel mit GND verbinden. Was entgegnest du?
  4. Verwende anstelle des pull-down-Widerstandes nun einen pull-up-Widerstand. Wie muss der Schaltplan geändert werden und was misst du nun, wenn der Taster nicht gedrückt ist?

Interne Pullups

Ist ein digitaler Port als Eingang geschaltet, so kann man elegant interne Pullups setzen, indem man

  pinMode(pinNummer, INPUT);
digitalWrite(pinNummer, HIGH);

verwendet. Intern wird der Eingang dadurch über einen 20 kΩ - Widerstand mit HIGH verbunden und kann dann durch ein externes Signal auf LOW gezogen werden. Das ist prima, denn so sparst du dir etwas Verkabelungsaufwand!

Einen kleinen Nachteil gibt es gegenüber dem pull-down-Widerstand aus dem Beispiel oben: Der Stromverbrauch ist höher, da permanent ein (allerdings sehr kleiner) Strom fliesst.

npn - Transistor als Verstärker

Um z.B. ein Relais mit dem µC zu steuern, oder um Musik in vernünftiger Lautstärke über einen Lautsprecher auszugeben reicht die maximale zulässige Stromstärke der Ports nicht aus. Abhilfe schafft ein Transistor (oder MOSFET), wobei es zwei verschiedene Möglichkeiten gibt, den Transistor zu betreiben:

  1. Proportionalbetrieb: Der Kollektor- bzw. Emitterstrom ist proportional zum Basisstrom.
    Je größer der Basisstrom desto größer der Kollektorstrom. Den Verstärkungsfaktor selbst kannst du im Datenblatt nachlesen. Da die Spannung UKE recht groß ist erwärmt sich der Transistor (Verlustleistung!).
  2. Schaltbetrieb: Der Transistor leitet entweder voll oder sperrt komplett.
    Der Basistrom muss groß genug sein, um den Kollektor- bzw. Emitterstrom nicht zu begrenzen. Ein Basiswiderstand ist für den Schutz des µC-Ausgangs nötig, nicht für den Betrieb des Transistors. Ohne Basiswiderstand würde der Ausgang des Arduino fast kurzgeschlossen, denn die Vorwärtsspannung UBE ist in der Regel etwa 0,7 V (Achtung: Beim Darlingtontransistor verdoppelt sich dieser Wert). Du benötigst deshalb einen Vorwiderstand von etwa 100 Ω, der den Strom durch den Port des µC auf maximal 40 mA begrenzt:

R

transistor_Steckplatinetransistor_Schaltplan

Hier wurde neben dem Widerstand ein Trimmpoti verwendet (genauer zwei Beinchen desselben), um die Lautstärke regeln zu können.

Beachte, dass du je nach Transistor im Datenblatt nachsehen musst, wo sich die Basis, der Collector und der Emitter befinden.

Aufgaben:

  1. Benutze tone(), um verschiedene Töne auszugeben und noTone(), um die Tonausgabe zu beenden.
  2. Kannst du den Poti alternativ zwischen den Minuspol des Lautsprechers und den Collector einbauen? Probiere es aus!
  3. Benutze zwei while-Schleifen, um eine auf- und abschwellende Sirene zu programmieren.

Festspannungsregler

verwendest du, wenn du eine stabilisierte Ausgangsspannung benötigst. Du kannst sie z.B. auch verwenden, um Servomotoren, die 6 V benötigen, an einer 9 V - Batterie zu betreiben.

Regler mit fester Ausgangsspannung (hier am Beispiel 7806, wobei die "6" für 6 V Ausgangsspannung steht):

Du benötigst zwei Kondensatoren und ggf. eine Diode (siehe Bemerkung). Auszug aus dem Datenblatt des Fairchild LM7806:

gehaesue

schaltbild LM7806

Bemerkung: Verwende bei induktiven Lasten eine Freilaufdiode (z.B. 1N4007) zwischen Aus- und Eingang. Sie schützt den Festpannungsregler, denn er geht kaputt, wenn die Ausgangsspannung größer als die Eingangsspannung ist (z.B. beim Abschalten einer induktiven Last).

Regler mit variabler Ausgangsspannung (LM317):

Du kannst den LM317 als Spannungsregler oder als Konstantstromquelle nutzen. Auf der Elektronikerseite findest du ein Schaltbild und ein kleines Tool, um - je nach gewünschter Ausgangsspannung - die passenden Widerstände zu berechnen.

Beispiel: Steuern mit einem Relais

Die Funktionsweise eines Relais wird sehr schön bei Strippenstrolch erklärt, außerdem kannst du dir dieses kleine Filmchen ansehen.
Wir wollen den Steuerstromkreis unseres Relais' natürlich mit dem µC ansteuern, das ganze sieht dann so aus:

relais_cc-by-sa_Cschirp

Die LED dient lediglich zur Darstellung, wann das Relais anziehen soll. Eine "normale" Diode wird immer antiparallel zur Spule des Relais' geschaltet, um Spannungsspitzen (durch den  Induktionsvorgang beim Ausschaltvorgang) abzufangen.

Literatur

Folgende Bücher lese ich gerne:

Titel Autor Verlag
ISBN
Arduino Cookbook (engl.) Michael Margolis O'Reilly 978-0-596-80247-9
Arduino: Ein schneller Einstieg in die Mikrocontroller-Entwicklung Maik Schmidt dpunkt.verlag 978-3-89864-764-9
Arduino: Mikrocontroller-Porgrammierung mit Arduino/Freeduino Ulli Sommer Franzis 978-3-645-65034-2
Arduino: Physical Computing für Bastler, Designer & Geeks Odendahl, Finn & Wenger O'Reilly 978-3-89721-893-2
Arduino: Praxiseinstieg Thomas Brühlmann mitp 978-3-8266-5605-7




Links

Link Beschreibung
hackaday.com/ Herrlich verrückte Bastelbeispiele
blog.littlebirdelectronics.com Nette Tutorials
jeremyblum.com Videotutorials
ladyada.net Neben dem Einsteigertutorial viele hacks
macherzin.net Viele nette Beispiele in deutscher Sprache.
tronixstuff Eine Einführung und gute Tutorials

home