🎵 Lekcja 12: Brzęczyk — sygnały i proste dźwięki

Generowanie tonu, częstotliwość, czas trwania, proste melodie. W tej lekcji uczysz się sterować brzęczykiem jak „głośnikiem 1-bitowym”.

1) Cel lekcji

W systemach embedded dźwięk jest często najprostszym „interfejsem użytkownika”: krótkie piknięcie potwierdza akcję, alarm informuje o błędzie, a melodia sygnalizuje start urządzenia.

  • generowanie sygnału prostokątnego na pinie,
  • zależność: częstotliwość ↔ wysokość dźwięku,
  • czas trwania tonu, pauzy, sekwencje sygnałów,
  • pierwsze „melodie” (tablica nut + czasy).
Po tej lekcji zrobisz: sygnały (beep), alarm oraz prostą melodię startową.

2) Jaki brzęczyk? Aktywny vs pasywny (ważne)

Są dwa popularne typy brzęczyków:

  • Aktywny (active buzzer) — wydaje dźwięk po podaniu stałego stanu (np. 5V), bo ma generator w środku.
  • Pasywny (passive buzzer / piezo) — wymaga przebiegu (np. 1–5 kHz), czyli musisz generować częstotliwość w programie.
Ta lekcja zakłada brzęczyk pasywny (piezo).
Jeśli masz aktywny — większość przykładów nadal zadziała jako „piknięcie”, ale melodie nie będą poprawne.

3) Podłączenie brzęczyka (prosty wariant)

Założenie: używamy pinu PD6 jako wyjścia na brzęczyk.

  • PD6 → brzęczyk (+)
  • GND → brzęczyk (−)
Bezpieczeństwo prądowe:
Piezo zwykle pobiera mało prądu, ale jeśli masz większy buzzer (elektromagnetyczny), rozważ tranzystor. Na kursie zaczynamy prosto, ale pamiętamy o ograniczeniach GPIO.

4) Zasada działania: sygnał prostokątny

Żeby uzyskać dźwięk, przełączasz pin HIGH/LOW z określoną częstotliwością. Dla częstotliwości f okres wynosi:

Okres: T = 1 / f
Połówka okresu: T/2 — tyle czekasz między przełączeniami (toggle).

Przykład: 1000 Hz (1 kHz) → okres 1 ms → połówka 0.5 ms = 500 µs.

5) Pierwszy dźwięk: 1 kHz przez 200 ms

#include <avr/io.h>
#include <util/delay.h>

#define BUZ_DDR   DDRD
#define BUZ_PORT  PORTD
#define BUZ_PIN   PD6

static void tone_1khz_200ms(void)
{
    // 1 kHz = 1000 cykli na sekundę
    // 200 ms = 0.2 s, więc ok. 200 cykli
    for (uint16_t i = 0; i < 200; i++)
    {
        BUZ_PORT ^= (1 << BUZ_PIN);
        _delay_us(500); // półokres 1 kHz
        BUZ_PORT ^= (1 << BUZ_PIN);
        _delay_us(500);
    }
}

int main(void)
{
    BUZ_DDR |= (1 << BUZ_PIN);

    while (1)
    {
        tone_1khz_200ms();
        _delay_ms(800);
    }
}
Check: powinieneś słyszeć krótkie „pik” co ~1 sekundę.

6) Funkcja uniwersalna: tone(frequency, duration)

Teraz robimy porządny fundament: jedna funkcja generuje dowolną częstotliwość i czas trwania. Parametry:

  • freq_hz — częstotliwość tonu (np. 440, 1000, 2000),
  • dur_ms — czas trwania w ms.
static void tone(uint16_t freq_hz, uint16_t dur_ms)
{
    // półokres w mikrosekundach: (1/f)/2
    // 1 sek = 1 000 000 us
    uint32_t half_period_us = 1000000UL / (2UL * freq_hz);

    // ile przełączeń potrzebujesz?
    // w czasie dur_ms: f * dur_ms/1000 cykli
    uint32_t cycles = (uint32_t)freq_hz * (uint32_t)dur_ms / 1000UL;

    for (uint32_t i = 0; i < cycles; i++)
    {
        BUZ_PORT ^= (1 << BUZ_PIN);
        _delay_us(half_period_us);
        BUZ_PORT ^= (1 << BUZ_PIN);
        _delay_us(half_period_us);
    }
}
Uwaga praktyczna:
_delay_us() ma ograniczenia (zależy od F_CPU). Dla edukacji OK. W profesjonalnych projektach do dźwięku używa się timerów/PWM.

