[098] Budujemy arduinowe radio cz. 2
![[098] Budujemy arduinowe radio cz. 2](/_next/image?url=https%3A%2F%2Farduino.pl%2Fimgproxy%2FEMvQeeq5rAtE3DW91ZBhOxDmUJwJNTJVogrUqRokox8%2Ff%3Awebp%2Fw%3A1200%2FbG9jYWw6Ly8vaW1hZ2VzLzAtNjdlL2FlNDcyLzFkZTJkLzAtNjdlYWU0NzIxZGUyZDkxMjk3OTQ5NC5wbmc%3D.webp&w=3840&q=75)
Zabawa, nauka i tworzenie czegoś praktycznego – dziś spotykamy się pod takim hasłem. Z prostego, acz działającego radia przedstawionego w poprzednim artykule będę chciał zrobić coś bardziej użytecznego. Równie ważną rzeczą, którą poznamy, a o którą padały już nieraz pytania, jest nieulotna pamięć EEPROM, która zachowuje swoją zawartość po odłączeniu zasilania. Aż się prosi, by w oparciu o nią zrobić programator – ale to za chwilę.
Pozostaje jeszcze jeden problem: co znaczy: radio użyteczne i w którą stronę podążać? Można oczywiście stworzyć pełen panel z wyświetlaniem częstotliwości, poziomów, komunikatów RDS i tak dalej. Ale to się w sieci znajdzie, a poza tym takie małe płytki raczej nie służą budowie tunerów wieżowych, tylko podręcznych radyjek. Bardziej użyteczny będzie prosty interfejs i taki właśnie zaprojektowałem. Zresztą zawsze może powstać trzecia część radiowej sagi.
Rozbudowę będziemy czynić etapami, dzięki czemu łatwiej będzie się w tym połapać. Kto nie czytał poprzedniego artykułu, zapraszam na wstępie tam, ponieważ wykorzystamy szkic tamże powstały i nie będę już omawiał elementów, o którym mówiłem poprzednio.
1
Na początek zróbmy użytek z potencjometru. Kręcenie gałką celem zmiany głośności jest o wiele wygodniejsze niż wciskanie przycisków. Na płytce edukacyjnej TME, której używam w projekcie, osadzono potencjometr, podłączając ślizgacz do portu A1. Kto takiej płytki nie ma, może sobie oczywiście podpiąć potencjometr do dowolnego wejścia analogowego z wyjątkiem dwóch ostatnich, które tworzą magistralę I2C, z której korzysta płytka tunera. Wróćmy do naszego szkicu i dopiszmy tylko dwie linie.
#include <radio.h> // Biblioteka obsługi serii tunerów FM
#include <RDA5807FP.h> // Biblioteka obsługi konkretnej wersji tunera.
// #include <RDA5807M.h>
// #include <SI4705.h>
// #include <SI47xx.h>
// #include <TEA5767.h>
RDA5807FP radio; // Nadaj układowi nazwę "radio"
// RDA5807M radio;
// SI4705 radio;
// SI47xx radio;
// TEA5767 radio;
const byte poprzednia = 4; // Pstryczek "Poprzednia stacja"
const byte nastepna = 7; // Pstryczek "Następna stacja"
const byte ciszej = 5; // Pstryczek "Zmniejsz głośność"
const byte glosniej = 6; // Pstryczek "Zwiększ głośność"
const byte mono = 8; // Pstryczek "Mono/Stereo"
const byte potencjometr = A1; // Potencjometr głośności.
void setup() {
radio.initWire(Wire); // Inicjuj tuner.
radio.setup(RADIO_FMSPACING, RADIO_FMSPACING_100); // Ustaw krok strojenia równy 100 kHz.
radio.setup(RADIO_DEEMPHASIS, RADIO_DEEMPHASIS_50); // Ustaw deemfazę obowiązującą w Europie.
radio.setBandFrequency(RADIO_BAND_FM, 9800); // Ustaw częstotliwość początkową na środek zakresu.
radio.setMono(false); // Ustaw tryb stereo.
radio.setMute(false); // Wyłącz wyciszenie.
radio.setVolume(4); // Ustaw poziom głośności na 1/4 zakresu.
pinMode(poprzednia, INPUT_PULLUP); // Deklaruj linie pstryczków jako wejścia podciągnięte wewnętrznie do wysokiego stanu.
pinMode(nastepna, INPUT_PULLUP);
pinMode(ciszej, INPUT_PULLUP);
pinMode(glosniej, INPUT_PULLUP);
pinMode(mono, INPUT_PULLUP);
}
void loop() {
if (digitalRead(poprzednia) == HIGH) { // Jeśli wciśnięto przycisk "Poprzednia stacja"...
radio.seekDown(1); // Przestrój się w górę do najbliższej stacji (zero skacze 100 kHz do góry).
while (digitalRead(poprzednia) == HIGH) {} // Czekaj aż przycisk zostanie puszczony.
}
if (digitalRead(nastepna) == HIGH) { // Jeśli wciśnięto przycisk "Następna stacja"...
radio.seekUp(1); // Przestrój się w dół do najbliższej stacji (zero skacze 100 kHz w dół).
while (digitalRead(nastepna) == HIGH) {} // Czekaj aż przycisk zostanie puszczony.
}
if (digitalRead(ciszej) == HIGH) { // Jeśli wciśnięto przycisk "Zmniejsz głośność"...
byte x = radio.getVolume(); // Pobierz z tunera poziom głośności.
radio.setVolume(--x); // Wyślij do tunera wartość pobraną, pomniejszoną o jeden.
while (digitalRead(ciszej) == HIGH) {} // Czekaj aż przycisk zostanie puszczony.
}
if (digitalRead(glosniej) == HIGH) { // Jeśli wciśnięto przycisk "Zwiększ głośność"...
byte x = radio.getVolume(); // Pobierz z tunera poziom głośności.
radio.setVolume(++x); // Wyślij do tunera wartość pobraną, powiększoną o jeden.
while (digitalRead(glosniej) == HIGH) {} // Czekaj aż przycisk zostanie puszczony.
}
if (digitalRead(mono) == HIGH) { // Jeśli wciśnięto przycisk "Mono/Stereo"...
radio.setMono(!radio.getMono()); // Zmień tryb: stereo <> mono.
while (digitalRead(mono) == HIGH) {} // Czekaj aż przycisk zostanie puszczony.
}
radio.setVolume(map(analogRead(potencjometr), 0, 1023, 0, 15)); // Wyślij do rejestru głośności przeliczoną wartość zmierzonego napięcia z potencjometru.
}
Zadeklarujemy port woltomierza, który nazwiemy potencjometr. Całe sedno siedzi w tej linii, którą dopisałem na końcu głównej pętli:
radio.setVolume(map(analogRead(potencjometr), 0, 1023, 0, 15));
Używając instrukcji, którą mamy już wyżej i obsługują one tam dwa bloki regulujące głośność za pomocą przycisków, wysyłamy do tunera zmierzone napięcie, przemapowane na zakres od zera do piętnastu, bo w takim okienku porusza się interpreter poziomu głośności w radyjku. O instrukcji map pisałem już wielokrotnie, jest ona przyjemna, bo w locie zamienia jeden przedział proporcjonalnie na drugi. Jak wiadomo, rozdzielczość woltomierza w Arduino wynosi 10 bitów, stąd wartość: 1023, bo to właśnie dwa do potęgi dziesiątej minus jeden. Po skompilowaniu będziemy mogli już kręcić gałką jak w starożytnym radiu wyprodukowanym w Polsce Ludowej.
Wróćmy do programu i przyjrzyjmy się wszystkim blokom w głównej pętli. Fragmenty te wykonają się tylko wtedy, gdy zajdzie warunek wciśnięcia odpowiedniego przycisku. Do tego dane wyjdą raz i program pozostanie zawieszony, dopóki przycisk związany z blokiem nie zostanie zwolniony. Inaczej jest w przypadku nowo dopisanej linii. Ona jest wykonywana w każdym przejściu pętli loop, czyli wartość potencjometru aktualizowana jest bez przerwy. Niby to działa, ale nie jest to eleganckie z kilku względów.
Po pierwsze, nie usunąłem bloków zmiany głośności przyciskami. A przestały działać, ponieważ cokolwiek tam wciśniemy, zaraz potem program uaktualni głośność zgodnie z pozycją potencjometru.
Po drugie, mamy ciągły ruch na magistrali, co w jakiś sposób może powodować wzrost zakłóceń. Zasada mówi, że należy minimalizować ruch danych do niezbędnego minimum. Wysyłanie w kółko informacji o głośności, która się nie zmienia, nie ma sensu.
Po trzecie wreszcie, brak tutaj zabezpieczeń przed niestabilnymi odczytami. W pewnych pozycjach ślizgacza kolejne odczyty mogą się zmieniać o jeden, co w pewnych przypadkach będzie mapowane także o jeden, a to – jeśli nastąpi szybką serią - będzie już słychać w postaci trzasków. Trzeba coś z tym zrobić.
#include <radio.h> // Biblioteka obsługi serii tunerów FM
#include <RDA5807FP.h> // Biblioteka obsługi konkretnej wersji tunera.
// #include <RDA5807M.h>
// #include <SI4705.h>
// #include <SI47xx.h>
// #include <TEA5767.h>
RDA5807FP radio; // Nadaj układowi nazwę "radio"
// RDA5807M radio;
// SI4705 radio;
// SI47xx radio;
// TEA5767 radio;
const byte poprzednia = 4; // Pstryczek "Poprzednia stacja"
const byte nastepna = 7; // Pstryczek "Następna stacja"
const byte ciszej = 5; // Pstryczek "Zmniejsz głośność"
const byte glosniej = 6; // Pstryczek "Zwiększ głośność"
const byte mono = 8; // Pstryczek "Mono/Stereo"
const byte potencjometr = A1; // Potencjometr głośności.
int napiecieStare; // Poprzednie zmierzone napięcie ustawione potencjometrem.
int napiecieNowe; // Aktualne zmierzone napięcie ustawione potencjometrem.
void setup() {
radio.initWire(Wire); // Inicjuj tuner.
radio.setup(RADIO_FMSPACING, RADIO_FMSPACING_100); // Ustaw krok strojenia równy 100 kHz.
radio.setup(RADIO_DEEMPHASIS, RADIO_DEEMPHASIS_50); // Ustaw deemfazę obowiązującą w Europie.
radio.setBandFrequency(RADIO_BAND_FM, 9800); // Ustaw częstotliwość początkową na środek zakresu.
radio.setMono(false); // Ustaw tryb stereo.
radio.setMute(false); // Wyłącz wyciszenie.
radio.setVolume(4); // Ustaw poziom głośności na 1/4 zakresu.
pinMode(poprzednia, INPUT_PULLUP); // Deklaruj linie pstryczków jako wejścia podciągnięte wewnętrznie do wysokiego stanu.
pinMode(nastepna, INPUT_PULLUP);
pinMode(ciszej, INPUT_PULLUP);
pinMode(glosniej, INPUT_PULLUP);
pinMode(mono, INPUT_PULLUP);
}
void loop() {
if (digitalRead(poprzednia) == HIGH) { // Jeśli wciśnięto przycisk "Poprzednia stacja"...
radio.seekDown(1); // Przestrój się w górę do najbliższej stacji (zero skacze 100 kHz do góry).
while (digitalRead(poprzednia) == HIGH) {} // Czekaj aż przycisk zostanie puszczony.
}
if (digitalRead(nastepna) == HIGH) { // Jeśli wciśnięto przycisk "Następna stacja"...
radio.seekUp(1); // Przestrój się w dół do najbliższej stacji (zero skacze 100 kHz w dół).
while (digitalRead(nastepna) == HIGH) {} // Czekaj aż przycisk zostanie puszczony.
}
if (digitalRead(ciszej) == HIGH) { // Jeśli wciśnięto przycisk "Zmniejsz głośność"...
byte x = radio.getVolume(); // Pobierz z tunera poziom głośności.
radio.setVolume(--x); // Wyślij do tunera wartość pobraną, pomniejszoną o jeden.
while (digitalRead(ciszej) == HIGH) {} // Czekaj aż przycisk zostanie puszczony.
}
if (digitalRead(glosniej) == HIGH) { // Jeśli wciśnięto przycisk "Zwiększ głośność"...
byte x = radio.getVolume(); // Pobierz z tunera poziom głośności.
radio.setVolume(++x); // Wyślij do tunera wartość pobraną, powiększoną o jeden.
while (digitalRead(glosniej) == HIGH) {} // Czekaj aż przycisk zostanie puszczony.
}
if (digitalRead(mono) == HIGH) { // Jeśli wciśnięto przycisk "Mono/Stereo"...
radio.setMono(!radio.getMono()); // Zmień tryb: stereo <> mono.
while (digitalRead(mono) == HIGH) {} // Czekaj aż przycisk zostanie puszczony.
}
napiecieNowe = analogRead(potencjometr); // Mierzymy napięcie ustawione ślizgaczem potencjometru.
if (abs(napiecieNowe - napiecieStare) > 3) { // Odejmujemy tę wartość od wartości zmierzonej poprzednio i jeśli wartość bezwzględna wyniku jest większa od 3...
napiecieStare = napiecieNowe; // Przepisujemy wartość zmierzonego napięcia do zmiennej napiecieStare.
radio.setVolume(map(napiecieNowe, 0, 1020, 0, 15)); // Wysyłamy ją także do rejestru głośności, po przemapowaniu do zakresu (0-15)
}
}
Zmienimy ostatni blok na modłę pozostałych – z warunkiem wejścia. Zaczniemy od pomiaru napięcia, ale zamiast go wysyłać od razu, przekażemy je do zmiennej napiecieNowe. Teraz odejmiemy od siebie owo świeżo zmierzone napięcie od zmierzonego w poprzedniej pętli – albo równego zero, jeśli właśnie nastąpił reset. Z wyniku weźmiemy wartość bezwzględną, bo interesuje nas fakt, czy oba odczyty różnią się o pewien próg, a już w którą stronę – to nie ma znaczenia.
Próg ten ustawiłem na 3, co daje redukcję rozdzielczości przetwornika o dwa bity. To i tak o wiele więcej od rozdzielczości rejestru głośności tunera, który jest czterobitowy. Im wyższy próg, tym większy odstęp od zakłóceń i śmiało można go zwiększyć nawet do kilkudziesięciu, jeśli mamy zamiar używać starych potencjometrów z Telpodu.
Jeśli powyższy warunek zajdzie, teraz dopiero przekazujemy zmiennej napięcieStare aktualną wartość napięcia i wysyłamy je – po przemapowaniu do tunera, jak poprzednio.
Podsumowując: wysyłanie danych nastąpi dopiero po przekroczeniu progu czterech jednostek napięcia, zabezpieczając nas przed drganiami potencjometru. Zwie się to histerezą i jest podstawą poprawnej pracy układów z analogowym odczytem manipulatorów. Mała uwaga – wartość map należy ograniczyć od góry o wartość progu.
Jest już dobrze, ale nieidealnie. Potencjometr w lewej skrajnej pozycji nie wycisza radia zupełnie, bo wartość zero to nie cisza, ale najniższa głośność. Spróbujmy i z tym wygrać. Zmienimy ostatni blok programu:
napiecieNowe = analogRead(potencjometr); // Mierzymy napięcie ustawione ślizgaczem potencjometru.
if (abs(napiecieNowe - napiecieStare) > 3) { // Odejmujemy tę wartość od wartości zmierzonej poprzednio i jeśli wartość bezwzględna wyniku jest większa od 3, to...
napiecieStare = napiecieNowe; // Przepisujemy wartość zmierzonego napięcia do zmiennej napiecieStare.
if (napiecieNowe < 4) { // Jeśli napięcie jest mniejsze od 4...
radio.setMute(1); // Wycisz radio.
} else { // W przeciwnym razie...
radio.setMute(0); // Wyłącz wyciszenie oraz...
radio.setVolume(map(napiecieNowe, 4, 1020, 0, 15)); // Wyślij wartość napięcia do rejestru głośności, po przemapowaniu do zakresu (0-15)
}
}
Dołożymy kolejny warunek, identyfikujący owo skrajne położenie potencjometru. Ustaliłem sobie przedział wąziutki, ograniczony czwórką, czyli w praktyce ten warunek zajdzie tylko dla potencjometru skrajnie skręconego w lewo. W tym wypadku nie będziemy wysyłać komunikatu o głośności, a inny – wyciszający tuner.
Jeśli ten warunek nie zajdzie, wracamy do procedury z poprzedniego szkicu, z tym że dokładamy jeszcze instrukcję zdjęcia wyciszenia. Teraz już potencjometr działa jak w prawdziwym radiu. Czas więc zająć się programatorem, ale o tym napiszę w kolejnym artykule.