Fyzikální kabinet FyzKAB
TechHobby ESP32 + MicroPython ESP32: Čidla a senzory v MicroPythonu ESP32: ovládání modulu JQ6500 přes UART

ESP32: ovládání modulu JQ6500 přes UART

V předchozích článcích jsme se seznámili s několika běžně používanými komunikačními rozhraními pro mikrokontroléry. Ať už to bylo SPI, které je velmi rychlé a vhodné pro komunikaci s pamětmi, displeji nebo jinými zařízeními s vysokými nároky na přenos dat, nebo sběrnice I²C, jež díky své jednoduchosti a možnosti připojit více zařízení na dvojici komunikačních vodičů nachází uplatnění u řady senzorů, RTC čipů nebo expandérů.

Přesto jsme dosud poněkud opomíjeli jedno z nejstarších a nejzákladnějších sériových rozhraní – UART.

Je tedy na čase to napravit!

Dnes si UART nejen představíme, ale rovnou si ukážeme jeho praktické použití na konkrétním příkladu – ovládání modulu JQ6500, což je malý, levný, ale překvapivě schopný přehrávač zvukových souborů, který se ovládá právě přes UART.

UART

UART (Universal Asynchronous Receiver/Transmitter) nebo také SCI (Serial Communication Interface) je jedno z nejjednodušších rozhraní pro přenos dat mezi dvěma zařízeními. Najdeme ho prakticky ve všech mikrokontrolérech, nejen u ESP32, ale i u Arduina, STM32, Raspberry Pi a dalších. UART používají i různé moduly, například GPS přijímače, moduly pro komunikaci přes GSM, některé RFID čtečky nebo právě zvukové přehrávače, jako je JQ6500.

Vlastnosti UART:

  • Asynchronní:Vzhledem k tomu, že není potřeba sdílený hodinový signál, přenos je řízen pouze startovacím a stopovacím bitem. Tento způsob je ideální pro jednoduché a flexibilní přenosy.
  • Full duplex: UART je schopný současně vysílat a přijímat data (to znamená, že dvě zařízení mohou komunikovat obousměrně bez vzájemného čekání).
  • Sériový přenos: Data jsou přenášena bit po bitu na jednom vodiči, což znamená, že místo paralelního přenosu (kde je potřeba více vodičů) stačí pouze dvě linky: TX (vysílání) a RX (přijímání).

Sběrnice UART je ideální pro jednoduchou a rychlou obousměrnou komunikaci mezi dvěma zařízeními. I když má oproti novějším rozhraním některá omezení (např. jen dvě zařízení na jedné lince), právě jeho jednoduchost z něj dělá stále velmi praktický nástroj pro řadu aplikací.

Pokud budeme chtít ve svém programu použít komunikaci přes UART, je třeba při konfiguraci této sběrnice nastavit několik základních parametrů:

  • Baudrate (rychlost přenosu): Udává, jak rychle budou data odesílána, obvykle v bps (bits per second). Například 9600 bps znamená 9600 bitů za sekundu.
  • Data bits: Obvykle 8 bitů, což znamená, že každý přenášený bajt obsahuje 8 bitů informací. (Toto nastavení vypadá trochu nelogicky, ale vězte, že existují zařízení, u kterých se informace přenáší kupříkladu jen pomocí 7 bitů.)
  • Stop bits: Obvykle 1 stop bit (někdy mohou být 2), který indikuje konec datového rámce.
  • Parity bit: Tento bit slouží k detekci chyb. Někdy se nepoužívá (je volitelný), což znamená, že přenos není kontrolován. V takovém případě je parametr Parity bit nastaven na None.

Přenos dat přes UART

Při přenosu jednoho bajtu dat přes UART je celý rámec tvořen těmito základními částmi:

Start bit Data bits Parity bit (volitelný) Stop bit

Příklad přenosu:

Pokud bychom chtěli přenést hodnotu 0xA0 (v binární podobě 10100000) bez nastaveného kontrolního paritního bitu, přenos by vypadal takto:

0 (Start) 10100000 (Data) 1 (Stop)
  • Start bit: Označuje začátek přenosu (vždy 0).
  • Data bits: 8 bitů dat, které přenášíme.
  • Stop bit: Ukončuje přenos (vždy 1).

Bližší práci se sběrnicí UART na modulu ESP32 si ukážeme dále při odesílání konkrétních příkazů pro zvukový modulu JQ6500.

Modul JQ6500

JQ6500 je populární čip, který slouží jako jednoduchý MP3/WAV přehrávač pro různé projekty. Je vybaven interní pamětí (flash nebo SD), do které můžeme nahrát vlastní zvukové soubory, a následně je přehrávat. Kromě jiného lze modul ovládat pomocí jednoduchých sériových příkazů.

JQ6500 - líc JQ6500 - rub
Obrázek č. 1 – zvukový modul JQ6500-16P (ver. 2.1)

Proč si vybrat JQ6500?

  • Nízká cena: Tento modul je cenově velmi dostupný (obvykle stojí méně než 100 Kč), což z něj činí ideální volbu pro DIY projekty.
  • Jednoduché ovládání: JQ6500 nevyžaduje složité programování. Přenos zvukových souborů a jejich následné ovládání je snadné.
  • Různorodé aplikace: Díky své jednoduchosti a malým rozměrům je tento modul je ideální pro zvukové efekty, mluvící zařízení nebo automatizované hlášení.

Jak vidíme, JQ6500 je ideální volbou pro naši dnešní praktickou demonstraci – přehrávač zvukových souborů ve formátu MP3/WAV, který budeme ovládat přes sériovou linku. A za tu cenu!? Ale není to jen o nízké ceně, ale hlavně o zábavě (a poučení) při práci s tímto modulem. 😊 Kdo z nás by si nechtěl postavit zařízení, které vydává zvuky lepší než pouhá sterilní pípání? Můžeme postavit mluvící budík, zařízení se zvukovým upozorněním na stav jiného zařízení, nebo dokonce přidat unikátní zvukové efekty do interaktivní hračky… Možnosti jsou omezené pouze fantazií bastlíře (a někdy také i velikostí paměti modulu JQ6500!).

Hlavní vlastnosti modulu JQ6500

  • Ukládání zvukových souborů: Námi používaný typ modulu má vestavěnou flash paměť, obvykle o kapacitě 4 MB nebo 8 MB. Do této paměti si můžete uložit vlastní zvukové soubory ve formátu MP3 nebo WAV.
  • Ovládání přes UART: Pro přehrávání souborů a ovládání modulu budeme používat sériovou linku UART. To znamená, že jakýkoli mikrořadič nebo počítač, který má UART, může tento modul ovládat (například ESP32, Arduino apod.).
  • Jednoduché ovládání: Příkazy k přehrávání, pauzování, přechodu na další skladbu nebo změně hlasitosti jsou opravdu jednoduché (viz tabulka dále). Modul přijímá příkazy ve formátu sériového přenosu (UART) a podle nich přehrává zvukové soubory, které byly dříve nahrány do paměti.

Verze modulů JQ6500:

Modul JQ6500 se vyrábí, jak již bylo naznačeno, v několika variantách. Některé moduly mají USB konektor, díky kterému můžete snadno nahrát zvukové soubory přímo z počítače. Jiné verze, které nemají USB port, obsahují pouze piny pro připojení k mikrokontroléru a vyžadují přenos souborů prostřednictvím jiných metod, například pomocí SD karty nebo jiných externích zařízení. V hobby elektronice je oblíbenou variantou modulu JQ6500 zejména verze s vestavěnou flash pamětí.

Pojďme se podívat na jeho pinout této verze a postupně si probereme, jak tento modul můžeme ovládat.

JQ6500 - pinout
Obrázek č. 2 – rozložení vývodů modulu JQ6500

Modul JQ6500 pro hlasový výstup má 16 pinů, jejichž funkce jsou vysvětleny v následující tabulce.

Pin Popis pinu Pin Popis pinu
RX Sériový vstup dat UART BUSY Indikátor přehrávání
TX Sériový výstup dat UART ADKEY AD port
(řízení pomocí vstupního napětí)
GND Zem SGND Zem (signálová)
VCC Napájení 3,5V–5V
(optimum 4,2 V)
K5 Přehrávání zvuku 5
ADC_R Sluchátka/zesilovač
(pravý kanál)
K4 Přehrávání zvuku 4
ADC_L Sluchátka/zesilovač
(levý kanál)
K3 Přehrávání zvuku 3
SPK– Reproduktor –
(8 Ω)
K2 Přehrávání zvuku 2
SPK+ Reproduktor +
(8 Ω)
K1 Přehrávání zvuku 1
Tab. č. 1 – rozložení pinů na modulu JQ6500

Ovládání tlačítky

