[091] Przejmowanie kontroli nad urządzeniami - multiefekt gitarowy - cz. 4

[091] Przejmowanie kontroli nad urządzeniami - multiefekt gitarowy - cz. 4

W tej, czwartej już części zakończymy projekt. Mając już działającą obsługę dwóch elementów multiefektu: regulacji niskich tonów i wysokich, możemy rozbudowywać sterownik w nieskończoność, a raczej do momentu przydzielenia gałkom wszystkich elementów. Jak wiadomo, płytka Uno ofiarowuje nam łaskawie sześć kanałów przetwornika i na razie poprzestaniemy na tychże. Istnieją dwie szkoły rozbudowywania programów, które pozornie robią to samo, ale w szczegółach – nie do końca. O ile regulacja basów i sopranów wyglądała identycznie: dziewięć pozycji z neutralną w środku, przy kolejnych efektach będzie inaczej. Jedna ze szkół mówi, by działać po kolei, dokładając po efekcie, druga – zróbmy wszystko naraz, może uda się od razu i nie trzeba będzie niczego poprawiać.


Ponieważ tutaj mamy owe tablice skoków, o których pisałem i kolejne ich modyfikacje byłyby nużące, postanowiłem realizować całą resztę naraz, bez uruchamiania etapów pośrednich. Przy czym tu także praca będzie dwuetapowa: najpierw rozmnożymy bloki sterujące zmianą parametrów kolejnych efektów, a potem uzupełnimy tablice i niezbędne zmienne.

Będziemy dokładać elementy kolejno, idąc w prawo, gdy spojrzymy na panel urządzenia. Zaraz za regulacją sopranów mamy blok modulacji – stąd skrót MOD w zmiennych. Ale jeszcze chwilę – wprowadźmy pewną zmianę: operacje wysyłania „klików” będziemy czynić już nie w chwili, gdy sąsiednie odczyty pozycji potencjometru przekroczą 3, a aż 7. Zyskamy jeszcze większe oddalenie od drgań ślizgacza, a rozdzielczość obrotu gałki to nadal 146 pozycji (1024 dzielone przez 7), dużo więcej niż nam potrzeba. Będzie jeszcze jedna zmiana, ale o tym za chwilę.

Wracamy do wspomnianego bloku regulacji efektów modulacyjnych. Instrukcja urządzenia wspomina aż o 109 pozycjach, które może przyjmować ten element:

  • OF - wyłączony

  • C1...C4/C5...C9 - Chorus1/Chorus2

  • F1...F4/F5...F9 - Flange1/Flange2

  • P1...P4/P5...P9 - Phaser1/Phaser2

  • t1...t4/t5...t9 - Tremolo1/Tremolo2

  • n1...n9 - Panner

  • b1...b9 - Vibrato

  • r1...r9 - Rotary Speaker

  • A1...A9 - AutoYa

  • E1...E9 - Envelope Filter

  • d1...d4/d5...d9 - Detune1/Detune2

  • H1...H9 - Pitch Shift (-12/-7/-5/-4/+3/+4/+5/+7/+12)

  • Y1...Y9 - Whammy (OctUp/2OctUp/OctDn/2OctDn/m3rd-Maj3rdUp/2nd-Maj3rdUp/3rd-4thUp/OctUp/OctDn)

Zaoferowano tu dwanaście algorytmów opóźniających, a każdy może przyjmować dziewięć wartości. Po przeanalizowaniu odrzuciłem wszystkie poza pierwszymi czterema. Oczywiście nie ma takiego obowiązku, po prostu nie używam pozostałych, a przyspieszy mi to operacje gałką potencjometru. Tak więc potrzeba tu 9*4 pozycji plus jedna, która wyłącza pracę tego bloku. Ponieważ mapowanie przeprowadzam od jedynki, ostatnią wartością będzie 38.

zmianaMOD = analogRead(potencjometrMOD);
if (abs(wartoscMOD - zmianaMOD) > 7) {
  wartoscMOD = zmianaMOD;
  skokMenu = tablicaMOD[numerPotencjometru];
  przewin();
  numerPotencjometru = 2;
  byte przeskalowanyMOD = map(wartoscMOD, 0, 1024, 1, 38);
  while (wirtualnyMOD > przeskalowanyMOD) {
    wcisnijMinus(86);
    wirtualnyMOD--;
  }
  while (wirtualnyMOD < przeskalowanyMOD) {
    wcisnijPlus(86);
    wirtualnyMOD++;
  }
}

