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.

Schreibe einen Kommentar