Modul JQ6500 podporuje ovládání pomocí fyzických tlačítek, což je užitečné pro projekty, kde chcete přehrávač ovládat manuálně bez nutnosti použití mikrokontroléru. Modul nabízí piny, které umožňují připojení tlačítek (proti zemi) pro spouštění jednotlivých skladeb (piny K1–K5). Dále je k dispozici tlačítko ADKEY, které umožňuje funkce jako play/pause, přechod na další/předchozí stopu, zvýšení/snížení hlasitosti a další.

Pokud využijete pin ADKEY, je nutné připojit ovládací tlačítka přes odporové děliče, která pak nastavijí různé napětí na pinu ADKEY. Různá napětí na tomto pinu jsou pak pro modul reprezentována jako různé příkazy. I když je toto řešení zajímavé, je třeba ale říci, že je náchylné na elektromagnetické rušení, což může ovlivnit spolehlivost tohoto ovládání.

Bylo by možné vytvořit zapojení, ve kterém by mikrokontrolér simuloval tlačítka K1–K5 nebo měnil napěťovou úroveň na analogovém vstupu ADKEY. Avšak pro náš dnešní projekt zvolíme jiný a sofistikovanější způsob ovládání.

Ovládání UART

Piny RX a TX slouží ke komunikaci modulu JQ6500 s připojeným mikrokontrolérem. Modul JQ6500 má jasně definovaný formát zasílaných příkazů. Některé příkazy řídí stav modulu (přehrávání, pauza…) a jeho parametry (např. nastavení hlasitosti, typ ekvalizéru…), jiné příkazy požadují od modulu JQ6500 odpovědi (např. délka přehrávaného souboru). Jednotlivé příkazy si za chvíli vysvětlíme při přípravě programu pro komunikaci mezi modulem ESP32 a JQ6500.

Zajímavostí UART komunikace modulu JQ6500 je jeho napěťová úroveň. I když je modul napájen napětím až 5 V, napěťová úroveň jeho komunikačních linek (RX, TX) je 3,3 V. To je ideální pro připojení k modulu ESP32, který používá právě tuto napěťovou úroveň. V případě použití mikrokontroléru s 5 V logikou (např. Arduino) je třeba si na to dát pozor a nezapomenout na řešení této rozdílné napěťové úrovně.

Integrovaný zesilovač a výstupy

Jednou z velkých výhod modulu JQ6500 je, že na něj můžete rovnou připojit reproduktor, protože modul má integrovaný zesilovač pro přímý výstup. Tento výstup je sice pouze mono a výkonově se pohybuje mezi 1–3 W (v závislosti na typu modulu), ale pokud potřebujete jednoduchý projekt s jedním reproduktorem, není nutné přidávat žádný externí zesilovač. Stačí připojit reproduktor k výstupu a modul je připraven přehrávat zvukové soubory.

Jestliže je požadován kvalitnější zvuk nebo stereo efekt, je to také možné. Modul JQ6500 nabízí stereo výstup, ale pro tento výstup je již nutné připojit externí zesilovač.

Napájení a napěťové úrovně

Jak jsme již zmínili, napájení modulu JQ6500 je na úrovni 5 V (doporučené napětí je 4,2 V), což je běžné pro tento typ zařízení. Nicméně, logika sběrnice UART je v tomto případě na úrovni 3,3 V, což je plně kompatibilní s modulem ESP32.

Nahrávání MP3 souborů do JQ6500

Pokud používáte modul JQ6500 s USB konektorem, proces nahrávání souborů je velmi jednoduchý. Modul se chová „tak trochu“ jako USB flash disk, takže po připojení k počítači je automaticky detekován jako běžný diskový prostor. Tento prostor je sice určen pro ukládání zvukových souborů, ale nepočítejte s tím, že na něj budete soubory ve formátech MP3 nebo WAV kopírovat například v Průzkumníku nebo Total Commanderu.

Postup při nahrávání souborů (modul s USB konektorem):

  1. Připojení modulu JQ6500 k počítači:

Po připojení modulu JQ6500 k počítači pomocí USB kabelu se modul automaticky připojí jako USB flash disk. V počítači se objeví nové zařízení, které bude vypadat jako obyčejný pevný disk.

  1. Otevření nástroje JQ6500 Music Download Tool:

Jakmile se pokusíme modul otevřít (např. poklikáním v Průzkumníku), automaticky se spustí nástroj JQ6500 Music Download Tool, který usnadní celý proces nahrávání souborů. Tento nástroj je v modulu JQ6500 k dispozici ve verzi pro OS Windows.

Music-Download-Tool - uvodni panel
Obrázek č. 3 – úvodní panel aplikace JQ6500 Music Download Tool
Poznámka:
Nástroj JQ6500 Music Download Tool je na modulu k dispozici v čínštině, což může být pro většinu uživatelů trochu problematické. Ale ovládání je poměrně jednoduché a intuitivní, že se to dá zápis zvukových souborů zvládnout. Nicméně existují i alternativy v angličtině (viz níže).
  1. Nahrání souborů do paměti:

Otevřeme nástroj JQ6500 Music Download Tool a na první záložce si můžete vybrat zvukové soubory (např. 01.mp3, 02.mp3, atd.), které chcete nahrát. Výběr souborů potvrďte tlačítkem vedle okna.

Music-Download-Tool - vyber souboru
Obrázek č. 4 – Výběr MP3 souborů v aplikaci JQ6500 Music Download Tool

Po výběru souborů se vrátíme na první záložku a jednoduše klikneme na tlačítko pro nahrání. Program začne kopírovat soubory do flash paměti modulu JQ6500.

Music-Download-Tool - zapis souboru
Obrázek č. 5 – Zápis zvukových souborů do modulu JQ6500
  1. Odpojení modulu:

Po dokončení nahrávání můžete modul bezpečně odpojit od počítače. Soubory jsou nyní uloženy v paměti modulu a připraveny pro přehrávání.

Nástroj v angličtině

Pro uživatele, kteří nechtějí používat verzi v čínštině, existují upravené verze nástroje v angličtině, které byly zveřejněny na komunitních webech. Tato verze je mnohem uživatelsky přívětivější, neboť nevyžaduje interakci s čínským rozhraním. Další výhodou je, že lze tento nástroj spustit rovnou ve svém počítači. Jednu z možných anglických alternativ lze stáhnout ze stránek: https://sparks.gogo.co.nz/jq6500/, kde lze dohledat i spoustu jiných užitečných informací o modulu JQ6500.

Music-Download-Tool - anglická verze
Music-Download-Tool - anglická verze
Obrázek č. 6 – anglická verze nástroje JQ6500 Music Download Tool
Poznámka:
Pokud máme modul JQ6500 bez USB konektoru, jde většinou o typ se slotem pro SD kartu. V takovém případě stačí soubory MP3 nakopírovat na tuto kartu pomocí čtečky TF karet a následně ji vložit do slotu zvukového modulu.

Pojmenování souborů

Obecně se uvádí, že (bez ohledu na způsob přenosu souborů do modulu JQ6500) je velmi důležité soubory správně pojmenovat. Každý soubor by měl mít číselný název, který odpovídá pořadí skladby. Například:

  • 01.mp3 pro první skladbu
  • 02.mp3 pro druhou skladbu
  • a tak dál.

Je však třeba upozornit, že pojmenování souborů neudává pořadí, ve kterém budou soubory přehrávány. O pořadí souborů rozhoduje jejich pořadí ve FAT tabulce modulu JQ6500. Použití očíslování souborů nám tedy pomůže při jejich načítání do nástroje Music Download Tool, tím určí pořadí jejich uložení do modulu (a tedy i pořadí zápisu do FAT). Soubor 01.mp3 bude následně odpovídat prvnímu souboru a bude možné jej přehrát příkazem pro přehrávání konkrétní skladby.

Pokud možno se vyvarujme používání dlouhých názvů nebo diakritiky (a mezer) v názvech souborů. Po jednom takovém pokusu náš modul JQ6500 začal „zlobit“ a občas začne přehrávat sekvence ze souboru, který už v něm dávno neměl být. Pravděpodobně došlo k poškození FAT tabulky.

Jak připojit JQ6500 k ESP32

Pokud jsme již „naplnili“ modul JQ6500 požadovanými MP3 soubory, můžeme přistoupit k připojení modulu k řídicímu mikrokontroléru ESP32 a začneme ho řídit přes UART – pochopitelně v MicroPythonu.

Modul ESP32 má obvykle tři UART rozhraní (UART0, UART1, UART2). Každý z těchto UART portů je nezávisle konfigurovatelný a umožňuje komunikaci přes sériovou linku. Ve výchozím nastavení je UART0 obvykle vyhrazen pro sériovou komunikaci s počítačem během nahrávání a ladění programu, zatímco UART1 a UART2 jsou volné pro uživatelské aplikace.

V našem případě použijeme UART2 (výchozí piny TX = GPIO17, RX = GPIO16). Propojení mezi ESP32 a JQ6500 zobrazuje následující obrázek a tabulka:

propojeni ESP32-JQ6500
Obrázek č. 7 – schéma propojení modulu ESP32 a přehrávače JQ6500
ESP32 Pin JQ6500 Pin
GPIO17 (TX) RX
GPIO16 (RX) TX
GND GND
5V VCC
Tab. č. 2 – propojení pinů na modulu ESP32 a přehrávače JQ6500
Poznámka:
Pokud pozorného čtenáře překvapilo, že na pin RX modulu JQ6500 je připojen pin modulu ESP32, který odpovídá lince TX, nejedná se o chybu tisku! Co je pro jeden modul vysílání (TX), to musí být pro druhý modul přijímání (RX).
Linky sběrnice UART jsou tedy překřížené, což je ale zcela běžná praxe.

Pro inicializaci UART v MicroPythonu použijeme třídu UART z modulu machine. Nejprve je třeba importovat tento modul a následně vytvořit instanci UART s požadovanými parametry. Tyto parametry obvykle zahrnují číslo UART, rychlost přenosu (baud rate) a piny pro RX a TX.

Následující kód ukazuje, jak inicializovat UART2 s rychlostí 9600 baudů a piny GPIO16 a GPIO17 pro RX a TX. V ukázce hned vidíme použití metod pro zápis a čtení informací na sběrnici UART:

from machine import UART, Pin
import time

# Inicializace UART2
uart = UART(2, baudrate=9600, tx=Pin(16), rx=Pin(17))
# Malé zpoždění po inicializaci
time.sleep(1)

# Zápis dat
uart.write("Hello, ESP32!")

# Čtení dat
data = uart.read()
if data:
    print("Received:", data.decode())

Odesílání příkazů do JQ6500:

Komunikační formát

Modul JQ6500 při komunikaci přes UART očekává následující základní parametry sériového rozhraní:

  • Rychlost (Baud Rate): 9600 baudů (standardní)
  • Datové bity: 8
  • Parita: Žádná
  • Stop bity: 1

Příkazy se posílají jako bajty dat, a to jak pro ovládání modulu, tak pro přijímání odpovědí. Každá komunikace začíná start bajtem 0x7E, následující bajt určuje počet bajtů zprávy (délka zprávy je počet bajtů mezi startovním a koncovým bajtem), následuje bajt specifikující příkaz. Poté následuje jeden nebo dva bajty parametrů příkazu (dle konkrétního příkazu). Celá zpráva je pak ukončena koncovým bajtem 0xEF (viz následující schéma struktury rámce):

[0x7E] [length] [command] [param1] [param2] [0xEF]

Pokud například chceme spustit přehrávání prvního souboru, musíme odeslat následující komunikační rámec:

0x7E 0x04 0x03 0x00 0x01 0xEF

kde struktura rámce je následující:

  • Start bajt → 0x7E
  • Délka paketu → 1 + 1 + délka dat, tedy: [délka (1) + příkaz (1) + data (2)] = 0x04
  • Příkaz → 0x03 (kód pro příkaz „Přehrát soubor podle indexového čísla“)
  • Data → 0x00 a 0x01 (číslo souboru – v pořadí horní osmice (0x00), pak dolní osmice bitů (0x01), zde tedy skladba číslo 1)
  • End bajt → 0xEF

Víme-li jak soubory odesílat do modulu JQ6500, pojďme se podívat na přehled všech příkazů pro modul JQ6500 – tak jak je uvádí čínský datový list.

Přehled příkazů modulu JQ6500:

Kód příkazu Popis funkce Rámec příkazu
0x01 Následující skladba (Next) 7E 02 01 EF
0x02 Předchozí skladba (Prev) 7E 02 02 EF
0x03 Přehrát soubor podle indexového čísla, pro SD (1–65535), FLASH (0–200) 7E 04 03 00 01 EF
Červeně je číslo skladby
Argument 1 = horních 8 bitů indexového čísla,
Argument 2 = dolních 8 bitů indexového čísla.
0x04 Zvýšení hlasitosti 7E 02 04 EF
0x05 Snížení hlasitosti 7E 02 05 EF
0x06 Nastavení hlasitosti (0–30) 7E 03 06 15 EF
Červená hodnota označuje rozsah hlasitosti od 00 do 1E
0x07 Nastavení ekvalizéru
Normální/Pop/Rock/Jazz/Classic/Base
(0/1/2/3/4/5)
7E 03 07 01 EF
Červená hodnota lze změnit od 0 do 5
0x09 Nastavení zdroje U/TF/AUX/SLEEP/FLASH
(0/1/2/3/4)
7E 03 09 01 EF
Červená hodnota lze změnit od 0 do 4
1 = 0x01 pro kartu SD kartu,
4 = 0x04 pro paměť flash na desce
0x0A Přejděte do režimu spánku – nízká spotřeba energie 7E 02 0A EF
Pozastaví přehrávání
0x0C Resetování čipu 7E 02 0C EF
Po odeslání je vhodné počkat 500 ms
0x0D Přehrávání (Play) 7E 02 0D EF
0x0E Pauza (Pause) 7E 02 0E EF
0x0F Přepínání mezi složkami (0/1)
Názvy složek na disku U a kartě SD musí být 01, 02,…99 (na flash nejsou složky podporované)
7E 03 0F 00 EF
Červená hodnota lze změnit od 0 do 1
1 – Další složka
0 – Předchozí složka
0x11 Přehrávání ve smyčce
ALL/FOL/ONE/RAM/ONE_STOP
(0/1/2/3/4)
7E 03 11 00 EF
Červená hodnota odpovídá odpovídajícímu režimu,
00 znamená plnou smyčku,
01 znamená jednu smyčku;
Například: Chcete-li přehrát druhou skladbu ve smyčce, nejprve odešlete 7E 03 11 01 EF a poté odešlete 7E 04 03 00 02 EF
0x12 Zadejte soubor ze složky, který chcete přehrát
Názvy složek na disku U a kartě SD musí být 01, 02,…99 (na flash nejsou složky podporované)
7E 04 12 01 02 EF
01 označuje složku
02 vzadu označuje soubor
To znamená, že přehrajete soubor 2 ve složce 1
Tab. č. 3 – přehled příkazů pro nastavení modulu JQ6500:

Příkazy pro získání odpovědi

Kód příkazu Popis funkce Formát příkazu
0x40 Chyba vrácení, požadavek na opětovné odeslání 7E 02 42 EF
0x42 Dotaz na aktuální stav 7E 02 42 EF
Odpověď celé číslo jako šestnáctkové znaky ascii) 0/1/2 pro Stopped/Playing/Paused.  Vestavěná paměť se nikdy po přehrání skladby „nezastaví“, pouze se „pozastaví“.
Při přehrávání se občas objeví chybná odpověď „Paused“, je třeba několikrát dotázat, aby bylo jasno.
0x43 Dotaz na aktuální hlasitost 7E 02 43 EF
Odpověď je celé číslo (jako hexadecimální ascii znaky) od 0 do 30
0x44 Dotaz na nastavení ekvalizéru 7E 02 44 EF
Odpověď je celé číslo (jako hexadecimální ascii znaky) od 0 do 5 (definice viz výše).
0x45 Dotaz na aktuální režim přehrávání 7E 02 45 EF
Odpověď je celé číslo (jako hexadecimální ascii znaky) od 0 do 4 (definice viz výše).
0x46 Dotaz na aktuální verzi softwaru 7E 02 46 EF
Odpověď je celé číslo (jako hexadecimální ascii znaky).
0x47 Dotaz na celkový počet souborů na SD kartě 7E 02 47 EF
Odpověď je celé číslo (jako hexadecimální ascii znaky).
0x48 Dotaz na celkový počet souborů v UDISK
(Co je UDISK?)
7E 02 48 EF
Odpověď je celé číslo (jako hexadecimální ascii znaky).
0x49 Dotaz na celkový počet souborů FLASH 7E 02 49 EF
Odpověď je celé číslo (jako hexadecimální ascii znaky).
0x4B Zjištění indexového čísla (tabulka FAT) aktuálního souboru na kartě SD. 7E 02 4B EF
Odpověď je celé číslo (jako hexadecimální ascii znaky).
0x4C Zjištění indexového čísla (tabulka FAT) aktuálního souboru na UDISK. 7E 02 4C EF
Odpověď je celé číslo (jako hexadecimální ascii znaky).
0x4D Zjištění indexového čísla (tabulka FAT) aktuálního souboru na kartě FLASH. 7E 02 4D EF
Odpověď je celé číslo (jako hexadecimální ascii znaky).
0x50 Získání pozice aktuálního přehrávaného souboru v sekundách. 7E 02 50 EF
Odpověď je celé číslo (jako hexadecimální ascii znaky).
0x51 Celková délka aktuálně přehrávaného souboru v sekundách. 7E 02 51 EF
Odpověď je celé číslo (jako hexadecimální ascii znaky).
0x52 Zjištění názvu aktuálního souboru na kartě SD (FLASH není podporována) 7E 02 52 EF
Odpověď ASCII znaky.
Pamatujte, že se vrátí název, i když se soubor nepřehrává, i když se přehrává soubor z vnitřní paměti, i když byla SD karta vyjmuta…! Ve skutečnosti to také není název souboru, chybí mu oddělovač přípony a má pravděpodobně maximální délku [8-nazev][3-pripona]
0x53 Celkový počet složek v aktuální složce, na kterou se dotazuje 7E 02 52 EF
Odpověď je celé číslo (jako hexadecimální ascii znaky).
Tab. č. 4 – přehled příkazů pro získání odpovědí z modulu JQ6500

Příklady příkazů

Přehrání první skladby

Příkazový rámec pro přehrání první skladby jsme si již ukazovali výše, takže se nyní podíváme, jak tento příkaz pomocí MicroPythonu odeslat přes UART sběrnici do modulu JQ6500.

from machine import UART, Pin

# Inicializace UART
uart = UART(2, baudrate=9600, tx=Pin(17), rx=Pin(16))

cmd = bytearray([0x7E, 0x04, 0x03, 0x00, 0x01, 0xEF])
uart.write(cmd)
Nastavení hlasitosti

Pokud bychom chtěli odeslat příkaz pro nastavení hlasitosti na zadanou úroveň, mohli bychom vytvořit funkci nazvanou set_volume: (všimněte si způsobu vložení obsahu proměnné level do odesílané struktury)

from machine import UART, Pin

# Inicializace UART
uart = UART(2, baudrate=9600, tx=Pin(17), rx=Pin(16))

def set_volume(level):
    level = max(0, min(31, level))   # omezíme hodnotu
    cmd = bytearray([0x7E, 0x03, 0xA7, level, 0xEF])
    uart.write(cmd)

set_volume(20)

Program ovládání modulu JQ6500

Jelikož velká část rámce příkazů zůstává stejná, pokusíme se vytvořit „univerzální“ funkci send_command, kterou následně využijeme pro vytvoření specifických funkcí pro ovládání modulu JQ6500.

Zároveň připravíme kód na příjem možné odpovědi od modulu JQ6500. Ještě předtím, ale vytvoříme metodu flush(), která bude čistit buffer odpovědí od případných předchozích příkazů.

from machine import UART, Pin
from time import sleep

# Inicializace UART
uart = UART(2, baudrate=9600, tx=Pin(17), rx=Pin(16))

# Vyčištění příp. odpovědí
def flush():
    # Vyprázdní přijímací buffer UARTu, pokud je v něm něco z předchozích komunikací
    while uart.any():   # pokud je k dispozici nějaký znak v bufferu
        uart.read()   # přečte a zahodí
        sleep(0.01)   # krátká pauza pro stabilitu čtení uart.read()

def send_command(cmd, data=[]):
    # Sestaví a odešle příkaz podle protokolu modulu JQ6500
    length = 2 + len(data)   # délka paketu = 1 + délka + 1 bajt příkaz + data
    body = [cmd] + data   # hlavní část zprávy
    message = [0x7E, length] + body + [0xEF]   # kompletní paket s hlavičkou a patičkou
    flush()     # před odesláním vyčistí buffer
    uart.write(bytes(message))   # pošle paket na UART

# TEST našich funkcí
# --- hlavní program ---
# Odešli PLAY příkaz
send_command(0x0D)
print("Příkaz PLAY odeslán.")

# Pauza a čtení odpovědi (volitelné, může být None)
sleep(0.2)
resp = uart.read()
print("Odpověď:", resp)

V načítání odpovědi modulu JQ6500 je však skrytý problém! Co se stane, když modul neodpoví? Opravdu musí modul JQ6500 na každý příkaz odeslat nějakou odpověď.

Zatím si ve výše uvedeném programy myslíme, že ano. Ale, co když ne? V takovém případě program uvízne při čekání na odpověď, která ale nikdy nepřijde. To rozhodně není ideální. Pro zpracování odpovědi od modulu JQ6500 si tedy vytvoříme vlastní funkci read_response, která bude obsahovat timeout. Pokud během určité doby neobdržíme odpověď, funkce čekání na načtení dat z modulu JQ6500 ukončí.

from time import ticks_ms, ticks_diff

def read_response(timeout_ms=500):
    # Čeká na odpověď modulu po dobu timeoutu (v ms) a vrací přijatá data nebo None
    start = ticks_ms()   # čas začátku čekání
    buffer = b''     # prázdný buffer pro ukládání příchozích dat
    while ticks_diff(ticks_ms(), start) < timeout_ms:
        if uart.any():    # pokud jsou data v UART bufferu
            buffer += uart.read()   # načte je a přidá do bufferu
        sleep(0.01)   # krátká pauza, aby CPU nebyl přetížen
    if buffer:
        return buffer
    else:
        return None   # pokud nic nepřišlo, vrátí None

Máme-li připravenou funkci pro odeslání příkazů a načtení jejich odpovědí, můžeme začít psát funkce pro jednotlivé příkazy:

Reset modulu:

def reset():
    # Resetuje modul příkazem 0x0C a čeká na odpověď (max 3 sekundy)
    send_command(0x0C)
    return read_response(1000)   # čeká na odpověď max 1s

Volba zdroje MP3 souborů:

def set_source(source=4):
    # Nastaví zdroj přehrávání (např. 4 = interní paměť, 1 = SD karta)
    send_command(0x09, [source])
    return read_response()

Nastavení hlasitosti:

def set_volume(level):
    # Nastaví hlasitost na hodnotu mezi 0 a 30
    level = max(0, min(level, 30))   # omezí hodnotu do povoleného rozsahu
    send_command(0x06, [level])
    return read_response()

Přehrání souboru zadaného indexu:

def play_index(index):
    # Přehrání skladby podle indexu (čísla skladby)
    hi = (index >> 8) & 0xFF   # vyšší bajt indexu (pro případ víc jak 255 skladeb)
    lo = index & 0xFF    # nižší bajt indexu
    send_command(0x03, [hi, lo])
    return read_response()

Zastavení aktuálního souboru:

def pause():
    # zastaví aktuální přehrávání.
    send_command(0x0E)   # Příkaz pro pauzu/stop
    return read_response()

Získání délky aktuální skladby:

def get_length():
    # Zjistí délku aktuálně přehrávané skladby v sekundách
    send_command(0x51)
    resp = read_response()
    if resp:
        try:
            # Odpověď modulu je ASCII hex (např. b'000b' => 11 sekund)
            length_sec = int(resp.decode('ascii'), 16)
            return length_sec
        except Exception:
            return None
    return None

Obdobně bychom mohli zakomponovat další příkazy podle výše uvedeného přehledu funkcí.

Neuvádíme zde všechny příkazy, protože je běžně asi uživatel stejně všechny nepotřebuje. A navíc, už dlouho jsme nezadali zvídavému čtenáři domácí úkol. Tak proč by to nemohlo být právě tohle? 😉

Upozornění:
Je nutné zmínit, že některé funkce fungují pouze u modelu JQ6500 s SD kartou! (Řada z nich byla již zmíněna v tabulce příkazů.) Překvapivě mezi takové příkazy patří i příkaz Stop. Pokud budeme chtít některou skladbu předčasně ukončit, u modelu s flash pamětí musíme použít příkaz Pause. Po příkazu Pause (který je v podstatě ekvivalentem příkazu Stop) není možné skladbu z flash paměti znovu spustit od stejného místa. Zkrátka, Pause je Stop, a příkaz Pause na modulu s flash pamětí prostě nefunguje! 😒

Než uvedeme celý ukázkový program, který předvede komunikaci mezi modulem ESP32 a JQ6500, ukážeme si, jak bude vypadat hlavní část programu při použití výše definovaných funkcí.

print('Iniciuji modul JQ6500')
reset()
sleep(0.5)

print('Nastavuji hlasitost')
set_volume(20)

print('Nastavuji vnitrni pamet jako zdroj MP3')
set_source(4)

print('Spouštím 1. skladbu')
play_index(1)

delka = get_length()
print(f'delka je {delka} sec.')
sleep(delka)   # cekani, nez skladba skonci

