Mememe: un reproductor MP3 para Ada

Ada es muy pequeña, pero muy lista. Podría poner música ella sola con el WinAMP si le dejara el ratón. Pero no le voy a dejar el ratón. Así que la alternativa era evidente: fabricarle un reproductor de música que funciona con muñecos. Pones un muñeco encima y suena su música. Un muñeco = una playlist. Lo quitas, pones otro, y cambia la música. Interfaz de usuario de nivel: un año y medio.

La idea

Primero, como en los proyectos serios, la fase de diseño. El wireframe está hecho sin KiCad ni nada. Las especificaciones técnicas importantes ya están todas ahí: que la tarjeta SD va en FAT16 o FAT32, que cada playlist es una carpeta numerada con sus mp3 dentro, y unos cuantos cables de colores que luego, sorprendentemente, resultaron ser más o menos verdad.

El esquema. No tiene pérdida.
El esquema. No tiene pérdida.

Los componentes

La lista de la compra es corta y barata:

  • Un Arduino Nano, que hace de cerebro.
  • Un DFPlayer Mini, un reproductor de mp3 chino que cuesta dos euros y lleva lector de tarjeta SD incorporado. Le hablas por serie y él reproduce lo que le pidas.
  • Un lector RFID RC522, también de dos euros, para detectar qué muñeco hay encima.
  • Un altavoz de 4 ohmios rescatado del cajón, claro.
  • Y la joya de la corona: una batería que venía de regalo con el Nesquik grande. Una powerbank promocional que llevaba años esperando su momento de gloria. Era este.
  • Algunos conectores y botones variados para rematar: Una entrada usb-c para recargarlo, un pulsador para pasar a la siguiente canción y poco más.
El prototipo en la protoboard: RC522, Nano, altavoz y las pastillas RFID con su pegatina.
El prototipo en la protoboard: RC522, Nano, altavoz y las pastillas RFID con su pegatina.

Ojo con las etiquetas RFID, que aquí hay trampa: hay principalmente dos tipos y no son compatibles entre sí. Las de 125 kHz (las típicas de llaveros de garaje) y las de 13,56 MHz (tipo MIFARE, las de los abonos de transporte). El RC522 solo lee las de 13,56 MHz, así que si compras las otras te quedas mirando el lector y el lector mirándote a ti. Yo compré unas monedas adhesivas de 13,56 MHz, que para pegarlas debajo de un muñeco son perfectas.

El código

La lógica es sencilla: el RC522 lee el identificador único de la pastilla RFID del muñeco, el Arduino lo busca en una tabla que dice qué carpeta de la SD le corresponde, y le ordena al DFPlayer que reproduzca esa carpeta. Cuando se acaba una canción salta a la siguiente, y cuando se acaba la carpeta, silencio. También hay un botón para saltar de canción, para cuando la canción del Pollito Pío ya ha sonado suficientes veces (spoiler: nunca ha sonado suficientes veces).

No os pongo aquí el tocho entero porque esto es un blog, no un repositorio, pero lo dejo desplegable para quien quiera copiarlo:

Ver el código completo del Arduino
#include <DFRobotDFPlayerMini.h>
#include <SoftwareSerial.h>
#include <SPI.h>
#include <MFRC522.h>

#define RST_PIN  9
#define SS_PIN   10
#define BUTTON_PIN 6  // Pin para el botón

MFRC522 mfrc522(SS_PIN, RST_PIN);
SoftwareSerial SerialMP3(4, 5);
DFRobotDFPlayerMini myDFPlayer;

struct TagFolder {
  byte uid[7];
  uint8_t folder;
};

TagFolder tagMap[] = {
  {{0x08, 0xD3, 0x0E, 0x01, 0x99, 0x37, 0xA3}, 1}, // dibujos
  {{0x05, 0xa1, 0x13, 0x16, 0x46, 0xA2, 0x89}, 2} // pollito pio

};

#define NUM_TAGS (sizeof(tagMap)/sizeof(tagMap[0]))

byte lastUID[7] = {0}; // Último UID leído
bool isPlaying = false;
bool tagPresente = false;
uint8_t currentFolder = 0;
uint8_t currentTrack = 0;

void setup() {
  Serial.begin(9600);
  SerialMP3.begin(9600);
  SPI.begin();
  mfrc522.PCD_Init();

  pinMode(BUTTON_PIN, INPUT_PULLUP);

  myDFPlayer.begin(SerialMP3);
  delay(2000);
  myDFPlayer.volume(20);
  myDFPlayer.EQ(DFPLAYER_EQ_NORMAL);
  myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD);


byte version = mfrc522.PCD_ReadRegister(mfrc522.VersionReg);
Serial.print("Versión del lector: 0x");
Serial.println(version, HEX);


  Serial.println("Sistema listo. Pon un moñeco.");
}

bool sameUID(byte *uid1, byte *uid2, byte len) {
  for (byte i = 0; i < len; i++) {
    if (uid1[i] != uid2[i]) return false;
  }
  return true;
}

uint8_t buscarCarpetaPorUID(byte *uid, byte uidSize) {
  for (uint8_t i = 0; i < NUM_TAGS; i++) {
    if (sameUID(uid, tagMap[i].uid, uidSize)) {
      return tagMap[i].folder;
    }
  }
  return 0;
}

void detenerReproduccion() {
  myDFPlayer.stop();
  memset(lastUID, 0, sizeof(lastUID));
  tagPresente = false;
  isPlaying = false;
  currentFolder = 0;
  currentTrack = 0;
  Serial.println("Fin de la carpeta.");
}

