Ansteuern des VS1011 MP3 Decoders mit dem AVR

Page content

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 Pinr: 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
REQ 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 demChip 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 aufMOSI(GRPA06) 0000.0010 übertragen (Bitwert wird immer bei positiver Clock-Flanke ü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	40	1	out	CS	1);
  //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.