Úkol našeho ukázkového programu je poměrně jednoduchý. Iniciuje modul JQ6500, nastaví se mu hlasitost, zvolí se zdroj načítání MP3 souborů z paměti flash a spustí se první skladba. Dále se načte délka skladby a následuje stejně dlouhá čekací smyčka, protože modul ESP32 „odstartuje“ zvukový modul JQ6500, a tím je pro něj hotovo. ESP32 už nečeká na nic dalšího – modul JQ6500 totiž přehrává skladbu zcela autonomně. Program v modulu ESP32 by tedy klidně běžel dál, může dělat cokoliv – třeba i spouštět další a další skladby, to ale určitě nechceme. Proto, jakmile skladbu spustíme a nemáme nic dalšího na práci, počkáme do na skončení skladby.

Zde je možná příležitost zamyslet se nad využitím pinu BUSY modulu JQ6500, který je aktivní po dobu přehrávání skladby. Tento problém jsme ale v našem případě neřešili hardwarově. Místo toho se teď spolehneme na informaci o délce skladby, která se načte z modulu. V některých zdrojích se uvádí, že modul JQ6500 může mít problém s určením délky některých MP3 souborů. My jsme se s tímto problémem nesetkali, možná je to způsobeno jiným nastavením některých MP3 souborů – přeci jen, není MP3 jako MP3.

Následující kód představuje celý program ukázkového projektu:

import machine
from machine import UART, Pin
from time import sleep, ticks_ms, ticks_diff

# Inicializace UART
uart = UART(2, baudrate=9600, tx=Pin(17), rx=Pin(16))

# Vyčištění příp. odpovědí
def flush():
    # Vyprázdní přijímací buffer UARTu, pokud je v něm něco z předchozích komunikací
    while uart.any(): # pokud je k dispozici nějaký znak v bufferu
        uart.read() # přečte a zahodí
        sleep(0.01) # krátká pauza pro stabilitu čtení uart.read()

def send_command(cmd, data=[]):
    # Sestaví a odešle příkaz podle protokolu modulu JQ6500
    length = 2 + len(data) # délka paketu = 1 + d0lka + 1 bajt příkaz + data
    body = [cmd] + data # hlavní část zprávy
    message = [0x7E, length] + body + [0xEF] # kompletní paket s hlavičkou a patičkou
    flush() # před odesláním vyčistí buffer
    uart.write(bytes(message)) # pošle paket na UART

def read_response(timeout_ms=500):
    # Čeká na odpověď modulu po dobu timeoutu (v ms) a vrací přijatá data nebo None
    start = ticks_ms() # čas začátku čekání
    buffer = b'' # prázdný buffer pro ukládání příchozích dat
    while ticks_diff(ticks_ms(), start) < timeout_ms:
        if uart.any(): # pokud jsou data v UART bufferu
            buffer += uart.read() # načte je a přidá do bufferu
        sleep(0.01) # krátká pauza, aby CPU nebyl přetížen
    if buffer:
        return buffer
    else:
        return None # pokud nic nepřišlo, vrátí None

def reset():
    # Resetuje modul příkazem 0x0C a čeká na odpověď (max 3 sekundy)
    send_command(0x0C)
    return read_response(1000) # čeká na odpověď max 1s

def set_source(source=4):
    # Nastaví zdroj přehrávání (např. 4 = interní paměť, 1 = SD karta)
    send_command(0x09, [source])
    return read_response()

def set_volume(level):
    # Nastaví hlasitost na hodnotu mezi 0 a 30
    level = max(0, min(level, 30)) # omezí hodnotu do povoleného rozsahu
    send_command(0x06, [level])
    return read_response()

def play_index(index):
    # Přehrání skladby podle indexu (čísla skladby)
    hi = (index >> 8) & 0xFF # vyšší bajt indexu
    lo = index & 0xFF # nižší bajt indexu
    send_command(0x03, [hi, lo])
    return read_response()

def pause():
    # zastaví aktuální přehrávání.
    send_command(0x0E) # Příkaz pro pauzu/stop
    return read_response()

def get_length():
    # Zjistí délku aktuálně přehrávané skladby v sekundách
    send_command(0x51)
    resp = read_response()
    if resp:
        try:
            # Odpověď modulu je ASCII hex
            length_sec = int(resp.decode('ascii'), 16)
            return length_sec
        except Exception:
            return None
    return None

# ===== hlavní program =====

print('Iniciuji modul JQ6500')
reset()
sleep(0.5)

print('Nastavuji hlasitost')
set_volume(20)

print('Nastavuji vnitrni pamet jako zdroj MP3')
set_source(4)

print('Spouštím 1. skladbu')
play_index(1)

delka = get_length()
print(f'delka je {delka} sec.')
sleep(delka) # cekani, nez skladba skonci

print('KONEC')

Trochu dlouhý program na přehrání jediného souboru, že?

Ale uvědomme si, že definované funkce v tomto programu dokáží mnohem více. Je skoro škoda mít tyto funkce v programu takto samostatně. Co kdybychom je dali do své první vlastní knihovny (nebo, jak říkají správní „pythoňáci“, do modulu)? 😉

JQ6500 - ESP32 - fotografie
Obrázek č. 8 – modul JQ6500 připojený k modulu ESP32, reproduktorem je 16 Ω vložka z audiosluchátka

Pojďme si napsat knihovnu v Pythonu!

Pokud se chceme v MicroPythonu posunout dál a napsat něco, co budeme moci opakovaně používat ve více projektech, je skvělé naučit se, jak vytvářet vlastní moduly. Modul (někdo tomu říká knihovna) je v podstatě soubor, který obsahuje funkce, třídy nebo proměnné, které můžete importovat do jiných skriptů a znovu použít. Tímto způsobem nemusíte každý kód psát znovu a znovu, což vám ušetří spoustu času a umožní psát čistější a přehlednější programy.

Například pokud potřebujete pracovat s LED diodami, můžete si napsat modul, který tyto LED diody ovládá, a poté tento modul použít ve více projektech.

Co je to třída a proč se používá?

Třída je v podstatě šablona pro vytváření objektů. Objekt je konkrétní instance třídy, která má své vlastní hodnoty a metody. Když chcete pracovat s nějakým druhem dat, který má více atributů a funkcí, je ideální použít třídu.

V Pythonu (a tedy i v MicroPythonu) jsou třídy definovány pomocí klíčového slova class. To, co je pro třídy typické, je speciální metoda __init__(), která je volána při vytvoření nové instance třídy. Tato metoda slouží k inicializaci objektu a k nastavení jeho počátečních hodnot.

Co znamená self?

Klíčové slovo self je v Pythonu (a MicroPythonu) speciální parametr, který odkazuje na aktuální instanci třídy. Je to něco jako „já“ pro daný objekt. Když v metodách třídy použijeme self, odkazujeme na konkrétní objekt, na kterém se metoda volá. To znamená, že každá instance třídy může mít vlastní hodnoty pro své atributy.

Příklad: Vytvoření vlastního modulu s třídou

Teď se podíváme na jednoduchý příklad. Vytvoříme modul, který bude obsahovat třídu pro práci s „osobou“ (vytvoříme třídu Osoba). Tento modul bude mít metody pro nastavení jména a věku osoby a pro jejich zobrazení.

1. Vytvoření modulu

Nejprve si vytvoříme soubor, který bude náš modul reprezentovat. V tomto případě ho pojmenujeme osoba.py. Tento soubor bude obsahovat naši třídu Osoba.

# osoba.py

class Osoba:
    # Konstruktor třídy (metoda __init__)
    def __init__(self, jmeno, vek):
        self.jmeno = jmeno   # Atribut jméno
        self.vek = vek   # Atribut věk

    # Metoda pro zobrazení informací o osobě
    def zobraz_info(self):
        print(f"Jméno: {self.jmeno}, Věk: {self.vek}")

2. Použití modulu

Nyní, když máme náš modul osoba.py, můžeme ho použít v jiném skriptu. Použijeme import, abychom si tento modul do našeho skriptu načetli a použili třídu Osoba.

# hlavni.py

# Importujeme náš vlastní modul osoba
import osoba

# Vytvoření nové instance třídy Osoba
osoba1 = osoba.Osoba("Jan", 30)

# Zavoláme metodu pro zobrazení informací o osobě
osoba1.zobraz_info()

Vysvětlení kódu:

  1. Soubor osoba.py:
    • Třída Osoba má metodu __init__(), která je automaticky zavolána při vytvoření nové instance třídy. Parametry jmeno a vek se při vytvoření objektu předají a inicializují. Atributy self.jmeno a self.vek. self jsou odkaz na konkrétní instanci třídy, takže každý objekt třídy Osoba může mít své vlastní hodnoty pro jmeno a vek.
    • Metoda zobraz_info() používá tyto atributy a vypíše informace o osobě.
  2. Soubor hlavni.py:
    • Tento soubor importuje modul osoba a vytváří novou instanci třídy Osoba s parametry "Jan" a 30.
    • Poté zavolá metodu zobraz_info(), která zobrazí (vypíše do výstupu) jméno a věk osoby.

