Głośnik bluetooth na Raspberry PI Pico 2W z I2S
Przedstawiam prosty w montażu projekt głośnika bluetooth zbudowany na Raspberry PI Pico 2W, który posiada moduł WIFI oraz Bluetooth.
Oprogramowanie bazuje na przykładzie z środowiska Arduino, zmodyfikowanym, tak aby odtwarzanie strumienia audio odbywało się po I2S, do którego podłączony jest DAC ze wzmacniaczem klasy D MAX98357 o mocy około 1W.
Jako bazę konstrukcji stanowi głośnik, do którego przykleiłem na taśmie dwustronnej płytkę Raspberry oraz wzmacniacza. I2S jest połączony z wykorzystaniem kabelków.
Konstrukcja jest zasilana z USB, gdzie źródłem zasilania może być cokolwiek np: powerbank.
Opis działania
Jeśli chodzi o oprogramowanie to nie napisałem prawie nic, bo wszystko było gotowe i zadziałało od razu.
Użyłem przykładu z środowiska Arduino, który można znaleźć pod linkiem: https://github.com/earlephilhower/arduino-pic...BluetoothAudio/examples/A2DPSink/A2DPSink.ino
Przedstawiony przykład, używa klasy PWMAudio. Chcąc użyć I2S, musiałem zrobić małą modyfikację. Usunąłem obiekt pwm i dodałem i2s:
// GPIO pin numbers
#define pBCLK 15
#define pWS (pBCLK+1)
#define pDOUT 17
// Create the I2S port using a PIO state machine
I2S i2s(OUTPUT, pBCLK, pDOUT);
Konieczna jest jeszcze podmiana inicjalizacji w funkcji setup():
// a2dp.setConsumer(new BluetoothAudioConsumerPWM(pwm));
a2dp.setConsumer(new BluetoothAudioConsumerI2S(i2s));
W przedstawionym kodzie zdefiniowałem 3 piny, które używane są na potrzeby transmisji strumienia audio.
W praktyce do połączenia z układem MAX98357 potrzebne jest 5 kabelków, bo dochodzi jeszcze masa i zasilanie(zasilam z 3.3V).
Pinu SD_MODE nie podpinałem, jest on na płytce podciągnięty do zasilania przez rezystor 1Mom.
Screen przedstawia wnętrze układu MAX. Który posiada DACa oraz wzmacniacz. Dane przesyłane są jako sample 16 bitowe. Na wyjściu jest jeden głośnik.
Scalak sumuje oba kanały dzieląc wynik przez 2(Left/2 + Right/2) tworząc w ten sposób pojedyncze wyjście mono.
Dane po I2S wysyłane są w ten sposób, że sygnał BCLK jest zegarem, na którego narastającym zboczu próbkowane są bity z sygnału DIN.
Sygnał LRCLK(WCLK) informuje czy aktualnie przesyłany jest lewy czy prawy kanał audio, jest on także referencją zegara dla DACa audio.
Jeśli chodzi o kod do obsługi I2S to znajduje się on pod linkiem https://github.com/earlephilhower/arduino-pico/blob/master/libraries/I2S/src/I2S.cpp
Do wysyłania danych używana jest maszynka PIO(https://github.com/earlephilhower/arduino-pico/blob/master/libraries/I2S/src/pio_i2s.pio), która zapewnia stabilną częstotliwość sygnału LRCLK. Dane kopiowane są z pamięci z wykorzystaniem DMA(Direct Memory Access). Dzięki czemu procesor nie zajmuje się kopiowaniem danych, a realizowane jest to automatycznie w tle. Pozostała część, czyli odbieranie danych z bluetooth, dekodowanie odbywa się już programowo.
Bluetooth
Schemat Raspberry Pi Pico 2W dostępny jest pod linkiem https://datasheets.raspberrypi.com/picow/pico-2-w-schematic.pdf
Projekt mi się podoba, bo działa to bardzo dobrze, na PCB jest umieszczony nawet filtr na 2.4GHz, gdzie na przykład moduły ESP tego nie mają.
Na płycie umieszczony jest układ Infineona CYW43439KUBG. Integruje w sobie Bluetooth 5.2 oraz WIFI IEEE 802.11b/g/n.
Bardzo dobra jest dokumentacja tego układu pod względem radiowym, gdzie inne alternatywne układy są bardzo ubogie i prawie nic nie ma.
Komunikacja między RP2350 a tym układem realizowana jest z wykorzystaniem SDIO w trybie 1bitowym.
CYW43439KUBG posiada w swoim wnętrzu mikrokontroler z rdzeniem Cortex-M3(a chyba nawet 2), którego oprogramowanie musi być wgrane podczas startu. Jest to binarka dostarczona przez Infineona. W przypadku arduino kod do konfiguracji dostarczany jest jako biblioteka statyczna libipv4-bt.a(https://github.com/earlephilhower/arduino-pico/blob/master/lib/rp2350/libipv4-bt.a). Źródła sterownika dostępne są pod linkiem https://github.com/georgerobotics/cyw43-driver/tree/main. Kod tego bloba jest zdefiniowany jako tablica i można zobaczyć pod linkiem https://github.com/georgerobotics/cyw43-driver/blob/main/firmware/cyw43_btfw_43439.h.
Repozytorium z driverem do cyw43 zawiera trochę mniej kodu niż ta biblioteka statyczna, choćby to, że obsługa SDIO zrealizowana jest z wykorzystaniem PIO w pliku libipv4-bt.a, a w podanym repo nie ma tego kodu. Zorientowałem się, że implementacja tego jest w PicoSDK https://github.com/raspberrypi/pico-sdk/tree/master/src/rp2_common/pico_cyw43_driver, który jest skompilowany do biblioteki statycznej.
Ciekawa jest też licencja tego drivera. Jest on tylko do zastosowań nie komercyjnych. Ale gdy jest używany wraz z urządzeniami Raspberry, to nie ma tego ograniczenia.
Ciekawa polityka, której nie rozumiem.
W środowisku Arduino należy podczas kompilacji wskazać że używamy stosu Bluetooth oraz ip. Wtedy dodawana jest do kompilacji wcześniej wspomniana biblioteka statyczna.
Wracając do projektu, to głośnik zrealizowany jest z wykorzystaniem A2DP (Advanced Audio Distribution Profile). Dokumentację można znaleźć na stronie bluetooth:
https://www.bluetooth.com/specifications/specs/advanced-audio-distribution-profile-1-4/
Specyfikacja ta opisuje protokół do przesyłania audio z użyciem bluetooth.
W Arduino jest reprezentowana przez bibliotekę BluetoothAudio(https://github.com/earlephilhower/arduino-pico/tree/master/libraries/BluetoothAudio)
W prezentowanym przykładzie na potrzeby głośnika używany jest objekt:
A2DPSink a2dp;Implementacja ta dostarcza interfejs do startowania odtwarzania, regulacji głośności, informacji o podłączeniu, nazwę obecnie odtwarzanego utworu. A także dekoduje i przekazuje strumień audio do objektu I2S.
Kod znajduje się pod linkiem https://github.com/earlephilhower/arduino-pic...ter/libraries/BluetoothAudio/src/A2DPSink.cpp
Szybka analiza kodu, oraz pliku wynikowego map po kompilacji z Arduino pokazuje, że implementacja ta korzysta bezpośrednio z funkcji udostępnianych przez wcześniej wspomnianą bibliotekę statyczną. Przy okazji zauważyłem, że jest tam zawarty stos bluetootha bstack( https://github.com/bluekitchen/btstack/tree/master).
A przy okazji znalazłem, że kod a2dp sink jest bazowany na przykładzie z tej biblioteki https://github.com/bluekitchen/btstack/blob/master/example/a2dp_sink_demo.c
Przy okazji można zauważyć, że biblioteka ta działa z wykorzystaniem interfejsu HCI(Host Controller Interface) zdefiniowany w specyfikacji Bluetootha(https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host-controller-interface/host-controller-interface-functional-specification.html).
Czyli driver cyw43 implementuje interfejs HCI z użyciem SDIO.
Audio nie jest przesyłane w postaci surowej przez Bluetooth. Wykorzystywana jest kompresja kodekiem SBC(https://en.wikipedia.org/wiki/SBC_%28codec%29), który powstał na potrzeby transmisji bluetooth. A2DP wspiera też inne kodeki, ale implementacja arduino wspiera tylko SBC. Doprecyzowując, dekoder SBC jest częścią btstack(https://github.com/bluekitchen/btstack/blob/master/3rd-party/bluedroid/decoder/srce/decoder-sbc.c) i jest to chyba jedyny zaimplementowany dekoder patrząc po plikach.
Drugim elementem klasy A2DPSink jest implementacja profilu bluetooth Audio/Video Remote Control Profile (AVRCP), który realizuje funkcjonalność sterowania źródłem dźwięku. Czyli zatrzymaj, odtwórz, regulacja głośności, informacje o odtwarzanej ścieżce. Funkcjonalność tego profilu dostarczana jest przez btstack(https://github.com/bluekitchen/btstack/blob/master/src/classic/avrcp.c).
Po zaprogramowaniu, urządzenie bluetooth rozgłasza się, co można zobaczyć na poniższym screenie:
Po podłączeniu widać, że działa z kodekiem SBC.
Podsumowując
Projekt jest prosty w uruchomieniu, dzięki gotowej bazie oprogramowania Arduino. Jeśli chodzi o działanie to testowałem kilka dni i gra dobrze.
Z pomysłów jakie mi przychodzą do głowy to rozbudować o klawiaturę z regulacją głośności i wyświetlaczem.
Sprawdzić czy da się uruchomić to przerobić jako zestaw głośnomówiący z mikrofonem na I2S.
Fajne? Ranking DIY