Jukebox controlado por RFID com Arduino 12

Jukebox é uma máquina antiga que toca músicas conforme você coloca moedas. Nos tempos modernos, podemos colocar tags RFID no lugar de moedas e fazer com que cada uma toque uma música específica. Este post descreve a montagem de um jukebox controlado por RFID onde a música a tocar é escolhida através de um tag RFID.

jukebox controlado por RFID com arduino

Material necessário para o jukebox controlado por RFID

Montagem do circuito

A figura abaixo mostra a interconexão entre as partes.

montagem jukebox controlado por RFID com arduino

Coloque no diretório \MP3 do cartão SD até nove músicas no formato MP3. Os arquivos devem ter os nomes começando com “0001” a “0009” (sem pular nenhum número).

transferindo arquivos para o jukebox controlado por RFID

Operação do jukebox controlado por RFID

O primeiro passo é associar os tags às músicas. Aperte o botão, vai aparecer 1 no display. Você tem até 5 segundos para ler o tag que será associado à musica 1. Se você não ler um tag neste tempo, a configuração será encerrada e o display irá apagar. Lido um tag, o display passa a apresentar o número seguinte, para você ler um outro tag. O processo se repete até você ler 9 tags ou ficar 5 segundos sem ler um tag.

Esta associação só precisa ser feita uma vez, pois ela é salva na EEProm do Arduino e continuará valendo mesmo que você reinicie ou desligue o Arduino.

Feita a configuração, basta aproximar um tag da antena para ser tocada a música correspondente.

Programação

Vamos dar uma examinada no código parte por parte.

No programa principal temos a tabela que armazena os tags conhecidos, a iniciação,  o loop principal e a rotina de configuração (associação dos tags às músicas).

// jukebox controlado por RFID
#include <SoftwareSerial.h>
#include <EEPROM.h>

// Tags associados as musicas
#define MAX_TAGS 9
#define T_IDTAG  5
static int nTags;
static uint8_t tags[MAX_TAGS][T_IDTAG];

// Iniciacao
void setup() {
  dispInit();
  botaoInit();
  leTags();  
  delay (2000); // tempo para modulo MP3 iniciar
  mp3Init();
  rfidInit();   // precisa ser depois de mp3Init
}

// Loop Principal
void loop() {
  static bool tocando = false;
  uint8_t id[T_IDTAG];

  // Verifica se acabou musica
  if (tocando && !mp3Tocando()) {
    tocando = false;
    dispApaga();
  }
  
  // Trata configuracao
  if (botaoApertado()) {
    mp3Para();
    configura();
    return;
  }

  // Trata leitura de tag
  if (rfidLe(id)) {
    for (int i = 0; i < nTags; i++) {
      if (memcmp(id, tags[i], T_IDTAG) == 0) {
        dispNum(i+1);
        mp3Para();
        mp3Toca(i+1);
        tocando = true;
        return;
      }
    }
  }
}

// Associa tags as musicas
static void configura() {
  uint8_t id[T_IDTAG];
  long timeout;
  int musica;
  
  dispPonto(true);
  botaoEsperaSoltar();
  for (musica = 0; musica < MAX_TAGS; musica++) {

    // Informa musica a associar
    dispNum(musica+1);

    // Espera ler tag
    timeout = millis()+5000; // 5 segundos de timeout
    while (!rfidLe(id) && (millis() < timeout))
      ;
    if (millis() >= timeout) {
      break;
    }

    // Associa o taga a musica
    memcpy(tags[musica], id, T_IDTAG);
  }
  if (musica > 0) {
    nTags = musica;
    gravaTags();
  }
  dispApaga();
  dispPonto(false);
}

O tratamento do botão é o costumeiro: é usada uma entrada digital com pullup.

static const int pinBotao = A4;

void botaoInit() {
  pinMode (pinBotao, INPUT_PULLUP);
}

bool botaoApertado() {
  return digitalRead (pinBotao) == LOW;
}

