company_banner

Чиптюн-музыка на ATtiny4 и трехцентовом Padauk

Автор оригинала: Tobias Girstmair
  • Перевод

Когда я услышал «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, и обошлись они примерно по доллару за штуку. Заказ был обработан довольно быстро, правда некоторые из них пришли с дефектами травления.

RUVDS.com
VDS/VPS-хостинг. Скидка 10% по коду HABR10

Комментарии 15

    +3
    Отлично! Ждем проект на jack 3.5
      0

      И без батарейки, с использованием присутствующего в гнезде 3,5 питания для электретных микрофонов.

      +3
      Меня во всём этом проекте потряс код, который звучит гармонично в течении 16 минут!

      echo "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));}"|gcc -xc -&&./a.out|aplay
      
        +3
        Нужна отдельная статья с разбором того, как это работает :)
          +4

          Размотала белиберду в такой код:


          int g( int i, int x, int t, int o)
          {
            return ((3 & x
                & (i * ((3 & i >> 16 ? "BY}6YB6%" : "Qj}6jQ6%")[t % 8] + 51) >> o))
                << 4);
          }
          
          int main(void) {
            int n, s;
            for (int 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));
            return EXIT_SUCCESS;
          }

          Так вам удобнее будет делать разбор того, как это работает.

          +1
          Напомнило одну программу, виденную ещё в 90-е, когда учился в ТГУ: крошечная программа, буквально в 2 килобайта, если правильно помню, рисовавшая на экране какие-то сложные фигуры. Причём, всё это работало под DOS, то есть вся графика была засунута в ту программу.
            0
            Есть целый жанр подобных демонстрационных программ, который называется «intro» (2K intro, 4K intro, даже 256 bytes intro бывают). Как правило, и графика, и звук там процедурные, т.е. создаются динамически при выполнении самой программы, а не хранятся в исполняемом файлы как ресурсы.
              +1

              Демосценушка родимая. А еще это всё можно прямо в MBR затолкать!

                +1
                Что-такое даже вспоминается. Правда, сейчас MBR уже только на совсем старых компах водится.
                  0

                  Разве legacy-режим не остался? Я просто технику меняю раз в двадцать лет

                    0
                    Без понятия. У меня нет ни одного компьютера с UEFI, самому новому 10 лет.
              +1
              Попробовал запустить, но на выходе почему-то тишина. Надо будет разобраться.
                0
                у меня работает
                  +1
                  Понял, в чём дело: у меня был запущен тот ролик с демонстрацией, хоть и с отключённым звуком, а aplay, похоже хочет монопольного доступа к устройству вывода. В общем, у меня тоже завелось.
                +2
                У меня жёстко искажался звук в некоторые моменты. Исправила добавив в конце " -f U8". На разных системах aplay по умолчанию выбирает разные форматы.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое