Atmel AVR Mikrocontroller mit OpenSuse: Ansteuerung vonLCD-Displays

LCD-Displays gibt es in großer Mannigfaltigkeit. Üblich ist, dass die Displays bereits einen Controller enthalten, der die Pixel ansteuert. Auch in diesem Bereich hat eine Art Standardisierung stattgefunden, so dass nur gegen wenige Controllertypen programmiert werden muss. Ein sehr gebräuchlicher Controller ist der HD44780.

LCD-Display Seiko L2432 mit 2 Zeilen zu 24 Zeichen

Auf dem Display sitzt ein Controller HD44780, also absoluter „Standard“.
Das Display hat 2 Zeilen zu 24 Zeichen.


Vorderseite des Seiko L2432.

Unterseite des Seiko L2432, rechts der Controller HD44780. Ganz rechts der 14-polige Konnektor, an den ein Flachbandkabel angelötet wurde.

Das Display wird über einen Connector mit 14 Pins angeschlossen. Sogar die Pin-Belegung des Connectors ist quasi-standardisiert.

Pin Function
1 GND
2 V+, normalerweise +5V
3 VLC, Kontrastreglung
4 RS – Register Set
5 RW – Read/Write
6 E – Enable
7 DB0
8 DB1
9 DB2
10 DB3
11 DB4
12 DB5
13 DB6
14 DB7
15 (A – Anode for Backlight) optional
16 (K – Kathode for Backlight) optional

In Rot sind die für die 4-Bit-Ansteuerung notwendigen Pins gekennzeichnet. Diese müssen mit freien Ports des AVRs (bzw. V+ und GND) verbunden werden.

Neben der Datenleitungen (8 Datenbits D0..D7 bzw. 4 Datenbits D4..D7 im 4-Bit-Modus, Controllerauswahl, R/W, RS, Enable sowie GND und Vcc), muss unbedingt auch -wenn vorhanden- die Kontrastregelung angeschlossen werden. Das gezeigte Display benötigt beispielsweise eine Spannung, die irgendwo zwischen 0 und 5V liegt. Wird keine passende Regelspannung für den Kontrast eingestellt, ist absolut nichts zu sehen und man kann nicht erkennen, ob das Display überhaupt funktioniert! Die Kontrastspannung wird über einen Trimmer angeschlossen von typischerweise 10 KOhm, so dass man auf unterschiedliche Lichtverhältnisse reagieren kann.

Als Open Source gibt es eine Bibliothek für den AVR-Controller (LCD Library von Peter Fleury), welche LCDs ansteuern kann. Sie nutzt die 4-Bit-Ansteuerung (oder einen Memory-Mapped-Mode, der bei bestimmten AVRs möglich ist). Je nach Controller wird ein 8-Bit-Modus, ein 4-Bit-Modus oder beides unterstützt. Der 4-Bit-Modus hat den Vorteil, dass am AVR weniger Pins benötigt werden.

Hinweis: Im 4-Bit-Modus werden die Datenleitungen D4..D7 eines LCD-Moduls/Controllers genutzt. Auch wenn in der Peter-Fleury-Bibliothek immer von D0..D3 (=die logischen Datenleitungen des 4-Bit-Modes) die Rede ist, gemeint sind im 4-Bit-Modus immer D4..D7 (=die physikalischen Datenleitungen des LCD-Moduls).

Im folgenden ist die Nutzung der Funktionen dargestellt (Codeauszug, kompletten Source Code gibts weiter unten):

...
#include "lcd.h"
 
...
/* clear display and home cursor */
lcd_clrscr();
 
/* put string to display (line 1) with linefeed */
lcd_puts("LCD Test Line 1n");
 
/* cursor is now on second line, write second line */
lcd_puts("Line 2");
 
/* move cursor to position 8 on line 2 */
lcd_gotoxy(7,1);
 
/* write single char to display */
lcd_putc(':');
 
DO("wait for keynr");
/* wait until push button PD2 (INT0) is pressed */
wait_until_key_pressed();
DO("after keynr");
 
/*
 * Test 2: use lcd_command() to turn on cursor
 */
 
/* turn on cursor */
lcd_command(LCD_DISP_ON_CURSOR);
...

Die Bibliothek muss im Makefile als zusätzliche Bibliothek eingetragen werden. Die Funktionsprototypen stehen alle im Header „lcd.h“, den man im eigenen Programm eintragen muss.