bool botaoEsperaSoltar() {
  while (botaoApertado()) {
    delay (100);
  }
}

O display de 7 segmentos é controlado por oito saídas digitais (uma para cada segmento mais uma para o ponto decimal).

static const int pinSegto[]  = { 10, 9, A2, A1, A0, 11, 12, A3 };
static const uint8_t digito[10] = {
  0x3F, 0x06, 0x5B, 0x4F, 0x66,
  0x6D, 0x7D, 0x07, 0x7F, 0x6F
};

// Inicia o display
void dispInit() {
  for (uint8_t i = 0; i < 8; i++) {
    pinMode (pinSegto[i], OUTPUT);
    digitalWrite (pinSegto[i], LOW);
  }
}

// Apaga o display
void dispApaga() {
  for (uint8_t i = 0; i < 7; i++) {
    digitalWrite (pinSegto[i], LOW);
  }
}

// Controla o ponto
void dispPonto(bool aceso) {
  digitalWrite (pinSegto[7], aceso ? HIGH : LOW);
}

// Mostra numero de 0 a 9
void dispNum(uint8_t num) {
  uint8_t segtos = digito[num];
  for (uint8_t iSegto = 0; iSegto < 7; iSegto++) {
    digitalWrite (pinSegto[iSegto], (segtos & 1) ? HIGH : LOW);
    segtos = segtos >> 1;
  }
}

O leitor RFID utiliza comunicação serial. Como o Arduino Nano usa a serial de hardware para a comunicação com o micro, utilizei uma SoftwareSerial. Nela entrada e saída são feitas usando uma entrada digital e uma saída digital. No caso do leitor, a saída não está conectada, apenas a entrada é usada. Uma limitação da SoftwareSerial é que apenas uma pode receber por vez. Vamos usar uma outra SoftwareSerial para comunicar com o módulo MP3, porém lá iremos somente transmitir. Para receber na SoftwareSerial ligada ao módulo RFID ela deve ser iniciada por último. Obs: como um módulo apenas transmite e o outro apenas recebe e a taxa usada é a mesma nos dois, poderia ter sido usada uma única SoftwareSerial, mas acho que ficaria mais confuso. A parte principal do tratamento do leitor é reconhecer e validar a mensagem recebida. Isto é feito através de uma máquina de estados, onde o estado atual é a posição do caracter sendo recebido. Para tratar em paralelo o botão e a leitura de RFID, a rotina rfidLe mantém o conteúdo de suas variáveis entre chamadas (reparar no uso de static nas declarações).

static const int pinRfidRx = 4;
static const int pinRfidTx = 5; // nao conectado
static SoftwareSerial rfidSerial(pinRfidRx, pinRfidTx);
static const uint8_t STX = 0x02;
static const uint8_t ETX = 0x03;

// Inicia
void rfidInit() {
  rfidSerial.begin(9600);
}

// Verifica se tem leitura
// Transmissao do leitor: STX hhhhhhhhhh cc ETX
bool rfidLe(uint8_t *id) {
  static uint8_t pos = 0;
  static uint8_t dig = 0;
  static uint8_t chk;
  static uint8_t msg[6];

  while (rfidSerial.available() > 0) {
    uint8_t c = rfidSerial.read();
    switch (pos) {
      case 0: // Aguardando inicio da mensagem
        if (c == STX) {
          chk = 0;
          dig = 0;
          pos++;
        }
        break;
      case 13:  // Aguardando fim da mensagem
        if (c == ETX) {
          // sucesso
          for (int i = 0; i < T_IDTAG; i++) {
            id[i] = msg[i];
          }
          pos = 0;
          return true;
        } else {
          // erro, aguarda nova mensagem
          pos = 0;
        }
        break;
      default:  // Recebendo corpo da mensagem
        if ((c >= '0') && (c <= '9'))
          c = c - '0';
        else if ((c >= 'A') && (c <= 'F'))
          c = c - 'A' + 10;
        else {
          // invalido, aguarda nova mensagem
          pos = 0;
          break;
        }
        if (pos & 1) {
          // primeiro digito
          msg [dig] = c << 4;
        } else {
          // segundo digito
          msg [dig] |= c;
          chk ^= msg [dig];
          dig++;
          if (pos == 12) {
            // confere check
            if (chk != 0) {
              // erro, aguarda nova mensagem
              pos = 0;
              break;
            }
          }
        }
        pos++;    
        break;
    }
  }
  return false;
}

