24 июня 2012 г.

Лампа настроения реагирующая на звук

Лампа настроения (mood lamp) - небольшая декоративная настольная лампа, которая периодически случайным образом плавно меняет свой цвет. В сети полно разных вариантов самодельных ламп настроения (в том числе и на AVR), но просто смена цвета случайным образом - это достаточно скучно. В этой статье я расскажу о том как сделать простую лампу настроения, которая реагирует на звук.

Идея

Итак, идея в том что бы снабдить привычную лампу настроения микрофоном, и заставить её менять цвет не просто случайным образом, а в зависимости от того что она "слышит" через этот микрофон. 
Лампа будет работать в трех режимах (режим переключается единственной кнопкой):

  • Простой режим, или режим обычной лампы настроения. Лампа просто периодически  случайным образом  меняет цвет.
  • Реакция на резкий звук. В этом режиме лампа не меняет цвет самопроизвольно, а делает это только когда услышит резкий звук, вроде хлопка, громкого крика или легкого удара по корпусу ламы или столу.
  • Анализ частоты звука. В этом режиме лампа меняет цвет в зависимости от частоты звука который она "слышит", то есть что-то типа цветомузыки.

Реализация

С идеей понятно, приступим к реализации.
Для такой лампы понадобятся, как минимум, три вещи: яркий трехцветный светодиод - что бы светить, микрофон - что бы слышать, и микроконтроллер - что бы управлять всем этим хозяйством.

Светодиод

В качестве светодиода я использовал сверхъяркий трехцветный (RGB) светодиод мощностью 3Вт (по 1Вт на каждую из цветовых компонент). Сам светодиод установлен на алюминиевой пластине-радиаторе, и выглядит вот так:
Светит достаточно ярко и имеет широкий "угол обзора" - нет яркого пятна посредине.

К сожалению, подключить такой светодиод напрямую к ножкам микроконтроллера не получится, потому что тока ему нужно около 200Ма на канал, а, например, максимально допустимый ток через ножку контроллера ATMega8 - всего 40Ма. Поэтому на каждый канал светодиода я поставил по транзистору - контроллер открывает/закрывает транзистор, меняя напряжение на базе. А транзистор зажигает/тушит светодиод, ведь максимально допустимый ток транзистора целых 800Ма (для BC337), что с головой хватает что бы зажечь светодиод.

Микрофон

В качестве микрофона используется обычный электретный микрофон (капсюль), подключенный к АЦП микроконтроллера через усилительный каскад. 


В принципе, можно было бы использовать цифровой микрофон, но на нашем радиорынке оказалось гораздо проще найти электретный микрофон.

Микроконтроллер

Здесь выбор казался вполне очевидным - ATMega8. Что-то слабее использовать не получится, так как нужно три канала ШИМа (по каналу на каждый из трех цветов светодиода). Использовать что-то более мощное , казалось, не имеет смысла. Поэтому я и остановил свой выбор на микроконтроллере ATMega8.

Но здесь меня подстерегали грабли. Дело в том, что для реализации программы управляющей лампой мне понадобилось использование арифметики с плавающей точкой, а AVR-ки, к сожалению, не имеют FPU. Поэтому для дробных чисел используется программная реализация FPU, которая занимает достаточно много места в памяти микроконтроллера. В результате память ATMega8 оказалась забита под завязку. Если бы сейчас я решил собрать еще одну лампу, то наверняка поставил бы как минимум ATMega32.

Схема и печатная плата

С основными компонентами разобрались, теперь осталось объединить все это воедино:
Обвязка микроконтроллера стандартная, как и в бинарных часах: питание, кнопка сброса, разъем программатора.

Для того что бы запустить контроллер на максимальной частоте (16Мгц), добавлен кварцевый осциллятор (в левом нижнем углу схемы).

На вход аналогового питания (AVCC) подключен LC-фильтр (дроссель и конденсатор), по рекомендации даташита на ATMega8 (см. раздел даташита "Analog Noise Canceling Techniques"). Правда, как оказалось - найти на нашем радиорынке подходящий дроссель достаточно проблематично, поэтому я поставил что нашел, а именно - старый советский дроссель на 120мкГн.

Кнопка BUTTON используется для переключения режимов работы лампы.

Светодиод DBG_LED использовался для отладки прошивки, и ставить его не обязательно.

В правом верхнем углу схемы изображен усилительный каскад для подключения микрофона к АЦП микроконтроллера. Сам микрофон подключается в разъем MIC.

