... и о котором почему-то нигде не написано.
Некоторое время назад я сел осваивать интерфейс I2S для работы с аудио ЦАПом высокого качества. За спиной у меня уже был большой опыт работы с различными SPI микросхемами, I2C микросхемами, методы работы в блокирующем режиме, неблокирующем режиме через прерывания, неблокирующем режиме через DMA. В общем, мне казалось, что меня уже ничем не удивить. Но за это мы и любим мир электроники и компьютеров — он сможет удивить даже самого искушенного разработчика.
Я использую библиотеку CMSIS, а информацию о работе с устройством смотрю напрямую в RefManual, чтобы брать информацию из первых рук. I2S является частью SPI модуля в моем любимом STM32F446. Первое ощущение было, что отличаются только пины, но суть от этого не меняется. Ты кладешь данные в регистр отправки, а устройство само все отрабатывает, как ему и положено.
Я настроил модуль, запустил программу и даже проверил на осциллографе временные диаграммы. Все было ровно. Только вот ЦАП вместо какой‑то тестовой ерунды выдавал 0. Что же, пора разбираться.
Я начал читать, что пишут люди в интернете. Люди в интернете не писали ничего нового, чего не было написано в рефмануале stm32. Значит, проблема в железе.
Я купил другой I2S ЦАП на случай, что мой по какой‑то причине был испорчен. Это также не помогло.
Пришлось писать моему другу, который уже заводил I2S ЦАП. Я описал ему свою проблему, он также не понял, почему у меня ничего не работает. Однако он любезно сгенерировал для меня тестовую прошивку под мой камень и мои пины.
Прошивка запустилась, начала генерировать какой‑то синус с неверной записью знаковой информации (но было видно, что друг хотел сгенерировать синус). И тут вскрылся маааааааленький нюанс, про который все почему‑то забыли написать.
SPI, I2C, USB, USART и другие протоколы работают по принципу «инициатива волнует инициатора». У вас есть SPI ЦАП. Вы в него шлете команду «установи значение напряжения в V/2», к примеру. А дальше тысячу лет молчите. ЦАП молча стоит и удерживает то значение, которое ему сказали держать. До тех пор, пока его не обесточат, конечно. Такая же ситуация и в других протоколах. Они общаются только тогда, когда есть что сказать. А вот I2S не такой!
В I2S‑коммуникации Мастер должен непрерывно, да еще и с правильными клоками, постоянно слать то значение, которое он хочет, чтобы ЦАП удерживал. Без этого, как вы понимаете, вообще ничего работать не будет.
Занимать процессор подобной бесполезной работой неэкономично. Поэтому DMA заставляют заниматься этим. Модулю DMA показывают 2 ячейки в памяти (ведь I2S стерео): вот тут будут лежать значения I2S ЦАПа. DMA‑канал запускают в циклический режим, чтобы он непрерывно валил числа из памяти камня внутрь ЦАПа. Вы в каком‑то своем темпе меняете данные внутри памяти (да хоть вообще не меняете), а DMA с пеной изо рта шлет их в эфир. И все работает отлично, если клоки настроены и тому подобное.
Перед написанием данной статьи я еще раз проверил выдачу гугла по теме I2S, но так и не нашел описания этого маленького нюанса. Поэтому и публикую текст. Если вы это знаете, дальше вам нужны любые статьи по теме, либо реф мануал на камень.
Я же в итоге под свое устройство выбрал четырехканальный 16-битный SPI ЦАП. Но это уже совсем другая история, которую я планирую рассказать в другом посте.
Спасибо за внимание и удачи в ваших исследованиях!
Комментарии (38)
Mnemonik
14.07.2024 17:26+10Да уж, какая-то не впечатляющая интрига вышла... Ведь в даташите любого DAC/ADC на I2S будет написано - либо подавайте опорную частоту на CLKIN, либо конфигурируйте чип на автоматическое выделение опорной частоты из BCK.
GennPen
14.07.2024 17:26+4В I2S-коммуникации Мастер должен непрерывно, да еще и с правильными клоками, постоянно слать то значение, которое он хочет, чтобы ЦАП удерживал. Без этого, как вы понимаете, вообще ничего работать не будет.
И если почитать описание протокола I2S или любой нормальный даташит на микросхему его использующий это и будет написано. А еще там есть про потерю синхронизации, повторную синхронизацию и еще много интересного.
Hidden text
jonic
14.07.2024 17:26+2Внимательно читал даташиты и не сталкивался с такими проблемами :) разве что для mtk пришлось паяться к пяточку что бы мастер клок вытащить, но в целом об этом на этой плате никто не думал, а я чет баловался.
MinimumLaw
14.07.2024 17:26Ну для первой статьи пойдет. Кратко суть проблемы изложена и без меня. В целом да - вопрос о чем писать он такой... К счастью на Хабре уже довольно давно можно полноценно жить не имея за плечами написанных статей. Местами это очень плохо, а местами... Как было в старой рекламе из 90-ых: "Иногда лучше жевать, чем говорить".
Так что с почином. Но продолжать в таком духе наверное не стоит.
goldexer
14.07.2024 17:26+2Так и я однажды, не дочитав мануал, полез заводить CAN на STMке. Оказалось, что и без трансивера контроллер обидится, и даже с ним линию с другой стороны надо хоть чем-то прикрыть... вроде очевидно, но когда только начинали осваивать микроконтроллеры, то делали вот такие же элементарные ошибки. Благо, у can-а ещё есть регистры ошибок, наглядно показывающие, почему обиделся камень, а Keil со своим драйвером обеспечивает просмотр переменных и регистров в реалтайме, а не только на паузе/по шагам, так что после AVRок отловить и наладить не составило труда.
rukhi7
14.07.2024 17:26+2В I2S-коммуникации Мастер должен непрерывно, да еще и с правильными клоками, постоянно слать то значение, которое он хочет, чтобы ЦАП удерживал. Без этого, как вы понимаете, вообще ничего работать не будет.
что-то я маленько сомневаюсь что это требование надо относить к "I2S-коммуникации". На сколько я понимаю это требование происходит из того что управляемая по I2S микросхема не имеет памяти и поэтому работает по клокам из I2S шины. Теоретически если повесить на ту же I2S шину микросхему с памятью она будет в состоянии "запоминать". Так что это не то что задано I2S, это все таки больше принцип работы конкретной микросхемы, читайте ПДФ-ки на микросхемы очень внимательно!
Mnemonik
14.07.2024 17:26+1Нет, это исходит из того что I2S это звук который передаётся с какой-то дискретизацией, 44100, 48000,.. вот эти вот смешные цифры из эпохи mp3, - это частота звукового цифрового потока. и I2S как протокол не подразумевает что эта частота всегда одна и та же, или какого-то соглашения между источником и потребителем о том какой она будет на всё время работы.
Живой пример - вы проигрываете файл mp3 на компьютере mp3 44100, он поступает на DAC как I2S прямо со своей частотой 44100. А потом вы берёте и открываете другой файл, который имеет частоту 48000. Тут возможно было несколько подходов как это могло бы быть сделано:
I2S мог иметь что-то типа SPI входа по которому можно было бы "устанавливать" текущую частоту, которую он как вы выражаетесь "запоминал бы". Но это бы означало внедрение во все чипы I2S какой-то рудиментарной микроконтроллерной логики, довольно дорогое решение для дизайна IC. Причём нужно было бы поддерживать эту логику с двух сторон - источник управляемый проигрывателем ставил бы её, а получатель ожидал. Довольно сложная схема.
I2S мог бы работать на установленной на старте частоте, и тогда бы любой источник должен был бы перекодировать все проигрываемые файлы к этой частоте, что про конверсиях 48000 -> 44100 давало бы "артефакты", ну просто потому что это не 1:2 и даже не какой-нибудь 11:54, - там очень неочевидная дробь, и заодной вызывала бы сердечный приступ и аудиофилов на форумах одним упоминанием "цифровых артефактов перекодировки" (кстати спойлер, - современные устройства практически не дают артефактов. я использую это в хардварном I2S микшере с несколькими входами и мне не удалось услышать ничего что можно и близко назвать искажениями). и это ещё ладно на компьютере, а как бы страшно усложнялись хардварные проигрыватели, где микросхем общающихся по I2S обычно несколько.
I2S мог быть сделан так, что в него всегда поступает опорная частота, и он просто работает на этой частоте разбирая поступающий по другому пину поток с этой скоростью. Меняется частота - меняется скорость, меняются места где ожидаются начала битов. На выходе это всё равно превращается в гладкую аналоговую синусоиду, какая разница с какой частотой там исходная лесенка... По этому пути и пошёл дизайн. Просто, надёжно, дешево.
rukhi7
14.07.2024 17:26+1I2S мог иметь что-то типа SPI входа по которому можно было бы "устанавливать" текущую частоту, которую он как вы выражаетесь "запоминал бы".
вряд ли это было бы решением, нам ведь надо или 44тысячи или 48 тысяч семплов отправить в секунду, мы управляем тем за сколько мы один семпл отсылаем, приемной микросхеме нет смысла это запоминать, потому что она просто должна откуда то взять следующий семпл.
Mnemonik
14.07.2024 17:26ну так принимающая микросхема должна же как-то понимать текущую частоту входящего потока? представьте что в канале тишина, а это постоянно происходит в звуке, при частоте семплов в 44100 в секунду, - огромная часть семплов будет просто пустая. как из стабильного нуля в канале понять с какой он частотой поступает?
определять из первых семплов которые отличаются от нуля? можно, на это нужно примерно несколько десятков семплов по спекам любого DAC. выглядеть это будет как отрезание доли миллисекунды на каждой смене тишина-звук. что в принципе может быть и незаметно, настолько быстро это происходит, но как минимум выглядит как знатный костыль, и конечно же вызовет широкий спектр инфарктов на форумах аудифилов, с вечным проклятием цифрового воспроизведения как неизлечимой ереси.
rukhi7
14.07.2024 17:26+1ну так принимающая микросхема должна же как-то понимать текущую частоту входящего потока?
Если это просто ЦАП он вроде как должен запомнить последний семпл и держать значение пропорциональное на выходе, получается этот ЦАП какой то не постоянный, то есть у него получается есть какое-то время удержания значения на выходе, после которого выход падает в ноль, как я понимаю, но это свойство именно этого ЦАПа, этой микросхемы, я это имел ввиду.
Соответственно, период текущей частоты не может быть меньше времени этого удержания, иначе искажения начнутся.
jonic
14.07.2024 17:26Все как раз таки логично, если клока нет Х времени, нужно перейти в 0, иначе дадим на канал постоянное напряжение которое динамики не любят, да и усилители тоже.
AVKinc
14.07.2024 17:26+1I2S шина синхронная и если вы посмотрите как вообще работают синхронные шины то поймете что там нет такого понятия как "стартовая частота" и вообще каких либо стандартных скоростей. Биты идут по фронту сигнала синхронизации и мы в процессе работы можем менять частоту как угодно. И сигнал синхронизации как бы и есть эта самая опорная частота.
Flammmable
14.07.2024 17:26+4Большое спасибо за статью. И поздравляю с вступлением в сообщество :)
Вам тут некоторые пуристы ставят на вид, что "в документации же всё написано". Однако быстрый ответ на стереотипную проблему, которая из раза в раз встречается у новичков (несмотря на то, что её решение формально есть где-то там в недрах документации) лично я субъективно и претенциозно считаю весьма полезным. Кому-то вы здорово сэкономили время ;)
Пожалуйста, продолжайте в том же духе :)
vadjuse Автор
14.07.2024 17:26Спасибо за теплые слова, я понимаю, писал пост на своем опыте и знаю, что не я один такой тугой.
Flammmable
14.07.2024 17:26Вообще, на мой субъективный и претенциозный взгляд, с пуристами в электротехническом сообществе есть достаточно неприятные проблемы.
Примечательно кстати, что пуризм того или иного комментатора не исключает отсутствие у него компетенций по обсуждаемому вопросу )))
vadjuse Автор
14.07.2024 17:26+4Мой пост не совсем про I2S. Скорее он про пример неясного дизайна, когда модуль работает не так, как ты ожидаешь.
Вся философия IT пронизана инкапсуляцией, когда пользователь не должен знать, что внутри черного ящика. Пользователь должен настроить (инициализировать) ящик, а дальше скармливать или принимать из ящика данные.
Ящик со своей стороны должен делать всё, чтобы с данными обошлись правильно, и они достигли точку назначения в правильном виде, понятном для этой точки назначения.Я пользователь, хочу увидеть на ЦАП V/2. Готов положить это число в регистр. Жду от модуля, который называет себя I2S, что он сделает всё красиво.
Понятно также, что разрабы STM32 сэкономили на интеграции функционала DMA внутрь I2S, чтобы не множить сущее.
По поводу "read the fkn manual" вспоминаю книгу "Психбольница в руках пациентов", когда боль от взаимодействия с плохим интерфейсом воспринимается в качестве достижения, а не проблемы, которой быть не должно. С этим я, конечно, не согласен.Flammmable
14.07.2024 17:26+2Вся философия IT пронизана инкапсуляцией, когда пользователь не должен знать, что внутри черного ящика.
На месте пуристов я бы сейчас рассказал, что-то вроде "У нас здесь всё не так как у вас. Не смейте касаться священной электроники своими нечистивыми руками, которыми вы хватались за инкапсуляцию!!11". Что конечно бред и вздор. Но подобное тем не менее периодически излагается. Поэтому не рекомендую оправдываться перед пуристами :)
боль от взаимодействия с плохим интерфейсом воспринимается в качестве достижения, а не проблемы, которой быть не должно
"Не читал, но одобряю" (с). Чувствую, книжка придётся мне по душе )))
vadjuse Автор
14.07.2024 17:26+2Она из девяностых - уже немного устарела. С приходом Эпол некоторые классические разрывы шаблонов уже пофиксены, но не все.
UFO_01
14.07.2024 17:26Но подобное тем не менее периодически излагается
Мне кажется, это пошло от тех, кто работал на военку или делал оборудование для жд/метро и прочих чувствительных отраслей с жуткой штукой под названием доказательство безопасности. Там проще сделать своё, нежели доказывать безопасность и отказоустойчивость чего-то готового.
Flammmable
14.07.2024 17:26Мне кажется, это пошло от тех, кто работал на военку или делал оборудование для жд/метро и прочих чувствительных отраслей с жуткой штукой под названием доказательство безопасности.
Ни в коем случае! Это пошло от людей, которые нахватались по вершкам и дуют щёки от важности.
Flammmable
14.07.2024 17:26+1А вот и оно: )))))
Программисты вдруг стали -электронщиками. Прошивку на чем только уже не пишут. Проблема одна только - это плодит псевдо электронщиков. Какие-то компании это устраивает, но часто это мелкие конторы, с мелкими проектами и мелкими бюджетами и маленькой ответственностью. И поверьте мужик который делает конечные автоматы на логике более квалифицирован, чем почитатели "давайте воткнëм МК лампочкой помигать".
alcotel
14.07.2024 17:26+2Вы взяли аудио-ЦАП. Они гарантированно хорошо воспроизводят звуковые частоты 20Гц-20кГц, но имеют ряд недостатков для других применений. Собственно, за счёт недостатков и получается сделать 16- или более -битный ЦАП таким дешёвым.
Аудио-ЦАП имеет право вообще не пропускать постоянную составляющую сигнала на выход. В звуковом тракте это не нужно, и бывает даже вредно. Но это в описании на конкретный чип надо смотреть. Некоторые DC-составляющую пропускают, некоторые - нет.
Точность выходного напряжения - никакая. У тех аудио-ЦАП и кодеков, с которыми я работал, по описанию повторяемость +-10% даже между двумя стерео-каналами. Для аудио-применений этого достаточно, но для других - не всегда. Тоже можете в описании к вашему чипу проверить.
Это сигма-дельта-ЦАП. Внутренняя структура у него очень простая, напоминает ШИМ. И линейность очень хорошая для аудио. Но чтобы он вообще работал, ему нужна тактовая частота. Причём желательно достаточно чистая, иначе фокуса не получится. И синхронная с данными, а не абы-какая. Потому что на этой частоте работает антиалайзинговый фильтр.
В выбранной Вами СТМке, как я понял, и так есть 2 встроенных ЦАП. Если не нужна большая скорость регулировки напряжения - во всех мк есть ШИМ. Аудио-ЦАП - штука, оптимизированная для конкретной задачи, и натягивание её на другие задачи требует внимательного прочтения описания.
alcotel
14.07.2024 17:26+1Я пользователь, хочу увидеть на ЦАП V/2. Готов положить это число в регистр. Жду от модуля, который называет себя I2S, что он сделает всё красиво.
Понятно также, что разрабы STM32 сэкономили на интеграции функционала DMA внутрь I2S, чтобы не множить сущее.
То, что предлагается для STM32 из коробки задарма - это просто обёртки для того же вкладывания числа в регистр. Кроме изучения библиотек всё равно приходится также читать доку на чип. Не пользователь Вы. Большинство вещей из так называемого "хардваре абстракшын" нихрена не "абстракшын", и часто даже не получается перенести код между разными семействами STM32F0, F1, F4.
В общем, если хотите не задумываясь сделать типа
echo "42" > /dev/i2s1
это другой уровень программной поддержки, мегабайты стороннего кода и вряд-ли бесплатно. Но говорят, тоже бывает. Слышал, даже на Python как-то люди для мк пишут.
AndronNSK
14.07.2024 17:26То, что вы ожидаете какую-то чушь от модуля - это ваша проблема. Проблема вашей неосведомленности, а не проблема какого-то там дизайна.
vvzvlad
14.07.2024 17:26Ух ты, надо понимать, как работает протокол, который ты используешь! Полезное знание.
Ну, и такое поведение в целом логично, потому что мало кому хочется, чтобы на динамиках осталось последнее постоянное напряжение, если вдруг от ЦАП отключили то, что на него гнало звук.
vadjuse Автор
14.07.2024 17:26обычно микропроцессор гонит данные на цап. Как можно отключить его от цапа?
gaba_m
14.07.2024 17:26Пользуясь случаем, задам вопрос. Типичный звуковой тракт состоит из цифрового источника -> цап -> усилителя мощности. Есть ли реализации без промежуточного аналогового преобразования, в которых цифра источника непосредственно задает скважность шима усилителя мощности? Где об этом почитать, по каким ключевым словам искать?
alcotel
14.07.2024 17:26Да, делают так. Усилитель класса D совмещают сразу с процессором. Можете поискать по ключевым словам "DSP PWM sound" или "ЦОС ШИМ звук". Вот пример, хоть и без кода, но с математикой и измеренными характеристиками.
Но для хорошего качества совсем без аналогового преобразования не получается - приходится оцифровывать напряжение питания, чтобы скомпенсировать его в ШИМе.
2128507
Можно еще короче: "Читайте даташиты, понимайте прочитанное буквально, не лезьте в DSP без понимания, что такое есть звук."
vadjuse Автор
Я в посте даже не предлагал генерировать звук, просто пытался выполнить цифро-аналоговое преобразование через I2S устройство. Поэтому не понимаю, причем тут DSP, и причем тут понимание звука.
Звук, кстати, можно сгенерировать даже через R2R лестничный ЦАП, важно чтобы смена сэмпла происходила на стабильной частоте, да хоть 54321 Гц.
Прошу, не примешивайте то, что к делу не имеет отношения.
sappience
Открою вам тайну, в названии протокола I²S буква S означает Sound.
firehacker
Почему это вдруг на стабильной? На какой нужно, на такой и меняйте. Я применял смену частоты для того, чтобы играть питчем звука. С другой стороны, я применял подход с абсолютно нерегулярной сменой сэмплов: когда крутизна нарастания сигнала должна была быть высокой, то и частота была высокой, когда же крутизна была низкой, то и смена сэмплов происходила редко.
vadjuse Автор
я имел в виду, чтобы звук был без нежелательных глитчей (если глитчи желательны, можно не париться за стабильность).
Чтобы сгенерить некоторый локальный питч, надо стабильно держать частоту. Если хотите менять питч, то либо меняете прескейлер таймера, либо меняете скорость изменения фазы осциллятора.
Но я вам не скажу за всю Одессу, методов напридумывать можно много.
Именно поэтому, кстати, 44100 = (2*3*5*7)^2
firehacker
И я то же самое имел в виду. Чтобы звук был без нежелательных глитчей, нет такого правила, что новый уровень на АЦП должен выставляться строго через фиксированные периоды времени.
Если у вас LPCM, где с АЦП отсчеты снимались с такой строгой периодичностью перед созранением звука, тогда да. Но кроме LPCM есть куча разных способов представить звук или меняющийся во времени сигнал. Сигма-дельта и всякие дифференциальные адаптивные кодировки.
В том числе, вместо того, чтобы хранить, каким в абсолютном выражении должен быть выход ЦАП через постоянные интервалы времени, можно хранить, в какую сторону сигнал должен измениться и через какой промежуток времени должно произойти это изменение на цену LSB.
Что такое «локальный питч»? Что означает фраза «сгенерировать локальный питч»?