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.
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.
- 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)
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
}
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
}
}
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;
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
- Uruchom PWM audio na Timer1 i wygeneruj ton 1 kHz.
- Zmień częstotliwość przyciskiem (min. 4 tony).
- Zrób sygnał: krótki beep po kliknięciu.
12) Zadania dodatkowe
- Zrób skalę C-dur (8 nut).
- Zrób alarm: naprzemiennie 700/900 Hz.
- Zrób regulację „głośności” przez OCR1A.
13) Zadania PRO (projektowe)
- Połącz z PWM LED: jasność LED zależna od tonu.
- Zrób „menu dźwięków” (maszyna stanów z lekcji 11).
- Zrób generator dźwięku bez _delay_ms() (scheduler z lekcji 13).
Następny naturalny krok to przerwania + PWM + scheduler — fundament prawdziwego firmware.