Стандартная tone() использует таймер 2 и дает неплохой звук, но у нее есть ограничения: нельзя играть больше одной ноты и сложно менять громкость.
Хак: Используем Timer1 в режиме CTC (Clear Timer on Compare Match). Это дает нам:
- Генерацию прямоугольного сигнала с частотой до 4 МГц.
- Возможность менять скважность (громкость) через ШИМ.
- Независимость от других таймеров (таймер 0 можно оставить для
millis()).
Код для генерации звука на пине 9 (OC1A):
void setupSound() {
// Настраиваем пин 9 как выход
DDRB |= (1 << PB1); // PB1 = пин 9
// Настройка Timer1 в режим CTC
TCCR1A = 0;
TCCR1B = 0;
// Устанавливаем частоту: 440 Гц (нота Ля)
// Частота = F_CPU / (2 * прескалер * (OCR1A + 1))
// Для 16 МГц и прескалера 64: OCR1A = 16000000/(2*64*440) - 1 = 283
OCR1A = 283;
// Включаем режим CTC (WGM12 = 1)
TCCR1B |= (1 << WGM12);
// Включаем прескалер 64 (CS10 и CS11 = 1)
TCCR1B |= (1 << CS10) | (1 << CS11);
// Включаем выход на пине OC1A (сравнение сбрасывает пин в 0)
TCCR1A |= (1 << COM1A0);
}
// Изменяем частоту (ноту) в любой момент
void setFrequency(uint16_t frequency) {
if (frequency == 0) {
TCCR1A &= ~(1 << COM1A0); // Выключаем звук
return;
}
OCR1A = (F_CPU / (2 * 64 * frequency)) - 1;
TCCR1A |= (1 << COM1A0); // Включаем звук
}
void loop() {
// Играем гамму!
uint16_t notes[] = {262, 294, 330, 349, 392, 440, 494, 523}; // До-Ре-Ми...
for (int i = 0; i < 8; i++) {
setFrequency(notes[i]);
delay(300);
}
setFrequency(0);
delay(1000);
}
Фишка: Если подключить к пину 9 динамик через конденсатор 100 мкФ, получите чистый звук без щелчков, который можно использовать как сирену для сигнализации или простой синтезатор.