Okazało się, już później, że o ile regulacja basów i sopranów działała niezawodnie, kręcenie gałką efektów gubiło część pozycji. Opóźnienie 32 ms dla tego elementu było zbyt krótkie. Niestety, z tym będziemy spotykać się często. Już w przykładzie z monitorem był taki problem: niektóre operacje mogły przebiegać szybciej, inne – wolniej. Metodą prób i błędów ustaliłem ów czas na 86 ms. Ale jak teraz zarządzać czasem, żeby za każdym razem używać możliwie jak najkrótszego?

Do tej pory używaliśmy podprogramów w sposób najprostszy: skaczemy, coś tam robimy i wracamy. Tym razem dobudujemy możliwość przekazywania parametru, który będzie czasem opóźnienia. Wstawia się go w puste dotąd nawiasy i odzyskuje w podprogramie, powołując co życia zmienną lokalną y, również w nawiasie.

wcisnijMinus(32);

void wcisnijMinus(byte y) {  // Procedura wciskania przycisku Minus.
  digitalWrite(przyciskMinus, HIGH);
  digitalWrite(led, HIGH);
  delay(y);
  digitalWrite(przyciskMinus, LOW);
  delay(y);
}

W ten sposób wchodząc tu z regulacji basów załadujemy 32 ms, a z wyboru trybu modulacji – 86 ms. Oczywiście wspomniane 32 ms należy teraz dopisać w bloku basów i sopranów. To teraz popracujemy hurtem.

zmianaDEL = analogRead(potencjometrDEL);
if (abs(wartoscDEL - zmianaDEL) > 7) {
  wartoscDEL = zmianaDEL;
  skokMenu = tablicaDEL[numerPotencjometru];
  przewin();
  numerPotencjometru = 3;
  byte przeskalowanyDEL = map(wartoscDEL, 0, 1024, 1, 29);
  while (wirtualnyDEL > przeskalowanyDEL) {
    wcisnijMinus(38);
    wirtualnyDEL--;
  }
  while (wirtualnyDEL < przeskalowanyDEL) {
    wcisnijPlus(38);
    wirtualnyDEL++;
  }
}

W czwartym bloku będziemy wybierać tryb echa. Mamy trzy jego rodzaje po 9 ustawień plus wyłączenie – razem 28 pozycji. Wykorzystamy wszystkie, więc wpisujemy tu 29. Empirycznie czas opóźnień ustaliłem na 38 ms.

zmianaTIM = analogRead(potencjometrTIM);
if (abs(wartoscTIM - zmianaTIM) > 7) {
  wartoscTIM = zmianaTIM;
  skokMenu = tablicaTIM[numerPotencjometru];
  przewin();
  numerPotencjometru = 4;
  byte przeskalowanyTIM = map(wartoscTIM, 0, 1024, 1, 102);
  while (wirtualnyTIM > przeskalowanyTIM) {
    wcisnijMinus(32);
    wirtualnyTIM--;
  }
  while (wirtualnyTIM < przeskalowanyTIM) {
    wcisnijPlus(32);
    wirtualnyTIM++;
  }
}

W piątym bloku wybieramy czas powtórek echa. Tym razem pozycji jest aż 101, więc przewinięcie wszystkich chwilę potrwa. Na szczęście czas opóźnień jest tu znowu tak krótki, jak w przypadku korektora: 32 ms No i w końcu mamy ostatni element: pogłos.

zmianaREV = analogRead(potencjometrREV);
if (abs(wartoscREV - zmianaREV) > 7) {
  wartoscREV = zmianaREV;
  skokMenu = tablicaREV[numerPotencjometru];
  przewin();
  numerPotencjometru = 5;
  byte przeskalowanyREV = map(wartoscREV, 0, 1024, 1, 56);
  while (wirtualnyREV > przeskalowanyREV) {
    wcisnijMinus(35);
    wirtualnyREV--;
  }
  while (wirtualnyREV < przeskalowanyREV) {
    wcisnijPlus(35);
    wirtualnyREV++;
  }
}

Różnych kombinacji mamy tutaj sześć w dziewięciu – jak zwykle – krokach plus odłączenie efektu, co daje 55 pozycji. Zmiany mogą być dość szybkie, wystarczy 35 ms. Czas zająć się deklaracjami i tablicami.

