Итак, я попробовал воспроизводить 16-ти битный звук при помощи двух ШИМ выходов ATmega8.
Собирал всё на макетке, той, что белая пластиковая с дырочками. Подход, который я использовал, описан в этой статье:
Построение ЦАП разрешением выше 12 бит на примере STM32 (не реклама). Последовательно с одним из резисторов воткнул переменный, чтобы можно было регулировать соотношение. Полученный сигнал пробовал загонять на вход активных колонок и в линейный вход звуковой карты. Для линейного входа пришлось добавить ещё один переменный резистор, чтобы отрегулировать максимальную амплитуду до уровня, не приводящего к перегрузке. Причём всё это работает даже без фильтра НЧ. Понятия не имею как правильно его рассчитать, учитывая такую связку сопротивлений и не зная внутреннего сопротивления звуковой карты, внутреннего сопротивления портов микроконтроллера. Пробовал подключать параллельно выходу конденсатор на 0.1 мкФ (из звука на слух сразу пропадают высокие частоты) и на 0.01 мкФ (звук на слух не изменяется или изменяется незначительно). По хорошему, наверно, нужно запилить повторитель на операционнике, чтобы выкинуть из уравнения хотя бы сопротивление звуковой карты (или колонок). У меня мало опыта в аналоговых вещах. Пока будет так, как есть. Тем более, что звук на удивление хорош.
Да, звук хорош! По сравнению с 8-ми битным ШИМ-ом сразу заметны детали и нет раздражающего шипения. Самое интересное, при вращении первого переменного резистора, качество звука на слух почти не изменяется. Видимо его значение влияет только на последние N бит, которые не вносят в аудиозапись сильно слышимых характерных искажений. Безусловно, если заменить его на другой номинал, то можно добиться и более слышимых изменений. Но я проводил эксперименты из того, что было под рукой.
Звук формировал на компьютере и отправлял на микроконтроллер по UART. Пока не пробовал использовать SD карту. Но ранее без проблем воспроизводил восьмибитный звук с SD карты на ATtiny85 и ATmega8. Карту использовал без ФС, чтобы упростить написание прошивки. Надеюсь, хватит ресурсов и для 16-ти битного звука, и не придётся при этом обращаться к ассемблеру.
Программная часть на компьютере была написана на JavaScript (Node.js). Соединение осуществлялось при помощи библиотеки SerialPort. Изначально я пытался прокинуть весь воспроизводимый звук со звуковой карты в UART. Для захвата звука в Node.js тоже есть библиотека, называется node-core-audio. Я уже успешно использовал всю эту связку перед Новым Годом, чтобы сделать светомузыку из RGB гирлянды. Но в данном случае нужно было передавать более внушительный поток данных, да ещё и с выдержанными таймингами, а в JavaScript с этим проблемы. Можно было реализовать это на каком-нибудь питоне или Си, но с питоном я не знаком, а на Си под винду почти не писал. А вот с Node.js и JavaScript в целом знаком неплохо. В общем, сделать захват и чтобы это корректно звучало у меня пока не вышло, поэтому я решил упростить задачу.
Я решил брать аудиофайлы, преобразовывать в нужный PCM формат (wav без заголовков) и отдавать порциями в UART (всё тем же JavaScript). Конвертировать любые аудио и видео в нужный формат можно при помощи популярной утилиты ffmpeg. Я имел с ней дело неоднократно, поэтому разобраться с подходящей командой заняло пять минут. Добавил вызов этой команды прямо в JavaScript.
Так как со строгими таймингами в JavaScript туго, да и частота как ни крути будет отличаться на компьютере и микроконтроллере, было решено ввести небольшую синхронизацию. Программа ждёт поступление по UART байта от микроконтроллера. Не важно значение байта, важен сам факт его приёма. После этого программа незамедлительно (насколько это возможно в JavaScript) отправляет порцию данных и ждёт следующего "сигнала". Вот и всё, такой вот рабочий цикл. Размер порции данных должен быть равен половине внутреннего кольцевого буфера в микроконтроллере.
Забегая вперёд, хочу сказать, что всё это заработало как надо только при размере буфера не менее 800 байт (не хило, учитывая, что на борту микроконтроллера всего 1024 байта оперативы). Причём, как мне кажется, проблема на стороне JavaScript, а не микроконтроллера (всякими протеусами не пользуюсь, всё отлаживаю сразу в железе, только хардкор!), потому что в контроллере отправить и принять байт через UART - это быстро и естественно. А вот в JavaScript лучше осуществлять трансфер данных порциями. И чем порция больше, тем быстрее будет реальная скорость передачи данных. Если, например, передавать по одному байту, то количество исполняемого оверхед кода для такой, казалось бы простой, операции возрастает неимоверно. В принципе, теперь, когда я отладил работу с файлами, возможно, получится реализовать изначальную задумку и с захватом звука на компе. Может попробую позже.
Программа в микроконтроллере выполняется в двух прерываниях: прерывание по приёму байта по UART и прерывание по Compare Match таймера для изменения регистров ШИМ, чтобы вывести очередной семпл. В main() только настраиваются все необходимые регистры и разрешаются прерывания. Для оптимизации внутри прерывания по приёму байта по UART нет никаких проверок по выходу за границы буфера. То есть весь код прерывания выглядит так: *(buffer++) = UDR; Это означает, что можно положить программу просто отправив с компьютера в UART подряд слишком много данных. Но на деле этого никогда не происходит, благодаря синхронизации, описанной выше. Для экспериментов и так сойдёт. Вся остальная работа происходит во втором прерывании, его код содержит около десяти строк. Это прерывание вызывается в два раза реже, чем то, поэтому я и вынес в него всю логику. Почему так? Потому что звук вывожу 16-ти битный, а это два байта на семпл, значит принять по UART нужно в два раза больше байт, чем нужно вывести семплов.
Поскольку я использую микроконтроллер, не имеющий на борту сверхскоростной ШИМ (как в ATtiny85), мне кажется, логичнее всего использовать такую частоту дискретизации звука (sample rate), чтобы частота ШИМ была кратной ей. Таким образом на каждый семпл будет отводиться всегда одинаковое количество импульсов ШИМ, что должно благоприятно сказаться на качестве звука. Я пробовал выводить семплы со скоростью равной частоте ШИМ, но микроконтроллер не успевает работать с такой скоростью. Поэтому я в экспериментах использовал скорость, равную частоте ШИМ, делённой на 2 (получается 2 импульса на каждый семпл). Это оптимальнй вариант, потому что делать семпл рейт ниже этого значения, сказывается на качестве звучания в худшую сторону, так как срезаются высокие частоты. Можно не привязываться к частоте ШИМ и подобрать такой параметр регистра, чтобы семпл рейт был близок к стандартным 44100, но мне субъективно показалось, что звучание хуже, если не соблюдать кратность с частотой ШИМ. Тем более, что на данный момент, ресемплинг аудиофайлов делается автоматически скриптом на компьютере и задать можно абсолютно любой семпл рейт.
По спецификации на ATmega8 стабильная работа обещается при частоте тактирования не выше 16 МГц. Тем не менее, я успешно на практике реализовывал стабильно работающие устройства с резонаторами на 20, 18.43 и 24 МГц. Под фразой "стабильно работающие устройства" я не подразумеваю системы навигации ракет, а лишь небольшие единичные поделки, но тем не менее. В описываемом эксперименте я применял резонаторы на 16, 20, 24 и вытащенный с ненужного модема 25 МГц. Работает почти стабильно на всех. Только на двух последних иногда слышны артефакты в виде единичных шипящих щелчков. Судя по всему, это происходит из-за того, что при передаче данных по UART теряется байт и поэтому сбивается вся посылка размером в половину буфера. Возможно дело не в высокой частоте так таковой, а в качестве соединений на макетной плате. Когда ко мне приедут с Китая ардуины, где всё спаяно как следует, попробую припаять туда резонаторы на разные частоты.
В каждом случае UART настраивался для работы на максимальной скорости. Попытки задать делитель не увенчались успехом, почему-то не было стабильного соединения. Возможно, дело в софте, используемом на компьютере или с моим USB -> UART адаптером. Почему-то работают либо общепринятые варианты скоростей вроде 9600 бод (что слишком медленно и не подходит под задачу), либо максимально возможная (UBRR = 0; UCSRA |= 1 << U2X;). Отмечу, что на максимальной скорости стабильно работало от всех резонаторов, даже от 25 МГц (ну не считая вышеописанных щелчков). В принципе, работает на максимуме и ладно, мне только лучше. Максимальный бодрейт UART в AVR можно вычислить, поделив тактовую частоту микроконтроллера на 8. А чтобы получить скорость в байтах в секундах, нужно дополнительно ещё поделить на 10 (1 байт = 10 бод). Следовательно для вычисления скорости в Б/с нужно делить тактовую частоту на 80.
Так как стоит задача выводить 16-и битный звук, то каждый семпл кодируется 2-мя байтами. Поэтому можно вычислить битрейт путём умножения семпл рейта на 2. Битрейт для удобства буду указывать в байтах в секунду (не в битах!). При выборе семпл рейта нужно не забывать, что если битрейт будет выше скорости UART, то шарманка не запоёт, а закрехтит, потому что данные физически не будут успевать передаваться.
Таким образом я имел на борту следующие варианты конфигурации:
При тактировании резонатором на 16 МГцPWM: 16 МГц / 256 = 62 500 Гц
SampleRate: PWM / 2 =
31 250 ГцBitrate: SampleRate * 2 = 62 500 Б/с
UART: 16 МГц / 80 = 200 000 Б/с
При тактировании резонатором на 20 МГцPWM: 20 МГц / 256 = 78 125 Гц
SampleRate: PWM / 2 =
39 062.5 ГцBitrate: SampleRate * 2 = 78 125 Б/с
UART: 20 МГц / 80 = 250 000 Б/с
При тактировании резонатором на 24 МГцPWM: 24 МГц / 256 = 93 750 Гц
SampleRate: PWM / 2 =
46 875 ГцBitrate: SampleRate * 2 = 93 750 Б/с
UART: 24 МГц / 80 = 300 000 Б/с
При тактировании резонатором на 25 МГцPWM: 25 МГц / 256 = 97 656.25 Гц
SampleRate: PWM / 2 =
48 828.125 ГцBitrate: SampleRate * 2 = 97 656.25 Б/с
UART: 25 МГц / 80 = 312 500 Б/с
Все дробные значения округлял как учили в школе. На слух скорость воспроизведения изменяется не нисколько не значительно.
Как видно, при использовании резонаторов на 24 и 25 МГц частота дискретизации выше стандартных 44100 и следовательно можно воспроизводить аудио файлы без потери качества, нужно только их предварительно пересемплировать. При этом размер файлов вырастет незначительно. При использовании резонатора на 20 МГц частота дискретизации чуть больше 39 кГц, на слух звучит так же прилично, как и предыдущие варианты, и это не удивительно, потому что эта частота дискретизации позволяет воспроизводить частоты почти до 20 кГц, а такие высокие звуки способен услышать не каждый, тем более на рядовой акустической системе. А вот при использовании резонатора на 16 МГц уже заметно, что не хватает верхов в звучании. Оно и понятно, частота дискретизации всего 31 кГц. В этом случае можно подредачить исходник программы микроконтроллера и настроить на частоту дискретизации, близкую к 44100, она не будет кратной с частотой ШИМ, но по крайней мере появятся высокие частоты.
Я пока остановился на конфигурации с резонатором на 20 МГц, потому что на нём работает полностью стабильно и ресурсов микроконтроллера для текущих потребностей хватает. В будущем буду использовать ардуино с ATmega328 на борту, а она в отличие от ATmega8, может стабильно работать на частоте 20 МГц (судя по даташиту). А ещё у неё есть шесть ШИМ выходов, а значит можно запилить даже стерео звук! Но прошивку для микроконтроллера придётся оптимизировать. Вряд ли получится, но мечтать не вредно!
P.S. Выкладываю обещанную запись. В архиве три wav файла. Оригинальный, взятый с lossless образа, фрагмент. Он же, но сконвертированный в 8-ми битный, для сравнения. И он же, но воспроизведённый через мою вундер вафлю с записью через линейный порт звуковой карты. Так как моя система поддерживает только моно звук, то я сделал запись дважды, отдельно левого и отдельно правого каналов, после чего объединил полученные дорожки в стерео трек (просто, чтобы слушать приятнее было). Запись и сведение осуществлялись в программе Audacity. Отчётливо слышно шипение на 8-ми битной записи и его отсутствие на 16-ти битной. Не исключено, что искажения, артефакты и шипение вы услышите и на 16-и битной записи, всё зависит от используемого оборудования, но 8-ми битный звук однозначно плох.
Использованная конфигурация: частота микроконтроллера 20 МГц, частота дискретизации 39 кГц (это частота при воспроизведении с микроконтроллера, запись на компьютере велась в 44100).
Ссылка:
Архив с демо записьюP.P.S. Аудиофилы, не качайте, оно вам не надо
