[Перевод] Чиптюн-музыка на ATtiny4 и трехцентовом Padauk
Когда я услышал «Bitshift Variations in С Minor» Роба Майлза — 16-минутный фрагмент 4-голосого полифонического аудио произведения — мне очень захотелось воплотить такое аппаратно. Реализовать это на любом микроконтроллере слишком уж просто, поэтому я решил взять самый мелкий, какой смог найти — ATtiny4. Чуть позже я портировал эту программу на небезызвестный трехцентовый микроконтроллер Padauk PMS150С.
Ах да — при этом он полностью уместился в RCA-штекер и автоматически обнаруживает подключение.
Плата ATtiny4, внутренняя сборка и готовое устройство.
Как он работает
На внешнем уровне он воплощает две функции: воспроизводит музыку и проверяет наличие подключения, чтобы накапливать энергию для работы в автономном режиме.
Генерация музыки
ATtiny имеет довольно мощный таймер/счетчик и выполняет двойную работу, генерируя как ШИМ-сигнал для аудио выхода, так и прерывания для генерации очередного PCM-сэмпла.
Говоря точнее, мы устанавливаем его в 8-битный не инвертированный ШИМ-режим без предварительного делителя и включаем прерывание переполнения. Это означает, что таймер отсчитывает от нуля до 255, используя ту же тактовую частоту 4МГц, что и ядро ЦПУ, ШИМ-выход поднимается от нуля до заданного коэффициента заполнения, и при достижении значения TOP сбрасывается к нулю, вызывая прерывание по переполнению.
Немного быстрых расчетов: самая высокая частота, на которой МК может работать, будучи запитанным от 3В «таблетки», равна 4МГц. Эти 4МГц, поделенные на 256 шагов, дают нам базовую частоту ШИМ в 15.625КГц. Слегка откалибровав внутренний генератор, мы можем добиться ее округления до 16КГц. Поскольку частота дискретизации исходного тона равна 8КГц, то новый сэмпл нужно генерировать только раз в два переполнения/прерывания. Это оказывается весьма удобно, так как генерация сэмплов в итоге занимает немногим более 400 циклов.
На этом графике я отразил счетчик, его значение для сопоставления, итоговый выход ШИМ, а также прерывание по переполнению и коэффициент заполнения/выполнение подпрограммы sample
, запускаемой этим прерыванием.
Ниже вы видите коэффициент заполнения и выход ШИМ на реальной микросхеме. Здесь отчетливо отражено, что в течение одного прерывания генерируется два сэмпла. (И этот вывод коэффициента заполнения/отладки позднее тоже пригодился, так как позволил откалибровать генератор через отслеживание частоты на осциллографе Rigol).
Канал 1 (нижний) показывает выход отладки, канал 2 (верхний) показывает сигнал ШИМ. Обратите также внимание на отображение частоты в верхнем правом углу.
Обнаружение подключения
Планируя механический дизайн, я понял, что физически кнопку выключения музыки встроить в корпус не смогу (не говоря уже о выключателе с фиксацией). Именно так и родилась идея автоматического обнаружения устройством подключения. В теории здесь все просто: когда оно подключено к приемнику аудио, возникает электрическая связь между аудио выходом и землей. Все что нужно, это регулярно подавать высокий сигнал на центральный контакт RCA-штекера и проверять его на понижение.
С программной стороны мы фиксируем, в каком месте произведения находимся, и входим в режим пониженного энергопотребления, который отключает таймер/счетчик и даже тактовый генератор. В этом состоянии мы ожидаем низкоуровневого (в смысле нулевого напряжения) внешнего прерывания, которое сработает при подключении устройства, после чего запускаем проигрывание.
На практике же все сложнее: типичное сопротивление линейного аудиовхода составляет 20–100кОм, что намного выше, чем у внутренних подтягивающих резисторов ATtiny, хотя это легко решается внешним подтягиванием. Кроме того, функциональность генерации прерывания при смене контактов недоступна на контакте выхода ШИМ, но и это легко обойти, просто закоротив их.
Согласно спецификации, потребление энергии в таком состоянии ниже 0.15 мкВ. Примерно с той же скоростью происходит саморазряд «таблетки», так что хватить ее должно на годы.
Фильтрация выхода
При использовании базовой частоты ШИМ 16кГц есть один недостаток: она слышима. Изначально я собирался просто это игнорировать, но один друг справедливо убедил меня добавить RC-фильтр нижних частот.
Быстрый и грубый спектральный анализ, сделанный с телефона, подтвердил, что эта частота действительно шумит. Границу среза и кривую отклика я выбрал довольно произвольно (глядя на коробку под SMD-компоненты) около 8кГц, тем не менее с задачей она справляется отлично, так что при заказе итогового списка материалов я придерживался именно ее.
Соотношение сигнал-шум после добавления фильтра существенно улучшилось.
Программное обеспечение
Разобравшись с теорией, осталось только прописать код. Я решил вручную воспроизвести Си программу Роба на ассемблере AVR, отчасти ради забавы, отчасти в качестве (поспешной?) стратегии по оптимизации. У ATtiny нет аппаратного mul/div/mod, и мне потребовалось лишь немного правосторонних множителей/делителей, для чего я написал несколько специальных оптимизированных вариантов.
Начал я с нетронутой программы Си и сначала просто ее упростил, после чего заменил каждую операцию на макрос Си, реализующий соответствующую инструкцию машинного кода. После каждого малейшего изменения я генерировал PCM-поток и сравнивал его с заведомо корректным образцом, чтобы избежать ошибок. Каждое изменение автоматически отправлялось в репозиторий, в результате чего получилось 136 коммитов под именем new version. Только затем я добавил код инициализации и произвел запуск на реальном микроконтроллере.
На этом этапе, сам того не ведая, я допустил ошибку при написании одного из псевдо-ASM макросов: я инвертировал условие ветвления в mod3, в результате чего оно переключалось, когда не должно было, и наоборот. Это привело к невозможности распознавания голосов 3 и 4 на микроконтроллере. Причину ошибки мне удалось обнаружить только год спустя, когда я вновь вернулся к проекту после того, как в simavr, наконец-таки, появилась элементарная поддержка семейства ATtiny 10. Когда я запустил gdb (1), проблема тут же стала очевидной, и для патча потребовалась всего одна инструкция машинного кода.
Гибкие печатные платы
Гибкие печатные платы, которые можно обернуть вокруг батарейки
Изначально я попробовал сделать двухточечную разводку эмалированным проводом. Затея эта с треском провалилась: припой не ложился на батарею, а альтернативный вариант с токопроводящим клеем получался грязным, и непрочным. При изоляции же батареи и компонентов с помощью нескольких слоев каптонового скотча изделие оказывалось слишком большим и в корпус штекера уже не входило.
Все эти проблемы на раз решила гибкая печатная плата. Я спроектировал ее так, чтобы она оборачивала «таблетку», при этом контакты батарейки находились внутри, а компоненты снаружи. Зажим, сделанный из скобы для бумаги, сдавливает этот «сэндвич», обеспечивая тем самым его питание. Сама плата также изолирует батарею, для чего достаточно одного оборота скотча по кругу, и делает этот мини комплект достаточно жестким.
И все же нужно было еще проложить два провода, идущих от платы к точкам пайки на внутренней стороне RCA-штекера. Задача оказалась непростой, и я не сразу додумался, как скрутить две части корпуса штекера, не прокручивая провода. Решением стало максимально глубоко задвинуть плату в корпус и слегка провернуть провода против часовой стрелки, чтобы при закрытии корпуса они, наоборот, развернулись.
Стек слоев в KiCad. PDF-схема.
Портирование на Padauk
При использовании ATtiny меня не покидало ощущение, что я мухлюю: он снабжен сравнительно богатой периферией и содержит много очень гибких регистров (16), которыми можно управлять напрямую. К тому же у меня завалялся самодельный программатор для микроконтроллеров Padauk, который подогнал мне один из участников форумов EEVBlog, а также около 500 штук PMS150C.
Эти микроконтроллеры прославились своей невысокой стоимостью — около 3 американских центов за экземпляр при приобретении в сравнительно небольших количествах. За свою цену они неплохо оснащены: ПЗУ на 1024 слова (программируемых один раз), 64 байта статического ОЗУ, 8-битный таймер с ШИМ, (несколько странный) 16-битный таймер, внутренний компаратор и источник опорного напряжения. По мнению некоторых, набор инструкций Padauk во многом следует более старой модели PIC, и большинство его операций происходят в одном накапливающем регистре.
Недостаток же в том, что производитель поскупился на документацию: спецификация не описывает, как мнемоника ассемблера сопоставляется с реальными единицами и нулями, то есть вы должны использовать их проприетарные (и доступные только для Windows) IDE, программатор и внутрисхемный эмулятор. Эта IDE даже не имеет компилятора Си, а использует странный язык, который они зовут «мини Си»: по сути, это ассемблер с позаимствованным из Си синтаксическим сахаром.
Талантливая группа любителей во главе с js_12345678_55AA, tim_ (cpldcpu) и spth (pkk), невзирая на отсутствие вышестоящей поддержки, создала впечатляющую и полностью открытую цепочку инструментов Си, включая компилятор, ассемблер, компоновщик, дизассемблер, симулятор, программное и аппаратное обеспечение программатора, а также низкоуровневую документацию.
Внутренняя сборка Padauk-версии
Для портирования чиптюнов на PMS150С потребовалось полностью перевести исходный Си-код в ассемблер, чтобы наилучшим образом использовать сжатые требования к циклам (в рамках которых я оставался с трудом: в худшем случае использовалось 507 из 512 доступных циклов). После того, как в процессе поиска правильного способа инициализации периферии я сжег тестовыми программами 5 схем, потребовалось еще всего 2, чтобы добиться полноценной отладки программы.
Итого семь микросхем, но при этом гораздо больше попыток: на деле можно программировать одноразовую память несколько раз при условии изменения только единиц на нули. Так что я оставил немного пространства под векторами сброса и прерывания, подставил новую версию кода и исправил инструкции GOTO, заменив их на NOP и добавив новый переход сразу же после. Скажу честно, я не столь скуп, но на неоднократную возню с ZIF-разъемом ушло бы больше времени, чем на этот обходной вариант.
Выделение некоторых отличий в версиях ассемблера: загрузка нот из памяти
Версия Padauk имеет небольшие отличия в сравнении с версией ATtiny: во-первых, здесь я задействую оба таймера, что позволяет использовать более высокую базовую частоту ШИМ (64кГц) и обойтись без ФНЧ. Во-вторых, внутреннее подтягивание Padauk достаточно высокое, и внешнее уже не требуется. Это означает, что мне удалось добиться полного отсутствия внешних компонентов.
И все же без сложностей не обошлось: t1sn M.n (тест старшего бита в статической ОЗУ и пропуск следующей инструкции) и set1 M.n (установка бита в области статической ОЗУ) работают только для первых 16 адресов; по данному поводу в спецификации толком ничего не сказано (заметил я это лишь потому, что в документации по реконструированным наборам инструкций присутствовало 4-битное адресное поле). В симуляторе микроконтроллера ucism было несколько ошибок, связанных с этими (и аналогичными) инструкциями, что слегка сбило меня с пути (патчи я отправил в список рассылки).
При этом также наблюдалось странное поведение режимов прерывания и ожидания timer16
, разобраться с которым мне помог JS. В остальном же весь процесс оказался достаточно плавным, и все благодаря качественной работе упомянутых выше ребят.
Обратите внимание на повышенную частоту ШИМ и более интенсивное использование ЦПУ в сравнении с версией ATtiny.
Я не стал озадачиваться созданием новых печатных плат; у меня оставались варианты без ФНЧ, и я просто оставил внешний подтягивающий резистор неподключенным.
Живое демо
Видео одного полного проигрывания музыки. Начало несколько затяжное, но с 1:35 становится интереснее.
Примечания
Считаю необходимым упомянуть проект Noiseplug от DoJoe. На него я наткнулся при поиске информации по ATtiny и могу сказать, что по своей сути он похож на мой.
Я собрал себе макетную плату из платы-переходника, поскольку шаг ее контактных площадок в точности соответствует одной стороне форм фактора SOT23–6. Вторую сторону я после подключил проводами. В другом, более раннем, варианте макетной платы использовалась миниатюрная адаптерная ATtiny, которую я приклеил на общую панель плат-переходников, чтобы ее расширить. Последнюю из них я представил на 35C3.
Макетные платы
Гибкие платы я заказал с OSHPark.com, и обошлись они примерно по доллару за штуку. Заказ был обработан довольно быстро, правда некоторые из них пришли с дефектами травления.