Seiko L2432 angeschlossen und erste Datenausgabe

Die Bibliothek ist auch konfigurierbar, es gibt z.B. Controller, die mehr als zwei Zeilen beherrschen, dies kann in der Bibliothek eingestellt werden. Die Spezifika des Displays (Anzahl Zeilen, Anzahl Zeichen, ..) und die Anschlussbelegung am AVR muss in „lcd.h“ eingetragen werden.

Im Code findet sich übrigens auch der Makroaufruf DO(„text“), dies ist ein Debug-Makro („DEBUG OUT“) von mir, welchen Daten via RS232 ausgibt. Damit kann man erkennen, was der Controller gerade tut. Wie die RS232-Schnittstelle angesprochen wird, wird weiter unten in meinem Text besprochen.

LCD-Displays Wintek WD-C2704M-1HNN mit 4 Zeilen zu 27 Zeichen

Das Display hat 4 Zeilen zu 27 Zeichen. Auf dem Display sitzen zwei Controller, kompatibel zum HD44780. Einer ist für die oberen zwei Zeilen, der andere für die unteren zwei Zeilen verantwortlich. Beide Controller teilen sich alle I/O-Leitungen. Statt einem E(nable)-Pin gibt es zwei Enable-Pins E1 und E2 für die Auswahl zwischen den beiden Controllern.

Die Bibliothek von Peter Fleury unterstützt allerdings nur Displays mit einem Controller. Ich habe das Problem dadurch gelöst, dass ich in alle Funktionen der Bibliothek wo notwendig zwischen beiden Controllern unterschieden habe. So wird z.B. aus der Funktion lcd_clrscr(void) eine Funktion lcd_clrscr(int controllerId), der die Werte LCD_CRTLR_1 und LCD_CRTLR_2 übergeben werden können.

Die Pin-Belegung des Moduls ist in folgender Tabelle dargestellt. Die oben erwähnte „Quasi-Standardisierung“ist hier leicht verändert, da 2 Enable-Pins existieren.

Pin Function
1 GND
2 V+, normalerweise +5V
3 VLC, Kontrastreglung (0..4V)
4 RS – Register Set
5 RW – Read/Write
6 E1 – Enable Controller 1
7 E2 – Enable Controller 2
8 DB0
9 DB1
10 DB2
11 DB3
12 DB4
13 DB5
14 DB6
15 DB7
16-21 Key Input for Input Keys

In Rot sind die für die 4-Bit-Ansteuerung notwendigen Pins gekennzeichnet. Diese müssen mit freien Ports des AVRs (bzw. V+ und GND) verbunden werden.

Im folgenden ist die Nutzung der modifizierten Funktionen dargestellt (Codeauszug, kompletten Source Code gibts weiter unten):

...
#include "lcd.h"
 
...
/* clear display and home cursor */
lcd_clrscr(LCD_CRTLR_1);
lcd_clrscr(LCD_CRTLR_2);
 
/* put string to display (line 1) with linefeed */
lcd_puts(LCD_CRTLR_1, "LCD Test Line 1n");
 
/* cursor is now on second line, write second line */
lcd_puts(LCD_CRTLR_1, "Line 2");
 
/* move cursor to position 8 on line 2 */
lcd_gotoxy(LCD_CRTLR_1, 7,1);
 
/* write single char to display */
lcd_putc(LCD_CRTLR_1,':');
 
DO("wait for keynr");
/* wait until push button PD2 (INT0) is pressed */
wait_until_key_pressed();
DO("after keynr");
 
/*
 * Test 2: use lcd_command() to turn on cursor
 */
 
/* turn on cursor */
lcd_command(LCD_CRTLR_1, LCD_DISP_ON_CURSOR);
...

Für LCDs mit nur einem Controller ist die Bibliothek natürlich so wie sie ist zu benutzen.


Im Bild ist der Anschluss des LCDs an das einfache AVR-Board zu erkennen.
Experimentalaufbau – die Display-Schutzfolie wurde auf dem Display belassen, daher die schlechte Ablesbarkeit.

Hier der Source-Code zum Display:  lcd_2controller.zip

Erschöpfende Infos zur LCD-Ansteuerung unter http://www.mikrocontroller.net/articles/AVR-Tutorial:_LCD

