[159] Arduino Q cz. 6

Przyznaję, że bardzo ciekawiło mnie praktyczne użycie przetworników cyfrowo-analogowych do emisji strumieni audio. Ponieważ w tej serii artykułów omawiamy różne elementy nowego kontrolera Arduino Q, dziś tylko pobieżnie – opiszę najprostszą realizację samplera, podobną do tego, co już przedstawiłem w artykule o „gadającym woltomierzu”, ale wykorzystującą świeży potencjał. Po pierwsze więc: mamy tutaj prawdziwy przetwornik D/A, po drugie zaś – 2 MB pamięci FLASH. Wstawiłem więc nowe Arduino w płytkę edukacyjną TME i wziąłem na warsztat kompilację, którą często przepuszczam przez różne urządzenia pokazywane w mojej serii YouTube’owej Towary Modne, a która świetnie wykrywa różne problemy z torem audio. Postanowiłem odtworzyć ją na Arduino Q.


Myślę sobie: dwa megabajty dla audio to niewiele, ale dwadzieścia sekund muzyki wejdzie. Jakim cudem? Ano po pierwsze – monofonicznej, po drugie – ośmiobitowej. Skoro kontroler jest znacznie mocniejszy od ośmiobitowego ATmega, użyję zmodyfikowanego patentu ze wspomnianego filmu: będziemy bazować tylko na ośmiu bitach, lecz przy dużo wyższej częstotliwości próbkowania: 96 kHz. Po co tak? By móc użyć szumu ditheringu, który zamaskuje niedoskonałości ośmiobitowego formatu. We wspomnianym rozwiązaniu pracowaliśmy z częstotliwością 62,5 kHz, przenosząc się jeszcze wyżej dostaniemy mnóstwo przestrzeni do zaszumienia, że się tak wyrażę i praktyczna dynamika będzie sięgała 60 dB. Teoretyczna dynamika dwunastobitowego zapisu to 72 dB, ale obcięty sygnał szesnastobitowy wprost da cichutkie, a nieprzyjemne brzęczenie w najcichszych fragmentach, zwłaszcza podczas zakończeń utworów. Metoda z ditheringiem obiektywnie brzmi lepiej, szum także jest słyszalny – wyłącznie przy dużej głośności i niskich poziomach sygnału właściwego, ale nie ma charakteru „dzwoniącego” i przede wszystkim nie rezonuje z samym sygnałem, jeśli miałby on takie właściwości.

Podsumowując: próbkę zapisałem jako RAW bez nagłówka, używając wcześniej konwersji przy widocznych tutaj parametrach. Oczywiście 20 sekund nie nadaje się do zrobienia walkmana, ale jak najbardziej można z tego zrobić interfejs głosowy, choćby taki jak wystąpił we wspomnianych filmach, bez użycia dodatkowych kart SD. Jeśli chcielibyśmy sobie zrobić grajka nieograniczonego czasem, możemy użyć karty pamięci. Przy okazji wspomnę, że metoda nie dotyczy tylko widocznego tu Arduino Q, a także wszelkich nowoczesnych kontrolerów z większą ilością pamięci FLASH.

Tym razem szkic podzieliłem na dwoje. O ile poprzednio próbki zajmowały niecałe 30 kB, w naszym przypadku tak dużą tablicę z próbkami warto trzymać osobno. Aby ją zbudować, przypomnę: należy przekonwertować treść sampla na format zgodny z C. Użyłem do tego małego programu bin2hex, ale metod na to jest mnóstwo.

Teraz należy utworzyć dwa pliki, oba z rozszerzeniem ino, położone w jednym katalogu. Pierwszym, o nazwie player.ino będzie nasz szkic właściwy:

extern const byte sample[];            // Tablica zawierająca próbki sampla.
extern const unsigned long sampleLen;  // Długość sampla (dźwięku).
unsigned long samplePos = 0;           // Adres aktualnie odtwarzanej próbki sampla.
void setup() {
  analogWriteResolution(8);  // Deklaracja głębokości bitowej przetwornika D/A
}
void loop() {
  for (samplePos = 0; samplePos <= sampleLen; samplePos++) {  // Powtarzaj dla wszystkich próbek sampla.
    unsigned long sampleStart = micros();                     // Zapamiętaj stan zmiennej micros.
    analogWrite(DAC0, sample[samplePos]);                     // Wyślij kolejną próbkę z tablicy na wyjście D/A
    while (micros() - sampleStart < 9) {}                     // Zaczekaj, aż micros zwiększy się o 10.
  }
}

W drugi, o nazwie sample.ino wrzucimy przekonwertowane dane oraz deklarację tablicy – na początku i linię, która oblicza jej długość – na końcu.

