[063] PWM i piski

[063] PWM i piski

Po pierwszych próbach wydobywania dźwięków spójrzmy na problem z zupełnie innej strony. Arduino posiada wbudowane generatory przebiegów o zmiennym wypełnieniu (PWM). Mają one tę zaletę, że po załadowaniu danych grają sobie bez końca i możemy w tym czasie robić cokolwiek. Mają także wadę: ściśle określoną częstotliwość pracy, choć można ją wybrać z ubogiej puli. Domyślnie wynosi ona 490 Hz i to jest dobra częstotliwość na pikania, ale zawsze taka sama. Jednak fakt nieangażowania głównej pętli jest na tyle kuszący, że w określonych przypadkach można z tego korzystać.


const byte pstryczek = 5;  // Adres pstryczka włączającego piszczenie.
const byte piszczek = 11;  // Adres portu, do którego podłączony jest piszczek.

void setup() {
  pinMode(pstryczek, INPUT_PULLUP);  // Deklaruj linię pstryczka jako wejście podciągnięte wewnętrznie do wysokiego stanu.
  pinMode(piszczek, OUTPUT);         // Deklaruj linię piszczka jako wyjście.
}

void loop() {
  if (digitalRead(pstryczek) == HIGH) {  // Jeśli przycisk został wciśnięty...
    analogWrite(piszczek, 127);          // Włącz generator PWM.
                                         // Reszta kodu.
  } else {                               // Jeśli zaś puszczony...
    analogWrite(piszczek, 0);            // Wyłącz generator PWM.
  }
}

Użycie tych generatorów daje nam dostęp do zmiany wypełnienia, co będzie mieć wpływ na brzmienie, łagodniejsze tym mocniej, im będziemy mocniej oddalać się od środkowej wartości, czyli 127. Wartość zero wyłącza generator.

Dostęp do dowolnej częstotliwości w tej idei jest możliwy po użyciu przerwań, ale coś takiego zostało zaimplementowane w samym systemie, w postaci funkcji tone i ta metoda będzie dla nas bardzo dobra.

const byte pstryczek = 5;  // Adres pstryczka włączającego piszczenie.
const byte piszczek = 11;  // Adres portu, do którego podłączony jest piszczek.

const int wysokosc = 1000;  // Wysokość generowanego tonu w Hz

void setup() {
  pinMode(pstryczek, INPUT_PULLUP);  // Deklaruj linię pstryczka jako wejście podciągnięte wewnętrznie do wysokiego stanu.
  pinMode(piszczek, OUTPUT);         // Deklaruj linię piszczka jako wyjście.
}

void loop() {
  if (digitalRead(pstryczek) == HIGH) {  // Jeśli przycisk został wciśnięty...
    tone(piszczek, wysokosc);            // Generuj ton.
                                         // Reszta kodu.
  } else {                               // Jeśli zaś puszczony...
    noTone(piszczek);                    // Przestań generować ton.
  }
}

Tym razem wysokość ma konkretne odniesienie do jednostek i ustala się ją w hercach. Cała instrukcja generacji wygląda tak: określamy port wyjściowy (piszczek, czyli 11) i wysokość tonu w hercach (wysokosc). Można także wstawić trzeci parametr – długość trwania dźwięku. U mnie dźwięk trwa tak długo, jak długo trzymany jest przycisk. Aby zakończyć generację, należy użyć funkcji noTone. Jedynym jej argumentem jest adres portu (piszczek).

Skoro poznaliśmy już cztery metody piszczenia, zróbmy sobie syntezator. Będzie bardzo skromny i obsłuży tylko pięć tonów, bo tyle mam przycisków na płytce edukacyjnej TME. Ale nic nie szkodzi, by sobie urządzenie rozbudować, choć czy warto – nie sądzę, opcji brzmieniowych tutaj nie ma żadnych i po jakimś czasie zabawa stanie się nużąca.

const byte g1 = 5;  // Adres pstryczka włączającego dźwięk g1
const byte c2 = 4;  // c2
const byte e2 = 8;  // e2
const byte g2 = 7;  // g2
const byte c3 = 6;  // c3

const byte piszczek = 11;  // Adres portu, do którego podłączony jest piszczek.

Zatem deklarujemy nazwy dla pięciu tym razem przycisków, zgodne z zapisem muzycznym.

const unsigned int g1Wysokosc = 392;   // Wysokość tonu g1
const unsigned int c2Wysokosc = 523;   // c2
const unsigned int e2Wysokosc = 659;   // e2
const unsigned int g2Wysokosc = 784;   // g2
const unsigned int c3Wysokosc = 1046;  // c3

Deklarujemy także wartości wysokości dźwięku dla każdego z przycisku. Są to wartości w hercach oczywiście, wyciągnięte z tabeli częstotliwości klawiatury muzycznej. System zaokrągla je do jednego herca, co sprawi nam lekkie fałsze, ale usłyszą je tylko ludzie mający słuch muzyczny.

void setup() {
  pinMode(g1, INPUT_PULLUP);  // Deklaruj linie pstryczków jako wejścia podciągnięte wewnętrznie do wysokiego stanu.
  pinMode(c2, INPUT_PULLUP);
  pinMode(e2, INPUT_PULLUP);
  pinMode(g2, INPUT_PULLUP);
  pinMode(c3, INPUT_PULLUP);

  pinMode(piszczek, OUTPUT);  // Deklaruj linię piszczka jako wyjście.
}
void loop() {
  while (digitalRead(g1) == HIGH) {  // Jeśli przycisk został wciśnięty...
    tone(piszczek, g1Wysokosc);      // Generuj ton g1
  }
  while (digitalRead(c2) == HIGH) {  // c2
    tone(piszczek, c2Wysokosc);
  }
  while (digitalRead(e2) == HIGH) {  // e2
    tone(piszczek, e2Wysokosc);
  }
  while (digitalRead(g2) == HIGH) {  // g2
    tone(piszczek, g2Wysokosc);
  }
  while (digitalRead(c3) == HIGH) {  // c3
    tone(piszczek, c3Wysokosc);
  }
  noTone(piszczek);  // Przestań generować ton.
}