Ansteuern des VS1011 MP3 Decoders mit dem AVR

Die VS10xx-Familie des Herstellers VLSI erlaubt die Dekodierung von MP3 Daten. Der Chip lässt sich von einem AVR aus relativ einfach über 7 Pins ansteuern und wurde schon diverse Male in Selbstbauprojekten für MP3 Player eingesetzt. Klar, dass ich mir den VS10xx auch mal anschauen musste…

Hardware

Der VS1011b ist im SOIC-28 Gehäuse (SMD) erhältlich. Die Pins haben 1mm Abstand, sind also noch freihand lötbar. Bei ELV habe ich einen wunderbaren Adapter SOIC28-DIL28 gefunden, so dass sich der Chip leicht auf ein Experimentierboard aufbringen lässt (Ich meine es war: SMD-Adapter ADP-SO 28 28-pol. SO-Gehäuse, Artikel-Nr.: 68-615-05).


VS1011 auf SOIC-28 zu DIL-28 Adapter

Der VS1011 wird mit 3,3 Volt betrieben. Nachdem ich mir zwei 2G-SD-Karten mit der Diodenlösung (Spannungsabfall von 5V auf 3,6V über zwei Dioden) zerstört hatte, bin ich auf die Spannungserzeugung mittels Regler LM1117 gekommen. Der VS1011 und große SD-Karten sind nicht billig, eine saubere Spannungsregelung zahlt daher sich aus.

Nutzung der Spannungsteiler (3K3 und 1K8) für die SD-Karte zusätzlich durch den VS1011 führte bei der SD-Karte zu Kommunikationsfehlern. Daher habe ich für beide SPI-Geräte eigene Spannungsteiler vorgesehen. In untigem Schaltbild ist die Beschaltung des ATMega weggelassen worden.Betriebsspannung VDD ist 3.3 Volt.

Der VS1011 wird über zwei verschiedene CS-Signale angesprochen:

  • xCS für Kommandos (Lautstärke ändern, Reset, …)
  • XDCS für MP3 Daten, die unmittelbar dekodiert werden

Es gibt einen „alten“ Kompatibilitätsmodus (für den VS1001) mit speziellen Signalleitungen (BSYNC, ..) der aber nicht mehr benutzt werden sollte. Im neuen Modus („SM_SDINEW“) steuert man den SPI-Bus über die beiden CS-Signale an. (Mit dem Modus „SM_SDISHARE“ kann man sogar auf die zweite CS-Leitung verzichten).

Aus dem Schaltbild ergab sich bei mir folgende Pinbelegung (VS1011-relevante Beschaltung ist hellblau hinterlegt. Die weißen Felder beziehen sich auf den Anschluss der SD-Karte):

VS1011
Pin
VS1011 Pin-Nr.
(SOIC28)
AVR Pin
(AtMega32)
AVR Pin-Nr.
(ATMega32)
Pollin-Bord J4 Nr. Beschaltung am
AVR als …
SD-Karte
Pin
SD-Karte
Pin-Nr.
SO 14 MISO (PB6) 7 15 in DO 7
SI 13 MOSI (PB5) 6 14 out DI 2
SCLK 12 SCK (PB4) 8 16 out CLK 5
/xCS 11 z.B. PA1 39 2 out
/xRESET 26 z.B. PA2 38 3 out
DREQ 1 z.B. PA3 37 4 in
/xDCS 4 z.B. PA4 36 5 out
z.B. PA0 40 1 out CS 1

Fliegender Aufbau des MP3 Players.

Nach erfolgreichen Zusammenspiel SD-Karte und MP3-Decoderchip habe ich das ganze stabiler auch einer Lochrasterkarte aufgebaut. Das Pollin-Board wird über die 40-polige Buchse angeschlossen (mittels handelsüblichem IDE-Kabel).

Software

In den Application Notes von VLSI ist sehr schön beschrieben, wie man sich mit dem Chip in Stufen vertraut macht. Mit einem Oszilloskop oder einem Logikanalysator kann man die Tests (wird der HW-Reset korrekt ausgeführt, funktioniert die SPI-Kommunikation etc.) genau nachvollziehen.

