http://sites.schaltungen.at/arduino-uno-r3/home/einfuehrung
http://www.linksammlung.info/
http://www.schaltungen.at/
Wels, am 2015-01-28
BITTE nützen Sie doch rechts OBEN das Suchfeld
[ ] [ Diese Site durchsuchen]
DIN A3 oder DIN A4 quer ausdrucken
**********************************************************************************
DIN A4 ausdrucken
*********************************************************
~015_b_PrennIng-a_arduino.uno.r3-home-einfuehrung (xx Seiten)_1a.pdf
Arduino Duemilanove alt
ARDUINO UNO Rev.3 NEU
1. Einführung
2. Programmierung
Beispiele
1. Temperaturerfassung
2. Balkenanzeige
3. Analogeingabe
4. Soft-Blinker
5. Farbwechsel
1. Einführung
Die Arbeit mit Mikrocontrollern und die Ansteuerung von Hardware hat selbst für Informatiker etwas von einer Geheimwissenschaft an sich.
Erst muss man dicke Datenblätter wälzen und dann seinen Code nicht in einer Hochsprache, sondern in Assembler schreiben.
Stimmt! Stimmt aber auch wieder nicht! Inzwischen können viele Controller auch in C programmiert werden - unter Umständen kostet der Compiler nur relativ viel.
Um das Jahr 2005 herum gabe es einen sehr Interessanten Umschwung: Studierende des Interaction Design Institute im italienischen Ivrea suchten nach einer einfachen Möglichkeit, um ihre Ideen für neuartige Interaktion zwischen Mensch und Maschine sowie ihre Kunst- und Roboterprojekte in funktionsfähige Prototypen umzusetzen.
Professor Massimo Banzi entwickelte daraufhin mit einigen Helfern ein einfaches, günstiges Mikrocontrollerboard und auch gleich die passende, C-ähnliche Programmiersprache.
Das Ganze nannte er "Arduino" nach einem lokalen König aus dem elften Jahrhundert.
Das kleine, mit einem Atmel-Prozessor bestückte Board trat schnell einen Siegeszug an, weil es sich an Designer oder Künstler und nicht so sehr an lötende Nerds richtete.
Also Menschen, die vorher nur in extremen Ausnahmefällen Software entwickelt oder gar eigene Hardware gebaut hatten.
Ein weiterer Vorteil: Board (Schaltung und Layout) sowie die Entwicklungsumgebung ist Open Source, also für jeden frei verfügbar und ebenso offen für Änderungen und Erweiterungen. Einige davon kann man auf der Arduino-Webseite bewundern.
Angesprochen wird Arduino über die USB-Schnittstelle. Zum einen wird darüber das Programm in den Controller geladen, zum anderen kann das Board über eine virtuelle Seriellschnittstelle mit dem PC kommunizieren.
Das kann über das im Entwicklunkssystem eingebaute ASCII-Terminal genauso geschehen, wie über ein Terminalprogramm (z.B. "putty" unter Windows oder "cu" unter Linux).
Oder man schreibt sich das Terminalprogramm mit einigen Zeilen Perl selbst.
Mit Sensoren und Aktoren versehen kann Arduino als Messfühler, Sinnesorgan oder Eingabegerät dienen und seine Daten an den PC weitergeben.
Unterstützt wird die Anwendung durch zahlreiche Bibliotheken, etwa für die Ansteuerung eines LC-Displays oder die Kommunikation per I2C-Bus mit angeschlossenen Sensoren. Inzwischen gibt es auch etliche Buchtitel zum Arduino.
Der Name Arduino bezeichnet auch die zugehörige kompakte integrierte Entwicklungsumgebung (IDE) für den Steuercode.
Hiermit entwickelt man kleine Programme, "Sketches" genannt (übrigens heissen die Boards zum Aufstecken auf den Arduino "Shields"), und sendet sie per USB als Maschinencode an das Arduino-Board. Dessen Bootloader schreibt das Programm selbst dauerhaft in seinen Flash-Speicher und führt es sofort danach aus.
Die IDE steht auf der Arduino-Homepage kostenlos für Windows, Linux und Mac OS X zum Download bereit. Sie ist nach Auspacken des ZIP-Archivs sofort gebrauchsfertig, es muss also nichts installiert werden.
Um die USB-Verbindung zum Rechner gegenüber dem Board als serielle Schnittstelle auszugeben, ist allerdings noch ein USB-Treiber für das jeweilige Betriebssystem notwendig.
Im Arduino-Paket stehen im Verzeichnis "drivers" solche Treiber zur Verfügung, die aber nicht besonders aktuell sind. älteren Version.
Am besten aktualisiert man den Treiber dann gleich noch.
Die Arduino-Programmiersprache ist ein Subset von C mit einigen Objekteigenschaften. Das Quellprogramm wird per GCC (GNU C Compiler) übersetzt.
Die Sprache ist sehr einfach zu erlernen, selbst wenn man bisher mit anderen Programmiersprachen gearbeitet hat.
Ein Arduino-Programm besteht grundsätzlich aus zwei Teilen, einem Initialisierungsblock (setup()), der bei Beginn einmal durchlaufen wird, und einer anschließenden Endlosschleife (loop()), was der typischen Controlleranwendung entspricht. Sinnvoll ist es auf jeden Fall, für die benutzten Pins des Arduino-Boards sprechende Namen zu wählen. Für die ersten Schritte sind die Arduino-Funktionen für Ein- und Ausgabe wichtig. So definiert man mit pinMode() den entsprechenden Pin entweder zum Eingang (pinMode(Pin, INPUT)) oder zum Ausgang (pinMode(Pin, OUTPUT).
Von einem Eingangs-Pin wird mit digitalRead(Pin) gelesen und mit digitalWrite(Pin, HIGH) schreibt man eine 1 (5 V) auf einen Ausgangspin. LOW hingegen stünde für die 0 (0 V).
Arduino Duemilanove alt (pin-13 hat OnBoard einen eingebauten Vorwiderstand für eine LED)
Sechs der Pins eines Arduino (A0 bis A5) führen zu einem Analog-Digital-Wandler (ADC) und erlauben das Einlesen analoger Werte.
Der ADC übersetzt die analogen Eingangsspannungen zwischen 0 V und 5 V in digitale Werte zwischen 0 und 1023 - es handelt sich also um einen 10-Bit-Wandler.
Gelesen wird mit der Funktion analogRead().
Die Ausgabe analoger Werte erfolgt jedoch nicht über einen Digital-Analog-Wandler, sondern per Pulsweitenmodulation (PWM) auf den Pins 3, 5, 6, 9, 10 und 11.
Es wird also kein echter Analogwert ausgegeben.
Vielmehr wird am jeweiligen Pin ein Rechtecksignal ausgegeben.
Das Verhältnis von Einschalt- zu Ausschaltzeit bestimmt den analogen Ausgabewert.
Auf diese Weise kann man beispielsweise eine LED dimmen (die Trägheit des Auges "integriert" den Wert).
Über ein Verstäkerelement könnte man die Drehzahl eines Gleichstrommotors steuern - hier ist es die Trägheit des mechanischen Systems, die integrierend wirkt.
Für einen sauberen Analogwert ist allerdings ein Tiefpassfilter nötig, wobei meist ein RC-Glied ausreicht. Die Ausgabe erfolgt mittels analogWrite().
Den Kern des Arduino-Boards bildete beim Duemilanove (2009) ein Atmel ATmega168, ein 8-Bit-Mikrocontroller mit 16 MHz Takt, 16 KByte Programm-Flash (davon sind 0,5 KByte vom Bootloader belegt), 1 KByte SRAM für Variablen.
Beim aktuellen Arduino Uno wird ein ATmega368 eingesetzt, der 32 KByte Programmspeicher (0,5 KByte Bootloader) besitzt.
Laut Datenblatt liefert jeder Pin bis zu 20 mA Strom. Auch darf die Summe der Ströme an bestimmten Ports nicht mehr als 100 mA betragen.
Das reicht, um beispielsweise Leuchtdioden direkt zu betreiben. Stärkere Ströme steuert man per nachgeschaltetem Transistor oder anderen Verstärkern. Im Betrieb wird der Arduino entweder per USB oder per Netzteil (7 bis 12 V) versorgt.
Der Arduino Uno wird zukünftig als Basis der Arduino-Produktlinie dienen.
Die Entwickler ersetzten den betagten FTDI-Chipsatz durch einen neu entwickelten USB-Serial-Konverter, der auf einem ATmega8U2 basiert, kürzere Latenzzeiten als sein Vorgänger aufweist und so programmiert werden kann, dass er sich beim System auch als USB-Tastatur anmelden kann.
Das Arduino-Mega-2560-Board basiert auf einem ATmega-2560-Chipsatz und ist ebenfalls mit 16 MHz getaktet.
Das Board bietet jedoch mehr Schnittstellen als der Uno: 54 digitale I/O-Pins, 16 analoge Eingänge, 4 serielle Schnittstellen und einen USB-Port.
Der Arduino Mega bietet 256 KByte Programmspeicher, von denen 8 KByte bereits vom Bootloader belegt sind.
Arduino-Boards gibt es in den verschiedensten Ausführungen, neben den neuen Platinen gibt es die Modelle Pro, Fio, Nano, Mini und LilyPad.
Der Uno kostet etwa 25 Euro, der Mega rund 50 Euro.
Erweitert wird das Basis-Board mit so genannten Shields, die über die Buchsenleisten angeschlossen werden.
Es ergibt sich so ein Stapel von Platinen, bestehend aus einem Arduino-Board und weiteren Shields.
Unter
shieldlist.org kann man mit großer Wahrscheinlichkeit ein Shield für eigene Entwicklungen finden, ohne dass eine eigene Hard- und Software-Entwicklung nötig ist.
Aktuell sind dort über 150 Shields gelistet. Für eigene Entwicklungen kann man Prototyp-Shields erwerben.
Die Arduino-Software besteht aus dem Bootloader für den Arduino (damit er Programme über die serielle Schnittstelle laden kann)
und aus einer integrierten Entwicklungsumgebung (IDE) und Core Libraries.
Die Core Libraries sind in C und C++ geschrieben und basieren auf avr-gcc und derAVR-Libc.
Die Entwicklungsumgebung ist in Processing geschrieben und, wie gesagt, Open Source.
Nach dem Starten der Arduino-IDE öffnet sich das Hauptfenster, in dem der Quelltext des Anwenderprogramms eingegeben und bearbeitet werden kann.
Die serielle Kommunikation erfolgt über das USB-Interface und einen virtuellen COM-Port auf dem PC.
Danach kann sofort mit der Eingabe von Quelltext begonnen werden. Die Programme (Sketches) tragen die Endung ".pde".
Im folgenden Bild kann man den allgemeinen Programmaufbau erkennen.
Die beiden oben erwähnten Funktionen setup() und loop() bestimmen die Programmstruktur eines jeden Arduino-Programms. setup() ist für die Initialisierungen zuständig und loop() stellt eine Endlosschleife dar, die die Aktionen des eigentlichen Anwenderprogramms enthält.
Wie bei C üblich, können außerhalb dieser Routinen weitere Funktionen definiert werden, die dann von setup() bzw. loop() aus aufgerufen werden. Im ersten Sketch wird das Blinken des als Ausgang geschalteten Pins 13 (PB5) erzeugt.
Über die serielle Schnittstelle wird anfangs der String "Hallo Welt" ausgegeben.
Danach wird eine Meldung bei jedem Ein- und ausschalten ausgegeben.
Damit man das Blinken auch sieht, sorgt die Funktion delay(1000) für eine Pause von einer Sekunde bis zur nächsten Aktion.
Durch ein mit C0M3 verbundenes Terminalprogramm oder die Terminalfunktion der IDE kann der ausgegebene String angezeigt werden.
Das ist dabei auf die passende Datenrate eingestellt (9600 bps).
Diese Seriellschnittstelle erlaubt es zudem, den Arduino als Peripherie für nahezu jedes Programm ins System einzugliedern.
So wäre es relativ leicht möglich, die Ausgaben des Boards über ein entsprechendes CGI-Programm in eine Webseite einzubinden.
Über die Schaltflächen an der Oberkante des Fensters oder die Menüleiste lassen sich alle Funktionen der IDE steuern.
Unterhalb des Textfensters wird der aktuelle Programmzustand angezeigt.
Das Feld ganz unten ist für Ausgaben des Compilers, des Linkers und des Downloaders vorgesehen.
Hier wird auch die Größe des erzeugten Codes ausgegeben.
Nach dem ersten Start der IDE sind lediglich zwei Dinge einzustellen: die verwendete Arduino-Variante und der verwendete COM-Port.
Wie das folgende Bildschirmfoto zeigt, macht es das System dem Einsteiger besonders leicht. über das Help-Menü ist auch die komplette Sprachreferenz der sehr C-ähnlichen Programmiersprache abrufbar. Im File-Menü gibt es auch ein Untermenü "Examples" aller Beispiele und Testprogramme (siehe Bild).
Das "Sketchbook" ist das Äquivalent der "Eigenen Dateien" für die erstellten Programme.
Die Lage des Sketchbooks kann über die Preferences eingestellt werden.
Auch dort beschränkt sich die IDE auf das Nötigste. Wirklich alle Einstellungen findet der Profi in einer Textdatei namens "preferences.txt".
In der folgenden Abbildung sehen Sie die Werkzeugleiste der IDE, mit der Sie schnellen Zugriff auf die wichtigsten Funktionen erhalten.
Mit der Schaltfläche Verify (übersetzen) können Sie das Programm kompilieren, das gerade im Editor ist.
Eigentlich ist die Bezeichnung "Verify" (= überprüfen) ungünstig gewählt, da die Funktion mehr als die syntaktische überprüfung des Programms vornimmt:
Sie erzeugt vielmehr aus dem Quelltext den Binärcode für das Arduino-Board.
Die Schaltfläche New (Neue Datei) erstellt ein neues Programm.
Der Inhalt des aktuellen Editor-Fensters wird gelöscht. Gegebenenfalls noch nicht gespeicherten Quelltext können Sie auf jeden Fall noch sichern.
Mit Open laden Sie eine Programmdatei von der Platte oder einem anderen Speichermedium.
Save speichert das aktuelle Programm.
Wenn Sie auf die Schaltfläche Upload klicken, kompiliert die IDE das aktuelle Programm (sofern nicht schon geschehen)und transferiert es auf das Arduino-Board.
Arduino kommuniziert mit dem Computer über die serielle Schnittstelle.
Wenn Sie auf die Schaltfläche Serial Monitor (Serielles Terminal) klicken, können Sie die Ausgaben des Arduino lesen und Daten zum Arduino zurück senden.
Die Schaltfläche Stop hält den seriellen Monitor an.
das Help-Menü verweist auf hilfreiche Informationen, die man auf der Arduino-Website finden kann, zusammen mit schnellen Lösungen für typische Probleme, Referenzinformationen und Anleitungen.
Das Ansteuern von LEDs und Siebensegmentanzeigen oder anderes muss beim real vorhandenen Arduino-Board zusätzliche Hardware leisten (auf dem Board ist nur die Pin-13-LED vorhanden).
Am Besten besorgen Sie sich entweder einen Kit, wie er z. B. von Watterot angeboten wird, oder Sie beschaffen die Bauteile im Elektronikhandel.
Auf jeden Fall gehört ein "Breadbord" dazu, ein Steckbrett zum schnellen Aufbau von Schaltungen. Im folgenden Bild ist eine Mini-Version des Steckbretts abgebildet.
Denken Sie auch daran, dass die LEDs immer einen Vorwiderstand benötigen.
Auf dem Bild sehen Sie neben LEDs und Widerständen auch Subminiatur-Taster, eine Diode, ein Trimmpotenziometer, einen Verstärker-Transistor und einen Piezo-Summer.
Beim Steckbrett sind jeweils fünf senkrecht übereinander liegende "Löcher" miteinander verbunden.
Die Bauteile werden passend zu Schaltung auf dem Brett platziert und weitere notwendige Verbindungen per Drahtbrücke hergestellt.
Zum Entwurf und zur Darstellung solcher Breadboard-Schaltungen gibt es unter dem Namen "Fritzing" sogar eine Software.
Wenn dann alles funktioniert, kann die Schaltung auf ein Prototyp-Shield übertragen werden - oder man entwirft eine eigene Platine und lässt diese anfertigen.
Dann muss sie nur noch bestückt werden.
www.netzmafia.de/skripten/hardware/Arduino/intro.html
http://www.netzmafia.de/skripten/hardware/Arduino/Arduino_Programmierhandbuch
*********************************************************
2. Programmierung
Die Programmiersprache für den Arduino ist eine Untermenge von C. Insofern sollte der mit C vertraute Programmierer kaum Schwierigkeiten haben.
ImGegensatz zum typischen C-Programm fehlt jedoch die Funktion
main(). An deren Stelle treten die zwei bereits erwähnten Funktionen:
-
init() wird beim Programmstart einmal ausgeführt und enthält alle Initialisierungen, die das Programm benötigt,
- z. B. dieFestlegung der Datenrichtung bei E/A-Ports etc.
-
loop() ist eine Endlosschleife, in der sich alle Befehle und Funktionen zur Ablaufsteuerung befinden.
Im übrigen ist alles wie bei C gewohnt. Es existieren alle Kontrollstrukturen von C, ebenso fast alle Operatoren. Codeblöcke werden wiegewohnt in geschweifte Klammern eingeschlossen. Deshalb wird darauf an dieser Stelle nicht weiter eingegangen.
Die Arduino-Datentypen sind an die Gegebenheiten des verwendeten ATmega-Prozessors angepasst:
-
Boole'sche Werte benötigen ein Byte Speicherplatz und können true oder false sein.
-
char-Variablen benötigen ein Byte Speicherplatz und stellen normalerweise ASCII-Zeichen dar.
-
Sie können entweder als Zahl (char X =64;) oder als Zeichen, eingeschlossen in Apostrophe, (char x = '@';) definiert werden.
-
byte-Variablen belegen ein Byte im Speicher und speichern Werte zwischen 0 und 255.
-
Eine int-Variable belegt zwei Byte im Speicher. Sie überstreicht den Wertebereich zwischen -32768 und 32767.
-
Das Gegenstück unsignedint belegt ebenfalls zwei Byte, speichert aber Werte zwischen 0 und 65535.
-
long belegt vier Byte im Speicher und speichert Zahlen zwischen -2147483648 und 2147483647.
-
Die vorzeichenlose Variante unsigned long speichertWerte zwischen 0 und 4294967295.
-
float und double sind momentan gleich. Sie verwenden diese Typen, um Gleitkommazahlen zu speichern.
-
Beide belegen vier Byte im Speicher undkönnen Werte zwischen -3.4028235E48 und 3.4028235E48 speichern.
-
void wird nur für Funktionsdeklarationen benötigt. Es zeigt an, dass eine Funktion keinen Wert zurück gibt.
-
Bei strukturierten Datentypen gibt es lediglich das Array. Structures und Pointer fehlen.Die Arduino-Programmiersprache kennt einige vordefinierte Konstanten:
-
TRUE und FALSE definieren boole’sche Werte. FALSE ist einfach als 0 (Null) definiert, während TRUE einen Wert ungleich Nullrepräsentiert.
-
HIGH und LOW definieren Pin-Level und werden beim Lesen oder Schreiben auf Digitalpins verwendet. HIGH ist logisch 1 bzw. ca. 5 Volt,während LOW logisch 0 bzw. ca. 0 Volt ist.
-
INPUT/OUTPUT werden von der Funktion pinMode() benutzt, um einen digitalen Pin entweder als Eingang oder Ausgang zu definieren.
Digital-Funktionen
pinMode(pin,mode)
Wird in setup() verwendet, um einen Pin entweder als Eingang oder Ausgang zu konfigurieren.
Als Modus kommen nur INPUT oder OUTPUT in Frage.
Diedigitalen Pins sind nach dem Einschalten als Eingänge konfiguriert, weshalb sie nicht extra als Eingänge festgelegt werden müssen- es ist aber guter Programmierstil, dies trotzdem zu tun. Als Eingang konfigurierte Pins sind hochohmig.
Bei der Ein- und Ausgabefestlegung der Pins gibt es noch eine Besonderheit. Im ATmega können 20-Kiloohm-Pullup-Widerstände per Softwarezugeschaltet werden, um offene Eingänge auf einen definierten 1-Pegel zu ziehen.
Auf diese eingebauten Pullup-Widerstände kann in folgenderWeise zugegriffen werden:
pinMode(pin, INPUT); // setzt "pin" als EingangdigitalWrite(pin, HIGH); // schaltet den Pullup-Widerstand ein
Als Ausgang konfigurierte Pins können maximal 40 mA Strom an angeschlossene Elemente liefern.
Dies ist genug um eine LED aufleuchten zulassen (Strombegrenzungswiderstand nicht vergessen), reicht aber nicht, um Relais, Magnetspulen oder Motoren zu betreiben. Kurzschlüsse an denArduino-Pins können, ebenso wie zu hohe Ströme den Ausgangspin oder gar den ganzen Chip zerstören.
Aus dem Grund sollten Ausgangspinsmit einem Widerstand von 220 Ohm oder mehr geschützt werden.
digitalRead(pin)
Diese Funktion liest den Wert von einem digitalen Pin aus und liefert als Resultat entweder HIGH oder LOW.
Der Pin kann entweder als Variable oderKonstante festgelegt werden (0-13).
digitalWrite(pin,value)
gibt einen Logiklevel (HIGH oder LOW) an einem Pin aus. Der Pin kann als Variable oder Konstante festgelegt werden (0-13).
Das folgende Beispiel liesteinen Taster an einem digitalen Eingang aus und schaltet eine LED eine wenn der Taster gedrückt wird (auf ein Entprellen das Tasters wird hierverzichtet):
int led = 13; // LED an Pin 13int pin = 7; // Taster an Pin 7int value = 0;void setup() { pinMode(led, OUTPUT); // Pin 13 Ausgang pinMode(pin, INPUT); // Pin 7 Eingang }void loop() { value = digitalRead(pin); // Wert einlesen digitalWrite(led, value); // und ausgeben }
Analog-Funktionen
analogRead(pin)
Liest den Wert eines festgelegten analogen Pins mit einer 10 Bit Auflösung aus.
DieseFunktion ist nur für Pins (0-5) verfügbar. Die resultierenden Integer Werte reichen aufgrund der Auflösung von 0 bis 1023.
Analoge Pinsmüssen im Gegensatz zu digitalen nicht zuerst als Eingang oder Ausgang deklariert werden.
analogWrite(pin, value)
Gibt analoge Werte aus.
Der Prozessor verwendet dazu hardwarebasierte Pulsweitenmodulation (PWM) am Ausgangspin - es handelt sich also umRechteckimpulse mit variablem Impuls-Pausenverhältnis. Für einen "echten" Analogwert müssten noch ein Tefpassfilternachgeschaltet werden.
Auf Arduinos mit dem ATmega 168/368 ist diese Funktion für die Pins 3, 5, 6, 9, 10 und 11 verfügbar.
Der Wert kann alsVariable oder Konstante im Bereich von 0 bis 255 festgelegt werden.
Das folgende Beispiel zeigt, wie man eine LED heller oder dunkler werden lassen kann.
int ledPin = 13; // PWM Pin fuer die LEDint ADC0 = 0; // Analogeingangint wert, volt;void setup() { Serial.begin(9600); }void loop() { wert = analogRead(ADC0); // Geschwindigkeit per Poti am volt = wert*5.0/1024.0; // Analogeingang einstellen, Serial.println(volt); // Spannung ausgeben for (int i=0; i<=255; i++) // heller werden { analogWrite(ledPin, i); delay(wert); } for (int i=255; i>=0; i--) // dunkler werden { analogWrite(ledPin, i); delay(wert); } }
Port-Manipulation
Bei manchen Anwendungen sind die Arduino-Input/Output-Funktionen wie analogRead() oder digitalWrite() zu langsam.
In solchen Fällen kann man die Ports des ATmega 328 direkt manipulieren.
Für die unsprüngliche Zielgruppe waren die Einzelbit-Befehle sicher isdeal, denn beim Zugriff auf die 8-Bit-Portssind unter Umständen Bitmanipulationsoperationen wie AND, OR oder XORnotwendig um einzelne Bits zu ändern. So ist der Durchgriff auf die Portsca. 50 mal schneller.
Bei Arduino können drei Ports genutzt werden:
Port D des ATmega führt auf die digitalen Pins 0 bis 7, Port B auf die digitalen Pins 8 bis 13 und Port C auf die analogen Pins 0 bis 5.
Die Ports werden von 3 Registern gesteuert, die mit "DDRx", "PORTx" und "PINx"bezeichnet werden.
Diese drei Register existieren für jeden Port, es gibt also zum Beispiel für Port B ein DDRB-, ein PORTB- und ein PINB-Register. Das jeweilige DDRx-Register steuert, ob die Pins als Input oder Output konfiguriert sein sollen (DDR = Data Direction Register), das PORT-Register ist ein Ausgang, eslegt fest, ob die Pins HIGH oder LOW sind und das PIN-Register gibt den Zustand der Pins an, die im DDR-Register auf Input gesetzt wurden.
Ist im DDRx-Register ein Bit auf 1 gesetzt, so wird das korrespondierende Bit desPORTx (Ausgang) auf den Anschlusspin geschaltet, ist das DDRx-Bit dagegen auf 0gesetzt, wird der entsprechende Anschlusspin an das zugehörige PINx-Bit geleitet.
Die einzelnen Register sind in der Arduino-Entwicklungsumgebung bereits als Namen vordefiniert, man kann also sofort loslegen.
Es muss lediglich die Headerdateiavr/io.h eingebunden werden .
Beispielsweise setzt der folgendeBefehl D0, D2, D6 und D7 als Eingänge sowie D1, D3, D4 und D5 als Ausgänge.
DDRD = B00111010;
Ähnlich funktionieren Ein- und Ausgabe. Auch hier werden alle Bits gleichzeitigein- oder ausgegeben, z. B.:
PORTB = B00001100;
#include <avr/io.h>void setup() { DDRB = B11111111; // alle Bits als Ausgang }// Wechselblinker mit allen Ausgaengenvoid loop() { PORTB = B10101010; delay(300); PORTB = B01010101; delay(300); }
Zeitmessung
millis()
Gibt den (long-)Wert in Millisekunden zurück, die seit dem Programmstart vergangen sind.
Dieser Wert läuft nach etwa 9 Stunden über unddie Zählung beginnt dann wieder bei Null.
Das folgende Programm zeigt, wie man mit Hilfe von millis eine LED blinken lassen kann, ohne delay zu verwenden. Praktischerweise verwende ich die LED, die sich schon auf dem Board befindet.
const int ledPin = 13; // LED auf dem Boardint ledState = LOW; // LED-Statuslong previousMillis = 0; // Zeitpunkt des letzten LED- // Status-Wechselslong interval = 1000; // Blink-Intervall (Millisekunden)void setup() { pinMode(ledPin, OUTPUT); }void loop() { unsigned long currentMillis = millis(); // aktueller Wert if (currentMillis - previousMillis > interval) { previousMillis = currentMillis; // aktuellen Wert speichern ledState = ((ledState == LOW)? HIGH : LOW); // toggeln digitalWrite(ledPin, ledState); // LED ansteuern } }
Bei jedem Schleifendurchlauf wird also geprüft, ob es Zeit ist, die LED umzuschalten - wenn also die Zeitdifferenz zwischen aktuellem Wert und dem Zeitpunkt des letzten Wechsels größer als die vorgegebenen Differenz ist.
Dieses Schema kann durchaus mit mehreren Vorgängen und unterschiedlichen Zeiten verwendet werden.
Es gibt nur zwei kleine Nachteile.
Erstens gibt die Zeit für einen Schleifendurchlauf den minimalen Abstand zwischen zwei Aktionen vor und zweitens muss man eventuell überlegen, was nach knapp 50 Tagen passiert. Da ist nämlich gegebenenfalls der aktuelle Millis-Wert nicht größer, sondern sehr viel kleiner als der aktuelle und das Blinken endet scheinbar unmotiviert.
micros()
Gibt den (long-)Wert in Mikrosekunden zurück, die seit dem Programmstart vergangen sind. Dieser Wert läuft nach etwa 70 Minuten über unddie Zählung beginnt dann wieder bei Null. Bei einem mit 16 MHz getakteten Prozessor beträgt die Auflösung 4 Mikrosekunden, beilangsameren Boards entsprechend mehr.
delay(ms)
Wartet für die Dauer der angegebenen Zeit in Millisekunden.
delayMicroseconds(us)
Wartet für die Dauer der angegebenen Zeit in Mikrosekunden.
Relativ genaue Zeiten erhält man nur bei Werten bis 16383 (kann sich beispäteren Versionen ändern).
Mathematische Funktionen
min(x, y)
Berechnet das Minimum von zwei Werten und gibt den kleineren Wert zurück.
max(x, y)
Berechnet das Maximum von zwei Werten und gibt den höheren Wert zurück.
sin(x), cos(x), tan(x)
Trigonometrische Funktionen für Sinus, Cosinus und Tangens. Der Parameter wird jeweils im Bogenmass angegeben.
abs(x)
Absolutwert von x.
sqrt(x)
Quadratwurzel von x.
pow(x, exponent)
Berechnung von xexponent.
constrain(x, min, max)
Begrenzung einer Zahl auf einen Bereich. x ist der zu begrenzende Parameter. für den Rückgabewert gilt:
x: wenn x zwischen min und max liegt,
min: wenn x < min ist,
max: wenn x > max ist.
map(x, low1, high1, low2, high2)
Abbildung eines Wertes x aus dem Bereich [low1..high1] auf den Bereich [low2..high2].
Es wird keine Begrenzung des Parameters x auf den Bereich[low1..high1] vorgenommen.
Die map()-Funktion verwendet Integer-Arithmetik!Ist die untere Grenze eines Bereichs größer als die Obergrenze, wird der Wert invertiert.
Auch negative Grenzen sind erlaubt, zum Beispiel:
y = map(x, 1, 100, 100, 1);y = map(x, 1, 100, 100, -100);
randomSeed(seed)
Setzt einen Startwert (engl. "Seed" = "Saat") als Ausgangspunkt für die Funktion random(). Der Arduino kann keinewirklichen Zufallswerte produzieren - er generiert vielmehr Pseudo-Zufallsfolgen. Mit randomSeed() kann man bessere Zufallsergebnisse erhalten.Als Seed können so zum Beispiel millis() oder analogRead() verwendet werden.
random(min, max)
Diese Funktion erlaubt die Erzeugung von Pseudo-Zufallszahlen innerhalb eines definierten Bereiches. y = random(100, 200); besetzt beispielsweise y miteiner Zufallszahl zwischen 100 und 200. Wird der Minimalwert weggelassen, setzt die Funktion ihn auf 0.
Das folgende Beispiel simuliert Kerzenlicht.Dazu "flackern" zwei gelbe und eine rote LED mit zufälliger Helligkeit:
int gelb1 = 9;int gelb2 = 10;int rot = 11;void setup() { pinMode(gelb1, OUTPUT); pinMode(rot, OUTPUT); pinMode(gelb2, OUTPUT); }void loop() { // LEDs mit zufälliger Helligkeit, aber mindestens etwa halbhell analogWrite(gelb1, random(115,255); analogWrite(rot, random(115,255); analogWrite(gelb2, random(115,255); // etwas warten delay(random(100)); }
Konvertierung von Zahlen
Für das, was man bei C mittels Typecast erledigt, hat der Arduino passende Funktionen:
-
char() liefert das char-Äquivalent von x zurück
-
byte() liefert das byte-Äquivalent von x zurück
-
int() liefert das int-Äquivalent von x zurück
-
word() liefert das word-Äquivalent von x zurück
-
long() liefert das longint-Äquivalent von x zurück
-
float() liefert das floate-Äquivalent von x zurück
Serielle Kommunikation
Serial.begin(rate)
öffnet den seriellen Port und setzt die Baud Rate (Datenrate) für die serielle übertragung fest.
Die typische Baud Rate mit dem Computerist 9600 Baud.
Wird serielle Kommunikation verwendet, können die digitalen Pins 0 (RX) und 1 (TX) nicht gleichzeitig für andere Zweckeverwendet werden.
Es sind folgende Datenraten (Bit/s) möglich: 300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600 und 115200.
Während der seriellen übertragung blinkern die LEDs "TX" und "RX" auf dem Board.
Serial.print (data)
sendet Daten zum seriellen Port,
Serial.println(data)
sendet Daten zum seriellen Port, gefolgt von einem automatischen Zeilenumbruch als Carrier Return und Linefeed.
Die beiden Ausgabeanweisungen senden numerische Daten als Dezimalzahl über die Schnittstelle.
Es kann ein optionaler zweiter Parameter alsFormatangabe angegeben werden:
DEC entspricht dem dezimalen Defaultwert, HEX erzeugt eine Hexadezimalzahl, OCT eine Oktalzahl und BIN erzeugt einen8-Bit-Binärwert.
Strings werden als ASCII-Zeichen geschickt, wobei auch die bei C üblichen Ersatzdarstellungen für Tabulator, Linefeed,Return usw. verwendet werden können.
Weitere Informationen finden Sie auf der Arduino-Website.
Serial.write(data), Serial.write(buf,len)
sendet Daten binär zum seriellen Port.
Bei Byte-Varablen wird genau ein Byte gesendet, bei Strings der komplette String.
Bei zwei Parametern istbuf ein Array, das byteweise gesendet wird. len legt die Anzahl der zu sendenden Bytes fest.
Serial.available()
gibt an, ob sich Zeichen im Empfangspuffer befinden.
Serial.read()
liest ein Byte von der seriellen Schnittstelle.
Serial.flush()
Löscht den seriellen Puffer.
Serial.end()
schließt die serielle Schnittstelle.
Beispiel:
byte eingabe;void setup() { Serial.begin(9600); }void loop() { if (Serial.available() > 0) { eingabe = Serial.read(); Serial.write(eingabe); } }
Sonstige Funktionen
shiftOut(dataPin, clockPin, bitOrder, value)
arbeitet als Software-Schieberegister, wobei der Byte-Wert über den dataPin hinausgeschoben wird.
Der zugehörige Schiebetakt wird an clockPinerzeugt. BitOrder gibt die Reihenfolge an: MSBFIRST oder LSBFIRST (Most Significant Bit First oder Least Significant Bit First).
tone(pin, frequenz), tone(pin, frequenz, dauer)
erzeugt ein Rechtecksignal (50% Tastverhältnis) mit der gewünschten Frequenz auf dem angegebenen Pin.
Wird keine Dauer angegeben, endet derTon erst wieder, wenn die Funktion noTone() aufgerufen wird.
Am entsprechenden Pin kann ein Piezosummer angeschlossen werden.
Es kann immer nur ein Tonerzeugt werden, ein Aufruf von tone() mit einen anderen Pin hat keine Wirkung.
Die Funktion beeinflusst die PWM-Ausgabe auf den Pins 3 und 11!
noTone()
stoppt die Erzeugung eines Tons.
Das folgende Beispiel erzeugt einen Sirenenton:
int Speaker = 9, i; // Lautsprecherport, Zaehlvariablevoid setup() { pinMode(Speaker, OUTPUT); }void loop() { // ansteigender Ton for(i = 120; i < 160; i++) { tone(Speaker,500,i); delay(1); } noTone(); delay(100); }
Interrupts
Bei einem Interrupt wird durch ein Ereignis (z. B. Timer, Signal an einem Pin etc.) die Hauptschleife
loop() unterbrochen und ein vorher festgelegtes Unterprogramm ausgeführt.
Nach Abarbeitung des Unterprogrammes wird das Programm an der ursprünglichen Stelle fortgesetzt.
Das gewöhnliche Arduino-Board kann zwei Interrupts erfassen:
- Interrupt 0 am digitalen Port 2 und
- Interrupt 1 am digitalen Port 3.
Der Arduino Mega hat sogar vier zusätzliche externe Interrupts.
Daneben gibt es diverse interne Interrupts von Timer, seriellen Schnittstellen,
I
2C, SPI usw., die durch die Arduino-Software genutzt werden,ohne dass der Anwender dies wahrnimmt.
Man kann diese Voreinstellungenändern, muss dann aber in den C-Code der Arduino-Libraries und in das Core-Fileeingreifen.
Interrupts werden bei Eintritt in die Interrupt Service Routine automatisch gesperrtund nach Rückkehr ins Hauptprogramm wieder freigegeben.
Variablen aus dem Hauptprogramm, die in der Interrupt Service Routine geändert werden sollen müssen als "volatile" deklariert werden.
Andernfalls nimmt der Compiler an, dass sich diese Variablen niemalsändern und er ersetzt sie schon beim Kompilieren durch Konstante. Beispiel:
volatile unsigned int Counter;
Damit der Arduino weiss, wie er auf Interrupts reagieren und welches Unterprogramm er beim Auftreten eines Interrupts ausführen soll, muss dessen Adresse festgelegt werden.
Dies erledigt man im
setup() mit der Methode
attachInterrupt().Die Interrupt-Funktionen sind:
attachInterrupt(interrupt, function, mode)
legt eine Interrupt-Serviceroutine (ISR) fest.
Die meisten Arduinos erlauben zwei externe Interrupts: Int 0 auf Digital-Pin 2 und Int 1 auf Digital-Pin3 (beim "Mega" kommen noch die Pins 4 und 5 hinzu).
Der Parameter interrupt legt die Nummer des Interrupts fest, function ist die Adresseder ISR (einfach den Funktionsnamen angeben) und mode definiert die Auslösebedingung:
- LOW: Interrupt, wenn der Pin Low-Pegel hat,
- CHANGE: Interrupt, wenn der Pin seinen Wert wechselt,
- RISING: Interrupt bei steigender Flanke am Pin,
- FALLING: Interrupt bei fallender Flanke am Pin.
Beim Arduino Due gibt es noch den Mode HIGH, wobei die Modi HIGH und LOWin den seltensten Fällen zur Anwendung kommen.
Innerhalb der ISR funktioniert delay() nicht und der Ergebniswert von millis() bleibt konstant.
Serielle Daten können verloren gehen. Die ISRsollte, wie bei Assembler, so kurz wie möglich gehalten werden.
Alle globalen Variablen, die in der ISR verändert werden, müssen alsvolatile deklariert werden.
detachInterrupt(interrupt)
Schaltet den per attachInterrupt() aktivierten Interrupt wieder aus.
interrupts()
Erlaubt Interrupts (wird nach einem Aufruf von noInterrupts() benötigt).
noInterrupts()
Alle Interrupts abschalten.
Da, wie schon erwähnt, die delay()-Funktion, millis() sowie dieserielle Schnittstelle auch mit Interrupts arbeiten, können diese Funktionennicht innerhalb einer Interruptserviceroutine verwendet werden.
Überhauptsollte die Routine so kurz und einfach wie möglich gehalten werden (eventuellnur Setzen von Flag-Variablen, die dann in loop() entsprechendeAktionen auslösen.
Ein Beispiel soll das Ganze etwas weiter verdeutlichen:
// irgendein Sensor ist an D2 angeschlossen#define Sensor 2// Ausgabe-LED#define LED 13volatile int zustand = LOW; // Kommunikation zwischen ISR und Hauptprogrammvoid setup() { pinMode(Sensor, INPUT); digitalWrite(Sensor, HIGH); // schaltet den Pullup-Widerstand ein pinMode(LED, OUTPUT); // Interrupt aktivieren attachInterrupt(0, MachWas, CHANGE); }void loop() { digitalWrite(ledPin, zustand); }// Interrupt-Service-Routinevoid MachWas() { zustand = ! zustand; }
Timer
Timer steuern zeitliche Abläufe im Prozessor, beispielsweise für die Zeitmessung,PWM (Pulsweitenmodulation) oder die Erzeugung von Tönen.
Durch geschickte Programmierungund Abfrage der
millis() lassen sich zwar viele Dinge ohne Timer erledigen.
Wenn es aber auf exakte Zeiten ankommt, geht es nicht ohne Timer.
Der Arduino besitzt einen 16-MHz-Quarz, weshalb der interne Taktgeber alle 62,5 Nanosekunden einen Impuls liefert.
Für die meisten Anwendungen ist das zu schnell. Deshalb gibt es den so genannten Prescaler, mit dem die diese Zeitbasis reduziert werden kann.
Diesist ganz einfach ein binärer Teiler für die Taktfrequenz.
Ein Prescaler-Wert von 8 erhöht die Pulsdauer auf 16/8 = 2, also eine halbe Mikrosekunde.
Nach dem Systemstart werden alle Timer mit einem Prescaler-Wert von 64 initialisiert.
Der Arduino hat, wie schon erwähnt, drei Timer. Jeder dieser Timer ist zwei PWM-fähigen Ausgangs-Pins zugeordnet:
Timer
|
Pin
|
Standardeinstellung
|
0
|
5, 6
|
1000 Hz
|
1
|
9, 10
|
500 Hz
|
2
|
3, 11
|
500 Hz
|
Es ist ohne großen Aufwand möglich, die PWM-Frequenz unseren konkreten Anforderungen anzupassen, indem die entsprechenden Register des Mikroprozessors direkt angesprochen werden.
Achtung:
Der Timer 0 ist u.a. um die Funktionen millis() und delay() zuständig.
Eine Änderung der Werte für den Timer 0 sollte also nur durchgeführt werden, wenn man genau weiss, was man tut.
Es folgt eine Übersicht der Timer-Register. Das Zeichen "x" ist ein Platzhalter für die Nummer des Timers.
Bezeichnung
|
Bedeutung
|
Funktion
|
TCCRxA
|
Timer Counter Control Register A
|
legt Betriebsart fest
|
TCCRxB
|
Timer Counter Control Register B
|
legt Prescaler fest
|
TCNTx
|
Timer Counter Register
|
Timer-Zähler
|
OCRxA
|
Output Compare Register A
|
Register für Interrupt-Auslösung
|
OCRxB
|
Output Compare Register B
|
Register für Interrupt-Auslösung
|
TIMSKx
|
Register Timer Counter Interrupt-Maskierung
|
Bedingungen für Interrupt-Auslösung
|
TIFRx
|
Timer Counter 0 Interrupt Flag Register
|
Anzeige ob Trigger-Bedingung vorliegt
|
Wer tiefer in die Timer-Programmierun einsteigen will, kommt um die Beschäftigungmit den o. a. Registern nicht herum. Für einfache Aufgaben gibt es die TimerOne-Library (http://playground.arduino.cc/uploads/Code/TimerOne.zip).
Die Bibliothek wird zunächst in das Libraries-Verzeichnis entpackt und anschließend mit #include in den Quelltext eingebunden.
Wenn Sie TimerOne verwenden, funktioniert analogWrite() für diedigitalen Pins 9 and 10 nicht mehr. Sie ist relativ übersichtlichund besteht aus folgenden Methoden:
initialize(period)
Diese Methode muss vor allen anderen in setup() aufgerufen werden. Optionalkann die Periodendauer des Timers in Mikrosekunden angegeben werden.
Per Default ister auf 1 Sekunde eingestellt.
setPeriod(period)
Setzt die Periodendauer des Timers in Mikrosekunden. Der Wert muss zwischen 1und 8388480 (ca. 8,3 Sekunden) liegen.
Beim Aufruf wird auch der Interrupt unddie PWM-Frequenz bzw. das Tastverhältnis geändert.
pwm(pin, duty, period)
Erzeugt ein PWM-Signal am angegebenen Pin (für Timer 1 sind dies PORT B 1 und 2,entsprechend den Pins 9 und 10). der zweite Parameter "duty" legt das Tastverhältnisfest, das zwischen 0 und 1023 liegen darf. Optional kann noch die Periodendauerin Mikrosekunden angegeben werden.
attachInterrupt(function, period)
Ruft immer wieder im durch "period" (in Mikrosekunden) Zeitabstand, dieFunktion auf, deren Name als "function" übergeben wird.
Denken Sie bittedaran, dass die Interruptserviceroutine innerhalb des durch "period" angegebenenZeitraums abgearbeitet und wieder beendet sein muss.
Andernfalls wird dieloop() niemals mehr durchlaufen. Fehlt der zeite Parameter, wird die vorherige Einstellung übernommen.
setPwmDuty(pin, duty)
Erlaubt das einfache Ändern des Tastverhältnisses. pwm() muss davormindestens einmal aufgerufen worden sein.
Danach kann man dann das Tastverhältnis mit setPwmDuty() schneller ändern.
detachInterrupt()
Disabled den mit attachInterrupt() initiierten Interrupt.
disablePwm(pin)
Schaltet die PWM-Ansteuerung des entsprechenden Pin ab.
read()
Gibt die Zeit in Mikrosekunden seit dem letzten Timerüberlauf zurück.
Das folgende Beispiel aus der Timer-Bibliothek erzeugt ein PWM-Signal an Pin 9 mit 50% Tastverhältnis und erzeugt einen Timer-Interrupt der den Pin 10 alle 500 ms umschaltet:
#include "TimerOne.h"#define LED 13 void setup() { pinMode(LED, OUTPUT); Timer1.initialize(500000); // initialize timer1, and set a 1/2 second period Timer1.attachInterrupt(callback); // attaches callback() as a timer overflow interrupt Timer1.pwm(9, 512); // setup pwm on pin 9, 50% duty cycle } void callback() { // Die LED blinkt unabhängig vom Programmcode in loop() digitalWrite(LED, digitalRead(LED) ^ 1); // EXOR invertiert } void loop() { // Der ganze Rest des Programms }
Alternativ kann man das Tastverhältnis per Potentiometer am analogenEingang steuern.
#include // LED an Pin 9#define PWMout 9// Analogeingang #define PWMsoll A0int PWMsollValue = 0; // aktueller Poti-Wertint t = 1000; // t in Mikrosekundenvoid setup() { Timer1.pwm(PWMout, PWMsollValue, t); // PWM initiieren } void loop() { // Einlesen des Potis (0..1023) PWMsollValue = analogRead(PWMsoll); // PWM-Tastverh. aendern setPwmDuty(PWMout, PWMsollValue); }
Bibliotheken, Header-Dateien
Wie schon erwähnt, lehnt sich die Arduino-Programmiersprache an C bzw. C++ an - nicht zuletzt, weil genau diese Sprachen auch "imHintergrund" wirken.
So ist es jederzeit möglich, auf C oder C++ auszuweichen und den erzeugten Binärcode in den Arduino zu laden.
Etliche der zahlreichen Bibliotheken (Libraries) des Arduino sind in C, C++ oder Assembler geschrieben.
Wie man eigene Bibliotheken erstellt schildertdie Dokumentation in einer Schritt-für-Schritt-Anleitung.
Sie können auch eine Bibliothek hinzufügen, die Sie beispielsweise aus demInternet herunter geladen haben.
Der entpackte Ordner der Bibliothek muss in den "Libraries"-Ordner im Sketchbook kopiert werden.
Gegebenfalls muss man ihn anlegen. Nach dem Neustart der IDE steht die Bibliothek zum Import bereit.
Meist müssen Sie auch nicht selbst programmieren, denn neben der Standard avr-libc gibt es u. a. Bibliotheken für:
-
EEPROM: Zugriff auf das interne EEPROM
-
Ethernet: benötigt ein Ethernet-Erweiterungsboard
-
LiquidCrystal: Anbindung von LCDs
-
SD: Anbindung von SD-Karten
-
Servo: Modellbau-Servos steuern
-
SPI: Zugriff auf den SPI-Bus
-
SoftwareSerial: emulierte serielle Schnittstelle auf digitalen Ports
-
Stepper: Steuern von Schrittmotoren
-
Wire: Zugriff auf die I2C-Schnittstelle
-
Matrix: Steuern von LED-Matritzen
-
Sprite: Animationen auf LED-Matritzen
Egal was man mit dem Arduino anfangen möchte, es gibt eigentlich immer schon eine komplette Bibliothek dazu.
Das vereinfacht vor allem das Programmieren für diejenigen, die bisher noch keine Erfahrung mit Mikrocontrollern haben.
An dieser Selle solle es beispielhaftum die Anwendung solcher Bibliotheken gehen.
Will man Bibliotheken verwenden, müssen sie, wie bei C üblich, ins Programm eingefügt werden.
Im Hauptmenü der IDE finden Sie unter "Sketch" den Befehl "Import Library".
Hier wählt man einfach die Bibliothek aus, die man verwenden will und im Sketch erscheint die Include-Zeile, etwa: #include <Servo.h>
Hiermit wird auf die in der Arduino IDE enthaltene Servo-Library zurückgegriffen.
Modellbau-Servos sind eine geschlossene Motoreinheit, deren Achse sich in einem Winkel von 180 oder 270 Grad bewegen lässt.
Ein auf der Achse sitzender Hebel betätigt dann die Ruder oder Klappen des Modells proportional zur Auslenkung des Steuerknüppels der Fernsteuerung.
Ein Servo enthält somit einem internen Regelkreis.
Als Stellgröße wird alle 20 ms ein Impuls mit einer Dauer von 1 ms bis 2 ms gesendet (Ruder auf Links- bzw. Rechtsanschlag).
Im folgenden Beispiel wird eine servoPulse-Funktion genutzt um das Servo von 1 bis 179 Grad und wieder zurück zu bewegen
(das Beispiel findet sich auch in der IDE unter "File" → "Examples" → "Servo"):
// Headerfile für die Servosteuerung einbinden#include <Servo.h>// Erzeugen eines Servo-ObjektsServo myservo;int pos = 0; // Speichern der Positionvoid setup() { myservo.attach(9); } // Servo-Datenleitung an Pin 9void loop() { // mit mittlerer Geschwindigkeit nach rechts drehen for(pos = 1; pos < 180; pos++) { myservo.write(pos); delay(15); } // mit mittlerer Geschwindigkeit nach links drehen for(pos = 179; pos > 0; pos--) { myservo.write(pos); delay(15); } }
Das Servo verträgt Steuerwerte zwischen 0 und 180, was bei einen 180-Grad-Servo exakt dem Winkel der Servoachse entspricht.
Im folgenden Beispiel wird eine per A/D-Wandler gelesene Eingangsspannung in eine Servoauslenkung transponiert.
#include <Servo.h> Servo myservo; // neues Servo-Objekt erzeugen int potpin = 0; // Analogpin, an dem das Potenziometer haengtint val; // Hilfsvariable pin void setup() { myservo.attach(9); // ordnet Pin 9 der Servo-Steuerleitung zu } void loop() { val = analogRead(potpin); // Poti einlesen (0 - 1023) val = map(val, 0, 1023, 0, 179); // Skalierung auf zulaessige // Servo-Werte (max. 180) myservo.write(val); // Servo positionieren delay(15); // etwas warten }
Beachten Sie, dass die Servo-Library Timer 1 verwendet und Sie deshalb keine PWM-Ausgabe bei den Pins 9 und 10 tätigen können.
Beispiele
1. Temperaturerfassung
Als erstes Beispiel soll die Erfassung der Temperatur dienen. Halbleiter-Temperatursensoren geben ein Ausgangssignal ab, das proportional zur Umgebungstemperatur ist.
Weit verbreitet sind die Typen LM35 bis LM335, die ein Ausgangssignal zur Verfügung stellen, das direkt proportional zur Celsius-Skala ist.
Die Bausteine werden in verschiedenen Genauigkeitsklassen über verschiedene Temperaturbereiche bereits auf dem Wafer abgeglichen, sodass ein externer Abgleich nicht vorgesehen werden muss. Insofern soll nur der preiswerte Typ LM335 betrachtet werden, der sich auf den absoluten Nullpunkt (-273,15 °C) bezieht und eine Spannung von 10 mV/K am Ausgang liefert. Bei 25° Celsius sollte sich also eine Spannung von etwa 2,98 V am Ausgang messen lassen.
Diese liegt etwa in der Mitte des Messbereichs des Arduino und daher kann der Sensor direkt angeschlossen werden.
Zum Betrieb benötigt er nur noch einen Widerstand von 2 Kiloohm gegen +5 V.
Für die Umrechung des Spannungswertes in die Temperatur gilt
temp = Uein * 100 - 273,15
Da der A/D-Wandler jedoch nicht die Eingangsspannung, sondern einen Wert von 0 bis 1023 liefert, der den Spannungsbereich von 0 bis 5 V entspricht, muss der Arduino folgendermaßen rechnen:
temp = Vcc * Uein / 1024.0 * 100.0 - 273.15
Für die Gleitpunktarithmetik mit begrenzter Stellenzahl ist die Formel jedoch ungünstig, da erst durch 1024 geteilt und dann mit 100 multipliziert wird.
Der bei der Division eventuell entstehende Rundungsfehler multipliziert sich anschließend auch mit dem Faktor 100. Also stellt man besser um:
temp = (Vcc * Uein * 100 / 1024.0) - 273.15 ' temp = (Vcc * Uein / 10.24) - 273.15
Beim preiswerten LM335 kann es vorkommen, dass das Sensorausgangssignal von der tatsächlichen Temperatur etwas abweicht.
Hier kann entweder per Hardware am Eingang adj des LM335 die Ausgangsspannung korrigiert werden oder man verwendet im Programm einem Korrekturfaktor.
Beim Einlesen von Sensordaten stellen sich auch immer leichte Schwankungen der gelesenen Werte ein, die beispielsweise durch elektromagnetische Störeinflüssen oder leichte Schwankungen der Messgröße hervorgerufen werden. Um den Eingangswert zu stabilisieren, kann man gegebenenfalls mehrmals den Wert erfassen und dann einen Mittelwert bilden. Das erste Testprogramm liest den Temperaturwert und gibt ihn auf der seriellen Schnittstelle aus.
// Analog input pin #define analogInPin A0// Referenzspannung A/D-Wandler#define Vcc 5.0int sensorValue = 0; // Sensorwertfloat temp; // Temperaturwertvoid setup() { // serielle Schnittstelle einschalten und auf 9600 bps setzen Serial.begin(9600); }void loop() { // Analogwert lesen sensorValue = analogRead(analogInPin); // in Celsiusgrade umrechnen temp = (Vcc * sensorValue / 10.24) - 273.15; // Ausgabe auf dem seriellen Monitor Serial.print("Sensor = " ); Serial.print(sensorValue); Serial.print("\t Temperatur = "); Serial.println(temp); // 1/2 Sekunde warten delay(500); }
2. Balkenanzeige
Für eine autarke Temperaturanzeige könnte man eine Siebensegmentanzeige ansteuern.
Wenn es nicht auf einen exakten Zahlenwert, sondern nur auf eine ungefähre Angabe ankommt, bieten sich auch andere, recht übersichtliche Anzeigemöglichkeiten an - etwa die lineare Anzeige eines herkömmlichen Themometers.
Das folgende Programm zeigt das Prinzip unter Verwendung der Digitalports des Arduino.
Deren Zahl ist allerdings nicht groß genug für eine halbwegs genaue Darstellung des aktuellen Tempera-turwertes.
Man müsste für eine praxistaugliche Lösung eine Erweiterung der Digital-ports vornehmen, etwa über Schieberegister.
Beim folgenden Beispiel fehlen die serielle Ausgabe und die Umrechnung in Celsiusgrade.
Die Digitalpins werden über ein Array adressiert, was das Programm beim Setup wie auch in der Hauptschleife wesentlich verkürzt, da sie per Schleife angesprochen werden:
// Analog input pin #define analogInPin A0// Referenzspannung A/D-Wandler#define Vcc 5.0int sensorValue = 0; // Sensorwertint pin[] = {10,9,8,7,6,5,4,3}; // Digital-Ausgängeint i; // Zählervoid setup() { for (i = 0; i < 8; i++) pinMode(pin[i], OUTPUT); }void loop() { // Analogwert lesen sensorValue = analogRead(analogInPin); // auf den Bereich 0 .. 8 skalieren sensorValue = map(sensorValue, 0, 1023, 0, 8); // Balkenanzeige for (i = 0; i < 8; i++) digitalWrite(pin[i],(i < sensorValue)); // 1/2 Sekunde warten delay(500); }
Die Funktion
map() bildet den Analogbereich von 0 bis 1023 auf die Zahl der LEDs (0 bis 8) ab. Bei der Balkenanzeige wird auch ein Programmiertrick verwendet:
Der Ausdruck
(i < sensorValue) ist entweder TRUE oder FALSE, was den binären Ausgabewerten LOW und HIGH entspricht.
Angenommen der (gemappte) Sensorwert ist 4, dann sind die LEDs 0, 1, 2, 3 eingeschaltet und 4, 5, 6, 7 ausgeschaltet.
3. Analogeingabe
Den Beschleunigungssensor ADXL 335 entstammt einer ganzen Familie von Beschleunigungssensoren, die zwei oder drei Achsen auswerten können und unterschiedliche Maximalbeschleunigungen "vertragen".
Auch ist die gesamte analoge Elektronik bereits auf dem Chip realisiert, sodass es nunr noch für jede Achse einen Ausgang gibt, dessen Spannung der gemessenen Beschleunigung proportional ist.
Der Ausgangsspannungsbereich passt ideal zum Arduino-Eingang.
Zum Anschluß an den Arduino werden die Anschlüsse Vcc, GND, X, Y und Z des Beschleunigungssensors mit kurzen Kabelstücken versehen.
An diese Kabel wird eine Stiftleiste gelötet, die zur Belegung des Arduino passt: X, Y und Z werden mit den Analog-Eingängen 1, 2 und 3 des Arduino verbunden.
Der Anschluss ST (Self Test) am Sensor wurde nicht belegt.
Die Versorgungsspannung für den Beschleunigungssensor ist laut Datenblatt mit 1,8 bis 3,6 V angegeben, der Sensor muss also aus dem 3,3-V-Anschluss des Arduino versorgt werden (nicht an 5 V!). Je nachdem, wie Sie das Board neigen, erhalten Sie entsprechende Werte des ADXL335.
Die Ausgabespannung des Sensors ist recht gut mit dem Controller kompatibel, sodass keine weitere Hardware nötig ist.
Der ADXL335 misst Beschleunigungen bis maximal 3 g. Braucht man andere Maximalwerte kann man u.a. ADXL322 (2 g), ADXL321 (18 g) oder ADXL 305 (5 g) verwenden.
Das Testprogramm gestaltet sich recht kurz:
// Pinbelegungconst int xpin = 1; // X-Achseconst int ypin = 2; // Y-Achseconst int zpin = 3; // Z-Achsevoid setup() { Serial.begin(9600); }void loop() { // Sensorwerte ausgeben Serial.print("X: "); Serial.print(analogRead(xpin)); Serial.print(", Y: "); Serial.print(analogRead(ypin)); Serial.print(", Z: "); Serial.println(analogRead(zpin)); // etwas warten delay(200); }
Im seriellen Monitor können jetzt die Werte für die drei Achsen X, Y, und Z abgelesen werden.
Dank der Erdbeschleunigung von 9.81 m/s2 kann die Funktion des Sensors ohne weitere Hilfsmittel überprüft werden.
Liegt beispielsweise die X-Achse genau waagrecht (parallel zum Erdboden, stellt sich ein Ausgangswert um 512 ein, bei negativem Winkel wird der Wert größer, bei positiver Neigung kleiner. Der Arduino mit diesem Sensor würde sich also auch als X-Y-Z-"Wasserwaage" eignen. Kombiniert mit der Datenaufzeichnung auf SD-Karte (siehe "Datalogger") und per Akku gespeist kann man den Arduino auch im Paket auf Reisen schicken und anschließend feststellen, wie gut oder schlecht das Paket behandelt wurden und wie lange es in irgend einem Lager herumlag.
4. Soft-Blinker
Dies Beispiel realisiert einen "Soft-Blinker", indem die LED per PWM mit einem sinusförmigen Signal angesteuert wird. Per Programm wird in 1-Grad-Schritten der Sinus im Bereich von 0 bis 180° berechnet und mit 255 multipliziert.
Da der Sinus in diesem Bereich nur Werte zwischen 0 und 1 annehmen kann, ergibt dass die ideale Ansteuerung für den Analogausgang.
// Pin der roten LED#define LEDRot 9// eine bekannte Konstante#define Pi 3.14159265int wert; // Ausgabewertint i; // Schleifenzaehlervoid setup() { // muss nicht sein, kann aber ... pinMode(ledPin, OUTPUT); }void loop() { for (i = 0; i < 180; i++) { // Sinus berechnen (Achtung: Bogenmass!) wert = int(sin(i*Pi/180)*255); analogWrite(ledPin, wert); delay(10); } }
Das Programm belastet den Prozessor durch die Sinusberechnung relativ stark.
Eine Alternative wäre ein Array mit den 180 Sinuswerten (gleich mit 255 multipliziert), auf das dann mit Index i zugegriffen wird.
5. Farbwechsel
Nachdem das so schön klappt, werden im nächsten Programm alle drei LEDs eingesetzt und ein Farbwechsel realisiert.
In der Funktion
setLeds() gibt es eine sinnvolle Anwendung der Speicherklasse
static.
Die aktuellen LED-Farbintensitäten werden in den Variablen
red, green und
blue von Aufruf zu Aufruf weitergegeben.
Ohne
static funktioniert es nicht, es würde immer mit Rot begonnen werden.
Die Funktion kann mit Werten zwischen 0 und 764 aufgerufen werden.
// Pinzuordnung der LEDs#define LEDBlau 11#define LEDGruen 10#define LEDRot 9void setup() { pinMode(LEDBlau, OUTPUT); pinMode(LEDGruen, OUTPUT); pinMode(LEDRot, OUTPUT); }void loop() { int licht; for (licht = 0; licht < 765; licht++) { setLEDs(licht); delay(20); } }void setLEDs(int i) { // Farbwerte mit Vorbesetzung, begonnen wird mit rot static int red = 255; static int green = 0; static int blue = 0; if (i < 255) // Phase 1: von rot nach grün { red--; // red down green++; // green up blue = 0; // blue low } else if (i < 510) // Phase 2: von grün nach blau { red = 0; // red low green--; // green down blue++; // blue up } else if (i < 766) // Phase 3: von blau nach rot { red++; // red up green = 0; // green low blue--; // blue down } analogWrite(LEDRot, red); analogWrite(LEDGruen, green); analogWrite(LEDBlau, blue); }
In
vorangegangenen Beispiel sind die Farbübergänge zwischen den LEDs nicht gleichmäßig.
Das liegt daran, dass das Auge die Lichtintensität nicht linear, sondern eher logarithmisch wahrnimmt. Ist die LED ganz dunkel, werden kleine Änderungen gut wahrgenommen, ist die LED dagegen schon ziemlich hell, merkt man die Zu- oder Abnahme erst nach mehreren Schritten.
Um das auszugleichen, kann bei der Ausgabe der Farbwert entsprechend angepasst werden.
Dies erfolgt am Besten mithilfe einer Umrechnungstabelle, die in einem Array gespeichert wird.
So ist es im folgenden Programm realisiert. Die Umcodierung erfolgt in der Funktion setColor(), in der auch die Ausgabe erfolgt.
In setLEDs() wird dann der auszugebende Farbwert berechnet. Im Gegensatz zum vorhergehenden Programm kann hier jeder individuelle Wert gesetzt werden.
Für die Eingabewerte von 0 bis 255 durchläuft die Funktion einmal den Farbkreis.
// Pinzuordnung der LEDs#define LEDBlau 9#define LEDGruen 10#define LEDRot 11void setup() { pinMode(LEDBlau, OUTPUT); pinMode(LEDGruen, OUTPUT); pinMode(LEDRot, OUTPUT); }void loop() { int licht; for (licht = 0; licht < 256; licht++) { setLEDs(licht); delay(100); } }// Wählt für einen Wert (0 .. 255) eine Farbe aus dem Farbkreisvoid setLEDs(int value) { int red, green, blue; if(value < 64) // rot nach gruen { red = 63 - value; green = value; blue = 0; } else if(value < 128) // gruen nach blau { red = 0; green = 127 - value; blue = value - 64; } else if(value < 192) // blau nach rot { red = value - 128; green = 0; blue = 191 - value; } else // rot nach weiss { red = 63; green = 255 - value; blue = 255 - value; } setColor(red, green, blue); }// Stellt die LED-Helligkeiten logarithmisch einvoid setColor(int red, int green, int blue) { // Tabelle der Helligkeitswerte, // logarithmisch ansteigend int logValue[64] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 25, 28, 30, 33, 36, 39, 42, 46, 53, 56, 60, 64, 68, 72, 77, 81, 86, 90, 95,100,105, 110,116,121,127,132,138,144,150, 156,163,169,176,182,189,196,203, 210,218,225,233,240,248,253,255 }; analogWrite(LEDBlau, logValue[blue]); analogWrite(LEDGruen, logValue[green]); analogWrite(LEDRot, logValue[red]); }
Manchmal
ist das RGB-Farbschema jedoch hinderlich.
Es wäre viel praktischer, wenn man im Programm nur einen einzigen Wert für die Farbe hätte und nicht drei.
Genau das erreichen Sie mit dem HSV-Farbschema. Der HSV-Farbraum beschreibt eine Farbe mit Hilfe des Farbtons (englisch hue), der Farbsättigung (saturation) und des Hellwerts (value).
Bei der Farbdarstellung wird der HSV-Farbraum gegenüber den Alternativen RGB (Rot, Grün, Blau) und CMYK (Cyan, Magenta, Yellow, Black) bevorzugt, weil es leichter fällt, eine Farbe zu finden: Man kann für die Farbmischung unmittelbar den Farbton wählen und dann entscheiden, wie gesättigt und wie hell (oder dunkel) dieser sein soll.
Für die Beschreibung des Farbortes im HSV-Farbraum werden folgende Parameter benutzt:
-
Farbton (hue) als Winkel H auf dem Farbkreis (z. B. 0° = Rot, 120° = Grün, 240° = Blau).
-
Die Farbtoneinstellung erfolgt durch Weiterdrehen auf einen der benachbarten Farbwerte.
-
So kann z.B. ein Blau in Richtung Cyan und Grün oder in Richtung Violett und Rot verschoben werden.
-
Der Farbton wird als Position auf dem Stan-dard-Farbkreis angegeben und daher in Werten zwischen 0° und 360° ausgedrückt.
-
Sättigung (saturation) S in Prozent (0 % = Neutralgrau, 50 % = wenig gesättigte Farbe, 100 % = gesättigte, reine Farbe).
-
Alternativ wird ein Intervall von 0 bis 1 verwendet.
-
Vom Sättigungsgrad einer Farbe ist es abhängig, ob wir einen Farbton als satt und kräftig oder als matt und schwach empfinden.
-
Sie beschreibt also das Verhältnis zwischen Farbe und Grauanteil. Auf dem Farbkreis nimmt die Sättigung vom Rand zur Mitte hin ab.
-
Helligkeit (value oder brightness) V als Prozentwert (0 % = keine Helligkeit, 100 % = volle Helligkeit).
-
Alternativ wird auch hier ein Intervall von 0 bis 1 verwendet, das auch "Dunkelstufe" genannt wird.
-
Das folgende Programm rechnet die HSV-Angaben in Werte für Rot, Grün und Blau um, die dann zur Ansteuerung der LEDs dienen.
Das Programm weicht vom oben angegebenen Schema insofern ab, als dass anstelle der Prozentangaben bereits die bei der Ausgabe möglichen Werte 0 bis 255 verwendet werden. Auf diese Weise kommt das Programm auch mit Integer-Arithmetik aus, was der Rechenzeit und dem Speicherbedarf zu Gute kommt.
// Pinzuordnung der LEDs#define LEDBlau 9#define LEDGruen 10#define LEDRot 11void setup() { pinMode(LedBlau, OUTPUT); pinMode(LedGruen, OUTPUT); pinMode(LedRot, OUTPUT); }void loop() { int licht; for (licht = 0; licht < 360; licht++) { // kompletten Farbkreis durchlaufen setLED(licht,255); delay(100); } }void setLED(int hue, int l) { // LED-Farben festlegen nach HUE und Intensität. // Sättigung ist hier immer auf Maximum gesetzt int col[3] = { 0,0,0 }; getRGB(hue, 255, l, col); // HSV in RGB umrechen analogWrite(LedRot, 255 - col[0]); // und ausgeben analogWrite(LedGruen, 255 - col[1]); analogWrite(LedBlau, 255 - col[2]); }void getRGB(int hue, int sat, int val, int colors[3]) { // Diese Funktion rechnet einen HSV-Wert in die ensprechenden // RGB-Werte um. Diese werden im Array ‚colors' zurückgegeben // colors[0] = ROg, colors[1]} = Gruen, colors[2] = Blau // hue: 0 - 359, saturation: 0 - 255, val (lightness): 0 - 255 int r, g, b, base; if (sat == 0) { // Sättigung = 0 --> Grauwert colors[0] = val; colors[1] = val; colors[2] = val; } else { base = ((255 - sat) * val) >> 8; if (hue < 60) { red = val; green = (((val - base)*hue)/60) + base; blue = base; } else if (hue < 120) { red = (((val - base)*(60-(hue%60)))/60) + base; green = val; blue = base; } else if (hue < 180) { red = base; green = val; blue = (((val - base)*(hue%60))/60) + base; } else if (hue < 240) { red = base; green = (((val - base)*(60 - (hue%60)))/60) + base; blue = val; } else if (hue < 300) { red = (((val - base)*(hue%60))/60) + base; green = base; blue = val; } else if (hue < 360) { red = val; green = base; blue = (((val - base)*(60 - (hue%60)))/60) + base; } colors[0] = red; colors[1] = green; colors[2] = blue; } }
Quelle:
FH München, FB 04, Prof. Jürgen Plate
DIN A4 ausdrucken
*********************************************************
Impressum: Fritz Prenninger, Haidestr. 11A, A-4600 Wels, Ober-Österreich, mailto:[email protected]
ENDE