Como dito, o módulo MP3 também utiliza comunicação serial. Vamos apenas enviar comandos para ele; o sinal Busy (ocupado) é usado para determinar se está tocando uma música. O MP3 DFPlayer Mini possui vários recursos interessantes, aqui usamos somente o básico.

static const int pinMp3Tx = 2;
static const int pinMp3Rx = 3; 
static const int pinMp3Busy = 8;
static SoftwareSerial mp3Serial(pinMp3Rx, pinMp3Tx);

static const uint16_t volume = 15;      // 0 a 48
static const uint16_t equalizacao = 1;  // 0=normal, 1=pop, 2=rock, 3=jazz
                                        // 4=classic, 5=bass
static uint8_t bufCmdMp3[10];

void mp3Init() {
  // prepara o buffer de envio de comand com os valores fixos
  bufCmdMp3[0] = 0x7E;  // marca do inicio
  bufCmdMp3[1] = 0xFF;  // versão do protocolo
  bufCmdMp3[2] = 6;     // tamanho dos dados
  bufCmdMp3[4] = 0;     // não queremos resposta
  bufCmdMp3[9] = 0xEF;  // marca do fim
  
  // pino para detectar fim da musica
  pinMode(pinMp3Busy, INPUT);

  // configura o módulo
  mp3Serial.begin(9600);
  mp3EnviaCmd(0x06, volume);
  delay(30);
  mp3EnviaCmd(0x07, equalizacao);
  delay(30);
}

// Inicia uma faixa no direrio \MP3
void mp3Toca(uint16_t faixa)
{
  // envia o comando
  mp3EnviaCmd(0x12, faixa);
    
  // aguarda começar a tocar
  long timeout = millis()+3000; // 3 segundos de timeout
  while ((digitalRead(pinMp3Busy) == HIGH) && (millis() < timeout))
  {
    delay(10);
  }
}

// Para de tocar
void mp3Para() {
  mp3EnviaCmd(0x16, 0);
  long timeout = millis()+3000; // 3 segundos de timeout
  while ((digitalRead(pinMp3Busy) == LOW) && (millis() < timeout))
  {
    delay(10);
  }
}

// Verifica se acabou de tocar
bool mp3Tocando()
{
  return digitalRead(pinMp3Busy) == LOW;
}

// Envia comando ao módulo
static void mp3EnviaCmd(uint8_t cmd, uint16_t param)
{
  // coloca comando e parametro no buffer
  bufCmdMp3[3] = cmd;
  bufCmdMp3[5] = param >> 8;
  bufCmdMp3[6] = param & 0xFF;

  // calcula o checksum e coloca no buffer
  uint16_t check = 0;
  for (int i = 1; i < 7; i++)
  {
    check += bufCmdMp3[i];
  }
  check = -check;
  bufCmdMp3[7] = check >> 8;
  bufCmdMp3[8] = check & 0xFF;

  // transmite o buffer
  for (int i = 0; i < 10; i++)
  {
    mp3Serial.write(bufCmdMp3[i]);
  }
}

Por último temos as rotinas para ler e gravar a configuração na EEProm:

// Carrega da EEPROM as tags
void leTags() {
  nTags = EEPROM.read(0);
  if ((nTags < 1) || (nTags > MAX_TAGS)) {
    // assume que nao configurou tags
    nTags = 0;
    return;
  }
  int pos = 1;
  for (int i = 0; i < nTags; i++) {
    for (int j = 0; j < T_IDTAG; j++) {
      tags[i][j] = EEPROM.read(pos);
      pos++;
    }
  }
}

