ELECTRONICS

Мелодия на зуммере: функция tone, ноты и длительности

Зуммер умеет не только пищать. Если он пассивный, частоту звука задаёт плата — а значит, из него можно извлечь любую ноту и сыграть мелодию. Всё, что для этого нужно, уже встроено в Arduino: функции tone и noTone, никаких библиотек.

Сразу важная оговорка: на активном зуммере мелодия не получится никогда. У него внутри собственный генератор с одной фиксированной частотой, и tone на неё не влияет — он будет пищать одинаково на любой ноте. Как отличить один от другого, написано на странице про подключение зуммера.

Как работает tone

Функция принимает номер вывода и частоту в герцах: tone(8, 440) — это нота ля. Плата начинает дёргать вывод с нужной частотой в фоне и сразу возвращает управление программе: звук идёт сам по себе, пока его не остановят.

Остановить можно двумя способами: вызвать noTone(8) или заранее указать третьим аргументом длительность — tone(8, 440, 500) отыграет полсекунды и замолчит сам.

const int PIN_BUZZER = 8;

void setup() {
  pinMode(PIN_BUZZER, OUTPUT);
}

void loop() {
  tone(PIN_BUZZER, 440);   // ля первой октавы
  delay(500);

  noTone(PIN_BUZZER);      // тишина
  delay(500);
}

Частоты нот

Ноты — это просто частоты. Вот первая октава, с неё начинают почти все мелодии; каждая следующая октава — умножение частоты на два.

нотаобозначениечастота, Гц
доC4262
реD4294
миE4330
фаF4349
сольG4392
ляA4440
сиB4494
до второй октавыC5523

В чужих примерах эти же числа часто лежат в отдельном файле pitches.h с именами вроде NOTE_C4. Это то же самое: константы с частотами, ничего волшебного в файле нет.

Мелодия из массива нот

Мелодия — два массива одинаковой длины: в первом ноты, во втором длительности. Дальше цикл проходит по ним и отдаёт каждую ноту в tone.

const int PIN_BUZZER = 8;

// ноты первой октавы, частоты в герцах
const int DO = 262, RE = 294, MI = 330, FA = 349, SOL = 392;

// сама мелодия и длительность каждой ноты в миллисекундах
int melody[]    = { MI, RE, DO, RE, MI, MI, MI };
int durations[] = { 300, 300, 300, 300, 300, 300, 600 };

void setup() {
  pinMode(PIN_BUZZER, OUTPUT);
}

void loop() {
  for (int i = 0; i < 7; i++) {
    tone(PIN_BUZZER, melody[i], durations[i]);

    // пауза чуть длиннее самой ноты, иначе ноты сольются:
    // tone не задерживает программу, и следующая начнётся поверх предыдущей
    delay(durations[i] * 1.3);
  }

  noTone(PIN_BUZZER);
  delay(2000);
}

Скетч собирается arduino-cli под Arduino UNO. Коэффициент 1,3 в паузе — не магия: это зазор между нотами, без него мелодия звучит слитно.

Почему ноты сливаются в один непрерывный писк

Самая частая ошибка новичка. tone не задерживает программу: вызвали — звук пошёл, а выполнение сразу идёт дальше. Если в цикле нет паузы или она короче самой ноты, следующая нота начинается поверх предыдущей, и мелодия превращается в один писк. Пауза должна быть чуть длиннее длительности ноты.

Пауза внутри мелодии

Ноль в массиве удобно держать как метку паузы, но передавать его в toneнельзя — нулевой частоты не бывает. Ноль обрабатывают сами: видим его — вместо ноты вызываем noTone и просто выжидаем нужное время.

// пауза внутри мелодии — нота с частотой 0 не годится,
// нужен просто delay без tone
int melody[]    = { MI, RE, 0, DO, RE };
int durations[] = { 300, 300, 200, 300, 300 };