const byte sample[] PROGMEM = {  // Tablica zawierająca próbki sampla.
  0x80, 0x80, 0x80, 0x81, 0x7E, 0x81, 0x80, 0x81, 0x7F, 0x7F, 0x81, 0x80,
  0x81, 0x7F, 0x7F, 0x80, 0x81, 0x81, 0x7E, 0x81, 0x80, 0x7D, 0x82, 0x80,
  0x82, 0x7D, 0x79, 0x83, 0x86, 0x82, 0x7C, 0x7E, 0x83, 0x83, 0x7E, 0x78,
};
const unsigned long sampleLen = sizeof(sample);  // Długość sampla (dźwięku).

Spójrzmy na typy zmiennych: dane w tablicy sample to bajty, zadeklarowane na stałe. Wymusiłem nieprzenoszenie ich do pamięci RAM dopisując polecenie PROGMEM. Liczbę elementów sampleLen określamy typem long bez znaku, bo tablica zawiera blisko dwa miliony sampli i oczywiście domyślny int nie jest w stanie pomieścić takiej wartości. Powyższy listing zawiera tylko trzy wiersze danych, pełen zestaw jest dostępny do pobrania w załączniku pod artykułem.

We właściwym programie należy zacząć od importu tablicy sample oraz jej długości sampleLen. Robi się to komendą extern, bacząc by zachować typ zmiennych – także fakt ich niezmienności. Deklarujemy też bieżący adres sampla samplePos, który będzie pobierał dane do wystawienia na port przetwornika.

Kolejno należy zadeklarować jego rozdzielczość. Gdybyśmy pominęli linię analogWriteResolution(8), domyślną wartością stałoby się 12, a ośmiobitowe sample wypełniłyby najmłodsze bity i dźwięk stałby się cichszy o 24 dB, jako że każdy bit daje 6 dB spadku lub wzrostu sygnału. Tak przy okazji, ładując ośmiobitowe sample bez ditheringu i przy standardowych częstotliwościach próbkowania, można się pokusić o wypełnienie pozostałych czterech bitów szumem, co dałoby lepsze efekty niż surowe puszczanie ośmiobitowych sampli. Ale to historia na inną okazję.

Moim celem jest tylko sprawdzenie brzmienia takiego przetwornika, więc samo odtwarzanie jest proste, żeby nie powiedzieć: prostackie i niezgodne ze sztuką – ale jak mówię, po to tylko, by sprawdzić, że działa. Toteż utworzymy sobie pętlę o długości całej tablicy, która będzie pobierać kolejnego sampla i czekać… no właśnie. Okazuje się, że i z funkcją delaymicroseconds coś jest nie tak i nie przyjmuje ona dowolnych wartości, a skokowe. Wyłączanie przerwań nic nie dało, internetowe porady także. Cóż, choroby wieku niemowlęcego…

Użyłem więc alternatywnej metody. Jak już nieraz mówiłem, Arduino ma na pokładzie zegar tykający co mikrosekundę. Wystarczy więc na początku pętli sczytać jego wartość do zmiennej sampleStart, potem zrobić swoje – czyli wyjąć z tablicy wartość próbki, wysłać ją na przetwornik i zaczekać, aż delaymicroseconds będzie większe od 9 od sczytanej. Tyle mniej więcej wyszło na ucho, bo to nie jest żadna dokładna metoda, a przybliżona, jeśli wziąć pod uwagę wysokość odtwarzanej próbki.

I już miałem posłuchać muzyki, gdy tymczasem wyleciał kolejny błąd. Okazało się, że kompilator na razie nie lubi tablic większych od 512 kB. Należałoby ją podzielić na cztery części albo po prostu ograniczyć się do pięciu sekund – wystarczy do testów. Przyciąłem więc tablicę do ¼ pierwotnej wartości i po chwili dźwięk z przetworników kontrolera wreszcie popłynął.

Cóż, mimo że to bardzo prymitywny sposób odtwarzania, nieużywający przerwań, to co było słychać zabrzmiało brzmi świetnie. I to bez jakiegokolwiek filtru; sygnał pobrałem wprost z pinu A0, który pełni alternatywną funkcję przetwornika, a każdy chętny może go posłuchać po ściągnięciu z załącznika. Jak pisałem, stereofonię, obsługę kart i profesjonalne rozwiązanie odtwarzania pozostawimy na inną okazję. Najważniejsze jest to, że to gra i robi to dobrze. Przy okazji pragnę przypomnieć, że mowa jest cały czas o kontrolerze, a nie procesorze, który ma własny tor audio, dostępny także przez USB czy BT. Na razie pozostawmy ten temat i rzućmy okiem na ostatni na razie blok, o którym tylko wspomniałem, czyli na blok diod świecących – ale to już w kolejnym artykule.

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2

Inne artykuły z tej kategorii

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2026 arduino.pl