Anonyme Arduinisten

aus dem Wiki des Entropia e.V., CCC Karlsruhe
Ein kleines Arduino-I²C-Netzwerk

Die Anonymen Arduinisten treffen sich nahezu jeden Dienstag ab ca. 19 Uhr im Club, um mit Arduino und anderer Elektronik zu spielen.

Projekte

LCD-Display

Ein aus dem Club-Müll gefischtes Sharp LM40A21

Nach den ersten paar LED-Spielereien (z.B. fadende LEDs und Wir-lernen-was-Pull-up-Widerstände-sind) haben wir ein 40x2 Text-LCD-Display (Sharp LM40A21) aus dem Elektronik-Müll des Clubs gefischt, und zuerst mit ein paar Kabeln in der 4-Bit-Variante an den Arduino gedrahtet. Als Software bzw. Library auf dem Arduino diente uns die LCD4BitLibrary, die uns das eigenständige Auscoden des ganzen Timings erspart hat. (Das nächste Mal würden wir aber wohl gleich die LiquidCrystal-Library aus dem Arduino-Zip nehmen...) Überraschenderweise hat das sogar funktioniert, allerdings hat das Display nach einer Weile nur noch Müll angezeigt -- wir haben natürlich gleich einen Bug gefunden:

Sharp LM40A21, 40x2, using HD44780A00. Mostly worked, adding a delay(2) after the CMD_HOME in cursorTo() fixed periodically garbled display problem. The HD44780 datasheet (pdf) I looked at specifies a 1.52ms delay for the Return home command at nominal f_osc of 270kHz, but it appears that this value could be as high at 2.16ms at low end of f_osc range, 190Khz.

--- LCD4Bit.orig/LCD4Bit.cpp	2006-10-16 04:29:44.000000000 +0200
+++ LCD4Bit/LCD4Bit.cpp	2009-11-04 17:51:50.205659741 +0100
@@ -202,7 +202,8 @@
 //if this is not a 2-line LCD4Bit instance, will always position on first line.
 void LCD4Bit::cursorTo(int line_num, int x){
   //first, put cursor home
  commandWrite(CMD_HOME);
+  commandWrite(CMD_HOME);
+  delayMicroseconds(1520); // neingeist, http://www.arduino.cc/playground/Code/LCD4BitLibrary
 
   //if we are on a 1-line display, set line_num to 1st line, regardless of given
   if (g_num_lines==1){

Danach hat alles sauber geklappt, das Display hat keine Mucken mehr gemacht. Allerdings haben wir das Delay später wieder rausgenommen, da cursorTo() ohnehin ineffizient implementiert war (siehe unten.)

Duinopong

Die lose Verkabelung des Displays an den Arduino war etwas unpraktisch, also haben wir das Display bzw. dessen Flachbandkabel an eine Lochrasterplatine gelötet, nicht ohne über eine Lücke zwischen zwei Steckleisten auf dem Arduino-Board zu fluchen: Diese hat nämlich nicht den für die Lochrasterplatine geeigneten Abstand von 2,54 mm (0,1 Zoll). Darauf kann man also nicht eine geeignet gestaltete und mit Pins versehene Lochrasterplatine stecken. Also haben wir die beiden Steckleisten benutzt, die ins Raster passen und die andere ignoriert.

Das verwendete Display bzw. dessen Steuerchip erlaubt das Erzeugen von 8 benutzerdefinierten Buchstaben (5x8). Warum also nicht diese benutzen um den Ball eines Pong pixelgenau in 200x16 statt 40x2 darzustellen? :-) Der eigentliche Code zum Setzen der benutzerdefinierten Characters ist nu nicht sehr schön gelungen, aber das nennt sich wohl Rapid Prototyping:

 #define CHAR_PADDLE paddle // 0 == left, 1 == right
 
 // gen char
 lcd.commandWrite(0x40+CHAR_PADDLE*8);
 for (uint8_t i = 0; i<8; i++) {
   if(i == (pos & 0x7) || i-1 == (pos & 0x7) || i+1 == (pos & 0x7) ) {
     if (paddle == 0) {
       lcd.print(0b00011);
     } else {
       lcd.print(0b11000);
     }
   } else {
     lcd.print(0b00000);
   }
 }

Außerdem mussten wir noch einen (ebenfalls aus dem Elektronik-Schrott geborgenen) Rotary-Encoder zur Kooperation überreden. Ein Rotary-Encoder kodiert eine Drehbewegung als Bitsequenz auf seine zwei Ausgänge, in unserem Fall hatten wir es wohl mit einem 2-Step-Rotary-Encoder zu tun: Eine Bewegung um einen "Klick" in die eine Richtung erzeugt eine Bitsequenz 00-01-11, eine Drehung in die andere Richtung die Sequenz 00-10-11 (bzw. ähnlich vom Zustand 11 nach 00). Das ist ein sogenannter Gray-Code: Es wird eigentlich nur hoch- bzw. runtergezählt, allerdings derart kodiert, dass sich immer nur ein Bit pro Änderung ändert -- was Fehler beim Auslesen minimiert. Letztlich haben wir dann ein wenig Code von mikrocontroller.net geborgt, um das ganze in der empfohlenen Methode umzusetzen: Ein Timer-Interrupt samplet die Werte "schnell genug" und stellt sie zum Auslesen in einer globalen Variable enc_value zur Verfügung.

Gray-Code Bedeutung
00 0
01 1
11 2
10 3

Naja und dann stellte sich noch ein weiteres Mal heraus, dass die LCD4BitLibrary Schrott ist: Das Cursor-Setzen ist gewaltig ineffizient implementiert ("Return-Home" und zig mal vor (0x14), statt einfach die "Cursor-Setzen"-Funktion (0x80) des Steuerchips zu verwenden), so dass uns das auch noch eine Weile aufhielt und wir schliesslich dann einfach die Library gefixt haben (statt gleich auf die gescheite LiquidCrystal-Lib zu nehmen...)

--- LCD4Bit.orig/LCD4Bit.cpp	2006-10-16 04:29:44.000000000 +0200
+++ LCD4Bit/LCD4Bit.cpp	2009-11-04 17:51:50.205659741 +0100
@@ -202,7 +202,8 @@
 //if this is not a 2-line LCD4Bit instance, will always position on first line.
 void LCD4Bit::cursorTo(int line_num, int x){
   //first, put cursor home
-  commandWrite(CMD_HOME);
+  //commandWrite(CMD_HOME);
+  //delayMicroseconds(1520); // neingeist, http://www.arduino.cc/playground/Code/LCD4BitLibrary
 
   //if we are on a 1-line display, set line_num to 1st line, regardless of given
   if (g_num_lines==1){
@@ -210,12 +211,15 @@
   }
   //offset 40 chars in if second line requested
   if (line_num == 2){
-    x += 40;
+    x += 64;
   }
   //advance the cursor to the right according to position. (second line starts at position 40).
-  for (int i=0; i<x; i++) {
-    commandWrite(0x14);
-  }
+  //for (int i=0; i<x; i++) {
+  //  commandWrite(0x14);
+  //}
+  
+
+  commandWrite(0x80+x);
 }
 
 //scroll whole display to left

I²C

Oszi-Grafik

PWM geglättet mit einem RC-Glied

Links

Zum löten üben:

Wichtigster Punkt dabei: Üben, üben und üben und immer genügend Flussmittel verwenden, am besten in der NoClean Ausführung, dann klebt hinterher nicht die ganze Platine. Entlötlitze sollte man auch immer da haben.