Немного ниже изображена схема для подключения RGB светодиода. Базы транзисторов подключены к ножкам контроллера которые умеют генерировать аппаратный ШИМ (OC1A, OC1B, OC2). Катоды светодиода через транзисторы посажены на землю. Джампер JP1 нужен что бы можно было отключать канал OC2 от транзистора. Дело в том, что OC2 является по совместительству и входом MOSI для программатора, поэтому при прошивке микроконтроллера со включенным джампером светодиод начинает раздражающе моргать. Ставить его, в принципе, не обязательно.

Печатную плату хотелось сделать полностью односторонней, но, к сожалению, не получилось. Пришлось кинуть несколько перемычек по другой стороне (изображены красными дорожками).


Корпус

В качестве корпуса используется выпотрошенный обычный светильник. Плата прикручена болтами к нижней части корпуса. Микрофон прижат припаянным к корпусу куском провода.

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

Собранная лампа в корпусе выглядит вот так:

Программная часть

Прошивка написана на С. Местами присутствует ярый говнокод, т.к. лампа делалась в качестве подарка, и нужно было обязательно успеть к празднику. Зато сам код достаточно хорошо закомментирован и разобраться несложно.

Сначала немного об общей структуре программы.
Поддержка разных режимов сделана с помощью указателей на функции, поэтому менять/добавлять новые режимы очень просто. Есть enum Mode, в котором перечислены все режимы в которых умеет работать лампа. Есть два массива с указателями на функции: mode_callbacks и mode_switch_callbacks. Массив mode_callbacks содержит указатель на главную процедуру для каждого режима. Массив mode_switch_callbacks, по аналогии, содержит указатели на функции смены режимов. Функция смены режима нужна что бы дать пользователю понять что произошло переключение в новый режим работы.

В функции main находится главный цикл который состоит ровно из трех строчек:

  • mode_callbacks[mode](); Фактически это вызов главной процедуры для текущего режима работы. Она будет выполняться до тех пор пока пользователь не нажмет на кнопку.
  • mode = (mode + 1) % ModeLastInvalid; Переходим к следующему режиму.
  • mode_switch_callbacks[mode](); И вызываем обработчик смены режима для нового режима. Все что сейчас делают эти обработчики - мигают пять раз каким-то цветом. После выполнения этой строки мы вновь возвращаемся в начало цикла.

Еще, пожалуй, стоит упомянуть функции schedule_delay и pick_random_color.

Функция schedule_delay используется для генерации задержек. Она необходима потому что нельзя просто вызвать стандартную функцию вроде _delay_ms и уснуть, ведь нужно считывать данные с АЦП и проверять состояние кнопки (не нажал ли её пользователь). Именно эти задачи функция schedule_delay и выполняет. На самом деле, можно было бы использовать режим непрерывного преобразования в АЦП, а обработку нажатия на кнопку повесить на прерывание, но как показала практика, в режиме непрерывного преобразования шумы на АЦП больше чем в режиме одиночного преобразования.

Функция pick_random_color просто выбирает случайный цвет (в формате RGB). Вернее не совсем просто, а с одним нюансом: что бы чаще появлялись красивые "чистые цвета" (красный, зеленый, синий) введена константа COLOR_CLEAR_PROBABILITY, которая в процентах задает частоту появления "чистых" цветов. То есть если её значение равно 50, то примерно каждый второй случайный цвет будет красным, зеленым либо синим.


Режим лампы настроения

Главная процедура режима - mood_lamp_mode. Это самый простой режим, и его поведение вполне очевидно из кода: выбираем и запоминаем новый случайный цвет, плавно меняем текущий цвет лампы к выбранному цвету, "удерживаем" его некоторое время и начинаем все сначала.

Режим реакции на громкий звук

Главная процедура режима - sound_lamp_mode. В целом все похоже на предыдущий режим, с парой нюансов. Во первых, режим "удержания" цвета не ограничен по времени - переключение к выбору нового цвета произойдет не по таймауту, а если лампа "услышит громкий звук". А во вторых, во время плавного перехода к новому цвету может произойти переключение к выбору нового цвета (опять же, если лампа услышит громкий звук).

Пару слов о функции sound_lamp_mode_get_mic. Её задача - получить текущий "уровень" звука на микрофоне. Работает она следующим образом: сначала отключаются все три канала ШИМ, потом прогоняется пара холостых преобразований АЦП и запоминается значение с АЦП, затем восстанавливаются значения на ШИМ-ах. Такие костыли пришлось добавить потому что работа ШИМа сильно портит показания АЦП (я подозреваю здесь сказывается китайский блок питания, который не может обеспечить стабильное напряжение).

Режим анализа звука

Главная процедура режима - sound_analysis_mode. По сути, этот режим представляет собой последовательное выполнение двух операций: определение частоты звука, и изменение цвета на цвет соответствующий этой частоте. Рассмотрим их более подробно.

