среда, 19 апреля 2017 г.

Простой индикатор уровня звука на WS2812B. Программа для Arduino Pro Mini.

Простой индикатор уровня звука на WS2812B. Программа для Arduino Pro Mini.

Всем привет :)

В предыдущей статье я показывал как подключить источник аудиосигнала к индикатору уровня звука. Но чтобы индикатор «заиграл», этого недостаточно. Поэтому в этот раз я расскажу о программной части устройства. И чтобы любой мог повторить/модернизировать VU-метр - приложу программный код для Arduino Pro Mini к статье и объясню как он работает. Поехали.

Для более лёгкого представления работы индикатора уровня звука предлагаю ознакомиться с блок-схемой программы:

Блок-схема работы простого индикатора уровня звука на WS2812B и Arduino Pro Mini.

Заметка: Блог не поддерживает подсветку программного кода, что, конечно же, меня расстроило. Как жить дальше?) На просторах интернета удалось найти решение проблемы. Попытка применения этого решения стоила мне заблокированного аккаунта, двух заблокированных блогов, созданных для экспериментов, и потраченного вечера. Мда... возможно, я что-то делал не так)) В итоге всё без проблем получилось с сервисом Gist от GitHub.
P.S. Первое решение заработало только на третьем блоге, но мне больше понравился сервис Gist))

Теперь, глядя на блок-схему, можно разбираться с самой программой. Первым делом, до зацикливания программы, контроллер нужно настроить. Т.к. я использую среду Arduino не совсем стандартным способом, то необходимо сбросить настройки контроллера, которые среда делает скрытно от пользователя скетча:

Как я уже говорил, нам необходимо 3 канала АЦП: опорный – для измерения постоянной составляющей и два канала для измерения аудиосигналов. Поэтому далее инициализируем АЦП микроконтроллера:

Для улучшения точности измерений частоту работы АЦП уменьшаем до минимально возможной: 125 кГц. Будем использовать все 10 бит, выдаваемых преобразователем. Обратите внимание, что в качестве максимально возможного измеренного значения берётся напряжение с ножки AVCC (аналоговое питание контроллера), а не AREF, как это указано на схеме из предыдущей статьи:

Схема подключения аудиоканалов к индикатору уровня звука на WS2812B и Arduino Pro Mini.

Это сделано для упрощения подключения сигналов к Arduino. Ножка AVCC объединена с VCC, поэтому и подключать её уже не нужно. А ножка AREF в Arduino Pro Mini никуда не выведена, а лишь подключена на общий провод через конденсатор для уменьшения помех во время измерений АЦП.

Для передачи кода цветов на умные светодиоды я захотел использовать аппаратный интерфейс SPI. Как выяснилось, из-за жёстких таймингов прерывать передачу нельзя, а хочется)... хочется в это время получать измеренные значения с АЦП. Поэтому сделано наоборот: передача очередного значения на WS2812B прерывает выполнение основной программы.

Прерывание от SPI разрешается непосредственно перед передачей первого байта и запрещается - после последнего. Очень важно, чтобы после передачи на шине MOSI был логический ноль, иначе светодиоды "запутаются" в принятых данных.

Вопрос: Если не настроить ножку 10 Arduino (аппаратный сигнал CS => PORTB 2) на выход или вход с подтяжкой, то передача по SPI не работает. При программировании контроллеров Atmel в IAR с таким не сталкивался. Возможно у кого-то есть ответ на такое поведение Arduino :)

SPI настраивается на максимальную частоту 8 МГц, равную половине тактовой частоты контроллера. В этом случае 1 бит цвета можно закодировать 1 байтом. И получим скорость передачи 1 Мбит информации о цвете в секунду. Таким образом можно рассчитать время, которое требуется для разовой передачи данных на все светодиоды. Один умный светодиод требует 24 бит - у меня их 64 штуки. Тогда на одну передачу уйдёт времени: 24 бит * 64 / 1 Мбит/с = 1.536 мс.

Эксперименты показали, что времени уходит в 5 с половиной раз больше, а именно 8.5 мс. Всему виной оказались прерывания - очень много времени теряется на вход в подпрограмму обработки прерывания и выход из неё. Это создавало сложности в виде ограничения времени нахождении в прерывании, но их удалось разрешить. Также я исследовал передачу по SPI без прерываний, на неё потребовалось 2.2 мс. Это уже ближе к теории :). "Почему обязательно нужны прерывания?" - спросите Вы. Не нужны, также как и не обязательно использовать SPI. С таким же успехом можно применить UART или просто программно дёргать ножкой. В программе простого VU-метра я даже заложил возможность выбора режима передачи по SPI с прерываниями и без. Это можно сделать с помощью макроса TRANSMISSION_INTERRUPT_DATA. Так что можно поэкспериментировать.

АЦП и SPI настроили - можно измерять сигналы, обрабатывать данные и передавать закодированные цвета на умные светодиоды WS2812B - да, всё верно. Вот только не хватает периодичности передачи - без таймера не обойтись :). Нам хватит 8-битного таймера, например, возьмём второй и настроим:

