V době pandemie je důležité udržovat veselou mysl. Rozhodl jsem se tedy vytvořit jednoduchý víkendový projekt pro bastlíře – přehrávač náhodných hlášek (mp3 souborů uložených na SD kartě). Velká část IT specialistů určitě zná legendární hlášky z opravy slovenského traktoru lakatoše – ne, nenasadíš ani kdybys ses … A aby to nebylo jenom o hláškách z lakatoše, přidal jsem do svého přehrávače i známe hlášky z českých filmů.

Kód pro ATtiny: https://github.com/tvecera/funny-radio

Dokumentace k DFPlayer Mini: https://wiki.dfrobot.com/DFPlayer_Mini_SKU_DFR0299

Fínální podoba krabičky

Potřebné součástky

  • 5 cm reproduktor
  • Digispark ATtiny85
  • DFPlayer Mini, nebo jeho klon
  • SD karta ze šuplíku
  • USB kabel
  • Krabička, tu si můžete vytisknout, nebo koupit

Díky použitému Digispark Attiny85 není potřeba mít ani TTL převodník z USB na UART.

Zapojení

Základem celého zařízení je modul DFPlayer Mini. Jedná se o modul miniaturního kompletního MP3 přehrávače s 3W zesilovačem. Pro připojení k ATTiny, nebo jinému MCU, stačí pouze UART rozhraní (RX, TX). Modul je možné použít taky samostatně po připojení ovládacích tlačítek. S ohledem na omezený počet použitelných vývodů u Digispark ATtiny85, jsem ve svém zapojení použil pouze linku Arduinio TX <–> Player RX, přes kterou jsou posílány příkazy pro přehrání konkrétní skladby, nastavení přehrávače a v neposlední řadě i jeho uspání v době, kdy se nepřehrává žádná skladba.

ATtiny DFPlayer Mini
5V 5V
GND GND
PB1 (UART TX) RX
PB2 BUSY

Důvod, proč jsem se rozhodl použít Digispark ATtiny 85 byl ten, že se mi jich několik válelo v šuplíku z předchozích projektů. Pro malé projekty jej mám opravdu rád. Pro naprogramování není nutné připojovat převodník na USB, je možné jej použít i jako klávesnici (např. jako tlačítka pro ovládání OBS studia atd.).

Nevýhodou tohoto modulu je především omezený počet využitelných pinů. Využít jde v podstatě pouze PB0 až PB3. Přičemž na PB1 je napojena led dioda. PB4, PB5 jsou využívány pro USB rozhraní. Jdou použít, USB je pak ale možné použít pouze pro programování. Na PB6 je často vyveden reset pin. Pro jednoduché projekty je ale počet vývodů dostatečný.

Pro získání informace o tom, že přehrávač přehrává skladbu jsem nakonec nepoužil sériovou komunikaci. Musel bych parsovat odpověď z přehrávače a celé řešení by to jenom komplikovalo. Informaci jde jednoduše získat z pinu 16 na modulu přehrávače. Ten je v případě, že se přehrává skladba nastaven na stav LOW.

Schéma zapojení

Schéma zapojení

K PB0 je přes odpor připojeno tlačítko pro play náhodné skladby. Při zapojování tlačítka si dávejte pozor, aby se vám nestalo to co mě. Po zapájení na univerzální PCB jsem zjistil, že jsem si jej otočil špatným směrem.

V některých zapojeních se doporučuje mezi TX (DFplayer) a RX(MCU) dát odpor cca 1k pro omezení šumu, já jej taky použil.

ATtiny85 nemá piny pro UART, proto je nutné použít knihovnu TinySuite. Jenom pozor, v příkladech ke knihovně je pro UART komunikaci použit Timer1. Když jej použijete, přestane vám fungovat příkaz delay, který jej využívá. Je potřeba použít Timer0.

Jestli vám nestačí hlídat pouze to, že přehrávač přehrává skladbu, můžete připojit i TX pin přehrávače na PB2 ATtiny. Pro komunikaci s přehrávačem pak stačí využít některou z knihoven.

Program

Samotný program je velmi jednoduchý. Pro vývoj jsem nepoužil Arduino IDE, přiznám se, že v poslední době s ním mám jeden problém za druhým. Drobné arduino projekty aktuálně vyvíjím pomocí PlatformIO.