Изначально я хотел использовать дискретное преобразование Фурье для определения частоты звука. Но уместить его в восьми килобайтах памяти микроконтроллера никак не получилось, поэтому пришлось использовать другой, более примитивный способ. Суть способа состоит в подсчете пересечения звуковым сигналом нуля. Реализовано это следующим образом - звуковой сигнал записывается в буфер, затем мы проходим по этому буферу и подсчитываем, как много последовательных пар элементов лежат по разные стороны от нуля. Только вот ноль это не математический ноль, а нулевая амплитуда, для которой используется константа SOUND_AMPL_ZERO. Что бы было понятнее рассмотрим пример: пусть нулевая амплитуда у нас это 100, тогда пара значений 95 и 120 пересекают ноль, т.к. они лежат по разные стороны от нулевой амплитуды (95 меньше 100 а  120 больше 100). А вот пара 80 и 90 не пересекают, т.к. оба этих значения меньше ста. Таким образом частота это количество пересечений нуля деленное на единицу времени.

Теперь о том как на основании частоты выбрать цвет. Цветовая модель RGB для этого слабо подходит, поскольку каждый её компонент фактически контролирует три параметра: тон, насыщенность и яркость, а мне же хотелось что бы частота влияла только на тон, а яркость и насыщенность оставались постоянными. Поэтому я выбрал цветовую модель HSV. Яркость и насыщенность постоянны и равны их максимальным значениям, а тон напрямую зависит то частоты. Когда на основании частоты получено значение цвета в HSV, выполняется преобразование из HSV в RGB, и полученный цвет выводится на светодиод. Весь процесс преобразования частоты звука в цвет в модели RGB выполняется в функции convert_freq_to_rgb.

Результат

Собственно результат, как и фото процесса изготовления, можно посмотреть на этом видео:


Файлы

Печатная плата в формате PNG и исходные коды прошивки: скачать.

32 комментария:

aoleynik комментирует...

Вот тут http://solusrex.livejournal.com/135701.html и тут http://elm-chan.org/works/akilcd/report_e.html вмещают фурье килобайта в 4. Может пригодится!

Alik комментирует...

aoleynik, спасибо за ссылки!

Анонимный комментирует...

Здравствуйте.
В какой программе скомпилировать исходные коды прошивки?

Alik комментирует...

Под Windows с помощью WinAVR (отлично интегрируется с AVR Studio).
В Linux есть AVR-GCC.

Анонимный комментирует...

Alik, спасибо за ответ.
Это моя первая поделка на микроконтроллере.
Очень понравилась лампа, красиво получилось.

Анонимный комментирует...

Здравствуйте
подскажите правильный у меня lamp.hex- 10 842 байт получился,
делал в Atmel Studio 6.1. Что то мне кажется он великоват.
Если не трудно выложите свой lamp.hex
шить буду понипрогом через 5проводков
мега8а..
спасибо

Alik комментирует...

Если hex файл в ASCII формате (можно открыть его текстовым редактором и посмотреть) то это нормально. Реальный размер кода и данных отображается в процессе сборки (в AVR Studio в окне Build):

Program: 6344 bytes (77.4% Full)
(.text + .data + .bootloader)
Data: 283 bytes (27.6% Full)
(.data + .bss + .noinit)

Вот только что собрал hex (18Kb): https://docs.google.com/file/d/0B5PhPe5tN4FlSW1KVnVJeGljTDQ/edit?usp=drive_web

Анонимный комментирует...

Alik, Спасибо большое, буду учиться.

Andrey Sagittarius комментирует...

Добрый день. Очень понравился данный девайс. Решил собрать на Новый год, вот только деталей нет нужных под рукой. Применил вместо BC337 полевой транзистор IRLZ44n. Светодиод горит только одним цветом, цвет можно поменять только прикоснувшись к резистору на ножке PB1-PB3. Подозреваю всему виной емкость полевого транзистора.
Подскажите как можно подправить софт под полевые транзисторы.
Заранее спасибо.

Alik комментирует...

Андрей, из программных настроек могу посоветовать разве что частоту ШИМ-а поменять.
Вообще, что бы понять в чем проблема, можно отключить два канала (скажем красный и зеленый), и попробовать поустанавливать разные значения для синего канала, посмотреть как при этом меняется цвет.

Анонимный комментирует...

Уважаемый автор, вы бы не могли выложить прошивку в HEX плюс фюзы?
Убил неделю с Atmel Studio 6.0 так ничего и не получилось,
а повторить очень хочется.

Alik комментирует...