Снова прерывания. И опять можно обойтись без них, т. к. временная точность не нужна. Но так захотелось, и, возможно, кому-то это пригодится. Таймер после прерывания перезапускается автоматически, поэтому дополнительно что-то настраивать не придётся. Выбранный делитель 1024 даёт возможность настроить периодичность прерывания от 1 до 16 мс. Это задаётся макросом TIME_UPDATE_LEDS. Кстати, период прерывания будет точным, если значение кратно числу 1.6. Это значение нужно выбрать так, чтобы прерывание не вклинилось в передачу по SPI. То есть время должно быть больше времени, затрачиваемого на полную посылку. У нас она занимает 8.5 мс, поэтому выбрано значение 10.0. Необходимо ещё учесть, что перед отправкой данные нужно подготовить, поэтому сделал запас.

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

В цикле для каждого из 64-х светодиодов передаётся по 8*3 бита. А так как 1 бит информации о цвете кодируется одним байтом (макросы LOGIC_1 и LOGIC_0), то необходимо передать 8*3 байт для одного светодиода. В качестве полного количества элементов, которое нужно передать на светодиоды использую количество байт цветов. То есть в качестве базиса взят байт цвета, что очень удобно.

Инициализация завершена, можно приступить к основной части программы. Во-первых, это периодическое считывание данных с АЦП для каждого из трёх каналов:

Последовательно по готовности узнаём отсчёты канала опорного напряжения, первого и второго каналов аудиосигнала. Функция возвращает номер канала, значение которого было получено от АЦП.

Во-вторых, если есть измеренные значения, то их можно куда-то сложить, обработать. Этим занимается следующая функция:

В коде есть комментарии, но на всякий случай поясню, что конкретно тут выполняется. Наш индикатор будет отображать среднеквадратичный уровень сигнала. Поэтому здесь мы вычисляем квадрат измеренного значения. Этот квадрат кладём в ячейку массива и попутно вычисляем сумму всех значений массива. Сумма квадратов нам нужна, чтобы потом (когда это будет необходимо) быстро вычислить среднеквадратичное значение, а массив нужен для того, чтобы потом вычитать из суммы неактуальные более значения (что ускорит процесс вычисления) и заполнять его новыми квадратами.

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

Отличие вычислений только в заполненности массива фильтрации и величине значения суммы квадратов. С заполненностью всё ясно - тут просто разный делитель. А что касается функции вычисления квадратного корня, то тут 2 нюанса. Стандартная функция, которая работает с типом float, требует для себя длительного времени вычисления, поэтому решено было использовать приближённые методы вычисления квадратного корня. Для более быстрого вычисления решил применить 2 функции, оптимизированные под размер значения. Одна работает только с числами размером до 2-х байт, включительно, а вторая до 4-х байт, включительно. 

В-третьих, необходимо подготовить данные для отправки на светодиоды. Это самая "творческая" функция, потому как от неё зависит в каком виде будет отображаться уровень звука на умных светодиодах. У нас простой VU-метр, поэтому способ отображения только один:

Разберём функцию void set_leds_buf(). Для начала, об этом я писал чуть выше, вычисляется для обоих каналов среднеквадратичное значение, которое говорит о том, сколько светодиодов должно зажечься. Далее, во вспомогательные буферы записываются яркости светодиодов, которые будут светиться. Яркости берутся из таблицы: user_multicolored_table (по сути двумерный массив). Столбец отвечает за номер светодиода канала, а строка за цвет. Порядок цветов можно задать с помощью макросов COLOR1COLOR2COLOR3. Сейчас установлен общепринятый порядок: RGB. 

Заметка: Стандартный порядок цветов (то есть последовательность байт, которую нужно передать) у светодиодов WS2812B: GRB. Чтобы не путаться тем, кто использует GRB и тем, кто привык к RGB, решил сделать возможность быстрой смены порядка отправки цветов.

Во время выполнения программы таблица эта хранится в энергонезависимой flash памяти, что экономит ОЗУ и не вызовет никаких проблем, если мы захотим создать много таких цветовых таблиц.

После того как цветовые буферы сформированы, остаётся их объединить в один массив и передать на светодиоды. И тут я предусмотрел ещё небольшое удобство, и связано оно вот с чем: от того как будут объединяться буферы зависит в каком порядке будут следовать каналы (первый -> второй или второй -> первый) и в каком порядке будут следовать светодиоды в каждом из каналов (в прямом или обратном). Всё это можно задать с помощью макросов: DISPLAY_PRIORITY_CH1 и DISPLAY_PRIORITY_CH2 (порядок отображения каналов), DIRECTION_LEDS_CH1 и DIRECTION_LEDS_CH2 (порядок отображения светодиодов в канале).

Приближаемся к финишу. В-четвёртых, осталось передать сформированные данные. Покажу только вариант с прерыванием по SPI (вариант без использования прерывания отличается незначительно). Он состоит из функции, которая запускает передачу и разрешает прерывание по SPI и непосредственно самого прерывания:

Вот, по большому счёту, вся программа. Ссылку на проект простого индикатора уровня звука на умных светодиодах WS2812B для Arduino Pro Mini со скетчем прилагаю: https://github.com/onikita/Simple-VU-meter.git Для скачивания проекта нужно нажать зелёную кнопку Clone or download и выбрать Download ZIP. Чтобы запустить проект, необходимо скачанную папку поместить в папку libraries среды Arduino. Проект проверен и работает в Arduino-1.0.6. Также напоминаю: узнать о схеме подключения VU-метра можно в этой статье.

С уважением, Никита О.