Im folgenden habe ich mit dem Logikanalysator die SPI-Kommunikation getestet. Der Test besteht darin, dass der VS1011-Chip selektiert wird und mittels SPI die Byte-Folge 0x02, 0x00, 0x00, 0x04 zum Chip gesendet wird (Soft Reset). Die Reaktion des Chips ist, dass DREQ auf 0 geht und nach Abschluss des Resets durch den VS1011 auf 1 gesetzt wird. Dieses Verhalten konnte ich nachvollziehen.

Das folgende Code-Stück sendet mittels spi_write_byte() die Folge 0x02, 0x00, 0x00, 0x04:


Im den folgenden Bildern ist der Kommunikationsablauf mit dem Logikanalysator dargestellt. Die einzelnen Signale heißen im Logikanalysator GRPAxx (Kurzform für GROUP A Probe xx), wobei xx hier 00,01,02,05,06 ist, Zuordnung zu den Chip-Signalnamen ist wie folgt:
GRPA06:    MOSI
GRPA05:    SCLK
GRPA02:    DREQ
GRPA00:    xCS


Der Cursor1 (=rote Linie) wurde zuerst auf die erste positive Clock-Flanke SCLK(GRPA05) nach dem Chip Select (xCS(GRPA00) geht auf Low) gesetzt. Es wird auf MOSI(GRPA06) 0000.0010 übertragen (Bitwert wird immer bei positiver Clockflanke übernommen), dann zweimal 0000.0000.
Das folgende Bild zeigt nun noch das letzte Byte 0000.0100 an. Nach Übertragung des letzten Bytes setzt der steuernde AVR das xCS(GRPA00) auf High, der VS1011 ist nicht mehr angesprochen.

Während der VS1011 die Signalfolge verarbeitet (hier: den SW Reset durchführt), hält er das Signal DREQ(GRPA02) auf Low. Auf dieses Signal („DREQ geht auf Low“) ist der Trigger hier gesetzt, so dass der Trigger (=gestrichelte rote Linie) erfolgt, wenn DREQ auf Low geht. Nach etwa 60ms geht DREQ wieder auf High, der VS1011 hat dann den SW Reset abgeschlossen. Dies ist hier nicht mehr dargestellt.

Im Ergebnis ist damit der SW Reset via SPI erfolgreich durchgeführt. Ähnlich lassen sich der Volume-Test und der Sinus-Test mit dem VS1011 durchführen. Beim Volume-Test wird der Lautstärkeregler des VS1011 zwischen minimaler und maximaler Laufstärke schnell hin- und hergeschaltet. Daraus resultiert ein Summton mit ein paar Khz Frequenz, den man in einem angeschlossenen Kopfhörer hören kann. Der Sinustest aktiviert schließlich einen Sinuston. Wenn man diese Tests erfolgreich durchgeführt hat, steht einem Test mit MP3-Daten nichts mehr im Weg.

Für ein 128KBit/s MP3 wird eine SPI-Frequenz von 128 Khz zum VS1011 benötigt (pro Takt wird 1 Bit übertragen). Dies entspricht 16Khz. Dieselbe Datenrate braucht man zusätzlich, um von der SD-Karte zu lesen. Macht 32Khz. Dies ist für den AVR kein Problem, er schafft mehrere Mhz. Ein Bit ist bei 128Khz 7,81 Mikrosekunden lang.

Der VS1011 muss passend zur Quarzfrequenz initialisiert werden (abhängig von Quarz mit interner Frequenzverdopplung). Meine Init-Sequenz sieht wie folgt aus:

/**
  * ingroup vs1011
  * HW Reset for vs1011.
  *
  * returns 0 on success.
  */
 uint8_t vs1011_init( void ) {
     // clear xreset pin -> hardware reset
     PORTA &= ~(1 << VS1011_XRESET);
     // set xreset pin -> vs1011 will go out of hardware reset state
     PORTA |= (1 << VS1011_XRESET);
     // wait until DREQ goes high
     loop_until_bit_is_set(PINA,VS1011_DREQ);
     _vs1011_init();
     return 0;
 }

 static void _vs1011_init( void ) {
     // set clock value
     vs1011_xcs_select();
     spi_write_byte(VS_WRITE);
     spi_write_byte(SCI_CLOCKF);
     spi_write_byte(0x98); // 12.288 Mhz and clock doubler
     spi_write_byte(0x00);
     vs1011_xcs_deselect();
     // set operating mode
     vs1011_xcs_select();
     spi_write_byte(VS_WRITE);
     spi_write_byte(SCI_MODE);
     //spi_write_byte(0x0c); // SM_SDINEW + SM_SDISHARE
     spi_write_byte(0x08); // SM_SDINEW
     spi_write_byte(0x00); //
     vs1011_xcs_deselect(); // end SCI cycle
 }