void loop() {
  static bool tagAnteriorPresente = false;
  static unsigned long lastDebounceTime = 0;
  static bool buttonLast = HIGH;

  // Botón para siguiente pista
  bool buttonState = digitalRead(BUTTON_PIN);
  if (buttonLast == HIGH && buttonState == LOW && millis() - lastDebounceTime > 200) {
    Serial.println("CLICK!");
    if (isPlaying) {
      currentTrack++;
      uint16_t n = myDFPlayer.readFileCountsInFolder(currentFolder);
      n = myDFPlayer.readFileCountsInFolder(currentFolder);
      n = myDFPlayer.readFileCountsInFolder(currentFolder);//tres veces por un bug raro
      if (currentTrack > n) {
        detenerReproduccion();
      } else {
        myDFPlayer.playFolder(currentFolder, currentTrack);
        Serial.print("Siguiente pista: ");
        Serial.println(currentTrack);
      }

      
    }
    lastDebounceTime = millis();
  }
  buttonLast = buttonState;

  // Lectura de tarjeta
  if (mfrc522.PICC_IsNewCardPresent()) {
    if (mfrc522.PICC_ReadCardSerial()) {
      byte *uid = mfrc522.uid.uidByte;
      byte uidSize = mfrc522.uid.size;

      // ¿UID diferente al anterior?
      if (!sameUID(uid, lastUID, uidSize)) {




        Serial.print("ID del moñeco detectado: ");
        for (byte i = 0; i < mfrc522.uid.size; i++) {
          Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
          Serial.print(mfrc522.uid.uidByte[i], HEX);
        }
      Serial.println();

  
        uint8_t folder = buscarCarpetaPorUID(uid, uidSize);
        if (folder > 0) {
          myDFPlayer.stop();
          delay(200);
          currentFolder = folder;
          currentTrack = 1;
          myDFPlayer.playFolder(currentFolder, currentTrack);
          Serial.print("Moñeco detectado! Reproduciendo carpeta: ");
          Serial.println(currentFolder);
          memcpy(lastUID, uid, uidSize);
          isPlaying = true;
          tagPresente = true;
        } else {
          Serial.println("Tag no reconocido.");
        }
      } else {
        // Mismo tag detectado
        if (false && !isPlaying) {
          myDFPlayer.start();
          isPlaying = true;
          Serial.println("Reanudando reproducción.");
        }
      }
      tagAnteriorPresente = true;
      mfrc522.PICC_HaltA();
    }
  } else {
    // No hay tag presente ahora pero antes sí → retirado
    if (false && tagAnteriorPresente) {
      myDFPlayer.pause();
      isPlaying = false;
      tagPresente = false;
      Serial.println("Tag retirado. Reproducción en pausa.");
      tagAnteriorPresente = false;
    }
  }

  // Comprobamos si ha terminado la pista
if (myDFPlayer.available()) {
  
  uint8_t tipo = myDFPlayer.readType();
  Serial.print("Evento recibido: ");
  Serial.println(tipo);
  if (tipo == 11) {
    
    delay(200);  // Evita saturar el módulo
    currentTrack++;

      uint16_t n = myDFPlayer.readFileCountsInFolder(currentFolder);
      n = myDFPlayer.readFileCountsInFolder(currentFolder);
      n = myDFPlayer.readFileCountsInFolder(currentFolder);//tres veces por un bug raro
      if (currentTrack > n) {
        detenerReproduccion();
      } else {
        myDFPlayer.playFolder(currentFolder, currentTrack);
        Serial.print("Avance automático a pista: ");
        Serial.println(currentTrack);
      }
    
  }
}


}

Detalle a apreciar: hay una consulta al DFPlayer que se hace tres veces seguidas «por un bug raro». No preguntéis. Funciona así y así se queda.

La caja

Todo el tinglado se mete en una caja de madera que en su vida anterior era una caja de lápices. Dentro van el altavoz, la electrónica, la batería del Nesquik y una cantidad de cable que en la protoboard parecía razonable y aquí dentro parece una cesta de mimbre.

Las tripas: altavoz, batería y espagueti de cables reglamentario.
Las tripas: altavoz, batería y espagueti de cables reglamentario.

Por fuera, en cambio, la cosa queda bastante digna: rejilla para el altavoz, una ruedecita de volumen (que me regaló hace muchos años un señor en Electrónica Burriana porque ya era muy vintage), el botón de pasar canción, el de encendido con su lucecita (me encanta ese botón) y, arriba, un hueco redondo donde encaja la peana del muñeco, justo encima del lector RFID.

Terminado. El hueco de arriba es donde va el muñeco.
Terminado. El hueco de arriba es donde va el muñeco.

Los muñecos

Y aquí viene la parte que no estaba en el plan: los muñecos se han convertido en una afición en sí misma. Los compro en el mercadillo de los domingos, figuritas de las de toda la vida, tipo ComicSpain, Bullyland y compañía. El protocolo es siempre el mismo: cada vez que a Ada le engancha una música nueva, hay que salir de expedición a buscar un muñeco adecuado (y barato), pegarle en la peana una base de Warhammer, meterle dentro la pastilla RFID, apuntar su identificador en la tabla del código, y a funcionar.

Así que la estantería va creciendo: Aladdín, Baloo, David el Gnomo, la Pantera Rosa en varias de sus encarnaciones… Cada uno con su playlist. Una discoteca de vinilos, pero en muñecos.

La colección en su estantería.
La colección en su estantería.

Al final, el aparato ha cumplido exactamente lo que prometía: Ada pone su música sola, la cambia cuando quiere, y el ratón sigue siendo mío. Y de regalo, una excusa estupenda para dar una vuelta y comprar muñecos en el mercadillo “por necesidad”. Todos ganamos.

volver al blog