const byte tablicaBAS[] = { 0, 2, 3, 4, 5, 6 };  // Tablice skoków przez menu, zależne od ostatnio użytego potencjometru.
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.

W przypadku deklaracji zmiennych sprawa jest prosta: powielamy pierwszy blok, zmieniając tylko nazwy i adresy pinów kolejnych potencjometrów. Z tablicami tak prosto nie jest. Musimy wrócić do poprzedniego artykułu i przeanalizować ich logikę. Tu tylko przypomnę: kolejne liczby oznaczają ile razy należy wcisnąć przycisk menu, by znaleźć się tutaj z poszczególnych pozycji. A więc dla basów: zero – jeśli już tu jesteśmy, dwie – z sopranów, trzy – z efektów modulacyjnych, cztery – z typów echa, pięć – z czasów echa i sześć – z pogłosów. Dla sopranów będzie podobnie, z tym że wracając tu z regulacji basów trzeba przycisk menu wcisnąć aż 11 razy. I tak dalej, aż do ostatniego – pogłosu.

const byte tablicaTRE[] = { 11, 0, 1, 2, 3, 4 };
const byte tablicaMOD[] = { 10, 12, 0, 1, 2, 3 };
const byte tablicaDEL[] = { 9, 11, 12, 0, 1, 2 };
const byte tablicaTIM[] = { 8, 10, 11, 12, 0, 1 };
const byte tablicaREV[] = { 7, 9, 10, 11, 12, 0 };

Czas na kompilację i – gdy wszystkie wartości będą ustawione zgodnie z założeniami, będziemy mogli już regulować dowolny z sześciu parametrów. Do niektórych będziemy mieć dostęp prawie natychmiastowy, inne będą wymagać cierpliwości, ale zawsze w końcu sczytają ustawienie potencjometrów. W praktyce przeprowadza się drobne korekty i w tym wypadku opóźnienia nie przeszkadzają. Czy można tu coś poprawić? Jak najbardziej. Wystarczy dołożyć transoptor do przycisku menu pozwalającego poruszać się w prawo. Wówczas najdłuższy skok, między basami, a pogłosem, wymagałby sześciu wciśnięć menu, a średnia skoków wyniosłaby trzy. Wypadałoby kodować skoki w jedną stronę liczbami dodatnimi, w drugą – ujemnymi. Myślę, że większość czytelników, którzy dotarli do tego momentu, nie miałaby problemów z rozbudowaniem programu o tę opcję. Sześć gałek to jednak trochę mało. Spróbujmy użyć ośmiu – ale o tym już w kolejnym artykule, a tu, na koniec cały listing gotowego programu, obsługującego sześć gałek.

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śnień klawisza Menu, zależna od ostatnio aktywnego potencjometru.

const byte tablicaBAS[] = { 0, 2, 3, 4, 5, 6 };  // Tablice skoków przez menu, zależne od ostatnio użytego potencjometru.
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 tablicaTRE[] = { 11, 0, 1, 2, 3, 4 };
const byte potencjometrTRE = A1;
int wartoscTRE = 512;
int zmianaTRE = 512;
byte wirtualnyTRE = 5;

const byte tablicaMOD[] = { 10, 12, 0, 1, 2, 3 };
const byte potencjometrMOD = A2;
int wartoscMOD = 512;
int zmianaMOD = 512;
byte wirtualnyMOD = 1;

const byte tablicaDEL[] = { 9, 11, 12, 0, 1, 2 };
const byte potencjometrDEL = A3;
int wartoscDEL = 512;
int zmianaDEL = 512;
byte wirtualnyDEL = 1;

const byte tablicaTIM[] = { 8, 10, 11, 12, 0, 1 };
const byte potencjometrTIM = A4;
int wartoscTIM = 512;
int zmianaTIM = 512;
byte wirtualnyTIM = 1;