for (int i = 0; i < 5; i++) {
  if (melody[i] == 0) {
    noTone(PIN_BUZZER);          // пауза
  } else {
    tone(PIN_BUZZER, melody[i], durations[i]);
  }
  delay(durations[i] * 1.3);
}

После tone перестал работать analogWrite на пинах 3 и 11

Функция забирает себе аппаратный таймер, который на Arduino UNO отвечает ещё и за широтно-импульсную модуляцию на третьем и одиннадцатом выводах. Пока звучит нота, плавно менять яркость на этих двух выводах не получится. Если в проекте нужны и звук, и плавный свет, перенесите светодиод на пятый, шестой или девятый вывод.

Сирена: плавно меняем частоту

Мелодия — не единственное, что умеет пассивный зуммер. Если менять частоту не ступенями, а в цикле с маленьким шагом, получается сирена: звук плавно ползёт вверх и обратно вниз. Это самый заметный сигнал тревоги, который можно сделать без единой дополнительной детали.

const int PIN_BUZZER = 8;

void setup() {
  pinMode(PIN_BUZZER, OUTPUT);
}

void loop() {
  // вверх: от 400 до 1200 Гц шагом по 10
  for (int f = 400; f <= 1200; f += 10) {
    tone(PIN_BUZZER, f);
    delay(5);
  }

  // и обратно вниз
  for (int f = 1200; f >= 400; f -= 10) {
    tone(PIN_BUZZER, f);
    delay(5);
  }
}

Шаг и задержка задают характер: шаг побольше и задержка поменьше — резкая «полицейская» сирена, мелкий шаг и delay(15) — тягучий вой.

Высота звука с потенциометра

Ещё один способ понять, что tone принимает любое число: скормить ему показания потенциометра. Получается инструмент вроде терменвокса — крутите ручку, и звук едет за ней.

const int PIN_BUZZER = 8;
const int PIN_POT = A0;

void setup() {
  pinMode(PIN_BUZZER, OUTPUT);
}

void loop() {
  int raw = analogRead(PIN_POT);          // 0…1023 с потенциометра
  int freq = map(raw, 0, 1023, 200, 2000); // в частоту 200…2000 Гц

  tone(PIN_BUZZER, freq);
  delay(20);
}

Функция map переводит диапазон потенциометра (от нуля до 1023) в диапазон частот. Подробнее про сам потенциометр — на его странице.

Файл pitches.h: откуда его берут

В чужих примерах ноты почти всегда подключают из файла pitches.h, а сам файл при этом никто не показывает — и новичок застревает на ошибке No such file or directory. Скачивать его неоткуда: это просто список констант, который каждый носит с собой. Вот он — создайте в скетче вкладку с таким именем и вставьте.

// первая октава
#define NOTE_C4  262
#define NOTE_CS4 277
#define NOTE_D4  294
#define NOTE_DS4 311
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_FS4 370
#define NOTE_G4  392
#define NOTE_GS4 415
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_B4  494

// вторая октава — те же ноты, частоты вдвое выше
#define NOTE_C5  523
#define NOTE_D5  587
#define NOTE_E5  659
#define NOTE_F5  698
#define NOTE_G5  784
#define NOTE_A5  880
#define NOTE_B5  988

После этого в мелодии можно писать NOTE_E4 вместо числа 330 — читается гораздо понятнее. Диез обозначается буквой S: NOTE_CS4 — это до-диез.

Чего tone не умеет

Одновременно звучит только одна нота: второй вызов tone на другом выводе просто перебьёт первый. Аккорд или двухголосие встроенными средствами не сыграть — для этого берут отдельные библиотеки, которые формируют сигнал иначе.

И ещё: пьезоизлучатель — не динамик. Он честно воспроизводит частоту, но тембра у него нет, поэтому любая мелодия звучит как писк из старой игрушки. Это не ошибка схемы, а физика: для приличного звука нужен динамик с усилителем.

Что дальше

Мелодия — частный случай работы с массивами: если цикл в примере выше кажется непонятным, начните со страницы про программирование.