[090] Przejmowanie kontroli nad urządzeniami - multiefekt gitarowy - cz. 3
![[090] Przejmowanie kontroli nad urządzeniami - multiefekt gitarowy - cz. 3](/_next/image?url=https%3A%2F%2Farduino.pl%2Fimgproxy%2FDti0HNId2XROdoPFVe7sXo7ejJLxxuEQ1vv3DV_jmls%2Ff%3Awebp%2Fw%3A1200%2FbG9jYWw6Ly8vaW1hZ2VzLzBjLTY3Lzk5MDA2LzYyMDE2LzBjLTY3OTkwMDY2MjAxNjQ5ODQ5NTk2OTIucG5n.webp&w=3840&q=75)
W trzeciej części poświęconej hackowaniu multiefektu gitarowego trzeba będzie rozwiązać jeszcze jeden duży problem. Udało się oprogramować dostęp do regulacji basów. Zanim zabierzemy się za kolejne elementy – jedna mała pomoc naukowa. Otóż, jak się zaraz okaże, część parametrów ma dużo większą rozdzielczość, a wysyłanie serii impulsów blokuje możliwość regulacji innych parametrów. Przydałoby się mieć jakąś informację o zajętości urządzenia. Co prawda na końcu program otrzyma postać, w której nawet bardzo szybkie kręcenie potencjometrami niczego nie zgubi, dobrze jednak byłoby wiedzieć, czy już nasz efekt jest gotowy na przyjęcie kolejnej porcji danych. Do tego wykorzystam sobie diodę z portu trzynastego.
const byte led = 13; // Numer pinu, do którego podłączona jest dioda świecąca.
const byte przyciskPlus = 11; // Numer pinu, do którego podłączony jest przycisk podwyższający edytowany parametr.
const byte przyciskMinus = 10; // Numer pinu, do którego podłączony jest przycisk obniżający edytowany parametr.
const byte potencjometrBAS = A0; // Adres portu potencjometru.
int wartoscBAS = 512; // Wartość odczytanego napięcia pochodzącego ze ślizgacza potencjometru.
int zmianaBAS = 512; // Tymczasowa wartość odczytanego napięcia do porównań.
byte wirtualnyBAS = 5; // Pozycja wirtualnego potencjometru odpowiedzialnego za poziom parametru.
bool aktywnyBAS; // Wskaźnik ustawiany po wykryciu zmiany położenia potencjometru.
void setup() {
pinMode(led, OUTPUT); // Zadeklaruj porty jako wyjścia.
pinMode(przyciskPlus, OUTPUT);
pinMode(przyciskMinus, OUTPUT);
}
void loop() {
digitalWrite(led, LOW); // Wyłącz diodę.
zmianaBAS = analogRead(potencjometrBAS); // Odczytaj wartość napięcia ze ślizgacza potencjometru.
if (abs(wartoscBAS - zmianaBAS) > 3) { // Jeśli odczytana wartość jest większa od 3 od wartości poprzedniej...
wartoscBAS = zmianaBAS; // Uaktualnij wartość napięcia dla pozostałych modułów programu.
aktywnyBAS = true; // Ustaw wskaźnik zmiany położenia potencjometru.
}
if (aktywnyBAS == true) { // Jeśli wykryto zmianę położenia potencjometru...
aktywnyBAS = false; // Kasuj wskaźnik zmiany położenia potencjometru.
byte przeskalowanyBAS = map(wartoscBAS, 0, 1024, 1, 10); // Przelicz położenie potencjometru na zakres zmian bieżącego parametru.
while (wirtualnyBAS > przeskalowanyBAS) { // Porównaj położenie potencjometru rzeczywistego z wirtualnym.
wcisnijMinus(); // Tyle raz wciskaj przycisk minus, aż oba będą miały tę samą wartość.
wirtualnyBAS--;
}
while (wirtualnyBAS < przeskalowanyBAS) {
wcisnijPlus();
wirtualnyBAS++;
}
}
}
void wcisnijPlus() { // Procedura wciskania przycisku Plus - dobierz opóźnienia do sterowanego urządzenia.
digitalWrite(przyciskPlus, HIGH);
digitalWrite(led, HIGH); // Włącz diodę.
delay(32);
digitalWrite(przyciskPlus, LOW);
delay(32);
}
void wcisnijMinus() { // Procedura wciskania przycisku Minus - dobierz opóźnienia do sterowanego urządzenia.
digitalWrite(przyciskMinus, HIGH);
digitalWrite(led, HIGH);
delay(32);
digitalWrite(przyciskMinus, LOW);
delay(32);
}
Ale procedury gmeraniem tą diodą umieszczę nietypowo: włączać będzie się za każdym razem po wejściu w obsługę wciskania klawiaturki multiefektu, natomiast wyłączać będę ją na początku pętli, bo tam znajdziemy się dopiero po wyrównaniu potencjometrów rzeczywistych z wirtualnymi. Teoretycznie wielokrotne włączanie diody jest marnowaniem czasu, jednak tam i tak czas marnujemy, ponieważ multiefekt wymaga pauz. Za to teraz metodą prób i błędów odjąłem po trzy milisekundy z każdej fazy, gdyż czas dodały nam tutaj pozostałe procedury.
Na tym etapie mamy już algorytm zamieniający obsługę przycisków „plus – minus” na taką, którą można zrealizować o wiele wygodniejszym potencjometrem, który do tego jeszcze pokazuje wskaźnikiem ustawioną wartość. Można to wykorzystywać w różnych projektach, bo – jak to często powtarzam – tutaj mamy konkret, ale podmiotem są metody, a nie budowa tego czy owego urządzenia.
Czas na wspomnianą ostatnią trudność do pokonania: sprawienie, że przekręcenie wybranym potencjometrem skieruje działania do wybranego parametru. Gdybyśmy teraz oprogramowali wszystkie potencjometry, dokładając klawiaturkę, która aktywowałaby konkretny – problemu nie byłoby. Należałoby tylko powielić wszystko co już zrobiliśmy tyle razy, ile potencjometrów przewiduje się tutaj zamontować. Ale co to za interes? Znowu jakieś dodatkowe przyciski i konieczność ich wciskania. Stwórzmy sobie wyzwanie: niech program sam zadecyduje o tym, jak wejść w dany parametr tylko na podstawie samego ruszenia gałką potencjometru. Wbrew pozorom nie jest to takie proste na pierwszy rzut oka, ale w realizacji okazało się być nieskomplikowane.
const byte led = 13; // Numer pinu, do którego podłączona jest dioda świecąca.
const byte przyciskMenu = 12; // Numer pinu, do którego podłączony jest przycisk wybierający parametr do edycji.
const byte przyciskPlus = 11; // Numer pinu, do którego podłączony jest przycisk podwyższający edytowany parametr.
const byte przyciskMinus = 10; // Numer pinu, do którego podłączony jest przycisk obniżający edytowany parametr.
byte numerPotencjometru = 0; // Numer ostatnio zmienianego potencjometru.
byte skokMenu = 7; // Ilość wciśnięć klawisza Menu, zależna od ostatnio aktywnego potencjometru.
const byte tablicaBAS[] = { 0, 2 }; // Tablice skoków przez menu, zależne od ostatnio użytego potencjometru.
const byte tablicaTRE[] = { 11, 0 };
const byte potencjometrBAS = A0; // Adres portu potencjometru.
int wartoscBAS = 512; // Wartość odczytanego napięcia pochodzącego ze ślizgacza potencjometru.
int zmianaBAS = 512; // Tymczasowa wartość odczytanego napięcia do porównań.
byte wirtualnyBAS = 5; // Pozycja wirtualnego potencjometru odpowiedzialnego za poziom parametru.
const byte potencjometrTRE = A1;
int wartoscTRE = 512;
int zmianaTRE = 512;
byte wirtualnyTRE = 5;
void setup() {
pinMode(led, OUTPUT); // Zadeklaruj porty jako wyjścia.
pinMode(przyciskMenu, OUTPUT);
pinMode(przyciskPlus, OUTPUT);
pinMode(przyciskMinus, OUTPUT);
przewin();
}
void loop() {
digitalWrite(led, LOW); // Wyłącz diodę.
zmianaBAS = analogRead(potencjometrBAS); // Odczytaj wartość napięcia ze ślizgacza potencjometru.
if (abs(wartoscBAS - zmianaBAS) > 3) { // Jeśli odczytana wartość jest większa od 3 od wartości poprzedniej...
wartoscBAS = zmianaBAS; // Uaktualnij wartość napięcia dla pozostałych modułów programu.
skokMenu = tablicaBAS[numerPotencjometru]; // Wyłuskaj z tablicy skoków przez menu ilość konkretnych skoków w zależności od ostatnio używanego potencjometru.
przewin(); // Przeskocz do wybranego parametru.
numerPotencjometru = 0; // Ustaw numer ostatnio ruszonego potencjometru na bieżący.
byte przeskalowanyBAS = map(wartoscBAS, 0, 1024, 1, 10); // Przelicz położenie potencjometru na zakres zmian bieżącego parametru.
while (wirtualnyBAS > przeskalowanyBAS) { // Porównaj położenie potencjometru rzeczywistego z wirtualnym.
wcisnijMinus(); // Tyle raz wciskaj przycisk minus, aż oba będą miały tę samą wartość.
wirtualnyBAS--;
}
while (wirtualnyBAS < przeskalowanyBAS) {
wcisnijPlus();
wirtualnyBAS++;
}
}
zmianaTRE = analogRead(potencjometrTRE);
if (abs(wartoscTRE - zmianaTRE) > 3) {
wartoscTRE = zmianaTRE;
skokMenu = tablicaTRE[numerPotencjometru];
przewin();
numerPotencjometru = 1;
byte przeskalowanyTRE = map(wartoscTRE, 0, 1024, 1, 10);
while (wirtualnyTRE > przeskalowanyTRE) {
wcisnijMinus();
wirtualnyTRE--;
}
while (wirtualnyTRE < przeskalowanyTRE) {
wcisnijPlus();
wirtualnyTRE++;
}
}
}
void wcisnijPlus() { // Procedura wciskania przycisku Plus - dobierz opóźnienia do sterowanego urządzenia.
digitalWrite(przyciskPlus, HIGH);
digitalWrite(led, HIGH); // Włącz diodę.
delay(32);
digitalWrite(przyciskPlus, LOW);
delay(32);
}
void wcisnijMinus() { // Procedura wciskania przycisku Minus.
digitalWrite(przyciskMinus, HIGH);
digitalWrite(led, HIGH);
delay(32);
digitalWrite(przyciskMinus, LOW);
delay(32);
}
void przewin() { // Procedura wciskania przycisku Menu.
for (byte x = 0; x < skokMenu; x++) {
digitalWrite(przyciskMenu, HIGH);
digitalWrite(led, HIGH);
delay(32);
digitalWrite(przyciskMenu, LOW);
delay(32);
}
}
Najpierw uproszczę nieco program, usuwając elementy związane z ustawianiem flag aktywności działań związanych z obsługą potencjometru. Jeśli całą obsługę osadzimy w miejscu, w którym wystawiane były owe flagi, nie będziemy ich musieli sprawdzać. W innych okolicznościach byłoby to niewskazane, ta konstrukcja programu jednak tego nie wymaga. Ale to nie jest bardzo istotne, kto chce, może sobie tamte elementy pozostawić. Skupmy się na czymś innym. W jaki sposób zorganizować program zgodnie z ideą, o której przed chwilą powiedziałem? Pewnym fortelem. Zbudujemy sobie tablice, tyle tablic ile jest potencjometrów.
const byte tablicaBAS[] = { 0, 2 }; // Tablice skoków przez menu, zależne od ostatnio użytego potencjometru.
const byte tablicaTRE[] = { 11, 0 };
W każdej z nich będzie taka sama ilość pozycji. Każda z nich będzie zawierać ilość wciśnięć przycisku menu, konieczną, by przenieść się do wybranej pozycji w multiefekcie.
Zduplikujmy sobie wszystko, co do tej pory powstało, zamieniając wszędzie człony BAS na TRE. Będzie to drugi parametr, który będziemy zmieniać: tony wysokie. Wygląda on dokładnie tak samo, czyli dziewięć wartości, tym razem z wyświetlaną kulfoniastą literą t, z wartością neutralną pod numerem t5

