http://sites.schaltungen.at/arduino-uno-r3/beginnerWels, am 2016-11-20BITTE nützen Sie doch rechts OBEN das Suchfeld [ ] [ Diese Site durchsuchen]DIN A3 oder DIN A4 quer ausdrucken ********************************************************************************** DIN A4 ausdrucken
*********************************************************
https://www.arduino.cc/en/Tutorial/HomePage http://www.ladyada.net/learn/arduino/ https://learn.sparkfun.com/tutorials/what-is-an-arduino http://www.sainsmart.com/sainsmart-uno-r3-starter-kit-with-16-basic-arduino-projects.html 704_b_ARDUINO-x_github - Beginner Kit for Arduino - Tutorial (133 Seiten)_1a.pdf ARDUINO für Beginner Dr. Mathias Wilhelm's pages on ARDUINO UNO R3 Schritt 1: Das erste Board Schritt 2: Legen wir los Schritt 3: Den Arduino steuern Schritt 4: Den PC als Dashboard Schritt 5: Jetzt wird es bunt http://www.mathias-wilhelm.de/arduino/beginner/schritt-5-jetzt-wird-es-bunt/ Schritt 6: LCD Displays (1602) Schritt 7: Pulsweitenmodulation (PWM) Bücher: Für meinen Teil kann ich zwei Bücher empfelen: Arduino Praxiseinstieg - Thomas Brühlmann - behandelt Arduino 1.0 - mitp-Verlag Die elektronische Welt mit Arduino entdecken - Erik Bartmann - O'Reilly-Verlag
Quelle:
http://www.mathias-wilhelm.de/arduino/beginner/ ********************************************************* BUCH: Arduino für KIDs, Erik Schernich, mitp-Verlag
ISBN 978-3-8266-9470-7
http://www.mitp.de/9470 Stückliste: 1x Arduino Uno 1x Breadboard Jumper-Set oder Kabel Dm 0,64mm 5x verschiedenfarbige LEDs (min. 5, in rot, gelb, grün) 1x Widerstandsset 1x RGB-LED (mehrfarbige LED) 1x Batteriepaket (8x Mignon = 9,6Vdc) 1x Fotowiderstand LDR07 3x Buttons (Miniatur Taster 7x7mm) 1x Piezo-Lautsprecher - Buzzer 1m abisoliertes Kabel Dm 0,64mm 2x Transistoren TUN TUP 2x DC-Motoren 5V 1x Poti 10k 1x Servo-Motor 1x L293D Motortreiber 1x LCD (mit HD4470-Controller) 1x Multimeter (DMM) 1x Ethernetshield (nur mit ARDUINO UNO verwendbar) 1x ARDUINO Leonardo (Kapitel 9 nur hiermit möglich) 1x Computer, auf dem die ARDUINO-IDE läuft http://www.mitp.de/media/vmi-buch/texte/leseprobe/9470_einleitung.pdf ********************************************************* Schritt 1: Das erste Board http://www.mathias-wilhelm.de/arduino/beginner/ Die Wahl ders ersten Boards kann recht richtungsweisen sein. Man kann sich mit der falschen Wahl als Anfänger recht schnell die Lust am Arduino nehmen. Derzeit sind sehr viele Arduino Boards und unterschiedliche Derivate im Umlauf. Dabei gruppiere ich diese gerne in vier Gruppen: original Arduino Uno und alle dazu kompatiblen Boards original Arduino Mega und die entsprechenden Boards Arduino Leonardo und seine Derivate Arduino Due und andere ARM basierende Arduino ähnliche Boards Für Anfänger kann ich nur die ersten beiden Gruppen empfehlen. Das mag verwunderlich sein, werden dorch derzeit immer mehr Leonardo basierende Boards angeboten, da diese so einfach und dafür kostengünstig zu produzieren sind. Leider hat der Leonardo eine Reihe an Nachteilen, welche aus meiner Sicht das Board für den Anfänger uninteressant machen. Im Gegenzug hat sich der Preisvorteil relativiert, seit es Anbieter von Arduino Uno Boards gibt, die diese Board für 9 USD anbieten. Der grösste Nachteil des Arduino Leonardo sind der Bootloader und die Treiberanbindung unter Windows: Der Arduino hat einen eigenen USB Chip, sodass der Bootloader nur ca. 500 Byte verbraucht. Der Leonardo hat keinen eigenen Chip, welcher die USB Anbindung vornimmt. Er muss ihn also emulieren, was von den 32kB Speicher fast 3 kB wegfrisst. Damit habe ich dann später oftmals Probleme, z.B. beim Ansteueren von TFT Displays Die eben beschriebene USB Anbindung führt dazu, dass das Board nicht immer von Windows erkannt wird. Genaueres dazu an dieser Stelle. Für einen Anfänger empfehle ich ein Board, welches die GPIO Pins nicht nur als Buchsenleisten sondern auch als Stiftleisten herausführt. Ein schönes Beispiel dafür ist das Board von Elecfreaks: Das schöne an diesem Board ist, dass an allen GPIO Pins auch Vcc und GND anliegt. Das wird sich später noch als sehr nützlich erweisen. Die Kosten dieses Boards liegen bei € 19.80 (komputer.de) bzw. CHF 25.45 (boxtec.ch) und sind somit recht attraktiv. Wer ein knappes Budget hat, dem seien die Quellen in China empfohlen: dort kann man einen Arduino Uno Nachbau schon für 9 USD bekommen (uctronics.com) bzw den Freaduino Nachbau für USD 19.78 (dx.com). Bei den Chinaimporten muss man aber mit Lieferzeiten von 3-4 Wochen rechnen und gegebenfalls Zoll und Umsatzsteuer entrichten.
*********************************************************
Schritt 2: Legen wir los http://www.mathias-wilhelm.de/arduino/beginner/schritt-2-legen-wir-los/ Im Nachfolgenden gehe ich davon aus, dass wir das Freaduino Board von Elecfreaks verwenden. Blink Sketch Der wohl am häufigsten ausgeführte Sketch ist der Blink Sketch. Er dient meist dazu, die Funktionsfähigkeit eines Boards zu prüfen. Wenn ich also die kleine LED zum blinken bringen kann, dann steht dem weiteren Experimentieren nichts im Wege. Ausserdem kann man es gut verwenden, um die Vorgehensweise beim Programmieren des Arduino zu erklären. Ein Sketch ist nur ein Teilprogramm des kompletten Arduino- Programms, welches vor dem Benutzer versteckt wird. Das ist auch sinvoll, da man als einfacher Benutzer des Boards kaum etwas davon hat, in die Tiefen dieser Programmierung abzutauchen. Daher beschränkt sich die Programmierung des Arduinos auf zwei elementare Routinen: setup() und loop(). Die Routine setup() wird einmal an Anfang aufgerufen, die Routine loop() danach ohne definiertes Ende. Man verwendet also setup() um den Controller und die angeschlossenen anderen Elemente zu initialisieren und in loop() führt man dann die gewünschte Aktion mit dem Controller aus. Ich habe oftmals in Demos gesehen, dass jemand in setup() eine unendliche while- Schleife einfügt ( while(1) {// was auch immer} ). Das bedeutet, dass man setup() nie verlässt, da while(1) immer true ist und somit diese Schleife ewig läuft. Diese Art der Programmierung ist erlaubt, zeugt aber von einem unsauberen Programmierstil, den man sich erst gar nicht angewöhnen sollte. Ich verwende immer mein eigenes Blink Sketch, damit ich sehen kann ob auch wirklich mein Code auf dem Board angekomen ist: // Pin 13 has an LED connected on most Arduino boards. // give it a name: int led = 13; // the setup routine runs once when you press reset: void setup() { // initialize the digital pin as an output. pinMode(led, OUTPUT); } // the loop routine runs over and over again forever: void loop() { digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level) delay(200); // wait for 200 milliseconds digitalWrite(led, LOW); // turn the LED off by making the voltage LOW delay(200); // digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level) delay(100); // digitalWrite(led, LOW); // turn the LED off by making the voltage LOW delay(100); // digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level) delay(200); // digitalWrite(led, LOW); // turn the LED off by making the voltage LOW delay(1000); // wait for a second } Dieser simple Sketch bietet aber die Möglichkeit, einen Schnelleinstieg in die Arduinoprogrammierung zu machen. Bauen wir also den Sketch ein wenig um: Speichern der Blinksequenz Als erstes möchte ich die jeweiligen Blinkpausen definieren. Dazu deklariere ich ein Array (eine benannte Reihe an indizierten Werten): int blinktimes[] = {200, 100, 200}; Diese Deklaration könnte im loop() stehen, würde dann aber bei jedem Aufruf von loop() zurückgesetzt. Wenn ich also die Deklaration ausserhalb von setup() und loop() mache, wird das Array einmal angelegt und initialisiert, spätere Änderungen bleiben erhalten. Die Größe des Arrays habe ich nicht festgelegt (leeres Klammernpaar), das das der Compiler für mich übernimmt, da ich danach eine Liste angebe. Man kann die Anzahl der Listenelemente angeben, sind es zu wenige beschwert sich der Compiler, sind es zu viele, macht das nichts. Greift man auf ein Arrayelement jenseits der Arraygröße zu, kommt es zu undefinierten Ergebnissen. Jetzt kann ich die delays in loop() mit den blinktimes füllen: void loop() { digitalWrite(led, HIGH); delay(blinktimes[0]); digitalWrite(led, LOW); delay(blinktimes[0]); digitalWrite(led, HIGH); delay(blinktimes[1]); digitalWrite(led, LOW); delay(blinktimes[1]); digitalWrite(led, HIGH); delay(blinktimes[2]); digitalWrite(led, LOW); delay(blinktimes[2]); delay(1000); } Die For-Schleife Was sofort auffällt ist das lästige Durchnummerieren der Arraywerte. Das kann man auch durch eine Laufvariable erledigen lassen: for (int i = 0; i < 3; i++) { digitalWrite(led, HIGH); delay(blinktimes[i]); digitalWrite(led, LOW); delay(blinktimes[i]);https://sites.schaltungen.at/arduino-uno-r3/beginner }https://sites.schaltungen.at/arduino-uno-r3/beginner die Variable i wird hier übrigens erst in der for-Schleife definiert. Das schützt uns davor, diese Variable anderweitig falsch einzusetzen, da der Compiler uns die Verwendung der Variable ausserhalb der Schleife meldet. Die Variable i in zwei Schleifen dieser Art sind übrigens nicht die Gleichen im Programm, da sie dynamisch generiert werden. Man beachte, dass Arrayindicees bei 0 anfangen. Was jetzt noch stört ist das hart codierte Arrayende in der Schleife. Das kann man sich auch berechnen lassen: int MaxI = sizeof(blinktimes)/sizeof(int); sizeof() gibt die Grösse eines Arrays in Byte wieder. Man muss daher durch die Grösse des verwendeten Typs dividieren. Eigene Routinen Wenn man den Code noch etwas mehr strukturieren will, dann bietet es sich an, wiederholte Programmteile in eine eigene Routine auszulagern. In unserem Beispiel wäre das das Blinken an sich: void Blink( int p, int w, int v) { digitalWrite(p, HIGH); delay(w); digitalWrite(p, LOW); delay(v); } Mit void wird eine Prozedure deklariert, die keinen Wert zurückgibt. Als Argumente habe ich den Pin p und die beiden Wartezeiten w und v. Die For-Schleife wird dadurch zu: for (int i = 0; i < MaxI; i++) { Blink(led, blinktimes[i], blinktimes[i]); } Der engültige Code Da man beim Arduino nur 32kB Speicher hat, sollte man immer darauf achten, dass man die kleinsten Variablentypen verwendet. So kann ich alle Integer (int) durch Byte (byte) ersetzen, wo der Wert der Variablen nicht grösser als 255 wird. Das führt dann zu diesem Code: // Pin 13 has an LED connected on most Arduino boards. // give it a name: byte led = 13; // define an array for blick times int blinktimes[] = {200, 100, 200, 50, 500, 50}; byte MaxI = sizeof(blinktimes) / sizeof(int); // the setup routine runs once when you press reset: void setup() { // initialize the digital pin as an output. pinMode(led, OUTPUT); } // the loop routine runs over and over again forever: void loop() { for (byte i = 0; i < MaxI; i++) { Blink(led, blinktimes[i], 100); } delay(1000); } void Blink( byte p, int w, int v) { digitalWrite(p, HIGH); delay(w); digitalWrite(p, LOW); delay(v); } Die Verwendung von Byte anstelle von int reduziert die Programmgösse von 1826 Byte auf 1804 Byte obwohl ich im code nur an 6 Stellen von 2 Byte (Integer) auf ein Byte umgestellt habe. Zugegeben, 22 Byte sind nicht viel, aber dieses Beispiel ist ja auch denkbar klein. Bei der Gelegenheit sei vermerkt, dass das Einführen der Variablen mehr Platz verbraucht, denn das Originalsketch verbraucht 1776 Bytes. Man muss als, wenn es mal eng wird, auch auf hard codierte Werte zurückgreifen. Weitere Einführung in C Ich könnte nun eine Einführung in C folgen lassen. Das gibt es aber schon zur Genüge, jedes Arduinobuch hat solch eine Einführung. Für die meisertn Projekte reichen diese Minimalkentnisse aus, zumal man beim Arduino meist auf bestehende Beispiele zugreift und dort die weiteren Kentnisse erwirbt. Also auf zum nächsten Schritt: Eine LED Lichterkette ********************************************************* Schritt 3: Den Arduino steuern Ein grosses Problem für mich am Anfang war es, den Arduino zu steuern. Die einfachste Art wäre es, dem Arduino ein paar Taster zu spendieren oder einen Poti bzw. Encoder. Aber das geht so einfach nicht, denn der Taster wil entprellt sein und der Poti ausgelesen. Beim Encoder wird es richtig spannend, denn dann geht es um Interrupts. Alles das will ich später noch genauer erklären, aber für den Anfänger gibt es eine weitaus einfachere Möglichkeit, den Arduino zu steuern: Die Serielle Schnittstelle. Serial IO Der Arduino hängt ja im Moment sowieso am USB Kabel, über das wir unsere Sketches hochgeladen haben. Was liegt also näher, als den Arduino durch dieses Kabel zu steuern. Und die Arduino Software macht es sogar besonders einfach, wie man gleich sehen wird. Wenn der Arduino Daten am seriellen Port empfängt, wird ein Interrupt ausgelöst. Interrupts sind Ereignisse, auf die der Microcontroller sofort reagiert. Er unterbricht seine aktuellen Aktionen und führt eine für diesen Interrupt definierte Routine aus. Das hat den Vorteil, dass man nicht ständig am seriellen Port lauschen muss, ob sich denn dort etwas tut. Will man die serielle Kommunikation nutzen, so muss man diese in setup() initialisieren: void setup() { Serial.begin(9600); // set up Serial library at 9600 bps } Dieser Aufruf legt die Transferrate auf 9600 bps fest, was die Voreinstellung beim IDE ist. Wenn bei der seriellen Kommunikation ein Zeichensalat auf dem Bildschirm erscheint, dann liegt es meist an falsch eingestellten Transferraten. Beim Arduino ist eine Interruptroutine für den seriellen Port vordefiniert und diese tut einfach nichts. Man kann sie aber durch eine eigene Routine ersetzen: void serialEvent(){ char ch = Serial.read(); buffer[bufferCount] = ch; bufferCount++; if(ch == 13){ evalSerialData(); } } Verwendet für die serielle Kommunikation wird hierbei ein Array, welches die ankommenden Daten speichert und mitzählt. Daher müssen wir diese noch am Anfang des Sketches definieren: int bufferCount; // Anzahl der eingelesenen Zeichen char buffer[80]; // Serial Input-Buffer Kommt ein Zeichen mit dem ASCII code 13, also ein "CR", zu deutsch ein Zeilenendzeichen, dann betrachten wir die Daten als abgeschlossen und versuchen die gesendeten Zeichen zu verstehen. Dazu rufen wir die Routine evalSerialData() auf. (genau betrachtet bedeutet CR Carriage Return, also Wagnerücklauf und geht auf die mechanischen Schreibmaschinen zurück, bei denen am Zeilenende diese Taste betätigt wurde, um in die neue Zeile zu kommen). Die einfachste Aktion, die wir mit einer gesendeten Zeichenfolge machen können, ist diese gerade wieder zurück zu schicken. Unser erster Sketch dafür sieht also so aus: int bufferCount; // Anzahl der eingelesenen Zeichen char buffer[80]; // Serial Input-Buffer void setup() { // start serial port at 9600 bps: Serial.begin(9600); while (!Serial) { ; // wait for serial port to connect. Needed for Leonardo only } } void loop() { // noch tut sich nichts hier } void serialEvent() { char ch = Serial.read(); buffer[bufferCount] = ch; bufferCount++; if (ch == 13) { evalSerialData(); } } void evalSerialData() { Serial.println(buffer); bufferCount = 0; } Wenn man diesen Sketch hochlädt und dann den Serial Monitor im IDE startet, kann man sehen, wie die eingegebenen Zeichen gerade wieder auf den Bildschirm zurückgeschickt werden. Dabei muss man prüfen, dass der Serial Monitor auch wirklich ein Zeilenende mitschickt (siehe Auswahl am rechten unteren Bildschirmrand). Beim Aufruf des Serial Monitors wird übrigens der Arduino zurückgesetzt (reset), also neu gestartet. Leonardo Special Für die nächsten Tests hate ich gerade einen Leonardo zur Hand und wollte die nächsten Abschnitte angehen. Doch zu meiner Verwunderung reagierte der Leonardo nicht auf meine Eingaben. Nach einiger Recharche habe nich nun eine weitere Eigenart des Leonardos entdeckt, was meine Meinung bestätigt, dass man die drei Euro für einen Uno doch sinnvollerweise investieren sollte! Zusammengefasst, wird SerialEvent() beim Leonardo nicht unterstützt. Wenn man sich den Code der Arduino Software anschut, sieht man aber auch, dass SerialEvent() nicht von den Interruptroutinen aus aufgerufen wird, sondern dass am Ende der loop()-Schleife geprüft wird, ob Daten am Seriellen Port anliegen und wenn dem so ist, wird SerialEvent() aufgerufen. Für uns bedeutet das, dass wir bei einem Leonardo das am Ende von loop() selbst aufrufen können. Die Schleife sieht dann so aus: void loop() { // noch tut sich nichts hier Blink(led, blinktime, waittime); #if defined(__AVR_ATmega32U4__) if (Serial.available()) serialEvent(); #endif } Protokolldefinition In evalSerialEvent() möchte ich nun die ankommenden Befehle interpretieren. Dieses Konzept stammt übrigens von Erik Bartmann und seinem Buch. Die hier verwendeten Definitionen bezeichnet man als Protokoll und ich kann hier frei meine Befehle definieren. Es ist bei serieller Kommunikation sinnvoll, die Befehle auf ihre Richtigkeit zu prüfen. Dazu definiere is als erstes, dass jeder Befehl mit einem speziellen Zeichen anfängt und mit einem speziellen Zeihen endet. Dazu wähle ich ">" für den Anfang und "<" für das Ende. Für unser einfaches Beispiel will ich zwei Befehle realisieren: die Änderung der Blinkdauer und die Änderung der Länge zwischen zwei Bilnks. Das Format sei: bxxxx und wxxxx für Blinkdauer respektive Waitdauer, wobei xxxx die Zeit in Millisekunden ist. Ein Änderung auf 500 ms sähe also so aus: >b0500< Im Programm sieht das dann so aus: void evalSerialData() { //Serial.println(buffer); if ((buffer[0] == '>') && (buffer[bufferCount - 2] == '<')) { switch (buffer[1]) { case 'b': blinktime = (buffer[2] - 48) * 1000 + (buffer[3] - 48) * 100 + (buffer[4] - 48) * 10 + (buffer[5] - 48); break; case 'w': waittime = (buffer[2] - 48) * 1000 + (buffer[3] - 48) * 100 + (buffer[4] - 48) * 10 + (buffer[5] - 48); break; } } bufferCount = 0; } Als erstes prüfe ich, ob der Befehl mit ">" anfängt und mit "<" aufhört. Dabei muss ich beachten, dass das Zeilenende auch im Buffer steht. Die switch Anweisung ermöglicht es mir, elegant die verschiedenen Inhalte der betrachteten Variablen (hier das Arrayelement buffer[1] ) zu behandeln. Für jeden Zustand schreibt man eine case-Anweisung, die durch ein break; abgeschlossen wird. Ohne das Break; wird fröhlich weiter ausgeführt, was in den nachfolgenden Zeilen steht. Das kann man sich zu nutze machen, ist aber riskant, da man leicht den Überblick verliert. Bei der Umrechnung der Zeichen in Zahlen muss ich beachten, dass eine "1" als ASCII 49 geschickt wird, ich also vom ASCII Wert 48 abzeihen muss, um den numerischen Wert zu erhalten. Delay or no delay Bei unserem Beispiel sieht man in der Ausführung, das es etwas dauert, bis eine Änderung stattfindet. Das liegt daran, dass in unserer Blinkroutine die Funktion delay() verwendet wird. Delay() hat aber den Nachteil, dass es den Prozessor "schalfen" legt, also keine Anweisung in dieser Zeit ausgeführt wird. Daher ist es sehr oft sinnvoll, seine eigene aktive Delayroutine zu schreiben. Die abgewandelte Blinkroutine sieht dann so aus: void Blink( byte p, int w, int v) { if (millis() > switchtime) { if (ledstate) { switchtime = millis() + v; digitalWrite(p, LOW); } else { switchtime = millis() + w; digitalWrite(p, HIGH); } ledstate = !ledstate; } } mit den global definierten Variablen: boolean ledstate = false; long switchtime = 0; Wenn also meine aktuelle Prozessorzeit millis() grösser ist als die berechnete Umschaltzeit switchtime, dann berechne ich die neue switchtime je nach dem ob die LED an ist (ledstate=true) oder nicht (ledstate=false), schalte die LED entsprechend an oder aus und invertiere den ledstate. Jetzt wird die Änderung quasi sofort aktiv. An dieser Stelle empfehle ich nun, mit dem Programm ein wenig zu spielen und die Erfahrungen zu vertiefen. Als kleine Anregung die Frage, was ist der Unterschied zwischen ledstate != ledstate und ledstate = !ledstate ? Hier noch der vollständige Code des aktuellen Beispiels: int bufferCount; // Anzahl der eingelesenen Zeichen char buffer[80]; // Serial Input-Buffer byte led = 13; int blinktime = 2000; int waittime = 1000; boolean ledstate = false; long switchtime = 0; void setup() { pinMode(led, OUTPUT); // start serial port at 9600 bps: Serial.begin(9600); while (!Serial) { ; // wait for serial port to connect. Needed for Leonardo only } } void loop() { // noch tut sich nichts hier Blink(led, blinktime, waittime); #if defined(__AVR_ATmega32U4__) if (Serial.available()) serialEvent(); #endif } void serialEvent() { char ch = Serial.read(); buffer[bufferCount] = ch; bufferCount++; if (ch == 13) { evalSerialData(); } } void evalSerialData() { //Serial.println(buffer); if ((buffer[0] == '>') && (buffer[bufferCount - 2] == '<')) { switch (buffer[1]) { case 'b': blinktime = (buffer[2] - 48) * 1000 + (buffer[3] - 48) * 100 + (buffer[4] - 48) * 10 + (buffer[5] - 48); break; case 'w': waittime = (buffer[2] - 48) * 1000 + (buffer[3] - 48) * 100 + (buffer[4] - 48) * 10 + (buffer[5] - 48); break; } } bufferCount = 0; } void Blink( byte p, int w, int v) { if (millis() > switchtime) { if (ledstate) { switchtime = millis() + v; digitalWrite(p, LOW); } else { switchtime = millis() + w; digitalWrite(p, HIGH); } ledstate = !ledstate; } } ********************************************************* Schritt 4: Den PC als Dashboard http://www.mathias-wilhelm.de/arduino/beginner/schritt-4-den-pc-als-dashboard/ Allem voran die Auflösung meiner Frage aus der vorherigen Seite: Den zusammengesetzte Operator (Compound Operator) "!=" gibt es als solchen laut Dokumentation nicht, wird aber vom Compiler föhlich verarbeitet. Das Ergebniss der Operation a != b ist immer a, das WErgebnis der Operation a = !b ist immer die Umkehrung von b. Wie immer glit: im Zweifelsfall die Anweisung ausschreiben. Angeregt durch einen Bericht über ein Android basierendes Dashboard habe ich ein Programm geschrieben, welches Befehle von einem Arduino Board über die serielle Schnittstelle entgegen nimmt und damit Bedienelemente auf dem PC darstellt, die wie eine Benutzeroberfläche für den Arduino verwendet werden können. Dadurch spart man sich das aufwändige kurzzeitige Verkablen von Tastern oder LCDs. Definieren des Dashboards Für den Anfänger ist dies ein einfaches Tool, welches es erlaubt, schnell mal etwas auszutesten. Als einfache Einführung möchte ich hier die Blinkfrequenz der LED ändern. Dazu muss man auf dem PC (leider nur Windows) mein Arduino Dashboard installieren. Die setup-Datei befidnet sich im Downloadbereich. Nach dem start präsentiert sich das Dashboard mit dem leeren Arbeistfeld: Der Arduino ist grau, da das Programm noch nicht die Verbindung mit dem Arduino hergestellt hat. Bei den COM Ports ist die Liste der gefundenen Ports angezeigt, mein Arduino ist hier ein Mega auf Port 14. Der Button Connect verbindet das Programm mit dem Board: Da mein Arduino Sketch auf die Verbindung mit der Mitteilung reagiert, dass es sich um einen Mega handelt, wird das entsprechende Board angezeigt. Andere Boards ausser Arduino oder Mega sind noch nicht verfügbar, wird aber kommen. Mit dem Button "Start Dashboard" wird der Arduino aufgefordert, die Dashboarddefinitionen zu senden. Das Ergbnis ist dann das folgende: Mit "Serial Log" kann man sich die Konversation zwischen Arduino und Progrmam anschauen: Der Befehlt SINITE führt zu den gesendeten Befehlen STTL1Dashboard DemoE SSLD001.0020.0070.0050.2000.0250.Led BlinktimeE SSLD011.0020.0150.0050.2000.0250.Led WaittimeE Der erste Befehl definiert eine Überschrift, die anderen beiden Befehle definieren einen "Slider", also Schieberegler. die ersten beiden Ziffern bezeichnen die ID des Sliders (ich kann also später Sliders neu positionieren oder parameterisieren), die dritte Ziffer zeigt an ob der Slider zu sehen ist (1) oder nicht (0). Die nächsten Parameter sind der Lesbarkeit durch Punkte getrennt. Der erste Parameter gibt die x, der zweite die y Position an, gefolgt vom kleinsten und grössten Wert und den Markerabständen. Als letztes kommt das Label. Alle Dashboardbefhle sind (in Arbeit) auf der Dashboardseite definiert. Das zugehörige Sketch sieht so aus: char* board; int bufferCount; // Anzahl der eingelesenen Zeichen char buffer[80]; // Serial Input-Buffer char temp[80]; int led = 13; boolean dbOpen = false; int blinktime = 2000; int waittime = 1000; boolean ledstate = false; long switchtime = 0; void setup() { // put your setup code here, to run once: board = "UNO"; #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1284P__) board = "MEGA"; #endif Serial.begin(115200); pinMode(led, OUTPUT); } void loop() { Blink(led, blinktime, waittime); #if defined(__AVR_ATmega32U4__) if (Serial.available()) serialEvent(); #endif } void serialEvent() { char ch = Serial.read(); buffer[bufferCount] = ch; bufferCount++; if (ch == 13) { evalSerialData(); } } void evalSerialData() { int ptr; int val; strcpy(temp, ""); if ((buffer[0] == 'S') && (buffer[bufferCount - 2] == 'E')) { strncat(temp, buffer, bufferCount - 1); if (strcmp(temp, "SQVERSIONE") == 0) { strcpy(temp, "AC 1.0.0"); Serial.print("SBRD."); Serial.print(board); Serial.println("E"); } if (strcmp(temp, "SINITE") == 0) { // setup the Dashboard Serial.println("STTL1Dashboard DemoE"); Serial.println("SSLD001.0020.0070.0050.2000.0250.Led BlinktimeE"); Serial.println("SSLD011.0020.0150.0050.2000.0250.Led WaittimeE"); dbOpen = true; } if (strcmp(temp, "SGoodByeE") == 0) { // stop the Dashboard; dbOpen = false; } if ((buffer[1] == 'S') && (buffer[2] == 'L') && (buffer[3] == 'V') ) { ptr = (buffer[4] - 48) * 10 + (buffer[5] - 48); val = (buffer[6] - 48) * 1000 + (buffer[7] - 48) * 100 + (buffer[8] - 48) * 10 + (buffer[9] - 48); if (ptr == 0) { Serial.print("Blinktime new: "); Serial.println(val); blinktime = val; } else { Serial.print("Waittime new: "); Serial.println(val); waittime = val; } } } else { if ((buffer[4] == 'C') && (buffer[5] == 'L') && (buffer[6] == 'E') && (buffer[7] == 'A') && (buffer[8] == 'R')) { Serial.println("Serial Clear"); } else { strcpy(temp, "BadCmd: "); strncat(temp, buffer, bufferCount); Serial.println(temp); } } bufferCount = 0; } void Blink( byte p, int w, int v) { if (millis() > switchtime) { if (ledstate) { switchtime = millis() + v; digitalWrite(p, LOW); } else { switchtime = millis() + w; digitalWrite(p, HIGH); } ledstate = !ledstate; } } Es handelt sich dabei um das gleiche Programm wie in den vorherigen Beispielen, nur dass in evalSerialData() jetzt die entsprechenden Befehle ausgewertet werden. Das Dashboard ist noch in der Entwicklung, funktioniert aber schon recht gut für die Steuerung des Arduinos. Weitere Funktionen wie die Übertragung von Datum und Uhrzeit sowie Polarplot und Histogram sind in Arbeit und teilweise schon fertig. Der nächste Schritt führt uns aber wieder zu Elektronik, denn nun wird es bunt ... ********************************************************* Schritt 5: Jetzt wird es bunt http://www.mathias-wilhelm.de/arduino/beginner/schritt-5-jetzt-wird-es-bunt/ Mein Ziel ist es, diese Einführung so einfach als auch kostengünstig zu gestalten. Bislang haben wir knapp 20 Euro für ein Board investiert. Für die nächsten Schritte möchte ich mich der dreifarbigen LED zuwenden, der RGB-LED, die so heist, da sie einen roten (R), grünen (G) und blauen (B) Farbkanal hat und somit 16 Millionen Farben darstellen kann, da jeder Kanal Werte zwischen 0 und 255 annehmen kann. Um die Sache zu vereinfachen, benutze ich eine RGB-LED vom Typ NeoPixel, der von Adafruit angeboten wird. Es gibt diese LEDs einzeln oder als Streifen, Ringe oder Matrix. Ein Streifen von 8 LEDs kostet ca € 5.66 (exp-tech.de) oder CHF 7.55 (boxtec.ch), ist also vergleichsweise günstig. Ausserdem kann man an diesen Streifen sehr einfach eine Stiftleiste anlöten und daran Kabel aufstecken um den Streifen mit dem Arduino zu verbinden. Das Besondere an diesen NeoPixeln ist, dass man sie über einen einzelnen Draht ansteuern kann. Wie das genau funktioniert soll uns erst einmal nicht interessieren. Dazu gibt es eine ausführliche Beschreibung bei Adafruit, die ich hier nicht wiederholen möchte. Ausserdem will ich die LEDs benutzen und nicht en Detail selbst programmieren: Darin liegt übrigens eine der Stärken der Arduino Platform: Man kann Dinge benutzen und erhält dafür alle notwenigen Bebliotheken bereit gestellt (Adafruit und Sparkfun tun sich hier besonders gut hervor). Wenn ich das Gleiche auf einer anderen Platform versuche, muss ich im schlimmsten Fall die Steruerung der LEDs selbst schreiben. Also ist der Anschluss des NeoPixel Streifens sehr einfach: GND an Masse, 4-7VDC an +5V und Din an einen freien Pin, z.B. den Pin 6. Diese LEDs zeihen recht viel Strom, wenn man einen langen Streifen verwendet. Bei unserem Streifen mit 8 LEDs sollte es keine Probleme geben. Von Adafruit erhalte ich also eine Bibliothek (library) für den NeoPixel welche ich sehr einfach in mein Arduino IDE einbinden kann. Es gibt in der Arduino IDE zwei Verzeichnisse, in der Libraries abgelegt werden: Im Installationsverzeichniss der IDE liegen die STandardlibraries der IDE, bei mir also: c:\opt\arduino-1.5.6-r2\libraries\ Hier sollten nur die Libraries abgelegt werden, die mit der IDE geliefert werden oder die für diese IDE spezifisch geschrieben sind. Alle anderen Libraries, also auch unsere NeoPixel Library gehören in der Benutzerveizeichnis, wo auch alle meine Sketches abgelegt werden. Bei mir ist das: c:\data\Arduino\libraries\ und dementsprechend: c:\data\Arduino\libraries\Adafruit_NeoPixel\ Leider benennt Adafruit ihr Library-Verzeichnis Adafruit_NeoPixel-master was der IDE überhaupt nicht gefällt. Generell mag die IDE keine Leerzeichen im Namen und auch keine Sonderzeichen als auch keine Namen, welche mit eine Zahl anfangen. Am Besten ist es, das Verzeichnis mit dem gleichen Namen zu bezeichnen wie das .ccp File in diesem Verzeichnis, welches den Code der Library enthält. Die Libraries werden nur beim Start der IDE geladen, was man manchmal vergisst und sich dann wundert, warum die Library nicht gefunden wird. Erhält man beim Übersetzen eines Sketches eine lange Liste an unbekannten Klassenelementen, sollte man immer an den Anfang der Lsite gehen und schauen, ob die Library überhaupt geladen wurde. Nach dem Start der IDE taucht nun die Adafruit Neopixel Library unter den Beispielen auf. Um unseren NeoPixel Streifen zu nutzen, braucht es nun nur noch ein paar kleine Schritte. Dazu schauen wir uns der Beispiel "strandtest" an: Am Anfang wird die Library durch den Aufruf #include <Adafruit_NeoPixel.h> eingebungen. die Klammern "<" und ">" weisen den Compiler an, die Datei im Verzeichnisbaum des Benutzers und der IDE zu suchen. Daher sollten die Namen der Dateien eindeutig sein. Drurch diesen Aufruf wird auch dei Datei Adafruit_NeoPixel.cpp eingelesen und alles das definiert, was wir zum Steuern der NeoPixel brauchen. #define PIN 6 Adafruit_NeoPixel strip = Adafruit_NeoPixel(8, PIN, NEO_GRB + NEO_KHZ800); Mit #define PIN 6 wird wie in der vorherigen Beispielen ein Pin definiert, an den wir den Neopixel angeschlossen haben. Die zweite Zeile definiert ein Objekt namens strip, welches wir für unseren Streifen benutzen wollen. Ein Objekt ist vereinfacht dargestellt die Zusammenfassung von allem, was ich brauche, um etwas zu verwenden, also die Speicherplätze die Routinen, um diese zu erstellen die Routinen, um diese zu bearbeiten die Routinen, um mit der Hardware zu reden Hier wird also ein Objekt erzeugt, welches einen NeoPixel-Streifen mit 8 LEDs steuert. Bei dem Beispiel müssen wir also nur den Pin ändern, wenn wir einen anderen Pin als den Pin 6 verwenden und die Anzahl der Pixel anpassen, wenn wir wie hier nur 8 statt der vorgegebenen 60 pixel haben. An dieser Stelle empfehle ich, das Sketch zu kompilieren und auf den Arduino zu laden. Sonnenbrille auf und einfach mal nur bewundern... NeoPixel programmieren Nun versuche ich mal, den NeoPixel Streifen zu programmieren: In setup() müssen wir das Objekt initialisieren: strip.begin(); strip.show(); Mit strip.begin() wird der Streifen initialisiert, mit strip.show() angezeigt. Da im Streifen noch nichts geladen ist, bleibt er also dunkel. Die LEDs werden von links nach rechts durchnummeriert, wobei man bei 0 anfängt. Die Routine strip.setPixelColor( index, color) setzt die Farbe für einen Pixel. Die Farbe wird dabei in Form eines 32-bit langes Wortes angegeben. Die Kodierung nimmt uns aber eine Routine der Library ab: strip.Color( r,g,b) wobei r, g und b die jeweiligen Farbanteile für Rot, Grün und Blau der gewünschten Farbe angeben. Die Werte können dabei zwischen 0 und 255 liegen. Das Triplet (0,0,0) ist also Schwarz, (255,0,0) Rot, )0,255,0) Grün und (0,0,255) Blau. (255,255,255) ist demnach Weis. In dem Beispiel von Adafruit ist eine Routine angegeben, welche uns die RGB Werte für einen Farbkreis errechnet, der in 255 Farben geteilt ist. Wie beim Arduino üblich, bedienen wir uns gerne solcher Codeschnipsel und bauen sie in unseren Sketch ein. Im loop() will ich nun einen Lichtpunkt von einer Seite des Streifens zu der anderen wandern lassen und wieder zurück. Dazu baue ich folgende for-Schleife: for (int i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, Wheel(wc)); t = i - 1; if (t < 0) t = strip.numPixels() - 1; strip.setPixelColor(t, strip.Color( 0, 0, 0)); strip.show(); delay(50); } Die Funktion strip.numPixels() gibt mir die Anzahl der Pixel, die ich bei der Definition des Objekts angegeben habe. Das ist praktisch, da ich so den Sketch für Streifen beliebiger Länge programmieren kann. In der ersten Anweisung in der Schleife setze ich die Farbe des jeweiligen Pixels. Dann errechne ich mir den letzten Pixel. Wenn ich unter Null gerate, sete ich diesen Index auf die höchste Pixelnummer. Diesem Pixel gebe ich nun die Frabe Schwarz, lösche also das Licht an der Stelle des letzten Schleifendurchlaufs. Dann zeige ich das Ergebnis meiner Bemühungen an und warte 50 Millisekunden um mich dem nächsten Pixel zu widmen. Bin ich einmal den Streifen entlang gelaufen, so geht es jetzt wieder in die andere Richtung: for (int i = strip.numPixels()-1; i >= 0; i--) { strip.setPixelColor(i, Wheel(wc)); t = i + 1; if (t >= strip.numPixels()) t = 0; strip.setPixelColor(t, strip.Color( 0, 0, 0)); strip.show(); delay(50); } Zum Schluss zähle ich noch den Zähler hoch, den ich für die Routine Wheel() verwende und wir sind fertig. Hier der gesamte Code: #include <Adafruit_NeoPixel.h> #define PIN 10 Adafruit_NeoPixel strip = Adafruit_NeoPixel(8, PIN, NEO_GRB + NEO_KHZ800); int wc = 0; void setup() { strip.begin(); strip.show(); // Initialize all pixels to 'off' } void loop() { // knight rider LEDs int t; for (int i = 0; i < strip.numPixels(); i++) { strip.setPixelColor(i, Wheel(wc)); t = i - 1; if (t < 0) t = strip.numPixels() - 1; strip.setPixelColor(t, strip.Color( 0, 0, 0)); strip.show(); delay(50); } for (int i = strip.numPixels()-1; i >= 0; i--) { strip.setPixelColor(i, Wheel(wc)); t = i + 1; if (t >= strip.numPixels()) t = 0; strip.setPixelColor(t, strip.Color( 0, 0, 0)); strip.show(); delay(50); } wc++; if (wc>255) wc=0; } uint32_t Wheel(byte WheelPos) { if (WheelPos < 85) { return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); } else if (WheelPos < 170) { WheelPos -= 85; return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3); } else { WheelPos -= 170; return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); } } Eigentlich hindert uns jetzt nichts, mit den Pixelstrip herum zu spielen. Ich habe mir zum Beispiel aus den Erfahrungen der vorherigen Seiten drei SLider im Dashboard definiert und kann damit die Farbanteile meiners Streifens steuern. Viel Vergnügen! Details Der Vollständigkeit halber hier noch ein paar Details: Weitere Routinen Es gibt zwei Routinen, die auch noch interessant sind: setBrightness(uint8_t b) - setzt einen generellen Skalierungsfaktor b (0 .. b .. 255) für alle Farben und damit eine Helligkeit getPixelColor(uint16_t n) - liest die zuletzt gesetzte Farbe eine Pixels aus. Die Farbinformation für alle Pixel wird in einem Array gespeichert und beim Aufruf der Routine show() in einem Schwung an die seriell verbundenen Pixel geschickt. Damit hat man also die jeweiligen Pixelwerte immer parat und kann sie entsprechend abfragen und muss sie nicht selbst zwischenspeichern. WS2801, WS2811, WS2812, WS2812S, WS2812B - was nun? Ich habe hier die RGB-LEDs des Neopixel Produkts von Adafruit besprochen. Diese basieren auf den WS2812 LEDs bzw. WS2811 Treibern. Den Streifen, welchen ich gekauft habe, verwenden den WS2812S, neuere verwenden den WS2812B. Was ist denn nun der Unterschied: WS2811 - das ist der Treiberchip WS2812S - ältere Version, besteht aus einer 5050-RGB-LED und dem WS2811 treiber in einem kompakten Gehäuse, hat 6 Pins WS2812B - neuere Version, Aufbau wie WS2812S, aber nur 4 Pins Die LED WS2801 wird komplett anders angesprochen und ist nicht mit den hier beschriebenen LEDs kompatibel. RGB-LED - eine dreifach LED Genau genommen hat man hier keine RGB-LED sondern eine LED, welche drei Einzel-LED in enem Gehäuse vereint. Das erkennt man ganz gut, wenn man sich eine klassische RGB-LED kauft, welche 4 Pins hat, drei für die jeweiligen Farben und eine gemeinsame Anode oder Kathode. Wenn man die NeoPixel mit sehr geringer Lichtstärke ansteuert, kann man die einzelnen LEDs auf dem Chip sehen. ********************************************************* Schritt 6: LCD Displays Als nächstes möchte ich etwas subtiler mit der Umwelt kommunizieren und verwende dazu ein LCD Display und ein paar Tasten. Diese gibt es übersichtlich angeordnet als fertiges Arduino Shield für ca 15 Euro bzw. 17 CHF. Ein Shield ist eine Aufsteckplatine welche man auf den Arduino aufsteckt (im AUSGESCHALTETEN Zustand) um eine spezielle Funktion dem Arduino hinzuzufügen. Es gibt derzeit eine umfangreiche Liste an Shields und die Website www.shieldlist.org versucht dieser Flut ein wenig gerecht zu werden (Stand zur Zeit der Erstellung diese Artikels: 317 Shields von 125 Herstellern). Ein Shield hat den Vorteil, dass ich keine freie Verdrahtung habe und der Aufbau geprüft und getestet ist. Oftmals müssen Shields zusammengebaut werden, wobei SMD Bauteile oft schon verlötet sind. Ich habe ein LCD-Shield von DFrobot (LCD Keypad Shield v1.0). Es gibt inzwischen LCD Shields, welche über den I2C-Bus angesprochen werden (meist beworben, dass sie nur zwei Kabel/Pins belegen). Diese werde ich an anderer Stelle behandeln. Selbstverständlich kann man das LCD auch direkt mit Kabeln an den Arduino anschliessen. Dazu komme ich am Ende dieser Seite, denn es geht mir erst einmal darum, mit dem Arduino und dem LCD meinen Spass zu haben und nicht Verdrahtungsproblemen hinterher zu jagen (habe so schon mal einen ganzen Abend vergeudet). Das LCD Shield Dieses Shield gibt es von verscheidenen Herstellern. Wichtig ist es bei diesen Shields die Pinbelegung zu erfahren, welche normalerweise in der Dokumentation angegeben ist. Allen gemeinsam ist die Pinbelegung des LCD und der Controller, ein HD44780 oder ein damit kompatibler Controller. Die Pins sind folgendermassen: Das LCD hat also 8 Datenleitungen (DB0-DB7) und vier Kontrollleitungen: Contrast (CONT), Register Select (RS), Read/Write (RW) and Enable (EN). Daneben gibt es noch die Anschlüsse für die Stromversorgung und die Hintergrundbeleuchtung (BKL+,BKLG) Shield Aktionen Das Shield ist schnell in Betrieb genommen. Da diese LCDs wohl von Anfang an genutzt wurden, ist die Library für das LCD im Standard Arduino-IDE enthalten. Wir müssen also nichts installieren und können direkt in der Beipielliste das Beispiel "Hello World" der LiquidCrysatal Library laden. Alles was wir an dem Beispiel ändern müssen ist die Pinbelegung, da der Aufruf für das LCD Objekt nicht die Pins unseres Schields verwendet: LiquidCrystal lcd(8, 9, 4, 5, 6, 7); Mit dieser Änderung sollte nun der Text "Hello World" auf dem Display erscheinen. Oftmals ist der Kontrast verstellt, so dass eventuell nichts zu sehen ist. Diesen Kontrast stellt man über den kleinen Poti mit einem Kreuzschlitzschraubendreher ein. ALs erste Übung möchte ich den Sketch aus den vorherigen Beispielen verwenden, der Daten von der seriellen Schnittstelle verarbeitet und diese zeilenweise darstellt und nach oben scrollt. Als erstes binde ich die Library ein und derfiniere das LCD Objekt und meine Puffer für die serielle Kommunikation: #include <LiquidCrystal.h> LiquidCrystal lcd(8, 9, 4, 5, 6, 7); int bufferCount; // Anzahl der eingelesenen Zeichen char buffer[80]; // Serial Input-Buffer Für die beiden LCD Zeilen definiere ich mir noch jeweils einen String (Zeichenkette): String str1 = ""; String str2 = ""; In setup() initialisiere ich das LCD als ein LCD mit 16 Zeichen und 2 Zeilen und gebe einen Text an der Stelle (0,0) aus, wobei die Position der Schreibmarke (Cursor) mit (Spalte,Zeile) angegeben wird. Die Cursorposition wird übrigens bei jeder Ausgabe weitergezählt. Gibt man also als Startposition (0,0) an und schriebt 3 Zeichen, steht der Cursor danach bei (3,0). lcd.begin(16, 2); lcd.setCursor(0, 0); lcd.print("Ready ..."); Die Routine evalSerialData() habe ich so abgewandelt: void evalSerialData() { int mx; mx = min(bufferCount-1, 16); str1 = str2; str2 = ""; for (int i = 0; i < mx; i++) { str2 += buffer[i]; } for (int i = mx; i < 16; i++) { str2 += ' '; } bufferCount = 0; lcd.setCursor(0, 0); lcd.print(str1); lcd.setCursor(0, 1); lcd.print(str2); } ALs erstes erreche ich die Länge des Strings (ohne das Zeilenende) und beschränke diese auf maximal 16 Zeichen. Danach kopiere ich den letzten String der zweiten Zeile in die erste Zeile und setze den zweiten String auf eine leere Zeichenkette. Dann kopiere ich alle Zeichen aus dem Buffer in den String und fülle bis zum Ende auf, damit ich alte Angaben überschreibe. Zum Schluss gebe ich die beiden Strings aus - einfach, oder? Hier der komplette Code: #include <LiquidCrystal.h> LiquidCrystal lcd(8, 9, 4, 5, 6, 7); int bufferCount; // Anzahl der eingelesenen Zeichen char buffer[80]; // Serial Input-Buffer String str1 = ""; String str2 = ""; void setup() { lcd.begin(16, 2); // start serial port at 9600 bps: Serial.begin(9600); while (!Serial) { ; // wait for serial port to connect. Needed for Leonardo only } lcd.setCursor(0, 0); lcd.print("Ready ..."); } void loop() { // noch tut sich nichts hier #if defined(__AVR_ATmega32U4__) if (Serial.available()) serialEvent(); #endif } void serialEvent() { char ch = Serial.read(); buffer[bufferCount] = ch; bufferCount++; if (ch == 13) { evalSerialData(); } } void evalSerialData() { int mx; mx = min(bufferCount-1, 16); str1 = str2; str2 = ""; for (int i = 0; i < mx; i++) { str2 += buffer[i]; } for (int i = mx; i < 16; i++) { str2 += ' '; } bufferCount = 0; lcd.setCursor(0, 0); lcd.print(str1); lcd.setCursor(0, 1); lcd.print(str2); } Hintergrundbeleuchtung über PWM Nun möchte ich mich dem Pin 10 unseres Shields widmen. Hier kann ich die Hintergrundbeleuchtung einstellen. Das dabei verwendete Prinzip brauchen wir später noch häufig. Da diese Thema in sehr vielen Bereichen verwendet wird, habe ich mich entschlossen, der Pulsweitenmodulation (PWM) ein eigenes Kapitel zu widmen äüö - die deutschen Sonderzeichen Wenn ich nun den Serial Monitor starte, werden alle Zeichen auf dem LCD wiedergegeben. Spannend wird es, wenn man Zeichen eingibt, die nicht im Standard-ASCII Zeichenraum von 0-127 stehen. Das sind zum Beispiel die Umlaute der deutschen Sprache. Das LCD gibt hier das aus, was es in seiner Zeichentabelle findet. Mein PC schickt aber das, was er nach ISO-8859 ausgeben soll. Wenn die nachfolgenden Beispiele nicht funktionieren hilft es nur, den Wert des Zeichens auf die Serielle Schnittstele auszugeben um herauszufinden, welche Zahl für welchen Umlaut geschickt wird. Um die Sache noch unnötig zu erschweren, werden Umlaute vom Compiler mit ganz anderen Werten in einem String gespeichert. Dazu gleich mehr, erst einmal die einfachen Dinge... Beginnen wir mit den Umlauten ä,ö,ü, ß und ° (Grad). Hier finde ich in der Beschreibung des LCD, dass diese Zeichen im Zeichensatz enthalten sind (das muss nicht für alle LCD gelten, scheint aber so bei SParkfun und Adafruit zu sein). Nur, dass sie an eine anderen Stelle stehen. Die Tabelle kann man bei Wikipedia nachschauen: https://de.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange Im code kann ich das über eine switch() Anweisung (siehe vorherige Beispiele) abfangen und die codes umsetzen. Dazu schreibe ich mir eine kleine Routine, die Umlaute prüft und den neuen Wert ausgibt. Für die ersten vier Umlaute sieht das dann so aus: char checkUmlaut(byte ascii) { switch (ascii) { case 228: return 225; break; // ä case 246: return 239; break; // ö case 252: return 245; break; // ü case 223: return 226; break; // ß case 176: return 223; break; // grad/degree default: return ascii; break; } } Im Gegensatz zu den bisherigen Routinen, welche mit dem Typ void definiert wurden, wird hier ein Datentyp (char) verwendet, es handlet sich also um eine Funktion, welche einen Wert zurück liefert. Das geschiet über die Anweisung return. Für alle Zeichen, die nicht in der Liste stehen wird das zurück gegeben, was in der default: Bedingung steht, hier also das unveränderte Zeichen selbst. Leider findet sich in der LCD Tabelle keine Zeichen für Ä, Ö und Ü. Hier können wir uns damit behelfen, dass wir eigene Zeichen für das LCD definieren können. Die Routine dafür heisst lcd.createChar( n, b), wobei n eine Zahl zwischen 0 und 7 ist und b ein Array, welches die Bitdefinition beinhaltet. Für diese Bitdefinition stelle man sich ein Zeichen von 5x8 pixel als gruppe von 8 Zeilen zu je 5 Bits vor, wobei jedes Bit angibt ob der Pixel leuchtet oder nicht. Ein vollständig erleuchtetes Zeichen hat also die Werte (B11111,B11111,B11111,B11111,B11111,B11111,B11111,B11111). Ein Ä könnte dann so aussehen: B01010 B00000 B01110 B10001 B10001 B11111 B10001 B10001 Hier die Arraydefinition für Ä, Ü und Ö: byte Auml[8] = { B01010, B00000, B01110, B10001, B10001, B11111, B10001, B10001 }; byte Uuml[8] = { B01010, B00000, B10001, B10001, B10001, B10001, B10001, B01110 }; byte Ouml[8] = { B01010, B00000, B01110, B10001, B10001, B10001, B10001, B01110 }; In setup() werden die Zeichen dann definiert: lcd.createChar(0, Auml); lcd.createChar(1, Uuml); lcd.createChar(2, Ouml); Als letzte Hürde hat dann der Compiler noch die Idee, den Umlauten noch ein spezielles Zeichen vorweg zu stellen und für die Zeichen andere Codes zu schicken. Das habe ich im Code auch abgefangen. Der komplette Code steht am Ende dieser Seite. Umlaut Code: #include <LiquidCrystal.h> LiquidCrystal lcd(8, 9, 4, 5, 6, 7); int bufferCount; // Anzahl der eingelesenen Zeichen char buffer[80]; // Serial Input-Buffer String str1 = ""; String str2 = " "; boolean inCode = false; byte Auml[8] = { B01010, B00000, B01110, B10001, B10001, B11111, B10001, B10001 }; byte Uuml[8] = { B01010, B00000, B10001, B10001, B10001, B10001, B10001, B01110 }; byte Ouml[8] = { B01010, B00000, B01110, B10001, B10001, B10001, B10001, B01110 }; void setup() { lcd.createChar(0, Auml); lcd.createChar(1, Uuml); lcd.createChar(2, Ouml); lcd.begin(16, 2); Serial.begin(9600); while (!Serial) { ; // Leonardo only } lcd.setCursor(0, 0); lcd.print("Ready ..."); } void loop() { #if defined(__AVR_ATmega32U4__) if (Serial.available()) serialEvent(); #endif } void serialEvent() { char ch = Serial.read(); buffer[bufferCount] = ch; bufferCount++; if (ch == 13) { evalSerialData(); } } void evalSerialData() { int mx; byte asc; uint8_t nc = 0; mx = min(bufferCount - 1, 16); str1 = str2; str2 = ""; for (int i = 0; i < mx; i++) { str2 += buffer[i]; } for (int i = mx; i < 16; i++) { str2 += ' '; } bufferCount = 0; lcd.setCursor(0, 0); for (int i = 0; i < 16; i++) { asc = str1.charAt(i); if ((asc == 195) || (asc == 194)) { inCode = true; } else { lcd.print(checkUmlaut(str1.charAt(i))); } } Serial.println(); lcd.setCursor(0, 1); for (int i = 0; i < 16; i++) { asc = str2.charAt(i); if ((asc == 195) || (asc == 194)) { inCode = true; } else { lcd.print(checkUmlaut(str2.charAt(i))); } } Serial.println(); } char checkUmlaut(byte ascii) { if (inCode) { inCode = false; switch (ascii) { case 164: return 225; break; // ä case 188: return 245; break; // ü case 182: return 239; break; // ö case 159: return 226; break; // ß case 132: return 0; break; // Ä case 156: return 1; break; // Ü case 150: return 2; break; // Ö case 176: return 223; break; // grad/degree default: return ascii; break; } } else { switch (ascii) { case 228: return 225; break; // ä case 252: return 245; break; // ü case 246: return 239; break; // ö case 223: return 226; break; // ß case 196: return 0; break; // Ä case 220: return 1; break; // Ü case 214: return 2; break; // Ö case 176: return 223; break; // grad/degree default: return ascii; break; } } } ********************************************************* Schritt 7: Pulsweitenmodulation (PWM) Bei der Pulsweitenmodulation nimmt man ein Rechtecksignal mit einer festen Frequenz und variiert die Breite der jeweiligen Pulse. Was einfach klingt lässt sich am besten an einer Grafik erläutern: Den Abstand zweier aufeinander folgender Wellen bezeichnet man als Wellenlänge, die Breite des aktiven Pulses (a) als Pulsweite. Die mittlere Spannung, welche an diesem Pin anliegt ist also gegeben durch das Verhältnis von a zu (a+b). Ist a also nur halb so breit wie die Wellenlänge, dann habe ich nur 50% der Signalspannung im Mittel zur Verfügung. Dieses Verhältnis von Pulsweite zu Wellenlänge bezeichnet man auch als "Duty Cycle" (also die Zeit, in der der Pin "im Dienst" ist). Als Grafik sieht das dann so aus: Beim Arduino kann ich die Pulsweite in Werten von 0-255 einstellen. Bei einem Wert von 0 kommt effektiv keine Spannung an, bei einem Wert von 255 habe ich effektiv eine Gleichspannung. Ein Duty Cycle von 25% entspricht dem Wert von 63, 50% dem Wert von 127 und 75% dem Wert von 191 (wir fangen bei 0 an...). Die PWM Pins Der Arduino kommt mit 6 PWM Pins: Pin-3, Pin-5, Pin-6, Pin-9, Pin-10 und Pin.11. Bei unserem LCD haben wir die Hintergrundbeleuchtung auf Pin-10 angeschlossen, was sich nun anbietet, über ein PWM SIgnal die mittlere Spannung zu variieren. Der Trick besteht darin, den Pin als OUTPUT Pin zu definieren, dann aber mit dem Befehl analogWrite den Duty Cycle Wert zu schreiben: pinMode(10,OUTPUT); analogWrite(10,255); // volle Helligkeit analogWrite(10,127); // halbe Helligkeit analogWrite(10,0); // keine Helligkeit Servos steuern Diese Pulswellenmodulation lässt sich wunderbar dazu verwenden, Servos zu steuern. Hier interpretiert der kleine Microcontroller des Servos die Pulsweite als WInkel für den Servoarm. Man kann also ganz einfach die drei Kabel von einen Servo an Plus und Masse legen und das Signalkabel an einen PWM-Pin und schon dreht der Servo zwischen 0 und 180 Grad je nach dem Wert, den wir mit analogWrite schreiben. Polarisationswechsel Richtig interessant wird es mit den PWM-Pins, wenn man den Bezug der Spannung ändert: Statt die Spannung fest gegen Masse zu betrachten, kann man sie auch in Bezug auf einen zweiten Pin betrachten, der ja die Zustände HIGH und LOW annehmen kann. Bei der Helligkeit unserer LCD Hintergrundbeleuchtung bringt das wenig und wir müssen bedenken, dass der Arduino Prozessor nur Ströme bis zu 30mA bieten kann. Zum steueren kleiner Motoren reicht es aber aus. Nehme ich den Bezug-Pin auf LOW, habe ich am PWM ein positives Signal. Nehme ich den Bezugs-Pin auf HIGH, wird das Signal am PWM-Pin relativ zum Bezugs-Pin negativ. Das heist also für einen Gleichsrommotor, dass er jetzt rückwärts läuft. Ein Grafik hift hier vielleicht weiter: Man muss hierbei nur beachten, dass sich bei deiser Umkehr auch der Effekt des PWM-Wertes ändert: Man muss also gegebenfalls den zu 255 komplementären Betrag ausrechnen, wenn man beim Richtungswechsel die Geschwindigkeit beibehalten will. Die schönste Methode, diese Pulswellenmodulation auszuprobieren ist die, einen kleinen Roboter zu steuern. Dazu bietet sich der XBotMicro von Didel.com an. Es ist mein Lieblingsroboter, da er eine filigrane Eleganz aufweist und nicht mit massiven Bauteilen aufwartet. Daher habe ich ihn in einem der nächsten Kaptiel beschrieben.
Quelle:
Dr. Mathias Wilhelm & Dominik Wilhelm Neuweg 3/1 D-79415 Bad Bellingen Tel. +49 (0)7635 / 821561 Fax. +49 (0)7635 / 821561 mailto:[email protected] http://www.mathias-wilhelm.de/arduino http://www.mathias-wilhelm.de/arduino/beginner/ Als ich mit dem Arduino anfing, wusste ich nicht so recht, wie ich an diesen Microcontroller herangehen sollte. Dadurch habe ich einiges an Zeit und Geld in Dinge investiert, welche heute nutzlos im Regal stehen. Daher möchte ich hier versuchen, einen Weg vorzugeben, wie man sich dem Arduino recht zügig nähern kann. DIN A4 ausdrucken ********************************************************* Impressum: Fritz Prenninger, Haidestr. 11A, A-4600 Wels, Ober-Österreich, mailto:[email protected] ENDE |