Start nového projektu, přidání nové desky, knihovny je v něm extrémně jednoduché. Jedná se o plugin do Visual Studia Code. Samotný kód pro Arduino se v něm píše mnohem lépe, než v Arduiono IDE.


void setup() {
  sei();
  delay(500);
  playerOn();
  delay(3000);
  setVolume(VOLUME);
 
  fisherYatesShuffle(&moviePlaylist);
  fisherYatesShuffle(&lakatosPlaylist);
 
  pinMode(BUTTON_PORT, INPUT);
  pinMode(PLAYER_BUSY_PORT, INPUT);
 
  randomSeed(analogRead(0));
  lastPlayTime = millis();
}
 
void loop() {
  if (digitalRead(BUTTON_PORT) == HIGH) {
    lastPlayTime = millis();
    playerOn();
    if (digitalRead(PLAYER_BUSY_PORT) == HIGH) {
      shufflePlay(&moviePlaylist);
    } else {
      shufflePlay(&lakatosPlaylist);
    }
  } else {
    sleep(SLEEP_MODE_IDLE);
  }
 
  if (millis() - lastPlayTime > IDDLE_TIME && !inSleep) {
    playerOff();
  }
}

Výzvou bylo zajistit, aby se po spuštění přehrávače vygenerovalo náhodné pořadí skladeb. Vygenerování náhodného čísla skladby nestačí. Narazíte totiž na to, že samotný příkaz random příliš náhodný není. Pro zlepšení random je potřeba použít příkaz randomSeed(analogRead(0)).

Ten zajistí, že funkce random vrací alespoň trochu náhodná čísla. Problémem je, že se jednotlivé skladby mohou celkem pravidelně opakovat.

Řešením je vygenerování náhodného seznamu skladem, v kterém bude každá skladba právě jednou. K tomu jsem využil Fisher-Yates algoritmus (jeho modernější verzi od Richarda Durstenfelda). Algoritmus je jednoduchý na implementaci a jeho O(n) asymptotická složitost je výhodná pro náš nevýkonný hardware (více na wikipedii).


void fisherYatesShuffle(struct Playlist* playlist) {
  for (uint16_t i = 0; i < playlist->files; i++) {
    playlist->songs[i] = i + playlist->startIndex;
  }
 
  for (uint16_t i = playlist->files - 1; i > 0; i--) {
    uint16_t j;
    j = random(RAND_MAX) % (i + 1);
    if (j != i) {
      uint16_t swap = playlist->songs[j];
      playlist->songs[j] = playlist->songs[i];
      playlist->songs[i] = swap;
    }
  }
}
 
void shufflePlay(struct Playlist* playlist) {
  uint16_t fileIndex = playlist->songs[playlist->currentIndex];
  playtrack(fileIndex);
  if (playlist->currentIndex < playlist->files - 1) {
    playlist->currentIndex = playlist->currentIndex + 1;
  } else {
    fisherYatesShuffle(playlist);
    playlist->currentIndex = 0;
  }
}

Sériová komunikace s modulem přehrávače je v celku standardní a komunikace probíhá pomocí fixních vět o délce 10 bajtů. Do přehrávače je nutné posílat větu v následujícím tvaru:

Byte Hodnota Význam
1 7E Byte identifikující začátek věty
2 FF Verze
3 06 Délka payloadu – 6 bytů
4 06 Příkaz. Např. 0x06 je příkaz pro nastavení hlasitosti.
5 00 Feedback, 1: feedback, 0: no feedback
6–7   Parametr pro daný příkaz. Např. hlasitost: 20 0000 0000 0001 0100 5 – High data byte 6 – Low data byte
8–9   Kontrolní součet
10 EF Byte identifikující konec věty
Univerzální PCB

Komunikace přes UART

  • Rychlost: 9600 bps
  • Data bits: 1
  • Checkout: NA
  • Flow control: NA

Použité příkazy


void send(uint8_t command, uint16_t parameter = 0) {
  uint8_t out[PACKET_SIZE] = {START_BIT,
                              VERSION_BIT,
                              SIZE_BIT,
                              command,
                              00,
                              static_cast<uint8_t>(parameter >> 8),
                              static_cast<uint8_t>(parameter & 0x00FF),
                              00,
                              00,
                              END_BIT};
  uint16_t sum = checksum(out);
  out[7] = (sum >> 8);
  out[8] = (sum & 0xFF);
 
  uartWriter.on(PLAYER_TX_PORT, PLAYER_BAUDRATE, Timer0Compare);
  for (uint8_t i = 0; i < PACKET_SIZE; i++) {
    uartWriter.write(out[i]);
  }
 
  while (uartWriter.outputRemaining()) {
    sleep(SLEEP_MODE_IDLE);
  }
 
  uartWriter.off();
}

