РадиоКот :: Полифонический MIDI проигрыватель на контроллере AVR
Например TDA7294

РадиоКот >Конкурсы >Поздравь Кота по-человечески 2021! >

Теги статьи: Добавить тег

Полифонический MIDI проигрыватель на контроллере AVR

Автор: Engineer_Keen
Опубликовано 23.09.2021
Создано при помощи КотоРед.

Поздравляем нашего Кота с шестнадцатилетием! Шерсти пушистой и мокрого кончика носа))) Ну а для ушек, вот эта статья...

Предисловие

В данной статье речь пойдет о несложном с точки зрения схемотехники музыкальном устройстве, которое может воспроизводить файлы стандарта MIDI. Для начала немного истории…
Однажды в интернете я наткнулся на интересную статью (https://gir.st/chiptunes.html), касающуюся чиптюна, в которой описывалось создание миниатюрного проигрывателя, размещенного в корпусе аудиоразъема. Несмотря на размеры и простейшую схему, он мог воспроизводить довольно длинную мелодию – почти 16 с половиной минут. Хитрость в том, что мелодия генерировалась процедурно, т.е. это была конечно не запись реального звука и даже не игра по нотам, каждый семпл генерировался «на лету», по заранее заданному алгоритму с очень удачно подобранными параметрами. Примерно вот таким образом:
g(i,x,t,o)
{
return((3&x&(i*((3&i>>16?"BY}6YB6%":"Qj}6jQ6%")[t%8]+51)>>o))<<4);
};
main(i,n,s)
{
for(i=0;;i++)
putchar(g(i,1,n=i>>14,12)+g(i,s=i>>17,n^i>>13,10)+g(i,s/3,n+((i>>11)%3),10)+g(i,s/5,8+n-((i>>10)%3),9));
}
Скомпилированная программа помещалась в крохотный ATtiny4, и это не удивительно, т.к. приведенный алгоритм, скомпилированный для AVR с учетом настройки периферии занимает всего 400(!) байт флеша (я потом даже проверил его в железе на ATtiny24). Мне стало интересно, а на что вообще способно ядро AVR с этой точки зрения? Что если взять контроллер побольше, с аппаратным умножением, использовать тактовую частоту побольше? Тем более я всегда хотел сделать какую-нибудь музыкальную игрушку, хотя в музыке я особо не разбираюсь и играть ни на чем не умею (ну разве что выучить что-нибудь простенькое, где не нужно больше чем на одну клавишу пианино за раз нажимать). Я уже давно читал статьи о таких устройствах, в том числе и тут (на радиокоте), например, мне понравилась статья «Midi player для AVR» (автор Роман Лут).
Таким образом я и стал разрабатывать что-нибудь похожее, что из этого вышло можно послушать тут:

Формат MIDI файлов

Кратко (на сколько это возможно) опишу формат MIDI-файла.
Файл MIDI состоит из событий, разделенных между собой дельта-временем. Событие, это каждое взятие ноты или ее отпускание, а так же другие важные и не очень действия: выбор инструмента, регулировка громкости, применение каких-либо эффектов, вывод текста песни. Дельта-время – это время между последовательными нотами (или другими событиями) в мелодии, задается оно в «попугаях» - оно зависит от темпа, который в свою очередь задается в специальном событии. Помимо этого события разделены по каналам. Всего в стандарте MIDI 16 каналов, каждый канал имеет свои настройки инструмента, громкости и других эффектов. Канал 10 особенный – он используется для партии ударной установки. Каждое событие начинается с дельта-времени, далее идет команда, состоящая из кода действия (4 старших бита) и номера канала (4 младших бита), после чего идут данные для этого события (номера нот, уровни громкости и прочее).
Типичный кусок MIDI-файла выглядит так (данные в шестнадцатиречном формате):

00 92 3c 6a 08 99 2a 00 00 23 00 44 95 3c 00 00 37 00 01 92 37 00 00 3c 00 0b 98 39 00 01 45 66

Зеленым цветом выделены дельта-времена, красным коды событий, в бирюзовым – данные. Например: $00 – дельта, $92 – взять ноту в канале 2, $3с – номер ноты, $6a – громкость, $08 – дельта, $99 – взять ноту в канале 9, $2a – номер ноты, $00 – громкость (выключить эту ноту), и т.д.
Все события в файле хранятся в виде треков. Трек может быть один, тогда события просто идут друг за другом в хронологическом порядке, в этом случае нет никаких проблем чтобы по очереди считывать их и воспроизводить. Но есть и другой формат – когда используется много треков, например для удобства редактирования, так в музыкальном редакторе можно отключить партию одного из инструментов или поменять сам инструмент на другой. В этом случае треки идут один за другим, и внутри каждого трека события идут в хронологическом порядке. Такой вариант не подходит для нас, т.к. для его воспроизведения нужно считать файл целиком и рассортировать события по хронологии.

Кроме этого есть еще некоторые особенности. Например, дельта-время в файле записывается в формате переменной длины от 0 до 4 байт, я проверил множество своих файлов, нигде не было дельты больше чем в 2 байта, это очень большая задержка! Само управление проигрыванием нот тоже не очень оптимально. По стандарту для взятия и прекращения ноты существуют две отдельные команды – NOTE ON и NOTE OFF, но по факту вместо NOTE OFF часто используется NOTE ON но с нулевой громкостью, помимо этого встречаются ноты с нулевой длительностью. Чтобы избавиться от этой неопределенности нулевые ноты можно игнорировать, а вместо использования пар команд NOTE ON-OFF, использовать одну команду NOTE ON но с дополнительным параметром – длительностью ноты, которая тоже записывается в формате переменной длины размером 1 или 2 байта. В файле есть еще много событий, которые не нужны для воспроизведения, их необходимо исключить перед передачей на проигрыватель. Помимо этого есть еще одна особенность описание которой я нигде не нашел и пришлось вникать в него самому: если следующее событие имеет такой же канал и такой же код как и текущее, то команда этого события пропускается и сразу за дельта-временем идут данные события.

Такой вариант приведен во фрагменте выше: после второго события «08 99 2a 00», сразу же по этому каналу выключается нота $23 («23 00»)Сделано это скорее всего с целью уменьшить размер файла, но для нас это только усложнит проигрыватель, так как придется запоминать лишнюю информацию.

События, которые используются проигрывателем кроме нот, это: установка темпа, установка громкости, баланса и инструмента, управление эффектом Pitch Wheel. Последнее событие есть далеко не во всех композициях, его смысл состоит в том, чтобы организовать изменение тона уже взятых на канале нот. Отличие можно услышать в следующем примере:

 

Чтобы разгрузить контроллер от лишней работы по сортировке событий по времени, исключению лишних событий и прочей оптимизации, мы применим уже использованный другими авторами подход – преобразуем мелодию на компьютере специальной программой-конвертером.

Конвертер файлов


Программа-конвертер написана на Delphi. Я не пользовался какими-либо сторонними компонентами для работы с MIDI-файлом, да и приоритет в разработке был за железной/программной частью проигрывателя, поэтому в конвертер пока далек от совершенства, но вполне работоспособен.
Рассмотрим окно программы.

 

 Кнопка загрузить - открывает файл. Программа поддерживает файлы *.mid формата 0 (1 трек) или 1 (несколько треков), *.rmi и *.kar (файлы с текстами песен для караоке). После открытия события автоматически сортируются по времени, заполняется таблица событий, параметры темпа.

В процессе работы программы, в окно лога выводятся некоторые сообщения, в основном диагностического или справочного характера. Например там будет отображаться открытие com-порта и информация о проигрывателе при успешном подключении.

В окне «Мелодия» отображаются ноты и события по каждому каналу в графическом виде вместе с названием инструмента. Обратите внимание, в отличии от стандарта MIDI, каналы нумеруются не с 1 по 16, а с 0 по 15! Помимо нот, отображаемых бело-салатовым цветом в графическом виде полосками отображаются и другие события: темно-зеленым – изменения громкости и баланса, красным – изменение инструмента, фиолетовым – эффект Pitch Wheel.Подходящий инструмент из банка проигрывателя автоматически выводится в колонке «инструмент». Если в файле находится событие с именем мелодии, то оно вписывается в окно «Название».

Колонка «Инструмент» показывает для каждого канала проигрывателя текущий инструмент, его можно сменить вручную (прямо во время воспроизведения), но данный выбор будет работать только до получения проигрывателем события смены инструмента, смены мелодии или до сброса. Рядом с этой колонкой расположены регуляторы предусилителя, они служат для усиления или подавления определенных каналов, например если кажется что нужно увеличить громкость определенного канала, то нужно передвинуть регулятор для этого канала вправо и переконвертировать мелодию.
Регулятор «глубина Wheel эффекта» - устанавливает силу эффекта модуляции частоты ноты (если в мелодии есть «фиолетовые» события). Этот параметр нужно подбирать для каждой мелодии индивидуально, для некоторых лучше ставить 0, т.к. в противном случае эффект на слух будет только хуже. В самом стандарте MIDI глубина этого эффекта контролируется особенным событием (RPN 0 - Pith Bend Sensivity), но в целях упрощения программы конвертора и проигрывателя, эта регулировка сделана ручной.

Окно «Проигрыватель» – управляет работой проигрывателя, и служит для записи в него мелодий. Перед записью или тестовым прослушиванием нужно сконвертировать мелодию, при этом готовая мелодия будет находиться в буфере программы. При нажатии кнопки воспроизведения «из ПК», мелодия будет воспроизводиться проигрывателем с заполнением буфера через COM порт. В этом режиме не стоит открывать новую мелодию или держать нажатой кнопку мыши на каких-либо регуляторах программы, иначе произойдет сбой воспроизведения, т.к. программа не сможет обработать таймер заполнения буфера проигрывателя! При нажатии кнопки воспроизведения «из флеш», будет воспроизведена мелодия, которая находится во флеш-памяти под определенным номером. Буфер проигрывателя при этом заполняется без участия компьютера. Кнопка «Тест» воспроизводит мелодию без использования буферов конвертера или проигрывателя, просто подавая ноты из таблицы по темпу. Такой вариант воспроизведения может быть использован вместо воспроизведения «из ПК», например если программа не успевает заполнять буфер проигрывателя. Любой режим воспроизведения останавливается кнопкой стоп «[]». Кнопка «загрузить» сохраняет мелодию из буфера во флеш-память под определенным номером. Кнопка «список» - выводит список всех мелодий во флеш-памяти.

Окно «Отладка» служит, естествено, для целей отладки, а так же для начального форматирования флеш-памяти. По умолчанию, ИМС DataFlash на 2МБ идет с размером страницы в 528 байт, это удобно при использовании каких-либо файловых систем, но не удобно для простой адресации контроллером, поэтому для работы проигрывателя нужно «отформатировать» размер страницы до 512 байт. Это быстрая однократная необратимая процедура, при которой контроллер просто посылает во флеш-память определенную команду.

Окно «Клавиатура» – показывает распределение кнопок на клавиатуре ПК, которые можно использовать как MIDI-клавиатуру. При нажатии на них, в проигрыватель посылается команда взять соответствующую ноту (длительностью 250мс). В нижней части можно выбрать октаву, а инструмент выбирается в первой строке колонки «инструмент» окна «мелодия». На рисунке ниже представлено это распределение кнопок на клавишах инструмента.

При запуске программы, она попытается открыть COM-порт, использованный в последний раз, после чего попытается подключиться к проигрывателю. Если все пройдет успешно, в лог будет выведено имя проигрывателя, версия ПО и данные о флеш-памяти.
Для записи мелодии в проигрыватель, нужно открыть MIDI-файл, установить необходимые параметры (название, усиление громкости, глубину эффекта «Wheel») сконвертировать, и задав номер мелодии, нажать «Сохранить».

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

ПО Проигрывателя. Синтез звука.

Теперь о самом главном, как работает проигрыватель MIDI файлов!
Звук в различных цифровых музыкальных устройствах можно создавать несколькими способами. Самый простой вариант, это генерация прямоугольного импульса, такой способ не стоит практически никаких затрат процессорного времени и памяти, но и на слух ничем интересным не выделяется. Путем хитрых манипуляций с фазой или коэффициентом заполнения можно достичь интересных эффектов. Следующее по сложности – генерация синуса, тут уже нужно вычислять значение синуса или брать его из таблицы. Синусы разной частотой и амплитудой можно складывать, теоретически получая вообще любой по тембру звук, можно также использовать различные фильтры, но все это очень сильно загружает контроллер расчетами. Один из простых по скорости, но затратный по памяти способ – это табличный синтез, когда воспроизводятся заранее записанные ноты. Естественно каждый семпл ноты занимает огромный по меркам 8-битных контроллеров объем памяти, поэтому напрямую это использовать не получится. Но есть хитрости…
Для получения более-менее правдоподобной формы звуковой волны используют огибающие. Одна из самых простых схем такой огибающей это ADSR-огибающая. Она состоит из следующих частей (фаз): A (attack) – скорость атаки (крутизна переднего фронта), D (decay) – скорость первичного затухания (крутизна заднего фронта), S (sustain) – уровень, на котором остается громкость, пока нота взята (удержание клавиши) и R (release) – скорость окончательно затухания, когда нота отпущена.

Даже простейший синус, обернутый в такую огибающую уже будет больше похож на музыкальный инструмент, чем на обычную пищалку. Таким образом, можно взять всего один период колебания любого (почти) инструмента, и обернув его в огибающую, получить ВСЮ ноту целиком! Так я и поступил в своем проигрывателе. Размер семпла взят в 128 байт, так 16 инструментов поместились всего в 2кБ памяти контроллера. Откуда я взял семплы? Я просто включил редактор MIDI, брал в нем ноту A2 для каждого инструмента (ниже объяснение), параллельно включив запись, запоминал форму огибающей, вырезал из записи все до одного колебания, сохранял и уже бинарным редактором из wav файла копировал 128 байт в виде «0x81,0x8e,0x9c,0xa9…». Эти последовательности сохранялись в inc-файле проекта в виде таблицы констант с директивой «.db».

Нота A2 взята не случайно, ее частота – 110Гц. А, так как частота выборки проигрывателя 13951 Гц, то при размере выборки в 128 байт, мы можем получаем сигнал с частотой 109 Гц, очень близкий к ноте A2, это позволяет легко выделить один период колебания нужного размера что в звуковом редакторе, что в бинарном. Дальше необходимо было определить параметры ADSR-огибающей для каждого инструмента. Тут пришлось просто воспроизводить ноту проигрывателем и по осциллографу смотреть результат, сравнивая его с запомненной при редактировании семпла формой. Подогнанные параметры сохранялись в тотже .inc-файл, образуя следующую после семплов таблицу. Структура получилась следующая: по 2 байта для A,D и R составляющих, 1 байт для уровня S, и 1 байт со специальным битом, который говорит о том, что в этом инструменте не используется S-фаза. Т.е. всего по 8 байт на инструмент.

Итак, у нас есть нота A2 для каждого из 16 инструментов, но нельзя играть мелодию одной нотой A2!
Всего MIDI стандарт использует 128 нот с С-1 по G9, с номерами соответственно 0-127. Чем отличаются ноты одна от другой? Частотой. Значит в простейшем случае можно получить любую ноту, просто выводя заранее записанные семплы с разной частотой. Но у нас одна постоянная частота – 13951 Гц, значит при каждой выборке из таблицы одного семпла, нужно просто прибавлять к номеру семпла определенное число (назовем его делитель фазы), причем для увеличения точности частоты использовать нужно дробное число. Такие делители в пределах одной октавы различаются нелинейно, но от октавы к октаве их значения для одинаковых нот (C0->C1, D0->D1 и т.д.) отличаются ровно в 2 раза. Они посчитаны и даны в таблице «OCT_FREQ», модуля sampler.asm. При вызове новой ноты значения этой таблицы пересчитываются для нужной ноты нужной октавы.


Отдельным образом воспроизводятся ноты барабанной установки, для нее семпл с периодическим сигналом не совсем подходит. Вместо этого используется генератор белого шума, по-простому – генератор случайных чисел. Случайное число немного доводится до ума прибавлением постоянной составляющей и заворачивается в свою ADSR огибающую. По-хорошему, для каждой ноты тут должен быть разный алгоритм и разные огибающие, т.к. в барабанную установку входят разные типы барабанов, тарелок, треугольников и прочих железяк, но я пока не реализовал все это многообразие и весь канал ударной установки представлен одним звуком.

Вся информация о нотах хранится в массиве NOTE, который делится на слоты – по количеству нот, заданных при компиляции.
В итоге получаем следующий алгоритм вывода семпла одной ноты:
При тактовой частоте в 25 МГц, каждые 10 мкс вызывается прерывание таймера в котором формируется запрос на обновление семпла с частотой 13951 Гц, т.е. в 7 раз реже. Частота ШИМ получается ~97 кГц, то есть за пределами слышимой области, из-за чего фильтровать выходной сигнал нет необходимости. Соотношение 1 к 7 выбрано как баланс между возможностью обработки максимального количества нот и в то же время выводом максимально высокой ноты с минимумом искажений (по теореме Котельникова это A8 - 7040 Гц, но на слух меньше, возможно из-за того, что сигнал не синусоидальный, да и ошибки при округлениях фазы никто не отменял).

В процедуре обновления семпла:

  1. Определяем по номеру канала инструмент, которым нужно играть;
  2. По номеру инструмента определяем стартовый адрес таблицы с семплом;
  3. Учитывая аккумулятор фазы, выбираем из таблицы семпл;
  4. Прибавляем к аккумулятору фазы значение делителя фазы, а также значение Pitch Wheel для канала;
  5. В зависимости от фазы-ADSR, увеличиваем или уменьшаем громкость с учетом значений скоростей изменения громкости A,D,R или просто ждем, если находимся в фазе S;
  6. Если в результате громкость получается нулевой, нота прекращается;
  7. Умножаем семпл из таблицы на громкость от ADSR-огибающей;
  8. Умножаем полученное значение на громкость ноты из данных MIDI;
  9. Умножаем полученное значение на громкости левого и правого аудиоканала;
  10. Складываем все полученные значения и обновляем данные регистров сравнения OCR для левого и правого аудио-каналов;

Немного остановимся на п.10. В вышеупомянутой статье (Midi player для AVR), был поднят вопрос, касающийся сложения (микширования) нескольких аудиосигналов. С одной стороны, по правилам акустики, чтобы сложить несколько (n) сигналов, нужно хитро складывать их логарифмы. Можно упростить задачу и просто складывать сами семплы, но при этом есть проблема. Чтобы уложиться в динамический диапазон контроллера, нужно разделять громкость поровну между всеми звуками (256/n), при этом, каждый звук будет звучать тише в n-раз. Поэкспериментировав с предыдущими проектами, а так же в этот раз я убедился, что для устройства такого уровня достаточно просто СКЛАДЫВАТЬ семплы между собой, при этом обрезая выход за границы байта. С точки зрения теории это должно выглядеть как обычная перегрузка, но учитывая большое количество умножений с округлением и потерей при этом некоторых бит, а также специфики MIDI-мелодий, сильных искажений я не заметил даже в редакторе, после записи с линейного входа.

Выше приведен фрагмент довольно сложной, с точки зрения одновременного воспроизведения множества звуков, мелодии. Как видно пики нигде сильно не обрезаны. На случай появления сильных искажений (перегрузки) можно воспользоваться регуляторами предусилителя в программе-конвертере.

ПО Проигрывателя. Воспроизведение мелодии

Теперь у нас есть разные инструменты и ими можно играть любую мелодию, чем и займемся. Если воспроизведение происходит по команде конвертера «Тест», то проигрыватель просто воспроизводит ноты, которые ему в определенное время выдает ПК через COM-порт, при этом самим проигрывателем не отмеряются никакие временные интервалы, эта задача полностью лежит на конвертере. В таком режиме проигрыватель можно использовать как музыкальный инструмент, если использовать клавиатуру компьютера или сделать отдельную клавиатуру и соединить её через UART с проигрывателем. При использовании буфера процесс немного сложнее. Ниже представлен фрагмент мелодии в таком виде, в каком он хранится во флеш памяти, таким он и читается в буфер проигрывателя.

События в буфере идут один за другим и представляются в таком формате:
дельта-время (0, 1 или 2 байта) : команда (1 байт) : данные (1-4 байта)
Дельта-время определяется следующим образом:
Если старший бит считанного байта = 1, то это команда, а дельта-время = 0.
Если старший бит считанного байта = 0, то это дельта-время, читаем следующий байт. Если у следующего считанного байта старший бит = 1, то это команда и дельта-время = первому байту. Если старший бит опять =0, то дельта-время = первый байт * 128 + второй байт, а третий считанный байт точно команда. Такое представление времени используется в самом стандарте MIDI, за исключением того, что там на дельта-время отводится до 4х байт.

Команды состоят из непосредственно кода операции (старшие 4 бита, самый старший всегда 1) и канала, к которому относится команда (младшие 4 бита). Ну то есть примерно также как и в файле MIDI. Всего используется 8 команд с соответствующими данными:

  • $8 – установка темпа, за ней следует два байта значения темпа;
  • $9 – взятие ноты, за ней следует номер ноты (1 байт), громкость (1 байт) и длительность (1 или 2 байта);
  • $A – установка громкости на канале, значение громкости (1 байт);
  • $B – установка баланса на канале, значение баланса (1 байт);
  • $C – установка инструмента на канале, номер инструмента (1 байт);
  • $D – зарезервировано;
  • $E – установка эффекта Pitch Wheel, положение колеса (2 байта);
  • $F – остановка воспроизведения в конце мелодии.

Темп представляет из себя количество миллисекунд, на которое умножаются значения дельта-времен и длительности нот. Старший байт этого числа – целая часть, младший – 1/256 часть миллисекунды. Дробное значение используется чтобы увеличить точность временных интервалов.

При взятии ноты проверяется несколько условий:

  • Если нотой с таким номером и таким инструментом уже занят один из слотов, то она берется в нем по-новой;
  • Если есть свободный слот, то используется он;
  • Если ни того, ни другого нет, то используется слот с самой старой на данный момент нотой;

После определения слота под ноту, происходит запись данных ноты в массив NOTE: канал, инструмент, громкость, параметры ADSR и прочее и нота начинает играть. Ниже представлен фрагмен вступления одной из мелодий (Settlers II), третья нота взята повторно (первый пункт в списке условий), поэтому ее огибающая резко прерывается и начинается сначала, причем для этого инструмента баланс лево-право выставлен не посередине.

Команды установки громкости, баланса и инструмента являются простыми аналогами таковых из стандарта MIDI.

Команда установки эффекта Pitch Wheel отличается от оригинальной из MIDI-файла тем, что значение эффекта зависит от положения соответствующего регулятора в программе-конвертере.

Независимо от источника данных (конвертер или флеш-память), при старте воспроизведения происходит первичное заполнение буфера, после чего из него считывается название мелодии (32 байта), которое копируется в буфер экрана, далее из буфера читаются данные мелодии (например длительность), после чего первый раз запускается процедура чтения событий. Первым событием обязательно должна быть команда установки темпа мелодии. В процедуре по очереди читаются все события мелодии, если у события нулевое дельта-время, то оно немедленно выполняется, иначе это дельта-время умножается на темп и сохраняется в переменной, при этом процедура чтения событий прерывается. В программе присутствует процедура таймера, которая вызывается каждую миллисекунду. В этой процедуре дельта-время ноты уменьшается на 1 мс, и при достижении нулевого значения, вновь вызывается процедура чтения событий. После чтения каждого события, буфер автоматически заполняется новыми данными. Размер буфера составляет 128 байт, этого хватает для 99% проверенных мною мелодий. Одно событие может занимать максимум 7 байт если это нота (2 дельта-время + 1 команда + 1 нота + 1 громкость + 2 длительность) и минимум 4, если это события типа громкости или инструмента (2 дельта-время + 1 команда + 1 параметр). Таким образом всего буфера хватает минимум на 18 нот. Самое тяжелое время для буфера это обычно начало мелодии, так как там идет много событий первоначальной настройки каналов (инструмент, громкость, баланс) с минимальным или даже нулевым дельта-временем (см. картинку ниже).

(Пока писал статью, немного переделал конвертер, теперь, если это возможно, он расставляет события в начале мелодии равномерно, от нулевого, до первого ненулевого дельта-времени)

При воспроизведении из флеш-памяти, буфер заполняется данными по мере вычитывания каждого события. После считывания одного события, несмотря на его размер, в буфере обновляется только 1 байт, это сделано с целью ускорения процедуры чтения в выполнения событий. При этом адрес буфера, из которого читаются данные, неизбежно приближается к адресу, в который данные записываются и если они пересекутся, то произойдет ошибка воспроизведения, т.к. очередной считанный байт будет устаревшим и структура события нарушится. Чтобы этого избежать, процедура обновления буфера вызывается постоянно еще и из главного цикла программы, т.е. когда контроллер ничем больше не занимается.

При воспроизведении с использованием COM-порта инициатором заполнения буфера является программа-конвертер, она проверяет и по необходимости обновляет данные в буфере. Если по каким-либо причинам она не успеет обновить буфер (например при зависании компьютера или обрыве связи), то произойдет ошибка воспроизведения.

Управлять воспроизведением можно собственными кнопками проигрывателя:

  • Prev/Next – Запускают воспроизведение предыдущей или следующей мелодий;
  • Play – Воспроизводит текущую мелодию сначала;
  • Pause – Приостанавливает воспроизведение;
  • Stop – Останавливает воспроизведение;
  • Mode – Включает режим повторения трека.

Если при подаче питания была нажата кнопка Play, то проигрыватель сразу начнет играть первую мелодию. В таком варианте устройство можно использовать как музыкальную шкатулку, установив выключатель питания, активируемый при её открывании.

Кратко о других модулях проигрывателя.

Системный таймер

Системный таймер задает все временные интервалы в программе кроме непосредственно вывода семплов. Основной процедурой таймера является обработка интервала в 1 мс, все остальные интервалы получаются из этого интервала через различные делители.
Помимо чтения событий, в процедуре таймера также идет отсчет двух параметров ноты. Первый параметр, это обратный отсчет длительности ноты, как только у ноты длительность станет равной нулю, нота считается отпущенной и для нее устанавливается R-фаза в ADSR-огибающей. Второй параметр – возраст ноты, он обновляется раз в 50 мс и служит для того, чтобы определять слот с самой старшей нотой, если возникнет необходимость освободить слот под новую.

С интервалом в 1 мс также вызывается фоновое обновление экрана (после ее запуска раз в 250 мс), и управление светодиодным индикатором статуса.

С интервалом в 50 мс, помимо возраста ноты, определяется количество одновременно играющих нот, а так же организовано сканирование клавиатуры и управление длительностью горения индикатора статуса.

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

С интервалом в 1 секунду происходит мигание статусного индикатора, обновление времени воспроизведения и скроллинг названия мелодии.

Модуль UART

UART используется для управления работой проигрывателя и загрузки мелодий во флеш-память. Команды, поступающие в проигрыватель всегда имеют размер 41 байт, размеры ответа зависят от конкретной команды, но не более 41 байта. Байты одной команды должны поступать без перерыва более 5 мс, иначе команда будет считаться ошибочной и проигнорируется. Всего поддерживается более 20 различных команд, более подробно они описаны в тексте программы.

Модули SPI и DataFlash

Эти модули используются для работы с внешней флеш-памятью, интерфейс SPI тактируется с максимально возможной скоростью – в половину тактовой частоты контроллера.

Модуль экрана

Модуль экрана управляет выводом текстовой информации на экран, если это необходимо. При включении на верхнюю строку выводится имя и версия прошивки. При воспроизведении на этой строке прокручивается имя мелодии, размером до 32 символов. На нижней строке выводится следующая информация: количество одновременно воспроизводящихся нот, счетчик прерванных нот, счетчик времени воспроизведения и длительность мелодии.

В прошивке остается свободным еще более килобайта программной памяти и 300 байт ОЗУ, поэтому потенциал для усовершенствования далеко не исчерпан.

Исходный код с полными комментариями и готовый HEX приложены к статье. Проект разрабатывался на ассемблере в AVR Studio 4.0.

Схемотехника

Изначально в схеме был только один контроллер ATMega8, с тактированием от кварца 20МГц, программа занимала всего пару килобайт и на мелодию оставалось целых 6кБ, но это было не очень интересно, т.к. более-менее сложные и длинные мелодии занимали все равно немного больше места. Тогда я решил добавить в схему отдельную микросхему флеш-памяти. Использование памяти типа Atmel DataFlash обусловлено тем, что я с ними уже работал и они были в домашних запасах, естественно после небольшой доработки программы, возможно использование других микросхем SPI-памяти. Принципиальные схемы основных частей проигрывателя приведены на рисунках 1-3. Создание схемы и разводка платы велись в ПО Proteus.

Питание

Схема блока питания построена на обычных линейных стабилизаторах серии 78xx и 1117 (low-drop), при этом стабилизатор DA2 (3.3В) можно использовать в самом маленьком корпусе, т.к. от него питается только микросхема памяти, а ее потребление не превышает 20мА в худшем случае (при записи). А вот от цепи питания 5В питается помимо контроллера и экрана еще и стереоусилитель, поэтому стабилизатор DA1 лучше брать в корпусе побольше (можно с радиатором). Впрочем, при питании от стабилизированного источника, напряжением 5В, стабилизатор DA1 можно не использовать и установить перемычку JP1. Корпуса DA1 - TO-220, DA2 - sot223. Конденсаторы C1, C2, C3 - тантал, тип B, остальные X7R 0805. Номинальное напряжение С1 зависит от входного и должно быть минимум в 2 раза выше, у С2 и С3 напряжение от 10В.

Контроллер

Основа устройства – микроконтроллер ATMega8 (можно перенести на ATMega88) в корпусе TQFP, слегка разогнанный до частоты 25 МГц. Кварц на плате типа HC49. Разгон не обязателен, но от тактовой частоты зависит количество одновременно играемых нот. При 20 МГц это 9 нот, при 25 уже 11. Т.е примерно по 2.2МГц на ноту. Конечно помимо вывода звука, контроллер занимается и другими делами: читает мелодию из флеш-памяти, выводит информацию на экран, сканирует кнопки, но все эти функции занимают гораздо меньше времени на обработку. Так как контроллер питается от 5В, а флеш-память от 3.3В, то уровни на линиях SPI лучше всего согласовывать. В линии данных от МК к памяти достаточно установить резисторы, а вот на единственную линию от памяти к МК лучше поставить буфер, т.к. при питании памяти от 3.3В, ее выходные уровни получаются на грани распознавания «0» и «1» для МК с питанием 5В. В качестве буфера используется одногейтовый элемент типа SN74LVC1G125. Дисплей используется с контроллером типа HD44780, двухстрочный, с подключением по 4 битной шине (конкретно в моем случае – Blaze BCB1602-03). На одной из линий данных висит светодиодный индикатор, вывод на экран и управление индикатором разведены во времени программно, поэтому друг другу они работать не мешают. Индикатор мигает раз в секунду, либо при приеме команд по UART. Клавиатура организована матрицей 2х3. Диоды импульсные, например LL4148 в корпусе mimiMELF. Связь с компьютером для тестирования, управления и загрузки мелодий осуществляется через UART, на скорости 115200 бод. Можно использовать переходники USB-UART, COM-UART или BlueTooth адаптеры, в зависимости от имеющихся в компьютере портов. Резисторы и конденсаторы размера 0805, тип конденсаторов любой, кроме C8-C8 - их лучше взять с диэлектриком C0G. Подстроечный резистор RV1 служит для регулировки контраста экрана.

Аудиовыходы и усилитель

Сигнал в ШИМ-выходов контроллера можно воспроизводить через разные аудиоустройства. Без регулировки громкости, аудиосигнал выведен через делитель на разъем линейного выхода, таким образом устройство можно подключать к любой стереосистеме. После регуляторов громкости (переменные резисторы VL-VR), аудиосигнал выводится на наушники либо на встроенный стереоусилитель небольшой мощности. Фильтры для подавления частоты ШИМ использовать не обязательно, т.к. она во много раз превышает верхний диапазон слышимых человеком частот. Усилитель собран на микросхемах LM4871 в корпусе SOIC-8. Эта микросхема представляет из себя одноканальный усилитель, работающий в мостовом режиме, с выходной мощностью до 3Вт при нагрузке 4Ома. Можно конечно использовать и варианты усилителей попроще, например те же мостовые усилители как в вышеупомянутой статье. Усилитель можно отключить, разомкнув цепь от входов 1 на землю. Конденсатор C20 электролитический на напряжение 16В, на плате предусмотрен его монтаж в SMD или выводном варианте.

Конструкция

Печатная плата сделана размерами 60х60 мм2. Все SMD-элементы находятся с одной стороны, разъемы и другие элементы для монтажа в отверстие с другой. Проводники с обратной стороны можно выполнить перемычками. В архиве есть PDF файлы с чертежами дорожек и изображением корпусов элементов, файлы для производства (формат gerber) и сам проект для Proteus (схема и плата), поэтому можно менять что угодно. Файл midi_bot_wiresonly.pdf - для варианта односторонней платы (с обратной стороны дорожки - перемычками). Можно переписать программу на воспроизведение из внутренней EEPROM, тогда буферная микросхема, флешка и стабилизатор со своими обвязками не нужны. Вообще, в минимальной конфигурации, например для звонка или музыкальной шкатулки с питанием от батарейки можно оставить только контроллер с кварцем и динамик, тогда всю схему можно вообще собрать навесным монтажем на Меге в DIP-корпусе, конечно в этом случае размер мелодии ограничен 512 байтами внутренней памяти контроллера.

Я собрал максимальный вариант схемы с внешней памятью и стереоусилителем, экраном и кнопками. Вот как выглядела плата в редакторе (с текстом в 3D режиме у протеуса обычно проблемы):

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

В передней панели из оргстекла я сделал полторы сотни отверстий, почти ровно...  Остальные стенки корпуса - 6мм фанера. Устанавливаем все органы управления, экран и плату на стойках на переднюю панель.

И соединяем все проводами (белая коробочка - преобразователь COM-UART, пока разместил внутри коробки):

Прошивка

При прошивке указать следующие фьюзы (для AVR Studio):

В отличии от заводских, изменены: источник тактирования на ВЧ-кварц, BOD на 4В и сохраниение EEPROM (не обязательно). Не забывайте перед изменением и записью прочитать фьюз-биты, если ваша среда для прошивки не делает этого автоматически, иначе есть шанс залочить контроллер. Если все сделано верно, то после прошивки файлом MIDI.HEX, начнет моргать светодиод HL1, а если используется и экран, то на него выведется название устройства. После этого можно подключать проигрыватель к компьютеру, загружать и слушать старые-добрые MIDI-файлы.

Пока писал статью вспомнил, как в детстве играл с сестрой в "Угадай мелодию". Если кто помнит, была такая игра для ПК, представляла собой набор файлов с незамысловатыми именами 1.mid, 2.mid,... и т. д. и непосредственно исполняемый файл. Программа была выполнена в стиле одноименной тв-передачи и предлагала для каждой мелодии несколько вариантов ответов. Так вот можно огранизовать нечто похожее с моим проигрывателем, например посадить ведущего за программу-конвертор и пусть воспроизводит какие-нибудь треки, а участники отгадывают. Тут многое конечно зависит еще от изначального качества файла MIDI, но ведь чем сложнее, тем интереснее!

Удачи в сборке, вопросы как всегда в форум!

 

 


Файлы:
Прошивка и исходный код
Конвертер файлов и его исходный код
Файлы с платой


Все вопросы в Форум.




Как вам эта статья?

Заработало ли это устройство у вас?

34 2 5