Danach kann man den Inhalt einer MP3-Datei direkt an den VS1011 senden, wobei xDCS selektiert sein muss. Das Lesen der MP3-Datei von SD-Karte (open_file_in_dir(), fat16_read_file())mache ich über Roland Riegels SD/MMS-Karten-Bibliothek für FAT16-Filesysteme. Man könnte aber auch eine andere Bibliothek hierfür nehmen. Die verwendete Puffergröße von 32 Bytes ist das Maximum, das man an den VS1011 senden kann ohne DREQ neu zu überprüfen. Solange DREQ low ist, kann man keine neue Daten an den VS1011 schicken.

fd = open_file_in_dir(fs, dd, "Hitchhiker Melodie.mp3");
      ....
     // write out whole mp3 file in a loop
     uint8_t buffer[32];

     // read in and write out chunks of size 32 bytes into buffer. vs1011 can handle
     // a maximum of 32 bytes if DREQ is high without retesting DREQ.
     while(fat16_read_file(fd, buffer, sizeof(buffer)) > 0) {
         // wait till vs1011 can receive at least 32 bytes
         VS_LOOP_UNTIL_DREQ;
         // activate xDCS
         vs1011_xdcs_select();
         // for each of 32 bytes in buffer ...
         for(uint16_t i = 0; i < sizeof(buffer); ++i) {
             // write single data byte from buffer
             spi_write_byte(buffer[i]);
         }
         // deactivate xDCS
         vs1011_xdcs_deselect();
      }

Der VS1011 holt sich alle notwendigen Daten für die Dekodierung aus dem Header der empfangenen MP3-Datei. Die Daten werden dann dekodiert und an den D/A-Wandler weitergegeben.

Abspieltests mit einer ganzen Menge von MP3-Songs zeigte, dass die Konstellation (8Mhz Quarz, 1Mhz SPI-Takt) mit 320KBit/s-MP3s zu Aussetzern führte. Nach der Rechnung weiter oben braucht hier die Datenübertragung 640KBit/s von den verfügbaren 1000KBit/s. Ich habe kurzerhand den 8Mhz-Quarz gegen einen 12Mhz-Quartz ausgetauscht, dadurch sind die Aussetzer verschwunden.

Eine weitere Optimierung wäre, die Daten gar nicht erst in den AVR zu laden, sondern die SD-Karte auf SPI schreiben zu lassen, zusätzlich den VS1011 zu selektieren (SDI-Mode, er liest dann Bytes vom SPI-Bus) und den Bus durch den AVR nur takten zu lassen. Dann wird nur die Hälfte der Busbandbreite benötigt.

Der Versuch, das Lesen von Karte und Schreiben in einer Interrupt-Routine durchzuführen funktioniert zwar, allerdings steht dann nur noch wenig freie Zeit für andere Interrupts zur Verfügung. So funktionierte eine hinzugenommene Dekodierung des Infrarotsignals einer Fernbedienung nur noch recht selten, man musste also mehrmals Pause etc. drücken, bis der AVR das Kommando wirklich erkannte. Deshalb bin ich von der Bearbeitung der MP3-Daten in der ISR abgekommen. Eine Analyse des Source-Codes diverser im Internet verfügbaren MP3-Player-Implementierungen zeigt, das alle untersuchten Implementierungen die MP3-Daten in der Hauptschleife des MP3-Players innerhalb von main() bearbeiten.

Experimenteller MP3 Player

Die Schaltung oben habe ich später wie erwähnt noch um ein graphikfähiges LCD zur Anzeige und eine Infrarot-Fernsteuerbarkeit erweitert.

Die Nutzung des LCDs ist hier beschrieben.

Nutzung von ID3-Tags

Wenn man MP3 abspielt, möchte man auch die ID3 Tags mit ausgeben. Während ID3V2 ziemlich komplex erscheint, ist ID3V1 eine triviale Sache. Mit folgenden Zeilen kann man  Artist, Titel etc. aus einer MP3-Datei herausziehen (meine Funktion „print_str()“ kann man ignorieren). Ich benutze bei meinem experimentellen MP3-Player nur Titel und Artist-Tag:

