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).
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.
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 (−)
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:
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);
}
}
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);
}
}
_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);
}
}
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
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);
}
}
Usłyszysz kolejno: krótki klik → sygnał OK → 3 niskie „error” → melodia startowa.
10) Zadania obowiązkowe
- Zrób sygnał „podwójny klik”: dwa krótkie piknięcia 1.5 kHz.
- Zmień melodię startową na 6 nut (Twoja sekwencja).
- Zrób alarm: 10 razy 800 Hz (80 ms) + przerwa 40 ms.
11) Zadania dodatkowe (dla lepszych)
- Połącz z przyciskiem: klik przycisku uruchamia beep_click().
- Długie przytrzymanie uruchamia beep_alarm() (z lekcji 11 logika czasu).
- Zrób „piano”: 4 przyciski → 4 częstotliwości (C4/D4/E4/F4).
12) Zadania PRO (projektowe)
- Zrób „menu dźwięków”: tryby (klik przełącza sygnał, LED pokazuje tryb).
- Zapisz melodię w dwóch tablicach: nuty i pauzy (odtwarzaj z przerwami).
- Dodaj funkcję tone_sweep(): płynne przejście częstotliwości (np. 400 → 1200 Hz).
Prawdziwe, stabilne generowanie tonu robi się timerem (PWM). Zrobimy to, gdy wejdziesz w timery.