Powodem skonstruowania radioodbiornika była chęć poznania stosunkowo mało popularnego modułu tunera radiowego z użytym układem TEF6686 firmy NXP Semiconductors. Jego producent jako główne zastosowanie wymienia aplikacje z branży automotive oraz wysokiej klasy konsumencki sprzęt audio. Ze względu na doskonałe parametry odbioru upodobały go sobie w szczególności osoby interesujące się tzw. DX-ingiem, czyli nasłuchem odległych stacji radiowych. Dostępność modułu z tym układem na polskim rynku jest niewielka, jednak przy odrobinie chęci można go znaleźć na zagranicznych portalach aukcyjnych.
Jednym z założeń projektu było uzyskanie unikalnego, nawiązującego do analogowych urządzeń interfejsu użytkownika. Dlatego też istotnym elementem omawianego odbiornika stał się wyświetlacz w technologii e-papieru. Podobne wyświetlacze są stosunkowo rzadko używane w tego typu aplikacjach, a dzieje się tak ze względu na ich wysoką cenę oraz niezadowalającą szybkość odświeżania zawartości. Na szczęście w ostatnim czasie pojawiły się modele w akceptowalnych cenach oraz zapewniające dobre parametry dynamiczne. Do projektu wybrany został 2,9-calowy model firmy WeAct, o rozdzielczości 296×128 pikseli, co gwarantuje dobre wrażenia estetyczne. Przy doborze panelu istotne było, aby dany model obsługiwał tzw. partial update, czyli częściowe przeładowanie treści na ekranie. Dzięki temu możemy osiągnąć zadowalającą responsywność interfejsu użytkownika. Dostępne są również biblioteki do obsługi tego wyświetlacza, co bardzo ułatwia implementację.
Centralnym elementem odbiornika jest mikrokontroler ESP32 (na płytce Devkit V1). Omawiany projekt nie stawia specjalnych wymagań co do mocy obliczeniowej bądź peryferiów mikrokontrolera. Wspominany model został wybrany głównie ze względu na logikę 3,3 V, na której operuje. Pozostałe elementy również pracują w logice 3,3 V, co eliminuje konieczność stosowania konwertera poziomów. Całości dopełnia enkoder obrotowy z wbudowanym przyciskiem, który umożliwia obsługę wszystkich funkcji za pomocą jednej gałki.
Obsługa
Po uruchomieniu urządzenie przechodzi w tryb skanowania całego użytecznego pasma FM. Na ekranie rysowana jest skala przypominająca analogowe odbiorniki. Zaraz pod nią mamy wykres prezentujący jakość sygnału na poszczególnych częstotliwościach.
Obsługa odbiornika podzielona jest na 4 tryby: Seek, Manual, Threshold oraz Parameters.
Tryb Seek
Po zakończeniu skanowania odbiornik przechodzi w tryb Seek i ustawia się na pierwszej stacji, której jakość odbioru przekracza zadany próg. Próg odbioru zaznaczony jest na wykresie linią przerywaną. Obrót gałki enkodera w lewo/prawo przestawia odbiór na poprzednią/następną stację spełniającą kryteria odbioru.
Tryb Manual
Krótkie wciśnięcie gałki enkodera w trybie Seek zmienia tryb na Manual. W trybie tym obrót enkodera przestawia odbiór na poprzednią/następną częstotliwość, niezależnie od jakości odbieranego sygnału.
Tryb Threshold
Wciśnięcie gałki w trybie Manual powoduje zmianę trybu na Threshold. W tym trybie możemy zmieniać próg wymaganej jakości sygnału, który brany jest pod uwagę w trybie Seek. Podczas kręcenia gałką w lewo/prawo, linia przerywana na wykresie jakości sygnału przesuwa się w górę lub w dół.
Tryb Parameters
Wciskając gałkę enkodera w trybie Threshold, przechodzimy do trybu Parameters. Wyświetlają się tutaj parametry dotyczące jakości odbioru aktualnej stacji oraz dane z RDS (fotografia 1).
Wartości parametrów są aktualizowane przez cały czas, dzięki czemu możemy obserwować, jak położenie anteny wpływa na parametry odbioru. Opcja ta może spodobać się bardziej wnikliwym użytkownikom odbiornika. Na ekranie wyświetlają się następujące parametry:
- poziom sygnału [dBuV],
- poziom szumów (USN) [%],
- detekcja efektu wielodrożności (multipath) [%],
- przesunięcie częstotliwości (offset) [kHz],
- szerokość pasma częstotliwości pośredniej (IF, Intermediate Frequency bandwidth) [kHz],
- głębokość modulacji FM [%],
- PTY (Program Type),
- PS (Programme Service),
- RT (Radio Text).
W każdym z powyższych trybów na wyświetlaczu znajdują się również informacje Programme Service (PS) oraz Radio Text (RT) z RDS. Długie naciśnięcie gałki powoduje przejście urządzenia w tryb uśpienia. Wybudzenie z trybu uśpienia następuje przez krótkie naciśnięcie gałki.
Oprogramowanie
Projekt firmware został zbudowany przy użyciu narzędzia PlatformIO, które stanowi doskonałą alternatywę popularnego środowiska Arduino IDE. Za pomocą PlatformIO można przeprowadzić cały proces developmentu w edytorze Visual Studio Code firmy Microsoft (włącznie z wgraniem programu do mikrokontrolera). PlatformIO dba również o zarządzanie zależnościami, czyli zewnętrznymi bibliotekami (o ile korzystamy z nich w naszym programie). Każdy projekt korzystający z PlatformIO zawiera plik platformio.ini, który opisuje parametry projektu, takie jak użyty mikrokontroler, framework, lista zależności, itp. W naszym projekcie wygląda tak, jak na listingu 1.
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
zinggjm/GxEPD2@^1.5.3
igorantolic/Ai Esp32 Rotary Encoder@^1.6
ciuri/TEF6686Library@^1.0.4
monitor_speed = 115200
Listing 1. Plik konfiguracyjny platformio.ini
Program zaraz po starcie wywołuje funkcję setup() (listing 2), która odpowiada za inicjalizację peryferiów, a także uruchamia task RTOS obsługi wyświetlacza. Konfigurowany jest również pin odpowiedzialny za wybudzenie mikrokontrolera ze stanu uśpienia.
{
Serial.begin(115200);
esp_sleep_enable_ext0_wakeup(GPIO_NUM_14, 0);
pinMode(ROTARY_ENCODER_A_PIN, INPUT_PULLUP);
pinMode(ROTARY_ENCODER_B_PIN, INPUT_PULLUP);
pinMode(ENABLE_POWER_TEF6686_PIN, OUTPUT);
digitalWrite(ENABLE_POWER_TEF6686_PIN, HIGH);
rotaryEncoder.begin();
rotaryEncoder.setup(readEncoderISR);
radioApp.Start();
display.init(115200, true, 50, false);
display.setPartialWindow(0, 0, display.width(), display.height());
display.setRotation(3);
xTaskCreate(UpdateScreen, "UpdateScreen", 20000, &radioApp, 5, NULL);
radioApp.ScanAll(10);
}
Listing 2. Ciało procedury inicjalizacji systemu
Po inicjalizacji następuje wejście w tryb skanowania jakości sygnału dla całego pasma. Tworzona jest mapa poziomu sygnału (listing 3), na podstawie której powstaje wykres widma sygnału.
{
scanning = true;
tef.Tune_To(tef.MODULE_FM, FREQ_DISPLAY_MIN);
do
{
qualityOK = 0;
tef.Tune_To(tef.MODULE_FM, tef.Currentfreq + step);
delay(50);
tef.UpdateQualityStatus();
qualityMap[tef.Currentfreq] = tef.quality.Level;
} while (tef.Currentfreq < FREQ_DISPLAY_MAX);
scanning = false;
tef.Tune_To(tef.MODULE_FM, FREQ_MIN);
Seek(10);
tef.Audio_Set_Mute(0);
}
Listing 3. Metoda odpowiedzialna za skanowanie pełnego pasma odbiorczego
W głównej pętli programu (funkcja loop()) wywoływane jest cykliczne pobranie danych z tunera (m.in. dane RDS, informacje o jakości sygnału) oraz obsługa enkodera. Do obsługi wspomnianego enkodera służy biblioteka Ai Esp32 Rotary Encoder.
Komunikacja z układem tunera odbywa się przez przesyłanie danych na magistralę I²C za pomocą standardowej biblioteki Wire.h. Z dokumentacji układu TEF6686 wynika, że zawiera on 4 logiczne moduły: FM, AM, AUDIO, APPL (rysunek 1). Każdy z nich ma przypisany identyfikator. W zależności od wywoływanej funkcji musimy każdą komendę adresować do odpowiedniego logicznego modułu.
W praktyce komunikację rozpoczynamy, wywołując metodę beginTransmission. Następnie - wywołując funkcję „write” - przesyłamy do bufora kolejno: Module, Cmd, Index, Param_1 ... Param_n. Kończymy, wywołując endTransmission, a tym samym wysyłając dane z bufora do urządzenia.
Strukturę ramki zapisu danych pokazuje rysunek 2:
- Addr - adres urządzenia na magistrali,
- Module - identyfikator logicznego modułu,
- Cmd - identyfikator komendy,
- Index - indeks parametru (zawsze równy 1),
- Param 1...n - parametry dla komendy (2 bajty na każdy parametr).
Jeśli chcemy odczytać jakieś dane z naszego tunera, realizujemy opisane wyżej kroki dla interesującej nas komendy żądania danych, a następnie używamy metody Wire.requestFrom. Później, za pomocą metody Wire.read, odbieramy tyle bajtów, ile zwróciła nam funkcja Wire.requestFrom.
Struktura ramki żądania danych zaprezentowana została na rysunku 3.
Metody obsługujące komunikację widoczne są na listingu 4.
{
Wire.beginTransmission(DEVICE_ADDR);
Wire.write(module);
Wire.write(cmd);
Wire.write(1);
Wire.endTransmission();
uint8_t dataLength = Wire.requestFrom(DEVICE_ADDR, (uint8_t)(responseLength * 2));
for (int i = 0; i < dataLength / 2; i++)
{
uint8_t msb = Wire.read();
uint8_t lsb = Wire.read();
response[i] = msb << 8 | lsb;
}
}
void TEF6686I²CComm::SetCommand(uint8_t module, uint8_t cmd, uint16_t *params, uint8_t paramsCount)
{
Wire.beginTransmission(DEVICE_ADDR);
Wire.write(module);
Wire.write(cmd);
Wire.write(1);
for (int i = 0; i < paramsCount; i++)
{
uint8_t msb = params[i] >> 8;
uint8_t lsb = params[i];
Wire.write(msb);
Wire.write(lsb);
}
Wire.endTransmission();
}
Listing 4. Metody obsługujące komunikację z modułem TEF6686
Program cyklicznie odpytuje moduł tunera o aktualną jakość sygnału i parametry odbioru. Zaimplementowane jest to w metodzie Update QualityStatus (listing 5). W tym przypadku, po starcie transmisji, jako pierwszy bajt wysyłamy identyfikator modułu równy 32 (Module FM), a po nim następuje numer komendy (Get cmd) równy 128. Kolejny bajt to index, który zawsze jest równy 1. Następnie wykonujemy żądanie 7 parametrów wyjściowych (każdy z nich to 2 bajty) - są one umieszczane w tablicy result. Kolejne elementy tej tablicy są przyporządkowane do struktury Quality.
{
uint16_t result[7];
tefI²CComm.GetCommand(MODULE_FM, 128, result, 7);
quality.AF_UpdateFlag = ((result[0]>>15 & 1)==1);
quality.QualityTimeStamp = (uint16_t)(result[0] & 0x3ff);
quality.Level = result[1];
quality.Noise = result[2];
quality.Wam = result[3];
quality.Offset = result[4];
quality.Bandwidth = result[5];
quality.Modulation = result[6];
}
Listing 5. Metoda pobierająca dane o jakości sygnału
Gorąco zachęcam do zapoznania się z dokumentacją tunera TEF6686. Znajdziemy tam opis wszystkich funkcji oraz szczegóły dotyczące komunikacji z modułem. W naszym odbiorniku układ pracuje na ustawieniach fabrycznych. Warto jednak poeksperymentować z parametrami i sprawdzić, jak wpływają na odbiór sygnału radiowego.
Wyświetlacz oparty jest na sterowniku SSD1680 i obsługiwany za pomocą biblioteki GxEPD2. Komunikacja odbywa się przez magistralę SPI. Cała obsługa grafiki zawarta jest w pliku Graphics.h. W funkcji UpdateScreen(), która uruchamia się cyklicznie w oddzielnym zadaniu RTOS, znajdziemy wszystkie elementy związane z rysowaniem elementów interfejsu graficznego.
Montaż i uruchomienie
Ze względu na bardzo prostą budowę prototyp został zmontowany na uniwersalnej płytce drukowanej (fotografia 2).
Całość zasilana jest z zewnętrznego zasilacza 5 V (gniazdo USB). Kondensatory C1, C2, C3, C4 służą do filtrowania napięcia zasilającego. Rezystory R1 i R2 podciągają szynę I²C do napięcia 3,3 V. Zarówno do tunera, mikrokontrolera, jak i wyświetlacza podane jest napięcie 5 V (moduł tunera ma wewnętrzny regulator napięcia na 3,3 V). Enkoder pobiera zasilanie z pinu 3,3 V na płytce mikrokontrolera ESP32.
Chociaż układ TEF6686 ma opcję przejścia w tryb standby, wybrana została opcja całkowitego odłączenia zasilania od modułu tunera w trybie uśpienia. Aby to umożliwić, użyto przekaźnika, który załączany jest przy starcie odbiornika. Do sterowania przekaźnikiem służy popularny układ Darlingtona ULN2803.
Gdy odbiornik przechodzi w tryb uśpienia, na wyjściu sterującym przekaźnikiem pojawia się stan niski, co powoduje rozłączenie styków. Wszystkie moduły operują na takich samych poziomach logicznych, co eliminuje konieczność stosowania konwertera poziomów napięć. Warto wspomnieć, że użyty moduł tunera TEF6686 ma wyprowadzenia w rastrze 2 mm, co utrudnia przylutowanie go do płytki lub umieszczenie w popularnych złączach. W prototypie zastosowany został konektor JST-PH 2 mm, w celu ułatwienia montażu. Wyjście audio układu TEF6686 zostało wyprowadzone bezpośrednio do złączy RCA radioodbiornika. Prawidłowo złożony układ wymaga oczywiście zaprogramowania mikrokontrolera. Aby uniknąć zakłóceń wpływających na parametry odbioru, należy dobrze rozplanować rozmieszczenie elementów na płytce drukowanej oraz podłączyć do masy wszystkie wymagane punkty.
Obudowa została zaprojektowana w aplikacji Fusion 360, a następnie wydrukowana na drukarce 3D z użyciem filamentu PLA (rysunek 5).
Podsumowanie
Omawiany odbiornik tylko w niewielkim stopniu korzysta z potencjału układu TEF6686. Zachęcam Czytelników do zapoznania się z jego dokumentacją i testowania na własną rękę, gdyż stwarza on ogromne możliwości.
Paweł Ciuraj
Linki:
- Kod źródłowy firmware odbiornika: https://github.com/ciuri/FMRadio_TEF6686
- Kod źródłowy biblioteki TEF6686: https://github.com/ciuri/TEF6686Library
- 4,7 kΩ (2 szt.)
- 100 μF/16 V (2 szt.)
- 100 nF (2 szt.)
- ULN2803
- Moduł tunera TEF6686
- ESP32 Devkit V1
- Wyświetlacz e-paper WeAct 2,9”
- Przekaźnik 5 V
- Enkoder obrotowy
- Gniazdo RCA stereo
- Gniazdo antenowe
- Gniazdo USB-C