Vorwort

Version 2011-06-26

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 und Segor 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.

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.

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. Wie viele verschiedene Werte können mit 10 bit unterschieden werden?
  3. 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 der Temperatur ändert.

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?

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:

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 / Bildquelle:
  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.

/*
  Einführung in die while-Schleife
 
 */
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.

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.

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: Verwendung eines Shields

...

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:

...

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

I2C-RTC (real time clock)

Wir verwenden den Uhrenbaustein DS1307 von Maxim (pinkompatibel zum PCF8583, der jedoch zusätzlich ein EEPROM und eine Alarmfunktion besitzt), um Zeitpunkte z.B. für eine Messwerterfassung abfragen zu können:

...

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.

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:

...

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 Hardware einsetzen. 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.

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 (cc-FDominec ) 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 = 10; // Klatschschalter, z.B. von Parallax, an Port D10 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.

Shields

Unter Shields versteht Platinen, die so gebaut wurden, dass sie gerade auf das Arduino-Board aufgesteckt werden können.

Network-Shield

...

Assembler

...

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, LOW);

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.
  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.

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: 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
tronixstuff Eine Einführung und gutte Tutorials

home