// Salva as tags na EEPROM
void gravaTags() {
  EEPROM.write(0, nTags);
  int pos = 1;
  for (int i = 0; i < nTags; i++) {
    for (int j = 0; j < T_IDTAG; j++) {
      EEPROM.write(pos, tags[i][j]);
      pos++;
    }
  }
}

Aperfeiçoamentos

Se você gostar bastante deste projeto, você pode montá-lo de uma forma mais definitiva em uma caixa, como a sugestão abaixo.

jukebox controlado por RFID

Para suportar mais de 9 músicas é preciso, além de alterar o define MAX_TAGS no código, substituir o display de 1 dígito. Você pode, por exemplo, usar um módulo com quatro dígitos (como https://www.makerhero.com/produto/modulo-74hc595-com-display-4-digitos/ e https://www.makerhero.com/produto/modulo-tm1637-com-display-7-segmentos-4-digitos/) com as devidas mudanças no software.

A montagem apresentada produz som monofônico, amplificado pelo módulo MP3. Você pode também conectar um amplificador estéreo aos pinos DAC_R e DAC_L para obter um som estereofônico e com mais potência.

E aí? curtiu fazer seu próprio jukebox controlado por RFID? Ajude-nos a melhorar o blog comentando abaixo sobre este tutorial.

Faça seu comentário

Acesse sua conta e participe

12 Comentários

  1. Boa tarde,

    Seria possível tirar uma dúvida minha?

    Eu fiz a ligação de todos os pinos como indicado no esquema da protoboard mas quando aperto o batão o display não acende.
    Coloque um Serial.print com uma mensagem “OK” após pressionar o batão a mensagem aparece no monitor serial enquanto estou pressionando o botão, mas quando o solto a mensagem cessa. Já para fazer aparecer algo no display tive que no comando “digitalWrite (pinSegto[i], LOW);” no lugar de “LOW” coloquei “HIGH” mas nada acontece depois de ele mostrar “0” no display. O que estou fazendo de errado?
    Desculpe-me a ignorância no assunto.

    1. Olá José,

      Seu display é catodo comum ou anodo comum?

      Abraço!
      Rosana – Equipe MakerHero

        1. Olá José,

          Você chegou a testar seu display separadamente? Está funcionando certinho?

          Abraço!
          Rosana – Equipe MakerHero

          1. Sim. Liguei-o no mesmo Arduíno nano associado a um resistor de 220 ohms, mas somente utilizei os pinos de +5 V e o GND. Sem botões e utilização de outros pinos

    2. José,

      Tenho a impressão que o seu problema não é no display. Para conferir, experimente colocar um dispNum(8) no setup(), após o dispInit(). O display deve apresentar todos os segmentos acesos (numero 8). Supondo que passou neste teste, repare se quando você aperta o botão o ponto do display acende. Se sim, solte o botão e aguarde até 3 segundos (timeout em mp3Para), depois disso deveria aparecer 1 no display. Se demorar 3 segundos, verifique a ligação do D8 do Arduino ao BUSY do DFPlayer.

      Abraço
      DQ

      1. Muito obrigado pela ajuda! Vou fazer os testes essa semana e lhes dou um retorno.

      2. O display deve apresentar todos os segmentos acesos (numero 8). – OK
        Quando você aperta o botão o ponto do display acende. – Não aparece o ponto.

        1. Eu consegui fazer o rfid e o display funcionar. A rotina de gravar e ler a tag esta funcionando mas ela não está acionando o df player mini. Você acha q poderia ser só uma ligação do busy?
          Para fazer o df player funcionar separadamente tive que colocar um resistor de 330 ohms.
          Obg pela ajuda!

  2. Boa tarde,

    A loja do site vende o kit para esse jukebox?

    1. Olá José,

      Infelizmente não vendemos como um kit. Apenas vendemos os materiais necessários separadamente.

      Abraço!
      Rosana – Equipe MakerHero