Zanim przejdę do tablic, powołamy do życia jeszcze dwie zmienne. Pierwsza (numerPotencjometru) to numer potencjometru, który ostatnio był ruszany. Będzie nam to potrzebne do określenia, który parametr właśnie został zmieniony. Druga zmienna (skokMenu) będzie zawierać ilość wciśnięć przycisku menu i będziemy ją wypełniać danymi sczytanymi z tablicy. Jak mówiłem – tablic będzie ich tyle, ile potencjometrów. Te tablice to takie mapy skoków. Teraz są tylko duelementowe, bo na razie obsługujemy dwa potencjometry. Przenieśmy się niżej.
skokMenu = tablicaBAS[numerPotencjometru]; // Wyłuskaj z tablicy skoków przez menu ilość konkretnych skoków w zależności od ostatnio
Tutaj ładujemy zmienną skokMenu wartością z tablicy ustawioną dla obsługi regulacji basów. W zależności od tego który ostatnio potencjometr był dotykany, będziemy ładować wartość zero – jeśli był to ten sam potencjometr albo dwa, jeśli ostatnio majstrowano przy wysokich tonach, bo żeby z nich przejść w basy, należy dwukrotnie wcisnąć menu.
W obsłudze wysokich tonów, jeśli poprzednio był ruszany potencjometr od basów, trzeba będzie wcisnąć menu aż 11 razy. Natomiast jeśli nie zmienił się – menu wciśniemy zero razy. Prawda, że sprytne? Oczywiście można byłoby nastawić pułapkę na „zeorokrotne” wykonywanie procedury wciskania menu, ale dorobiłoby to bajtów niepotrzebnie. Ach, bo bym zapomniał: po wciskaniu menu należy uaktualnić wartość ostatnio ruszanego potencjometru:
numerPotencjometru = 0; // Ustaw numer ostatnio ruszonego potencjometru na bieżący.
Trzeba jeszcze pamiętać, by na początku programu, po resecie wskoczyć w odpowiednią pozycję, w tym wypadku wybrałem wysokie tony. Skok znajduje się w części setup, a ilość wciśnięć – w deklaracji zmiennych. Inaczej zaczęlibyśmy zmiany gdzieś w zupełnie przypadkowym miejscu i całość rozsynchronizowałaby się.
Jak to będzie teraz działać? Znakomicie, choć niestety ślamazarnie. O ile przejście z sopranów w basy następuje natychmiast, w drugą stronę trzeba chwilę zaczekać. Po to wstawiłem wcześniej diodę zajętości. Ale najważniejsze jest to, że nic nie ginie. Możemy sobie poustawiać oba potencjometry szybko, a ich pozycje uaktualnią wartości w multiefekcie, gdy nadejdzie czas. Cóż, dwa elementy pracują jak należy, pozostało zająć się resztą. Ale o tym – a także o rzeczach, o których pisałem wcześniej, napiszę w kolejnym artykule.