Парой комментариев выше я давал ссылку на hex (вот она https://docs.google.com/file/d/0B5PhPe5tN4FlSW1KVnVJeGljTDQ/edit?usp=drive_web). Фьюзы дефолтные, за исключением CKSEL - их нужно установить на использование внешнего часового кварца в качестве источника тактового сигнала.
Вы также можете скачать Atmel Studio 4 (вот здесь http://www.atmel.com/tools/studioarchive.aspx).

Saulitis комментирует...

Alik,большой привети большое спасибо за статьи. Давно искал статью-учебник про 3D принтер. Хочу перевести на свой родной язык Надо ваше согласие. Есть еще идеи.Напишите свои контакты на saulepost@gmail.com C уважением (Саулюс)

Alik комментирует...

Saulitis, конечно переводите, я не против. Отписал вам на email.

liapa81 комментирует...

Скажите, а прошивка в HEX точно нормальная? А то что-то не хочет у меня работать лампа как у вас на видео.

Alik комментирует...

Должна быть нормальная. Но я бы на вашем месте собрал hex самостоятельно из исходников, тогда будет возможность поиграться с параметрами и понять что не так.

liapa81 комментирует...

В этом я дуб! Имею в виду написание прошивки. Может поможете?! Я вообще хотел вашу платку использовать с лазерными указками трёх цветов. Но так как у вас у меня цвета не сменяют один одного. Что не так не знаю. Может всё же скинете на почту мне ещё раз прошивочку и желательно со всеми фьюзами?!

liapa81 комментирует...

Ах, да... Адрес такой если уговорил. liapa81@mail.ru На сим спасибо!

Alik комментирует...

Отписался вам на почту

Антон комментирует...

Здравствуйте Alik, хотел бы с Вашего разрешения опубликовать эту статью в каталоге Микроконтроллеры и Технологии, с указанием авторства и обратной ссылкой. Жду Вашего ответа admin(dog)radioparty.ru

Игорь комментирует...

Здравствуйте, Alik. Скажите пожалуйста, почему нулевая амплитуда взята 100? И в цикле:
for (unsigned short i = 0; i < SOUND_BUF_SIZE; ++i)
{
update_adc();
update_adc();
update_adc();

buf[i] = mic_level;

update_adc() - 3 раза? Заранее спасибо)

Alik комментирует...

Игорь, число 100 получилось экспериментально, как значение на ADC когда микрофон находится в полной тишине.
Преобразование ADC я запускаю несколько раз и беру результаты только последнего для повышения точности. Об этом много где написано, и, если не ошибаюсь, даже в даташите к ATMega.

Игорь комментирует...

Большое спасибо за ответ! Теперь все понятно.))

Игорь комментирует...

Alik, простите что пытаю, но уж очень хочется разобраться!))
Скажите, для чего нужен этот цикл:
while (H >= 360)
H -= 360;
и зачем здесь: unsigned short H = 359 * freq * 2
умножать на 2?
Заранее спасибо за ответ, больше приставать не стану!!!)

Alik комментирует...

Игорь, насколько я помню, меня не устраивало что небольшие изменения в частоте слабо отражаются на цвете, поэтому я сделал этот воркэраунд. Сначала частота умножается на 2 чтобы небольшие изменения сильнее влияли на тон. А потом этим циклом значение H возвращается в диапазон 0 - 360.

И комментирует...

Спасибо, Alik, что не бросили в беде)) Вроде разобрался. Все работает!!!

Анонимный комментирует...

Ссылка не работает ... Скиньте исходники , если у кого есть

Alik комментирует...

Похоже, яндекс удалил файлы. Завтра перезалью куда-нибудь.

Alik комментирует...

Перезалил исходники и обновил ссылку.

Алексей Савьюк комментирует...

Здравствуйте, а как должен называться пункт для выставления фьюз на внешний кварц 16 МГц в avr studio(точное название будь то int.osc или ext clock)? А то боюсь не правильно что-то выставлю и залочу мк. Заранее спасибо

Alik комментирует...

Согласно датащиту ATMega8 раздел System Clock and Clock Options: бит CKOPT=0 и биты CKSEL3..1 = 111. Если вы пользуетесь AVR Studio, там разные конфигурации фьюзов будут подписаны, что уменьшает вероятность убить микроконтроллер.

Алексей Савьюк комментирует...

Спасибо, разобрался. Если кому будет интересно как называется пункт для выставления фьюз на внешний кварц на 16 МГц, то это Ext. Crystal/Resonator High Freq; start-up time 16CK + 64 ms. Это для avr studio 6.2 и 7 версии. В остальных не проверял

Отправить комментарий