uint8_t print_id3_tags( struct fat16_file_struct*fd ) {
     int32_t offset = -128L;

     // ID3V1 tags are at the last 128 bytes of a MP3 file - seek to that position
     if (fat16_seek_file( fd, &offset, SEEK_END ) == 0) {
         return 0;
     }
     // read magic value of length 3 bytes, it should contain value "TAG"
     if (fat16_read_file(player.fd, buffer, 3) == -1) {
         // TODO handle read failure
     }
     // check for magic bytes
     if (strncmp( buffer, "TAG", 3) != 0)
         return 1; // not an error, but no tags are there

     // if we arrive here, ID3V1 tags may exist

     // tag: title
     if (fat16_read_file(player.fd, buffer, 30) == -1) {
         // TODO handle read failure
         return 1;
     }
    print_str(0,(LINE_TITLE*CHAR_H) % (12*14),"          ");
    print_str(0,(LINE_TITLE*CHAR_H) % (12*14),buffer);

    // tag: artist
     if (fat16_read_file(player.fd, buffer, 30) == -1) {
         // TODO handle read failure
         return 1;
     }
     print_str(0,(LINE_ARTIST*CHAR_H) % (12*14),"        ");
     print_str(0,(LINE_ARTIST*CHAR_H) % (12*14),buffer);

#ifdef COMPLETE_TAGS
        // tag: album
     if (fat16_read_file(player.fd, buffer, 30) == -1) {
         // TODO handle read failure
         return 1;
     }
     // tag: year
     if (fat16_read_file(player.fd, buffer, 4) == -1) {
         // TODO handle read failure
         return 1;
     }
     // tag: comment
     if (fat16_read_file(player.fd, buffer, 30) == -1) {
         // TODO handle read failure
         return 1;
     }
     // tag: genre
     if (fat16_read_file(player.fd, buffer, 1) == -1) {
         // TODO handle read failure
         return 1;
     }
 #endif
     // set back fp pointers to oiginal value
     offset = 0L;
     if (fat16_seek_file( fd, &offset, SEEK_SET ) == 0) {
         return 0;
     }
     return 1;
 }

Fernsteuerbarkeit per Infrarot-Fernbedienung

Hardware

Die Fernsteuerbarkeit habe ich durch Hinzunahme eines TSOP1136 als Infrarotempfänger erreicht. Bei der TSOP-Serie stehen die beiden letzten Stellen 11xx für die Trägerfrequenz. (z.B. TSOP1136 = 36Khz). Sehr häufig oder gar am häufigsten sind die 36Khz bei europäischen Fernbedienungen zu finden. 4 meiner 5 Fernbedienungen funktionieren mit 36Khz. Eine passende (sehr simple) Schaltung findet sich entweder im Datenblatt des TSOP1136 oder auch hier.  Der TSOP1136 stellt TTL-Level zur Verfügung und kann daher direkt an einen AVR-Pin angeschlossen werden.


Der dreibeinige TSOP1136 ist im Bild oben rechts zu sehen. Er steckt in einer 4-poligen Fassung, die aus einer alten IC-Fassung „angefertigt“ wurde.

Hier ist beispielhaft die Ausgabe des TSOP1136 für ein komplettes Signal (14 Bit) der Fernbedienung zu sehen. Ein Bit wird in Manchester-Codierung in 1,778 ms übertragen.

Software

Auch hier gibt es im Netz fertig nutzbare Bibliotheken. Eine sehr saubere, leicht einzubindende ist hier zu finden. Grundlagen zu dem RC5-Code, der allen handelsüblichen Fernbedienungen zugrundeliegt, ist auf Wikipedia zu finden (http://en.wikipedia.org/wiki/Consumer_IR). Die angesprochene Bibliothek kapselt das komplette Signalhandling weg und stellt der Anwendung in einer Struktur im wesentlichen zwei Werte zur Verfügung. Die Adresse steht für die Geräteadresse der Fernbedienung, so dass zwischen Signalken verschiedener Fernbedienungen unterschieden werden kann. Der Code steht für den Code der gedrückten Taste. Wenn der Code in einem AVR läuft, kann man durch Drücken der benötigten Tasten deren Code einfach ausgeben lassen.