LXF158:Arduino: Немного пошумим
|
|
|
Электроника Вашей плате под силу сыграть Бетховена... или нечто вроде
Arduino: Немного пошумим В этой громкой статье маэстро Ник Вейч превращает свой Arduino в музыкальный инструмент, употребляя слово «настройка» в буквальном смысле.
Во многих проектах Arduino – будь то хоровод светодиодов или контакты ЖК-экрана – ведущую роль играет свет. Это очень мило, но есть другие измерение, которым мы пока пренебрегали: это звук. Помигать перед кем-то светодиодами – штука хорошая, но люди ведь должны на них сначала посмотреть; прелесть же звука в том, что его не так легко игнорировать, в чем скоро убедятся ваши друзья и родные.
Создаем музыку
Простейший способ добыть звук – воспользоваться микросхемой тонального генератора. Они очень дешевы, требуют минимума дополнительных компонентов для подключения динамика, и ими легко управлять с помощью импульсов с одного из цифровых выходов Arduino. Такая схема слишком проста, чтобы описывать ее здесь.
Сформировать собственный звук ненамного сложнее. Звук – это всего лишь ударная волна, и с подходящим генератором волн (например, динамиком) мы получим простые звуки прямо с выводов Arduino. Мало того, при всей несложности данной задачи, в ПО Arduino есть еще и специальная библиотека, которая об этом заботится.
Библиотека тонов формирует прямоугольные колебания (т. е. «вкл» и «выкл»), манипулируя внутренними генераторами тактов микросхемы Atmega. Она просто задает частоту и направляет результат на нужный вывод. Прямоугольные колебания дают грубый и сырой звук, но в этом есть и свои плюсы. В реальном мире вы вряд ли подключите эту схему к своей системе Hi-Fi, но она прекрасно подойдет на роль дверного звонка. Помня обо всем этом, давайте покорежим классический опус – «Оду к радости» Бетховена. Так или иначе она нам подходит. Первое, что нам нужно знать – как сгенерировать звук. Это просто:
tone(pin, pitch, duration);
Обязательных аргументов у функции tone два: номер вывода (pin) и тон (pitch). Третий, необязательный аргумент – продолжительность звучания (duration). Номер вывода – обычный номер контакта Arduino. Тон – это произвольно указанная частота; воспроизводить стандартные ноты вы не обязаны.
Для вашего удобства, в библиотеке есть заголовочный файл с определениями нот, поэтому можно пользоваться такими обозначениями:
tone(7,1568);
tone(7,NOTE_G6);
Оба варианта вызова воспроизведут одну и ту же ноту [«соль» средней октавы, – прим. ред.]. Если продолжительность звучания (целое число миллисекунд на воспроизведение ноты) не указана, то в нужный момент придется его остановить:
noTone(pin);
В один момент времени можно воспроизводить несколько нот; в стандартном Duemilanove Arduino – до трех, но в большинстве случаев на практике есть ограничение в две ноты. Это связано с тем, что для получения прямоугольной волны используется встроенный генератор тактов, который также обеспечивает генерацию ШИМ-сигналов и функцию внутреннего таймера миллисекунд. Заняв его тремя нотами, вы больше не сможете отследить прохождение времени, что может стать проблемой. Учитывая качество звука, получаемого с такой схемой, часто достаточно вообще одного канала.
Сохраняем мелодии
Итак, звуки издавать мы теперь умеем, но как создать музыку? Для каждой ноты нам нужно знать три вещи – тон, время звучания и интервал до следующей ноты. В примерах из библиотеки мелодий эти параметры хранятся в различных массивах, что, с одной стороны, упрощает код, а с другой – усложняет его понимание. В идеальном случае всю информацию о мелодии нужно хранить в одном массиве. Тут нам помогут структуры – struct, это пользовательский тип данных, который легко определить и которым удобно пользоваться. Он пришел из стандартного языка C, на котором, кстати, мы и пишем большую часть кода для Arduino (удивлены? – а мы вроде об этом упоминали). Вот синтаксис объявления структуры:
struct mydata { <type> <propertyname>;
<type> <propertyname>;
…
}
Структуру для ноты можно организовать так:
struct note{
uint16_t pitch;
uint8_t duration;
uint8_t pause;
};
note first={880,12,8};
int duration =first.duration*25;
Мы создали простую структуру данных с тремя значениями. Частота – 16-битное целое число (0 – 65535), а продолжительность звучания и пауза – 8-битные числа (0 – 255). Два последних значения являются скорее условными, чем истинными. Время нужно задавать в миллисекундах в диапазоне где-то от 50 до 2000. Но если хранить эти значения в 16-битных числах, для хранения каждой ноты понадобится на 50 % больше места (шесть байт вместо четырех), и мы сильно раздуем объем требуемой памяти. Так как нам вряд ли нужно воспроизводить ноты с миллисекундной точностью, проще хранить в памяти меньшие числа, а при использовании умножать их на коэффициент. Если умножать их на 25, получится диапазон от 0 до 6375, т. е. более шести секунд, и этого должно быть вполне достаточно.
Сами ноты тоже при желании можно уместить в один байт – если вы будете играть музыку, то скорее всего по нотам, а не звуками произвольной частоты, поэтому можно ограничиться диапазоном 255 нот (уж этого-то хватит!), который поместится в одном байте (еще одно беззнаковое 8-битное целое число). Наконец, стоит ввести обозначения для нот, которые мы хотим воспроизводить:
// notes in the melody:
enum { GG,A,B,C,D,E,F,G};
static const uint16_t frequency[] ={
784, // GG
880, //A
988, //B
1047,
1175,
1319,
1397,
1568//G
};
Объявление их как статических констант означает, что значения никогда не будут и не смогут измениться. Вы можете счесть это скаредными попытками сэкономить используемое место, но иначе память растратится довольно быстро. Пусть наша мелодия не слишком длинная, но хранение данных таким образом сэкономит до 1 КБ даже на коротком наигрыше, и это важно, если вы намерены сделать в программе что-то еще.
Теперь можно перейти к мелодии. Для краткости мы приведем только первый такт «Оды к радости», но в листинге DVD приведена вся мелодия:
note tune[] ={ {E,10,12},{E,10,12},{F,10,12} ,{G,10,12},
{G,10,12},{F,10,12},{E,10,12} ,{D,10,12},
{C,10,12},{C,10,12},{D,10,12} ,{E,10,12},
{E,12,14},{D,7,8},{D,24,32},
…
Пробелы при парсинге кода не воспринимаются, и вы можете отформатировать свой код так, чтобы его было проще читать или редактировать. Для каждой ноты приведены частота, продолжительность и пауза. Пауза всегда длиннее продолжительности, потому что в коде мы сделаем так, чтобы пауза включала время воспроизведения всей ноты. Наконец, нам нужен код для воспроизведения мелодии:
void loop() {
for (int i = 0; i < (sizeof(tune)/3); i++) {
tone(7, frequency[tune[i].pitch], tune[i].duration*25);
delay(tune[i].pause*25);
}
delay(15000);
}
Этот код, разумеется, стоит обернуть в собственную функцию, а не повторять все время в главном цикле. Хотя в этих строках много чего происходит, код очевиден.
При инициализации цикла мы можем определить количество нот, получив размер массива (в байтах) и разделив его на три, так как для каждой ноты используется три байта.
Настраиваемся
В следующей строке мы воспроизводим ноту с помощью функции библиотеки тонов. Мы выдаем ее на вывод 7 (он не хуже любого другого), и это первый аргумент функции. Частоту ноты возьмем из созданного ранее массива. В качестве индекса мы используем значения из перечисления, которые хранятся в нашем массиве tune в поле структуры pitch. Так, tune[i].pitch содержит число от 0 до 7 (числами мы обозначили ноты, для простоты прочтения), которое используется в качестве индекса массива, где хранятся настоящие частоты.
Последний нужный нам параметр – продолжительность звучания. Он должен быть в миллисекундах, но мы для экономии памяти делили значения на 25, и теперь нужно умножать их на 25.
Пауза между нотами реализуется стандартной функцией delay(), которая аналогичным образом использует данные из массива tune. Чтобы гарантированно прервать воспроизведение, можно было вызвать функцию noTone(7), но на практике в этом нет необходимости.
Для воспроизведения мелодии можно подключить небольшой динамик прямо к выводам Arduino. Но чтобы не повредить сам динамик, понадобится небольшой резистор – где-то в диапазоне от 100 Ом до 1 кОм. Если вы используете пьезоэлемент или пьезодинамик, без резистора можно и обойтись: они часто обладают большим внутренним сопротивлением, рассчитанным на сигналы логических уровней. Формула, которую вам нужно знать, выглядит так:
P =
248868.png Vpeak2
2 R
Это всего лишь приближение! Динамики устроены сложнее, чем вы думали – они содержат индуктивную нагрузку, и в определении мощности по отношению к динамику есть масса тонкостей. Нам же важно просто его не сжечь, поэтому достаточно и приближенной формулы; но требуются меры предосторожности. В данном случае R – резистивная нагрузка (сопротивление динамика должно быть указано на его задней поверхности – обычно это 4, 8 или 16 Ом), а V – пиковое напряжение – в нашем случае 5 В без использования усилителя мощности. С нагрузкой 16 Ом это даст нам примерно 1,5 Вт.
Предотвращаем перегрузку
Что произойдет при перегрузке динамика? Пьезоэлемент, если не доходить до беспредела, просто будет издавать ужасные звуки, пока не выйдет из строя, но вообще-то на удивление вынослив. В обычном динамике с бумажным конусом и сердечником вы либо повредите сердечник, либо растрясете его на части, либо повредите конус – а часто и то, и другое, и третье; поэтому здесь тоже нужно принять меры предосторожности. Из-за способа своего изготовления динамики чреваты также опасностью для Arduino. Крошечный пьезоэлемент не причинит ему вреда, но чем больше индуктивность, тем больше шумы скачков напряжения и тем больше мощность цепи. Хотя в цепях для управления динамиками с помощью Arduino, которые вам могут встретиться, его обычно нет, рекомендуется добавить развязывающий конденсатор емкостью около 250 мкФ, подключив его выход к «плюсу» динамика. Он не только сглаживает переходные процессы, но и поглощает постоянную компоненту сигнала, и сердечник будет перемещаться в обоих направлениях, как и предписано его природой.
Дополнительный звук
Мы вступаем на территорию аудиоцепей, и она может показаться вам незнакомой, потому что в аудиоцепях присутствуют циклические напряжения, которые роднят их с цепями переменного, а не постоянного тока – поэтому мы и добавили развязывающий конденсатор в нашу главную схему.
Дело в том, что в цепях переменного тока компоненты ведут себя по-другому, и это заметнее всего на примере скромного конденсатора. В цепях постоянного тока мы пользовались конденсаторами для буферизации напряжения и сглаживания переходных процессов, и, возможно, иногда использовали их в качестве примитивного таймера. С переменным током они ведут себя иначе: они его пропускают. На самом деле никакого тока через них не течет – они просто сохраняют и освобождают электрическую энергию (это если вы не пробьете конденсатор, подав на него слишком большое напряжение, чего мы делать не советуем, потому что он щелкнет и премерзко завоняет), но из-за циклической зарядки и разрядки обоих пластин создается видимость прохождения тока.
Это поведение зависит только от частоты приложенного напряжения и, разумеется, емкости самого конденсатора – если вникнуть, то чем выше частота, тем больше циклов зарядки и разрядки выполняется каждую секунду, и тем больше ток, «проходящий» через конденсатор. Свойство зависимости от частоты называется реактивным сопротивлением компонента и определяется по формуле
Reactance(X) =
248871.png 2πfC
1
Это сопротивление в чем-то аналогично обычному сопротивлению резистора в цепях постоянного тока. Но так как оно зависит от частоты проходящего через компонент сигнала, то чрезвычайно удобно для создания аудиофильтров.
Простой резистор и конденсатор (RC) можно использовать в качестве делителя напряжения сигнала, изменяющего его уровень в зависимости от частоты. Это означает, что сигналы с одними частотами пройдут через эту цепочку без изменений, а сигналы с другими – существенно снизятся; что и пригодится для подавления нежелательных сигналов и наводок в нашей системе. Частотный диапазон звука, слышимого человеческим ухом, нам известен, и можно отфильтровать ненужные колебания, подобрав подходящий конденсатор.
В зависимости от резистора и конденсатора можно создать фильтр верхних частот (т. е. отрезать нижние частоты – таким способом удобно избавляться от шума электросети питания) или фильтр нижних частот (с его помощью удобно отфильтровывать другие помехи, такие как шум от генератора тактов), или объединить два фильтра и получить полосовой фильтр.
Мы не будем слишком углубляться в теорию звука, потому что это не тема нашей серии, да и вычисления очень усложнятся – но мы и не собираемся ею пренебрегать. Присоединяйтесь к нам в следующий раз для увлекательного путешествия с генерацией синусоидальной волны со сдвигом частоты для эмуляции терменвокса. |