Tento příklad ukazuje, jak vytvořit velmi jednoduchý vlastní modul v MicroPythonu, jak definovat třídu, používat konstruktor __init__ a jak pracovat s objekty a metodami.

Není to jasné?

Tak to ještě více „zatemníme“ tím, že se právě nabyté informace pokusíme aplikovat na příklad vytvoření modulu pro ovládání modulu JQ6500.

V následujícím příkladu si ukážeme, jak převést množství funkcí pro komunikaci s audio modulem JQ6500 do jedné třídy a jak tuto novou třídu použít v praxi.

Vytvoření třídy pro JQ6500

Vytvoříme třídu JQ6500, která bude obsahovat všechny potřebné metody pro ovládání tohoto modulu. Kromě samotných funkcí pro modul JQ6500 rovnou připojíme také příkazy pro komunikaci s UART do strukturovaného objektu.

Jak bude vypadat třída JQ6500 (soubor jq6500_FK.py)

# jq6500_FK.py

from machine import Pin, UART
from time import sleep, ticks_ms, ticks_diff

class JQ6500:
    # Konstanty pro zdroje přehrávání (interní paměť a SD karta)
    SOURCE_INTERNAL_MEMORY = 4
    SOURCE_SD_CARD = 1

    def __init__(self, uart_id=2, tx_pin=17, rx_pin=16, baudrate=9600):
        # Inicializace UARTu podle zadaných pinů a parametru uart_id
        self.uart = UART(uart_id, baudrate=baudrate, tx=Pin(tx_pin), rx=Pin(rx_pin))

    def flush(self):
        # Vyprázdní přijímací buffer UARTu
        while self.uart.any():
            self.uart.read()
            sleep(0.01)

    def send_command(self, cmd, data=[]):
        # Sestavení a odeslání příkazu na modul
        length = 2 + len(data)
        body = [cmd] + data
        message = [0x7E, length] + body + [0xEF]
        self.flush()
        self.uart.write(bytes(message))

    def set_source(self, source=SOURCE_INTERNAL_MEMORY):
        # Nastaví zdroj přehrávání
        self.send_command(0x09, [source])
        return self.read_response()

atd…

Metoda __init__() je nyní využita jako konstruktor, který inicializuje UART a připojení k pinům pro přenos dat. Jakmile tedy vytvoříme nějakou instanci této třídy, bude v rámci jejích metod zajištěno i propojení přes UART. Všimněme si, že v rámci třídy jsme vytvořili i konstanty, které pak mohou svým názvem lépe reprezentovat hodnoty, které zastupují.

Použití třídy v hlavním programu

Nyní, když máme třídu, můžeme ji použít v hlavním programu. Vytvoření instance třídy je snadné, stačí pouze specifikovat UART port a piny pro přenos a příjem dat. Náš dříve uvedený ukázkový program, pak můžeme nyní přepsat do následující podoby:

# hlavní.py

from machine import Pin
from jq6500_FK import JQ6500

# Vytvoření instance třídy JQ6500 s nastavením UART
mp3 = JQ6500(uart_id=2, tx_pin=17, rx_pin=16)

# Iniciace modulu JQ6500
print('Iniciuji modul JQ6500')
mp3.reset()

# Nastavení hlasitosti
print('Nastavuji hlasitost')
mp3.set_volume(20)

# Nastavení zdroje přehrávání na interní paměť
print('Nastavuji vnitrni pamet jako zdroj MP3')
mp3.set_source(JQ6500.SOURCE_INTERNAL_MEMORY)

# Spuštění první skladby
print('Spoustím 1. skladbu')
mp3.play_index(1)

# Zjištění délky skladby
delka = mp3.get_length()
print(f'Délka skladby: {delka} sekundy.')

# Počkej, než skladba skončí
sleep(delka)

print('KONEC')

Vytvoření objektu třídy JQ6500 je základním krokem pro práci s tímto modulem v našem programu. Objekt je vlastně konkrétní instance třídy, která má k dispozici všechny metody a vlastnosti definované v třídě. V tomto případě bude objekt mp3 obsahovat všechno, co je potřeba pro komunikaci s audio modulem JQ6500, a umožní nám snadno přistupovat k různým funkcím, jako je přehrávání skladeb, nastavování hlasitosti nebo kontrola délky aktuální skladby.

K vytvoření objektu mp3 použijeme následující zápis:

mp3 = JQ6500(uart_id=2, tx_pin=17, rx_pin=16)

V tomto příkladu předáváme do konstruktoru třídy JQ6500 tři argumenty:

  1. uart_id=2   : Tento parametr určuje, který UART port bude použit pro komunikaci s modulem.
  2. tx_pin=17   : Tento parametr určuje pin na desce, který bude použit pro odesílání dat na modul (transmit pin) – v našem případě je to pin GPIO17.
  3. rx_pin=16   : Tento parametr určuje pin pro příjem dat z modulu (receive pin) – v našem případě je to pin GPIO16.

Tato tři nastavení (UART port a piny pro TX a RX) jsou zásadní pro správnou komunikaci mezi naším mikrořadičem (například ESP32) a audio modulem JQ6500. Po vytvoření objektu mp3 je tento objekt připraven přijímat a odesílat data na těchto specifikovaných pinech, což nám umožňuje ovládat modul JQ6500 pomocí metod, které jsme definovali v třídě JQ6500.

„Tahák“ pro tvorbu tříd a knihoven

Pokud se rozhodnete organizovat svůj kód do tříd a knihoven, může to být skvělý způsob, jak zjednodušit práci s hardwarem a celkově zpřehlednit váš kód. Abychom vám tento proces co nejvíce usnadnili, připravili jsme pro vás stručný průvodce („tahák“), který vám krok za krokem ukáže, jak na to.

Tato tabulka vám poskytne jasné pokyny, jak vytvořit třídu, jak ji použít v hlavním programu a jak ji následně transformovat do samostatného modulu, který budete moci opakovaně využívat ve více projektech. Postupujte podle jednotlivých kroků a nikdy nezapomeňte, že každá třída, kterou vytvoříte, je vlastně nástrojem, který vám pomůže efektivněji komunikovat s vaším hardwarem, šetřit čas a zjednodušit údržbu kódu.

Krok Popis Příklad
1. Definice třídy Začneme definováním třídy pomocí klíčového slova class. Třída bude obsahovat atributy (proměnné) a metody (funkce), které souvisejí s konkrétním objektem nebo zařízením. class JQ6500:
    # Inicializace třídy
2. Inicializační metoda __init__() Metoda __init__() je konstruktor třídy, který se volá při vytvoření nové instance třídy. Nastavujeme zde počáteční hodnoty atributů třídy. def __init__(self, uart_id, tx_pin, rx_pin):
    self.uart = UART(uart_id, tx=Pin(tx_pin), rx=Pin(rx_pin))
3. Definice metod třídy Vytváříme metody, které budou reprezentovat funkce související s objektem. Tyto metody mohou přijímat parametry a manipulovat s atributy třídy. def send_command(self, cmd, data=[]):
    self.uart.write(bytes(data))
4. Vytvoření objektu (instance třídy) Pro použití třídy musíme vytvořit její instanci (objekt). Při vytváření objektu předáváme parametry konstruktoru třídy. mp3 = JQ6500(uart_id=2, tx_pin=17, rx_pin=16)
5. Použití metod objektu Po vytvoření objektu můžeme volat metody, které jsme definovali ve třídě, k provádění různých operací (např. posílání příkazů, nastavování hodnot, apod.). mp3.reset()
mp3.set_volume(20)
mp3.play_index(1)
6. Vytvoření samostatného modulu Pokud chceme třídu používat v několika projektech, můžeme ji uložit do samostatného souboru (modulu). Tento soubor pak jednoduše importujeme v hlavním programu. Uložte třídu do souboru jq6500.py.
V hlavním programu použijte:

from jq6500 import JQ6500
7. Import a použití knihovny Když máme svou třídu jako samostatný modul, můžeme ji importovat a používat v jiných projektech, aniž bychom museli kopírovat kód. from jq6500 import JQ6500
mp3 = JQ6500(uart_id=2, tx_pin=17, rx_pin=16)

Tipy pro tvorbu knihovny v (Micro)Pythonu:

  1. Modularita: Knihovnu rozdělte na menší, přehledné soubory, pokud obsahuje mnoho funkcí, tříd nebo souvisejících částí. Například můžete mít jednu třídu pro UART komunikaci a jinou pro samotné ovládání zařízení.
  2. Dokumentace: Každou třídu, metodu a funkci vždy dokumentujte, aby bylo jasné, co daný kód dělá. Používejte docstringy (např. """     Popis funkce     """).
  3. Znovupoužitelnost: Při vytváření knihovny mějte na paměti, že ji budete moci použít v dalších projektech. Snažte se, aby byla co nejvíce univerzální a flexibilní – například tím, že umožníte nastavení různých parametrů (např. UART porty, piny apod.).
  4. Testování: Před použitím knihovny v hlavním programu ji otestujte na malých příkladech, abyste se ujistili, že vše funguje, jak má. To zahrnuje kontrolu komunikace s hardwarem nebo testování metod, které manipulují s daty.