7) Sygnały użytkowe: klik, OK, błąd, alarm

W realnych urządzeniach nie gra się melodii cały czas — częściej są krótkie sygnały. Oto gotowe „pakiety”:

static void beep_click(void)
{
    tone(1500, 30);
}

static void beep_ok(void)
{
    tone(1200, 80);
    _delay_ms(60);
    tone(1600, 80);
}

static void beep_error(void)
{
    for (uint8_t i = 0; i < 3; i++)
    {
        tone(400, 120);
        _delay_ms(80);
    }
}

static void beep_alarm(void)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        tone(900, 60);
        _delay_ms(40);
        tone(700, 60);
        _delay_ms(40);
    }
}
Tip dydaktyczny:
Takie funkcje można wykorzystać jako „feedback” do przycisków z lekcji 10–11.

8) Podstawy melodii: tablica nut i czasów

Najprostsza melodia to dwie tablice: częstotliwości i czas trwania.

Przykładowe częstotliwości (przybliżone)

  • C4 ≈ 262 Hz
  • D4 ≈ 294 Hz
  • E4 ≈ 330 Hz
  • F4 ≈ 349 Hz
  • G4 ≈ 392 Hz
  • A4 ≈ 440 Hz
  • B4 ≈ 494 Hz
  • C5 ≈ 523 Hz
Nie musisz znać nut.
Traktuj to jak „częstotliwości” i „czasy”.

Melodia startowa (bardzo prosta)

static void melody_startup(void)
{
    uint16_t notes[] = { 523, 659, 784, 1047 };   // C5, E5, G5, C6
    uint16_t dur[]   = { 120, 120, 120, 220 };

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

9) Pełny program lekcji (buzzer + testy)

#include <avr/io.h>
#include <util/delay.h>

#define BUZ_DDR   DDRD
#define BUZ_PORT  PORTD
#define BUZ_PIN   PD6

static void tone(uint16_t freq_hz, uint16_t dur_ms)
{
    uint32_t half_period_us = 1000000UL / (2UL * freq_hz);
    uint32_t cycles = (uint32_t)freq_hz * (uint32_t)dur_ms / 1000UL;

    for (uint32_t i = 0; i < cycles; i++)
    {
        BUZ_PORT ^= (1 << BUZ_PIN);
        _delay_us(half_period_us);
        BUZ_PORT ^= (1 << BUZ_PIN);
        _delay_us(half_period_us);
    }
}

static void beep_click(void) { tone(1500, 30); }

static void beep_ok(void)
{
    tone(1200, 80); _delay_ms(60);
    tone(1600, 80);
}

static void beep_error(void)
{
    for (uint8_t i = 0; i < 3; i++) { tone(400, 120); _delay_ms(80); }
}

static void melody_startup(void)
{
    uint16_t notes[] = { 523, 659, 784, 1047 };
    uint16_t dur[]   = { 120, 120, 120, 220 };

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

int main(void)
{
    BUZ_DDR |= (1 << BUZ_PIN);

    while (1)
    {
        beep_click();  _delay_ms(400);
        beep_ok();     _delay_ms(700);
        beep_error();  _delay_ms(900);
        melody_startup(); _delay_ms(1200);
    }
}
Check:
Usłyszysz kolejno: krótki klik → sygnał OK → 3 niskie „error” → melodia startowa.

10) Zadania obowiązkowe

  1. Zrób sygnał „podwójny klik”: dwa krótkie piknięcia 1.5 kHz.
  2. Zmień melodię startową na 6 nut (Twoja sekwencja).
  3. Zrób alarm: 10 razy 800 Hz (80 ms) + przerwa 40 ms.

11) Zadania dodatkowe (dla lepszych)

  1. Połącz z przyciskiem: klik przycisku uruchamia beep_click().
  2. Długie przytrzymanie uruchamia beep_alarm() (z lekcji 11 logika czasu).
  3. Zrób „piano”: 4 przyciski → 4 częstotliwości (C4/D4/E4/F4).

12) Zadania PRO (projektowe)

  1. Zrób „menu dźwięków”: tryby (klik przełącza sygnał, LED pokazuje tryb).
  2. Zapisz melodię w dwóch tablicach: nuty i pauzy (odtwarzaj z przerwami).
  3. Dodaj funkcję tone_sweep(): płynne przejście częstotliwości (np. 400 → 1200 Hz).
Zapowiedź kolejnego kroku:
Prawdziwe, stabilne generowanie tonu robi się timerem (PWM). Zrobimy to, gdy wejdziesz w timery.