Курс Arduino с нуля · Звук
Урок 20. Пианино из света: играем на фоторезисторах
Пять фоторезисторов превращаются в пять клавиш, к которым не нужно прикасаться. Заодно делаем терменвокс и учим пианино запоминать мелодию.
Что понадобится
- Плата Uno из набора
- Кабель USB
- Макетная плата
- 5 фоторезисторов
- 5 резисторов 470 Ом
- Зуммер
- 8 проводов «папа-папа»
Все детали есть в обучающем наборе, по которому идёт курс.
В наборе пять фоторезисторов — и это не случайность. Сегодня они станут пятью клавишами, к которым не нужно прикасаться: достаточно поднести палец и заслонить свет. Такое пианино есть в брошюре набора, и оно того стоит — эффект почти волшебный.
Урок собирает вместе всё, что мы умеем: делитель напряжения из пятнадцатого урока, массивы из шестого, свои пороги и команду tone из прошлого урока.
Как клавиша без клавиши
Каждая клавиша — это уже знакомая пара: фоторезистор и обычный резистор, соединённые последовательно. Плата смотрит на точку между ними. Светло — число большое; заслонили фоторезистор пальцем — сопротивление выросло, и число упало.
Резистор здесь на 470 Ом, а не на 10 кОм. Так сделано в брошюре набора, и так удобнее: комнатный свет даёт числа побольше, а разница между «светло» и «тень» получается резкой. Если у вас в комнате темно, попробуйте 1 кОм — станет чувствительнее.
Собираем схему
Схема большая, но однообразная: пять одинаковых цепочек плюс зуммер. Собирайте по одной клавише и проверяйте каждую — так вы точно не запутаетесь.
Отключите USB. И заранее решите, где будут стоять клавиши: между ними нужен зазор в палец, иначе тень будет накрывать сразу две.
Проверьте перед включением: у каждой клавиши фоторезистор идёт от шины «+», а резистор 470 Ом — в шину «−»; провод на вход платы уходит из того же столбика, где они встречаются; зуммер стоит «плюсом» к выводу 12.
Шаг 1. Сначала цифры, потом музыка
Не спешите играть. Сперва посмотрим, что вообще видит плата на пяти входах: без этих чисел подобрать порог невозможно.
const int KEYS[5] = {A1, A2, A3, A4, A5};
void setup() {
Serial.begin(9600);
}
void loop() {
// Печатаем пять чисел в строку — по одному на каждую клавишу.
for (int i = 0; i < 5; i++) {
Serial.print(analogRead(KEYS[i]));
Serial.print(" ");
}
Serial.println();
delay(300);
}
Откройте монитор порта. Должно быть пять чисел в строке. Поводите ладонью над клавишами по очереди и запишите два значения: сколько показывает освещённая клавиша и сколько — закрытая пальцем. Числа будут разные для каждой клавиши, и это нормально.
Если все пять чисел почти нулевые — скорее всего, перепутаны местами фоторезистор и резистор. Фоторезистор должен идти от шины «+», а резистор — в шину «−».
Шаг 2. Порог: где кончается «светло»
Клавиша нажата или нет — решает одно число, порог. Всё, что ниже, считаем нажатием.
const int BUZZER = 12;
const int KEY = A1;
const int LEVEL = 400; // ниже этого числа считаем, что клавишу закрыли
void setup() {
pinMode(BUZZER, OUTPUT);
}
void loop() {
if (analogRead(KEY) < LEVEL) {
tone(BUZZER, 262); // нота «до»
} else {
noTone(BUZZER);
}
}
Одна клавиша, одна нота. Поставьте в LEVEL своё число (то самое, что записали на прошлом шаге) и проверьте: подносите палец — звучит «до», убираете — тишина. Если нота не замолкает или, наоборот, не появляется, порог подобран неверно.
Шаг 3. Все пять клавиш
Копировать if пять раз мы не будем — вместо этого возьмём два массива, как в уроке про бегущий огонёк: в одном входы, в другом ноты.
const int BUZZER = 12;
// Клавиши и закреплённые за ними ноты: до, ре, ми, фа, соль.
const int KEYS[5] = {A1, A2, A3, A4, A5};
const int NOTES[5] = {262, 294, 330, 349, 392};
const int LEVEL = 400;
void setup() {
pinMode(BUZZER, OUTPUT);
}
void loop() {
int playing = -1; // номер нажатой клавиши; -1 — ни одной
for (int i = 0; i < 5; i++) {
if (analogRead(KEYS[i]) < LEVEL) {
playing = i;
}
}
if (playing >= 0) {
tone(BUZZER, NOTES[playing]);
} else {
noTone(BUZZER);
}
}
Переменная playing хранит номер нажатой клавиши, а -1 означает «ни одной». Это частый приём: одним числом мы описываем и «что выбрано», и «ничего не выбрано».
Обратите внимание, что цикл не прерывается на первой найденной клавише. Если закрыть сразу две, победит та, что правее: её номер запишется в playing последним. Зуммер всё равно умеет только одну ноту за раз, так что выбрать кого-то придётся.
Шаг 4. Пусть пианино настроит себя само
Одно и то же число-порог для всех пяти клавиш работает плохо: у одной фоторезистор смотрит в окно, у другой — в тень от платы. Да и вечером в комнате всё меняется. Поэтому попросим программу измерить освещённость при включении и подобрать порог каждой клавише отдельно.
const int BUZZER = 12;
const int KEYS[5] = {A1, A2, A3, A4, A5};
const int NOTES[5] = {262, 294, 330, 349, 392};
int level[5]; // свой порог для каждой клавиши
void setup() {
pinMode(BUZZER, OUTPUT);
Serial.begin(9600);
// Замеряем, сколько света на каждой клавише сейчас,
// и берём 70 % от этого числа как порог.
for (int i = 0; i < 5; i++) {
int light = analogRead(KEYS[i]);
level[i] = light * 7 / 10;
Serial.print("клавиша ");
Serial.print(i + 1);
Serial.print(": светло ");
Serial.print(light);
Serial.print(", порог ");
Serial.println(level[i]);
}
}
void loop() {
int playing = -1;
for (int i = 0; i < 5; i++) {
if (analogRead(KEYS[i]) < level[i]) {
playing = i;
}
}
if (playing >= 0) {
tone(BUZZER, NOTES[playing]);
} else {
noTone(BUZZER);
}
}
Приём тот же, что в уроке про датчик света: в setup замеряем «тишину» и считаем от неё. Семьдесят процентов — хорошая отправная точка: тень от пальца роняет число куда сильнее.
При включении не держите руки над пианино. Плата запоминает то, что видит в первую секунду. Если в этот момент закрыть клавишу, её порог получится заниженным, и нота будет звучать не переставая. Лечится кнопкой сброса на плате.
Шаг 5. Терменвокс: играем ладонью
Клавиши — это ступеньки. Но фоторезистор умеет и плавно: чем ближе ладонь, тем ниже число. Свяжем его напрямую с частотой — и получится инструмент, на котором звук «плывёт».
const int BUZZER = 12;
const int SENSOR = A1;
void setup() {
pinMode(BUZZER, OUTPUT);
Serial.begin(9600);
}
void loop() {
int light = analogRead(SENSOR);
// Числа 60 и 600 подставьте свои — те, что увидели в мониторе порта.
int freq = map(light, 60, 600, 200, 1600);
freq = constrain(freq, 200, 1600);
tone(BUZZER, freq);
Serial.print(light);
Serial.print(" -> ");
Serial.print(freq);
Serial.println(" Гц");
delay(40);
}
Числа 60 и 600 в map — это «совсем закрыто» и «полностью открыто» у вашего фоторезистора. Возьмите свои из монитора порта: чем точнее диапазон, тем послушнее инструмент. constrain нужен на случай, если свет окажется ярче ожидаемого — без него частота может уехать за пределы разумного.
Так устроен настоящий терменвокс — инструмент, изобретённый в 1920 году в Петрограде. Только там ладонь меняет не свет, а электрическое поле вокруг антенны. Играть на нём одинаково сложно: звук непрерывный, и попасть в ноту можно только на слух.
Не работает? Разбираемся
- Одна нота звучит не переставая. Порог этой клавиши выше, чем её обычное освещённое значение. Посмотрите числа в мониторе и опустите порог.
- Клавиша не срабатывает совсем. Наоборот: порог слишком низкий, тень до него не дотягивает. Или клавиша светит прямо в лампу — попробуйте прикрыть её ладонью полностью.
- Соседние клавиши срабатывают вместе. Они стоят слишком близко, и тень накрывает обе. Разнесите фоторезисторы шире — в схеме между ними по четыре свободных столбика как раз для этого.
- Играет всегда одна и та же нота. Скорее всего, перепутаны провода на входах: проверьте, что столбик 4 идёт на A1, 9 — на A2 и так далее. Цвета проводов на схеме для этого и разные.
- Вечером всё сбилось. Это нормально: света стало меньше. Используйте программу из четвёртого шага — она подстраивается сама при каждом включении.
Задания для тренировки
- РазминкаПеренастройте пианино на другие ноты — например, на первые пять нот знакомой мелодии. Частоты возьмите из таблицы нот.
- ПосложнееДобавьте «октаву»: если закрыть сразу две крайние клавиши, все ноты становятся вдвое выше. Подсказка: частоту достаточно умножить на 2.
- Со звёздочкой ⭐Научите пианино запоминать сыгранное и повторять его, если три секунды никто не играет. Разбор ниже.
Показать разбор «пианино с памятью»
Записывать нужно две вещи: какую ноту играли и сколько она звучала. Значит, снова два массива — как в мелодии из прошлого урока. Только теперь программа заполняет их сама.
const int BUZZER = 12;
const int KEYS[5] = {A1, A2, A3, A4, A5};
const int NOTES[5] = {262, 294, 330, 349, 392};
const int LEVEL = 400;
const unsigned long SILENCE_MS = 3000; // столько тишины — и играем записанное
int song[24]; // номера сыгранных нот
unsigned long lengths[24]; // сколько каждая звучала
int count = 0;
int last = -1; // что звучало на прошлом проходе
unsigned long changed = 0; // когда это началось
// Какая клавиша закрыта прямо сейчас; -1 — ни одной.
int pressedKey() {
for (int i = 0; i < 5; i++) {
if (analogRead(KEYS[i]) < LEVEL) {
return i;
}
}
return -1;
}
void playSong() {
Serial.println("играю записанное");
for (int i = 0; i < count; i++) {
tone(BUZZER, NOTES[song[i]]);
delay(lengths[i]);
noTone(BUZZER);
delay(60);
}
count = 0; // запись очищена, можно играть заново
}
void setup() {
pinMode(BUZZER, OUTPUT);
Serial.begin(9600);
changed = millis();
}
void loop() {
int key = pressedKey();
if (key != last) {
// Что-то изменилось — значит, прошлая нота только что закончилась.
if (last >= 0 && count < 24) {
song[count] = last;
lengths[count] = millis() - changed;
count++;
}
last = key;
changed = millis();
if (key >= 0) {
tone(BUZZER, NOTES[key]);
} else {
noTone(BUZZER);
}
}
// Долго тихо, а записанное есть — повторяем мелодию.
if (last == -1 && count > 0 && millis() - changed > SILENCE_MS) {
playSong();
changed = millis();
}
}
Главная мысль здесь — ловить изменения, а не состояние. Пока играется одна и та же нота, программа ничего не пишет; как только клавиша сменилась, она записывает закончившуюся ноту и её длительность. Тот же приём мы разбирали в одиннадцатом уроке с кнопкой.
Обратите внимание на count < 24: массив не резиновый, и программа обязана проверять, что место ещё есть. Забыть об этом — самая частая причина, по которой плата начинает вести себя необъяснимо.
Что мы узнали
- Датчик может работать как кнопка: достаточно решить, ниже какого числа считать «нажато».
- Пять одинаковых узлов — это не пять копий кода, а два массива и один цикл.
- Число
-1удобно использовать как «ничего не выбрано». - Пороги лучше не вписывать вручную, а измерять при включении: свет в комнате меняется, а устройство должно работать.
- Тот же фоторезистор умеет управлять звуком не ступеньками, а плавно — получается терменвокс.
В следующем уроке зуммер, светодиод и кнопка соберутся в настоящую игру — на скорость реакции. И заодно мы научим плату бросать жребий.
Частые вопросы
Как фоторезистор заменяет кнопку?
Фоторезистор в паре с обычным резистором образует делитель напряжения, и плата читает число на их стыке. Светло — число большое, заслонили пальцем — маленькое. Достаточно выбрать порог: всё, что ниже него, считаем нажатием.
Почему в этом уроке резистор 470 Ом, а не 10 кОм?
Так сделано в брошюре набора: при комнатном освещении такая пара даёт резкий перепад между «светло» и «тень». Если у вас в комнате темновато, поставьте 1 кОм — клавиши станут чувствительнее.
Почему клавиши работают днём, а вечером перестают?
Изменилось освещение, а порог остался прежним. Надёжнее не вписывать порог вручную, а измерять освещённость каждой клавиши при включении и брать от неё примерно 70 процентов. Тогда пианино подстраивается само.
Можно ли нажать две клавиши сразу?
Плата увидит обе, но зуммер умеет играть только одну ноту за раз — команда tone работает с одним выводом. В программе побеждает та клавиша, чей номер записался последним. Аккорд на одном зуммере получить нельзя.
