🔊 Lekcja 15: PWM — sterowanie brzęczykiem

Dźwięk na PWM, zmiana częstotliwości i wypełnienia. To lekcja, w której generowanie dźwięku robisz sprzętowo, a nie pętlą opóźnień.

1) Cel lekcji

W lekcji 12 generowałeś dźwięk „ręcznie”, przełączając pin w pętli. Działało, ale blokowało CPU i było mało stabilne.

Teraz robimy to profesjonalnie:

  • generator dźwięku oparty o PWM i timer,
  • częstotliwość tonu = częstotliwość PWM,
  • wypełnienie = głośność (w uproszczeniu),
  • CPU wolne do obsługi logiki, przycisków i trybów.
Po tej lekcji: generujesz dźwięk o stabilnej częstotliwości, bez _delay_ms().

2) PWM jako generator dźwięku

Brzęczyk piezo reaguje na zmianę napięcia. Jeśli na wyjściu PWM pojawia się sygnał o częstotliwości np. 1 kHz, to właśnie tę częstotliwość słyszysz jako dźwięk.

Kluczowe różnice:
  • LED → PWM ≈ jasność (wypełnienie),
  • buzzer → PWM ≈ częstotliwość (wysokość tonu).

3) Który timer i pin?

Użyjemy Timer1 (16-bit), bo:

  • daje dużą precyzję częstotliwości,
  • łatwo zmieniać „TOP” (ICR1),
  • idealny do audio/PWM.

Wyjście PWM:

  • PB1 — OC1A → brzęczyk (+)
  • GND → brzęczyk (−)

4) Wzór na częstotliwość PWM (Timer1)

Fast PWM, TOP = ICR1:
f = F_CPU / (preskaler × (1 + ICR1))

Przykład (F_CPU = 8 MHz, preskaler = 8):

  • ICR1 = 999 → f ≈ 1000 Hz
  • ICR1 = 499 → f ≈ 2000 Hz

5) Konfiguracja Timer1 — PWM audio

#include <avr/io.h>

#define BUZ_DDR   DDRB
#define BUZ_PIN   PB1   // OC1A

static void pwm_audio_init(void)
{
    // OC1A jako wyjście
    BUZ_DDR |= (1 << BUZ_PIN);

    // Fast PWM, TOP = ICR1 (WGM13:0 = 14)
    TCCR1A = (1 << COM1A1) | (1 << WGM11);
    TCCR1B = (1 << WGM13) | (1 << WGM12);

    // Preskaler = 8
    TCCR1B |= (1 << CS11);

    // startowo brak dźwięku
    ICR1 = 0;
    OCR1A = 0;
}

int main(void)
{
    pwm_audio_init();

    while (1)
    {
        // dźwięk generuje hardware
    }
}

6) Funkcja: ustaw ton (częstotliwość)

Tworzymy prostą funkcję do ustawiania tonu:

static void buzzer_set_freq(uint16_t freq_hz)
{
    if (freq_hz == 0)
    {
        OCR1A = 0; // cisza
        return;
    }

    // ICR1 = (F_CPU / (preskaler * freq)) - 1
    uint32_t top = (8000000UL / (8UL * freq_hz)) - 1;

    ICR1 = (uint16_t)top;
    OCR1A = ICR1 / 2; // 50% wypełnienia
}
50% wypełnienia daje najczystszy dźwięk dla piezo.

7) Test: kilka tonów

#include <util/delay.h>

int main(void)
{
    pwm_audio_init();

    while (1)
    {
        buzzer_set_freq(440);  _delay_ms(500); // A4
        buzzer_set_freq(660);  _delay_ms(500);
        buzzer_set_freq(880);  _delay_ms(500);
        buzzer_set_freq(0);    _delay_ms(700); // cisza
    }
}
Check: słyszysz czyste, stabilne tony.

8) PWM a głośność (wypełnienie)

Możesz zmieniać „głośność” przez OCR1A:

// cicho
OCR1A = ICR1 / 6;

// średnio
OCR1A = ICR1 / 2;

// głośno
OCR1A = (ICR1 * 3) / 4;
Uwaga:
Piezo reaguje głównie na częstotliwość. Regulacja głośności przez wypełnienie działa, ale w ograniczonym zakresie.

9) Mini-melodia na PWM (sprzętowo)

static void melody_pwm(void)
{
    uint16_t notes[] = { 523, 659, 784, 1047 };
    uint16_t dur[]   = { 300, 300, 300, 500 };

    for (uint8_t i = 0; i < 4; i++)
    {
        buzzer_set_freq(notes[i]);
        _delay_ms(dur[i]);
    }
    buzzer_set_freq(0);
}

10) Połączenie z przyciskiem (praktyka)

Klik przycisku zmienia ton (np. 4 poziomy).

uint16_t tones[] = { 440, 554, 659, 880 };
uint8_t idx = 0;

if (button_click())
{
    idx++;
    if (idx >= 4) idx = 0;
    buzzer_set_freq(tones[idx]);
}

11) Zadania obowiązkowe

  1. Uruchom PWM audio na Timer1 i wygeneruj ton 1 kHz.
  2. Zmień częstotliwość przyciskiem (min. 4 tony).
  3. Zrób sygnał: krótki beep po kliknięciu.

12) Zadania dodatkowe

  1. Zrób skalę C-dur (8 nut).
  2. Zrób alarm: naprzemiennie 700/900 Hz.
  3. Zrób regulację „głośności” przez OCR1A.

13) Zadania PRO (projektowe)

  1. Połącz z PWM LED: jasność LED zależna od tonu.
  2. Zrób „menu dźwięków” (maszyna stanów z lekcji 11).
  3. Zrób generator dźwięku bez _delay_ms() (scheduler z lekcji 13).
Zapowiedź:
Następny naturalny krok to przerwania + PWM + scheduler — fundament prawdziwego firmware.