0x03 – přehrání skladby

Přehraje skladbu z root adresáře SD karty. Skladby musí být pojmenovány čísly se 4 číslicemi. Tzn. 0001.mp3, 0002.mp3, až 2999. Toto číslo se ale liší, podle toho, jaký čínský klon tohoto přehrávače máte k dispozici, nebo originál.

Příkaz pro přehrání první skladby: 7E FF 06 03 00 00 01 FF E7 EF

0x06 – nastavení hlasitosti přehrávání.

Nastaví hlasitost výstupu. Defaultně je nastavena hlasitost na 30. Záleží na použitém reproduktoru, nicméně pro přehrávání v kanceláři je i hlasitost 20 hodně.

Parametr: hlasitost od 0 až 30

Příkaz pro natavení hlasitosti na 20: 7E FF 06 06 00 00 0F FF D5 EF

0x09 – nastavení zdroje pro přehrávání

Nastaví, z jakého zařízení se bude skladba přehrávat. DFPlayer podporuje několik vstupů, kromě SD karty je to např. USB flash disk, nebo PC (opět se trochu liší dle použitého klonu, originálu). Nás zajímá SD karta a speciální mód SLEEP, do kterého player přepneme v okamžiku, kdy se nepřehrává žádná skladba. Po přepnutí na SD kartu potřebuje přehrávač cca 200ms na inicializaci zdroje (to říká datasheet k modulu, viz. níže).

Parametr:

  • 0x01 vybírá SD kartu
  • 0x03 sleep

Příkaz pro nastavení SD karty: 7E FF 06 09 00 00 01 FE F1 EF

0x0A – stand by režim

Přepne přehrávač do standby režimu. Dle dokumentace by v tomto režimu měl přehrávač konzumovat max. 20 mA. Odběr celého zařízení jsem nakonec neměřil. Původní představa byla, že součástí krabičky bude i baterie a obvod pro nabíjení. Nakonec jsme zvolil externí napájení. Opět, dle některé dokumentace se DFplayer přepíná do tohoto režimu automaticky, což min. v mém případě neplatilo.

To jsou všechny příkazy, které potřebujeme. Proto jsem nepoužil některou z dostupných knihoven a do kódu implementoval pouze tyto příkazy. Pro uspání jsem se snažil vypnout taky DAC, na to slouží příkaz 0x1A. Bohužel v mém klonu tento příkaz nefungoval správně. DAC u přehrávače šel vypnout, ale pak už nešel zapnout. Jedna z častých nevýhod, kdy při svých projektech použijete čínské klony. Co kus, to originál.

Některé hlášky z lakatoše jsou docela vulgární. Proto jsem implementoval dva playlisty, jeden, z kterého se spustí skladba při stisknutí tlačítka v okamžiku, kdy přehrávač nic nepřehrává. Stisknutí tlačítka při přehrávání skladby dojde k přehrání skladby z playlistu lakatoše, nikdo nechce přehrávat skladbu Já to nebudu dělat, nééé… když jde kolem šéf :).

Finální podoba

Při návrhu krabičky jsem počítal s tím, že v ní bude i baterie. Nakonec jsem pouze vyvedl kabel s USB konektorem mimo krabičku. Použil jsem univerzální PCB. Viz. obrázek níže.

Otevřená krabička

V mém případě jsem v kódu musel počítat i s docela dlouhou inicializací zařízení po připojení na napájení. Rozhodně to nebylo zmiňovaných 200 ms pro inicializaci připojené SD karty. Po připojení k napájení přehrávač nastartoval za cca 1-2 vteřiny. Po připojení k napájení vydá reproduktor divný zvuk, asi by šlo vyřešit filtračními kondenzátory.

Řešení není určitě ideální, pro změnu hlasitosti, přidání písniček je nutné nahrát nový program do ATtiny. Šlo by více optimalizovat spotřebu, mít samostatná tlačítka pro změnu hlasitosti atd.

I tak je s ním docela velká sranda, hlavně když se přehrávaná skladba trefí do kontextu :). Prostě jednoduchý víkendový projekt.

Přeji hodně zábavy