Na závěr tohoto článku uvedeme celou ukázkovou knihovnu jq6500_FK.py pro základní práci s modulem JQ6500. I když je tato knihovna mírně rozšířena o další metody (oproti výše uvedeným příkladům), musíme zmínit, že neobsahuje všechny příkazy pro modul JQ6500. Jsou zde vybrané pouze ty, které nám přišly pro tento článek zajímavé a důležité.

Kód modulu jq6500_FK:

from machine import Pin, UART
from time import sleep, ticks_ms, ticks_diff

class JQ6500:
    # Příkazy
    CMD_RESET = 0x0C
    CMD_SET_SOURCE = 0x09
    CMD_SET_VOLUME = 0x06
    CMD_PLAY_INDEX = 0x03
    CMD_NEXT_TRACK = 0x01
    CMD_PREV_TRACK = 0x02
    CMD_GET_LENGTH = 0x51
    CMD_PAUSE = 0x0E
    CMD_GET_FILE_COUNT = 0x49
    CMD_GET_PLAYER_STATUS = 0x42
    CMD_SET_EQUALIZER = 0x07
    CMD_GET_EQUALIZER = 0x44

    # Konstanty pro zdroje přehrávání
    SOURCE_INTERNAL_MEMORY = 4 # Interní paměť modulu
    SOURCE_SD_CARD = 1        # SD karta

    # Přidání konstant pro stavy přehrávače
    STATUS_STOPPED = 0x00
    STATUS_PLAYING = 0x01
    STATUS_PAUSED = 0x02

    # Režimy ekvalizéru
    EQ_NORMAL = 0
    EQ_POP = 1
    EQ_ROCK = 2
    EQ_JAZZ = 3
    EQ_CLASSIC = 4
    EQ_BASS = 5

    def __init__(self, uart_id=2, tx_pin=17, rx_pin=16, baudrate=9600):
        """
        Inicializuje instanci třídy JQ6500.
        Nastaví UART pro komunikaci s modulem JQ6500 a provede základní nastavení.
        :param uart_id: ID UART portu, výchozí hodnota je 2.
        :param tx_pin: Pin pro odesílání dat (TX), výchozí hodnota je 17.
        :param rx_pin: Pin pro příjem dat (RX), výchozí hodnota je 16.
        :param baudrate: Baudrate pro UART, výchozí hodnota je 9600.
        """

        self.uart = UART(uart_id, baudrate=baudrate, tx=Pin(tx_pin), rx=Pin(rx_pin))
        sleep(0.01) # krátká pauza, aby se UART stabilizoval
        self.flush() # vyčistí UART buffer, aby nezůstaly staré data

    def flush(self):
        """
        Vyprázdní přijímací buffer UARTu.
        Pokud je v bufferu něco z předchozích komunikací, tento příkaz to odstraní.
        Tento krok je důležitý pro zajištění, že následující data budou čistá.
        """

        while self.uart.any(): # pokud je k dispozici nějaký znak v bufferu
            self.uart.read() # přečte a zahodí
            sleep(0.01)    # krátká pauza pro stabilitu čtení

    def send_command(self, cmd, data=[]):
        """
        Sestaví a odešle příkaz podle protokolu modulu JQ6500.
        Příkaz je sestaven z hlavičky, délky, příkazu a dat, a následně odeslán na UART.
        :param cmd: Příkaz, který se má odeslat (např. reset, nastavení hlasitosti).
        :param data: Seznam dat, která se mají připojit k příkazu.
        """

        length = 2 + len(data) # délka paketu = 1 + délka + 1 bajt příkaz + data
        body = [cmd] + data # hlavní část zprávy
        message = [0x7E, length] + body + [0xEF] # kompletní paket s hlavičkou a patičkou
        self.flush() # před odesláním vyčistí buffer
        self.uart.write(bytes(message)) # pošle paket na UART

    def read_response(self, timeout_ms=500):
        """
        Čeká na odpověď modulu a vrací přijatá data nebo None.
        Čekání probíhá po dobu stanoveného timeoutu v milisekundách. Pokud žádná odpověď
        nepřijde, vrátí funkce hodnotu None.
        :param timeout_ms: Doba čekání na odpověď v milisekundách.
        :return: Přijatá data nebo None, pokud není odpověď.
        """

        start = ticks_ms() # čas začátku čekání
        buffer = b'' # prázdný buffer pro ukládání příchozích dat
        while ticks_diff(ticks_ms(), start) < timeout_ms:
            if self.uart.any(): # pokud jsou data v UART bufferu
                buffer += self.uart.read() # načte je a přidá do bufferu
                sleep(0.01) # krátká pauza, aby CPU nebylo přetížené
        if buffer:
            return buffer
        else:
            return None # pokud nic nepřišlo, vrátí None

    def reset(self):
        """
        Resetuje modul JQ6500.
        Pošle resetovací příkaz modulu a čeká na odpověď po dobu maximálně 1 sekundy.
        :return: Odpověď modulu nebo None, pokud žádná odpověď nepřijde.
        """

        self.send_command(self.CMD_RESET)
        return self.read_response(1000)

    def set_source(self, source=4):
        """
        Nastaví zdroj přehrávání.
        Může to být interní paměť modulu nebo SD karta.
        :param source: Zvolený zdroj přehrávání (např. 4 pro interní paměť, 1 pro SD kartu).
        :return: Odpověď modulu.
        """

        self.send_command(self.CMD_SET_SOURCE, [source])
        return self.read_response()

    def set_volume(self, level):
        """
        Nastaví hlasitost přehrávače na požadovanou úroveň.
        Hlasitost je omezena na hodnoty mezi 0 a 30.
        :param level: Úroveň hlasitosti (0-30).
        :return: Odpověď modulu.
        """

        level = max(0, min(level, 30)) # omezí hodnotu do povoleného rozsahu
        self.send_command(self.CMD_SET_VOLUME, [level])
        return self.read_response()

    def play_index(self, index):
        """
        Přehrává skladbu na základě indexu (čísla skladby).
        Funkce převádí index na dvě hodnoty (vyšší a nižší bajt) a pošle příkaz pro přehrání.
        :param index: Index skladby (např. 1 pro první skladbu).
        :return: Odpověď modulu.
        """

        hi = (index >> 8) & 0xFF # vyšší bajt indexu (pro případ víc jak 255 skladeb)
        lo = index & 0xFF # nižší bajt indexu
        self.send_command(self.CMD_PLAY_INDEX, [hi, lo])
        resp = self.read_response()
        return resp

    def next_track(self):
        """
        Přehraje další skladbu v seznamu.
        Pošle příkaz pro přehrání další skladby.
        :return: Odpověď modulu.
        """

        self.send_command(self.CMD_NEXT_TRACK)
        return self.read_response()

    def prev_track(self):
        """
        Přehraje předchozí skladbu v seznamu.
        Pošle příkaz pro přehrání předchozí skladby.
        :return: Odpověď modulu.
        """

        self.send_command(self.CMD_PREV_TRACK)
        return self.read_response()

    def get_length(self):
        """
        Zjistí délku aktuálně přehrávané skladby v sekundách.
        Pošle příkaz pro získání délky skladby a vrátí ji jako celé číslo v sekundách.
        :return: Délka skladby v sekundách nebo None, pokud došlo k chybě.
        """

        self.send_command(self.CMD_GET_LENGTH)
        resp = self.read_response()
        if resp:
            try:
                # Odpověď modulu je ASCII hex (např. b'000b' => 11 sekund)
                length_sec = int(resp.decode('ascii'), 16)
                return length_sec
            except Exception:
                return None
        return None

    def pause(self):
        """
        Zastaví aktuální přehrávání skladby.
        Pošle příkaz pro pauzu, aby se přehrávání zastavilo.
        :return: Odpověď modulu.

        """
        self.send_command(self.CMD_PAUSE) # Příkaz pro pauzu
        return self.read_response()

    def get_file_count(self):
        """
        Získá počet souborů na interní paměti modulu JQ6500.
        Pošle příkaz pro získání počtu souborů a vrátí výsledek jako celé číslo.
        :return: Počet souborů na paměti nebo None, pokud došlo k chybě.
        """

        self.send_command(self.CMD_GET_FILE_COUNT)
        response = self.read_response()
        if response:
            try:
                file_count = int(response.decode('ascii'), 16)
                return file_count
            except Exception:
                return None
        else:
            return None

    def get_player_status(self):
        """
        Získá aktuální stav přehrávače (Stop, Playing, Pause).
        Pošle příkaz pro zjištění stavu přehrávače a vrátí jeden z následujících stavů:
        - 'STOPPED' pro 0x00
        - 'PLAYING' pro 0x01
        - 'PAUSED' pro 0x02
        :return: Stav přehrávače jako řetězec ('STOPPED', 'PLAYING', 'PAUSED', nebo 'ERROR').
        """

        self.send_command(self.CMD_GET_PLAYER_STATUS)
        resp = self.read_response(1000)

        if resp:
            try:
                status = int(resp.decode('ascii'), 16)
                if status == self.STATUS_STOPPED:
                    return 'STOPPED'
                elif status == self.STATUS_PLAYING:
                    return 'PLAYING'
                elif status == self.STATUS_PAUSED:
                    return 'PAUSED'
                else:
                    return 'UNKNOWN'
            except Exception:
                return 'ERROR'
        else:
            return 'NO RESPONSE'

    def set_equalizer(self, mode):
        """
        Nastaví ekvalizér podle zvoleného režimu.
        Možné režimy:
        0 = Normal
        1 = Pop
        2 = Rock
        3 = Jazz
        4 = Classic
        5 = Bass
        :param mode: Režim ekvalizéru (hodnota mezi 0 a 5).
        :return: Odpověď modulu.
        """

        if mode not in [self.EQ_NORMAL, self.EQ_POP, self.EQ_ROCK, self.EQ_JAZZ, self.EQ_CLASSIC, self.EQ_BASS]:
            mode = 0

        self.send_command(self.CMD_SET_EQUALIZER, [mode])
        return self.read_response(1000)

    def get_equalizer(self):
        """
        Získá aktuální režim ekvalizéru.
        Pošle příkaz pro získání aktuálního režimu ekvalizéru a vrátí hodnotu mezi 0 a 5.
        :return: Aktuální režim ekvalizéru (0-5) nebo "ERROR", pokud došlo k chybě.
        """

        self.send_command(self.CMD_GET_EQUALIZER)
        resp = self.read_response(1000)
        if resp:
            try:
                eq_mode = int(resp.decode('ascii'), 16)
                if eq_mode in range(6):
                    return eq_mode
                else:
                    return "ERROR"
            except Exception:
                return "ERROR"
        else:
            return "ERROR"