W głównej pętli tym razem rozbudujemy poprzedni szkic do obsługi pięciu przycisków. Jeśli któryś z nich będzie wciśnięty, usłyszymy dźwięki o częstotliwości zadeklarowanej na wstępnie. Po skompilowaniu możemy zagrać na przykład krakowski hejnał, bo tyle właśnie i takich dźwięków potrzeba, by go odtworzyć. Tak na marginesie, jest to rozłożony na części akord durowy. Skoro możemy już grać melodie, czas na samograja.

const byte piszczek = 11;  // Adres portu, do którego podłączony jest piszczek.

const int n55 = 392;    // Wysokość tonu 55 (g1)
const int n60 = 523;    // 60 (c2)
const int n64 = 659;    // 64 (e2)
const int n67 = 784;    // 67 (g2)
const int n72 = 1046;   // 72 (c3)
const int off = 65535;  // cisza

const int tablica[] = {  // Tablica nut: wysokość w zapisie MIDI, czas trwania w długościach "szesnastki".
  n60, 1,
  off, 3,
  n64, 1,
  n67, 1,
  off, 1,
  n72, 1,
  off, 8,

  n67, 1,
  off, 3,
  n64, 1,
  n60, 1,
  off, 1,
  n67, 1,
  off, 1,
  n67, 1,
  off, 1,
  n67, 1,
  n67, 1,
  off, 3,

  n64, 1,
  off, 3,
  n67, 1,
  n64, 1,
  n60, 1,
  n55, 1,
  off, 4,
  n60, 1,
  n64, 1,
  off, 1,
  n67, 1,
  off, 4,
  n64, 1,
  n60, 1,
  off, 1,
  n55, 1,
  off, 8,

  n60, 2,
  off, 2,
  n64, 1,
  n60, 1,
  n64, 1,
  n67, 2,
  off, 3,
  n64, 1,
  n60, 2,
  n67, 2,
  n67, 1,
  off, 1,
  n67, 1,
  n67, 3,
  off, 9,

  n64, 1,
  off, 7,
  n67, 1
};

byte licznik;  // Licznik bieżących danych odczytywanych z tablicy.

void setup() {
  pinMode(piszczek, OUTPUT);  // Deklaruj linię piszczka jako wyjście.

  for (licznik = 0; licznik < 108; licznik = licznik + 2) {  // pętla, w której będą pobierane wysokości dźwięków i czasy ich trwania.
    tone(piszczek, tablica[licznik]);                        // Pobierz wysokość dźwięku.
    delay(150 * tablica[licznik + 1]);                       // Generuj dźwięk przez odpowiedni czas.
    noTone(piszczek);                                        // Wycisz dźwięk.
    delay(10);                                               // Wprowadź krótkie przerwy, by dźwięki nie zlewały się ze sobą.
  }
}
void loop() {
}

Tym razem nie będzie żadnych przycisków, gdyż melodia zostanie zapisana w tablicy. Ale użyłem tutaj innego sposobu zapisywania dźwięków, nawiązującego do systemu MIDI, w którym tony klawiatury są kodowane cyframi, przy czym środkowe C ma numer 60. To jest po prostu standard w muzyce związanej z komputerami i tego będę się trzymał. N – na początku nazw częstotliwości to skrót od Note On, komunikatu midi włączającego generowanie dźwięków. Off natomiast to skrót od Note Off, czyli komunikatu wyłączającego je.

Zrobiłem tutaj jednak małe oszustwo. Cisza nie będzie ciszą do końca, ale tonem o wysokości sięgającej daleko w ultradźwięki (65535). Tak jest prościej, a działa jak trzeba. Drugi argument w każdym wierszu to czas trwania nuty albo ciszy. Jest on kodowany w tak zwanych szesnastkach, jedynka to jedna szesnasta taktu i tak dalej; im wyższa wartość, tym dłużej gra dźwięk albo dłużej trwa cisza. Tablicę zaprojektowałem sobie „w głowie”, używając motywu przed chwilą prezentowanego, ale w formie nieco funkowej, bo oryginalna jest straszna ;)

Jak to działa? Pojawi się tutaj zmienna licznik, która w jednorazowej pętli będzie się zwiększać o dwa za każdym razem, pobierając z tablicy dane. Indeks nieparzysty wyłuska wysokość dźwięku, a parzysty – czas jego grania. On steruje nam pętlą opóźniającą. Na końcu wyłączamy dźwięk i dodajemy jeszcze chwilkę ciszy. Umieściłem tę krótką pauzę po to, by otrzymać charakterystyczne staccato, czyli dźwięki oddzielone od siebie, a nie zlewające się. Niektórzy mówią: stukato i mniej więcej o to chodzi. Wartość w pętli delay (150 i 10) można modyfikować, otrzymując szybsze bądź wolniejsze granie.

No i na koniec jedna uwaga: cały program siedzi w pętli setup, ponieważ pozytywka ma zagrać raz, po resecie i koniec. Wkrótce poznamy ambitniejsze metody na wydobywanie dźwięków, w tym – całkiem realnych.

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