const byte tablicaREV[] = { 7, 9, 10, 11, 12, 0 };
const byte potencjometrREV = A5;
int wartoscREV = 512;
int zmianaREV = 512;
byte wirtualnyREV = 1;

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) > 7) {                      // 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śc 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 wirtualnymm.
      wcisnijMinus(32);                                       // Tyle raz wciskaj przycisk minus, aż oba będą miały tę samą wartość.
      wirtualnyBAS--;
    }
    while (wirtualnyBAS < przeskalowanyBAS) {
      wcisnijPlus(32);
      wirtualnyBAS++;
    }
  }

  zmianaTRE = analogRead(potencjometrTRE);
  if (abs(wartoscTRE - zmianaTRE) > 7) {
    wartoscTRE = zmianaTRE;
    skokMenu = tablicaTRE[numerPotencjometru];
    przewin();
    numerPotencjometru = 1;
    byte przeskalowanyTRE = map(wartoscTRE, 0, 1024, 1, 10);
    while (wirtualnyTRE > przeskalowanyTRE) {
      wcisnijMinus(32);
      wirtualnyTRE--;
    }
    while (wirtualnyTRE < przeskalowanyTRE) {
      wcisnijPlus(32);
      wirtualnyTRE++;
    }
  }

  zmianaMOD = analogRead(potencjometrMOD);
  if (abs(wartoscMOD - zmianaMOD) > 7) {
    wartoscMOD = zmianaMOD;
    skokMenu = tablicaMOD[numerPotencjometru];
    przewin();
    numerPotencjometru = 2;
    byte przeskalowanyMOD = map(wartoscMOD, 0, 1024, 1, 38);
    while (wirtualnyMOD > przeskalowanyMOD) {
      wcisnijMinus(86);
      wirtualnyMOD--;
    }
    while (wirtualnyMOD < przeskalowanyMOD) {
      wcisnijPlus(86);
      wirtualnyMOD++;
    }
  }

  zmianaDEL = analogRead(potencjometrDEL);
  if (abs(wartoscDEL - zmianaDEL) > 7) {
    wartoscDEL = zmianaDEL;
    skokMenu = tablicaDEL[numerPotencjometru];
    przewin();
    numerPotencjometru = 3;
    byte przeskalowanyDEL = map(wartoscDEL, 0, 1024, 1, 29);
    while (wirtualnyDEL > przeskalowanyDEL) {
      wcisnijMinus(38);
      wirtualnyDEL--;
    }
    while (wirtualnyDEL < przeskalowanyDEL) {
      wcisnijPlus(38);
      wirtualnyDEL++;
    }
  }

  zmianaTIM = analogRead(potencjometrTIM);
  if (abs(wartoscTIM - zmianaTIM) > 7) {
    wartoscTIM = zmianaTIM;
    skokMenu = tablicaTIM[numerPotencjometru];
    przewin();
    numerPotencjometru = 4;
    byte przeskalowanyTIM = map(wartoscTIM, 0, 1024, 1, 102);
    while (wirtualnyTIM > przeskalowanyTIM) {
      wcisnijMinus(32);
      wirtualnyTIM--;
    }
    while (wirtualnyTIM < przeskalowanyTIM) {
      wcisnijPlus(32);
      wirtualnyTIM++;
    }
  }

  zmianaREV = analogRead(potencjometrREV);
  if (abs(wartoscREV - zmianaREV) > 7) {
    wartoscREV = zmianaREV;
    skokMenu = tablicaREV[numerPotencjometru];
    przewin();
    numerPotencjometru = 5;
    byte przeskalowanyREV = map(wartoscREV, 0, 1024, 1, 56);
    while (wirtualnyREV > przeskalowanyREV) {
      wcisnijMinus(35);
      wirtualnyREV--;
    }
    while (wirtualnyREV < przeskalowanyREV) {
      wcisnijPlus(35);
      wirtualnyREV++;
    }
  }
}
void wcisnijPlus(byte y) {  // Procedura wciskania przycisku Plus.
  digitalWrite(przyciskPlus, HIGH);
  digitalWrite(led, HIGH);  // Włącz diodę.
  delay(y);
  digitalWrite(przyciskPlus, LOW);
  delay(y);
}
void wcisnijMinus(byte y) {  // Procedura wciskania przycisku Minus.
  digitalWrite(przyciskMinus, HIGH);
  digitalWrite(led, HIGH);
  delay(y);
  digitalWrite(przyciskMinus, LOW);
  delay(y);
}
void przewin() {  // Procedura wciskania przycisku Menu.
  for (byte x = 0; x < skokMenu; x++) {
    digitalWrite(przyciskMenu, HIGH);
    digitalWrite(led, HIGH);
    delay(86);
    digitalWrite(przyciskMenu, LOW);
    delay(86);
  }
}

Powiązane tematy

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2Sprawdź tutaj

Przeczytaj również

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2025 arduino.pl