Abychom mohli ukázat použití modulu JQ6500_FK v praxi, vytvoříme jednoduchý program. Tento program bude komunikovat s modulem JQ65000 pomocí naší knihovny jq6500_FK.py a různým způsobem přehrávat skladby uložené v modulu.

Předpokládáme, že v modulu JQ6500 jsou nahrány alespoň 4 soubory MP3 a že bude použit standardní pinout pro UART komunikaci.

Kód zkušebního programu pro modul jq6500_FK:

from jq6500_FK import JQ6500 # Import třídy JQ6500 z knihovny

mp3 = JQ6500(uart_id=2, tx_pin=17, rx_pin=16) # vytvoření instance třídy JQ6500 s nastavenými piny

print('Iniciuji modul JQ6500')
mp3.reset() # reset modulu na výchozí stav

print('Nastavuji vnitrni pamet jako zdroj MP3')
mp3.set_source(JQ6500.SOURCE_INTERNAL_MEMORY)   # Nastaví interní paměť jako zdroj
# mp3.set_source(JQ6500.SOURCE_SD_CARD)         # Nastaví SD kartu jako zdroj

print('Nastavuji hlasitost')
mp3.set_volume(25)   # nastaví hlasitost na úroveň 25 (max 30)

print("Nastavuji ekvalizér na Bass...")
mp3.set_equalizer(JQ6500.EQ_BASS)

# Získání aktuálního režimu ekvalizéru
print("Zjišťuji aktuální režim ekvalizéru...")
current_eq = mp3.get_equalizer()
print(f"Aktuální režim ekvalizéru: {current_eq}")

# Získání stavu přehrávače
status = mp3.get_player_status()
print(f'Aktuální stav přehrávače: {status}')

print('Získávám počet souborů v paměti...')
if file_count is not None:
    print(f'Počet souborů na interní paměti: {file_count}')
else:
    print('Nepodařilo se získat počet souborů.')

print()
# Smyčka pro přehrání skladeb 1 až 5
for i in range(1, file_count+1):
    print(f'Spouštím skladbu {i} ... ', end='')
    mp3.play_index(i) # přehraje skladbu s indexem i
    delka = mp3.get_length()
    print(f'delka je {delka} sec.')
    sleep(delka)

print(f'Dokončeno přehrávání {file_count} skladeb.')
sleep(3)

print('\nTed prehraji skladbu č. 3')
mp3.play_index(3)
delka = mp3.get_length()
sleep(delka)

print('\nTed prehraji nasledujici skladbu')
mp3.next_track()
sleep(2.6)

# Zkontroluje aktuální stav přehrávače
status = mp3.get_player_status()
print(f"Aktuální stav přehrávače: {status}")

# Pokusí se zastavit přehrávač, pokud je v režimu přehrávání nebo pauzy
print('Ted jsem to zastavil...')
mp3.pause()

status = mp3.get_player_status()
print(f"Aktuální stav po pokusu o zastavení: {status}")

Co program dělá?

Program nejdříve resetuje modul JQ6500, nastaví zdroj MP3 souborů (flash paměť) a nastaví výchozí hlasitost. Poté program nastaví styl ekvalizéru na hodnotu BASS a okamžitě se dotáže toto nastavení. Návratová hodnota ukazuje, zda nastavení proběhlo správně (návratová hodnota 5 odpovídá nastavení BASS).

Dále se vypíše stav přehrávače. Pokud jej pouštíte poprvé, bude STOPPED, jinak pak již PAUSED.

Konečně nastává přehrávání. Zjistí se počet skladeb v paměti modulu a následně se všechny popořadě přehrají. U každé skladby se zjistí a vypíše její délka v sekundách. Zároveň program vždy čeká po dobu délky skladby, aby další skladbu nespustil předčasně.

V závěrečné části programu se nejdříve spustí skladba se zadanou absolutní pozicí, pak relativní (přehraj další). Zároveň se vypíše stav přehrávače při přehrávání skladby. Daná skladba je pak okamžitě zastavena a opět je vypásá dotaz na stav přehrávače. Protože byla skladba ukončena příkazem Pauza, je i stav zastaveného přehrávače PAUSED.

Na následujícím obrázku vidíme výpis výstupu programu při použití pěti zvukových souborů v modulu JQ6500.

vystup zkusemniho programu - Thonny IDE
Obrázek č. 9 – vystupní okno prostředí Thonny IDE s výpisem výstupů programu

Závěr

A je to! Teď už byste měli mít solidní základy pro ovládání modulu JQ6500 pomocí modulu ESP32 v MicroPythonu. Prošli jsme si vše od nastavení UART komunikace, přes vytváření třídy pro JQ6500 až po praktické použití ve svém vlastním kódu.

Pokud jste se při čtení občas zastavili, popřemýšleli a možná i provedli „lehký restart“, nic se neděje? To k tomu prostě patří. Naučili jsme se, jak správně komunikovat s hardwarem a jak napsat knihovnu, která nám pomůže ušetřit čas při budoucích projektech. Ať už budete chtít přehrávat na modulu JQ6500 MP3 soubory, upravovat hlasitost nebo zjišťovat délku skladby, teď máte nástroje k tomu, abyste to zvládli s přehledem.

Ať už se rozhodnete tento základní projekt vylepšit o vlastní funkce, nebo si vytvoříte úplně nový, věřte, že možnosti jsou neomezené. Pokud vás čekají nové výzvy, ať už na poli automatizace, zvukových projektů nebo jiných, nezapomeňte – takto postavený základ je skvělým odrazovým můstkem pro další rozvoj.

Takže hurá do dalších projektů! Ať už se pustíte do přehrávání dalších zvukových souborů, nebo se rozhodnete ovládnout jinou další hardwarové komponenty, teď víte, jak na to. A kdo ví, třeba se pod vašima rukama zrodí projekt, který místo pouhého přehrávání MP3 bude rovnou streamovat audio z oblíbených streamovacích služeb – ale to už je na vás, jak si své bastlířské sny naplníte. 😊

S mikrokotrolérem ESP32 a MicroPythonem máte v rukou velmi silný nástroj, který vás s tím pomůže a podrží vás na každém kroku.

Pokračování: Asi bude, ale zatím nepřipravujeme…
UPOZORNĚNÍ:
Nesouhlasíme s vyřazením Newtonových zákonů, Ohmova zákona a zákona zachování energie z učiva fyziky základních škol v České republice!