Друзья, привет!
Сегодня, с замиранием сердца, начинаю разбираться с программированием новых для меня недорогих 32-битных китайских микроконтроллеров на ядре RISC-V серии CH32V003. Начинаю с изучения основных библиотек, работы с входами-выходами, системным таймером и аналого-цифровым преобразователем (АЦП). В качестве полезной нагрузки я спроектирую и изготовлю простой светодиодный индикатор уровня звука (в просторечье и по-научному - UV-метр), работающий от микрофона.
Все подготовительные действия (выбор программного обеспечения и изготовление простейшей отладочно-прошивочной платы) я описал в предыдущем материале.
По ходу дела начала накапливаться информация о полезных нюансах работы с чипами серии CH32V003, которой я поделюсь в отдельной статье.
Как и раньше в качестве подопытного контроллера я буду использовать самый младший (по количеству выводов, поскольку кристаллы у всех одинаковые) экземпляр - CH32V003J4M6. В качестве среды программирования я остановился на VS-Code с надстройкой PlatformIO.
Поехали!
Немного теории про измерение уровня звука
В научных публикациях между терминами "уровень звука" и "громкость" проводится четкое различие. Первый термин отражает результаты объективных измерений в соответствии с некоторым алгоритмом, второй - субъективную оценку слушателем. Часто эти термины используются без дифференциации. Я тоже так буду делать, хотя будет и алгоритм и устройство для оценки. В конце-концов, я не научную публикацию пишу, и постараюсь теорию изложить кратко.
Алгоритмов для определения уровня звука существует предостаточно. Есть как аппаратные устройства, так и программные реализации. В простейшем случае нужно измерять среднее или максимальное значение сигнала за определенный период и использовать его для управления линейкой светодиодов. Но при таком способе индикатор будет некрасивым, поскольку у него будет отсутствовать инерционность. То есть, если звук пропал, то индикатор мгновенно обнулится. Такое дергание индикатора неприятно для глаз. По-хорошему нужно, чтобы индикатор уходил в "ноль" плавно. То же самое касается и нарастания громкости, хотя здесь скорость реакции должна быть гораздо выше. Тем не менее индикатор не должен мгновенно реагировать на короткие "чиркания".
Подобные соображения привели меня к необходимости моделирования поведения некой инерционной системы. Например емкости с водой, наполнение которой происходит быстро, а слив медленно. В радиотехнике емкость с водой - это конденсатор, который заряжается большим током, а разряжается малым.
Общую схему такого инерциального устройства я себе представил так.
На вход схемы сигнал подается от, например, микрофонного усилителя. Сигнал, в общем случае, двухполярный. Чтобы сигнал мог только заряжать конденсатор его надо продетектировать (пропустить только положительную полуволну). Сделает это диод D1. Таким образом, конденсатор, который и будет той самой емкостью с водой будет заряжаться только положительной полуволной сигнала. Ток зарядки конденсатора, а, следовательно и скорость зарядки будет определяться входным сопротивлением Rin. Вместо диода можно использовать, например, диодный мост, тогда мы инвертируем отрицательную полуволну в положительную область и заставим ее тоже работать на зарядку. Получится двухполупериодный выпрямитель.
А вот когда уровень сигнала упадет меньше чем напряжение на конденсаторе, диод окажется заперт и конденсатору ничего не останется кроме как разряжаться через резистор Rout. То есть, скорость заряда определяется сопротивлением Rin, а скорость разряда - сопротивлением Rout. Для быстрого заряда Rin должно быть небольшим, а для медленного разряда Rout, наоборот, большим. Как видно из последнего графика (синяя кривая), при пропадании звука конденсатор будет медленно разряжаться через Rout и уровень сигнала упадет не сразу.
Поведение такой системы мы и должны будем смоделировать при помощи микроконтроллера.
Схемотехника индикатора уровня звука на микроконтроллере
Не смотря на то, что описанную выше схему несложно реализовать аппаратными методами, я буду делать это программными средствами. В этом случае у меня остается возможность изменить алгоритм обработки на любой другой. В этом и сила цифровой обработки сигналов, а 32-битная разрядность CH32 прямо-таки подталкивает использовать его для этих целей. И хотя сам алгоритм, который мы реализуем, не очень сложен, в будущем я обязательно попробую и фильтрацию и быстрое преобразование Фурье!
Таким образом требования к аппаратной части устройства довольно просты. Нужно получить сигнал с микрофона, усилить его, желательно на всю шкалу АЦП, загнать его в микроконтроллер и организовать вывод уровня звука на линейку светодиодов.
Если про звук все понятно, то что со светодиодами? Я решил, что сделаю индикатор "в лоб". Сколько ножек останется, столько светодиодов и повешу. Да, может быть будет немного! Да, лучше использовать адресные светодиоды! Но нет, я этого делать не буду! Для изучения свойств микроконтроллера пойдет и так. А адресные светодиоды у меня еще впереди...
И так, схемотехника устройства получилась следующая.
Микрофонный усилитель реализован на операционном усилителе AD8605. Это недорогой усилитель rail-to-rail. Это значит, что он способен усиливать сигнал практически до напряжения питания. Так можно будет использовать всю шкалу АЦП. Кроме того, усилитель хорошо работает с однополярными источниками питания и имеет хорошие частотные характеристики вплоть до единиц Мегагерц. Мне очень понравилось с ним работать в ходе проектирования осциллографического пробника.
Краткие пояснения. Для электретного микрофона Q1 требуется напряжение питания, которое подается через резистор R1. Его сопротивление можно взять в диапазоне от 4,7 до 10 кОм. Если бы нам нужен был чистый звук, то в схему питания нужно было-бы установить еще и RC-фильтр для снижения наводок по питанию. Но небольшие помехи при измерении громкости нам погоды не сделают. Поэтому схему я упростил. Разделительный конденсатор C1 убирает постоянную составляющую с микрофона. Делитель напряжения на резисторах R3,R4 выводит рабочую точку (уровень нуля) операционного усилителя на уровень половины напряжения питания. Пара резисторов R2,R5 задает коэффициент усиления, который равен отношению R5/R2 и в нашем случае составляет около 1000. Это позволяет улавливать даже относительно тихие звуки. Резистор R6 (100...150 Ом) выполняет двойную функцию. Во-первых, он снижает влияние емкостной нагрузки (на входе АЦП - емкость) на устойчивость схемы, предотвращая ее самовозбуждение. А во-вторых, он защищает контроллер и операционный усилитель от больших токов в случае, если 8-я ножка контроллера будет по ошибке сконфигурирована на выход. Конденсатор C3 нужно ставить только если усилитель все-же возбуждается. Увеличивать для этого сопротивление R6 нежелательно, поскольку это снижает скорость зарядки конденсатора АЦП. Как следствие может пострадать точность оцифровки сигнала. На практике этот конденсатор мне приходилось ставить очень редко.
Микрофонный усилитель подключается к 8-й ножке контроллера. Это не случайно! Если использовать эту ножку в качестве выхода, то последующая прошивка контроллера будет заблокирована, поскольку на эту же ножку повешена функция обмена сигналами с программатором (SWIO). "Раскирпичить" контроллер можно при помощи утилиты WCH-LinkUtility. Пункт меню: "Targrt->Clear All Code Flash-By Power Off". Просто делать это каждый раз перед прошивкой утомительно!
На все оставшиеся выводы я решил повесить светодиоды. А всего осталось 5 выводов. Ну пять, так пять! И то неплохо! Светодиоды LED1...LED5 - обычные, 3-миллиметровые. Сопротивления резисторов R7...R11 зависят от напряжения питания. Для 5 Вольт вполне подойдет 1 Килоом. Для 3 Вольт их сопротивление можно снизить до 470...680 Ом.
Изготовление индикатора уровня звука
Схемы я обычно проектирую в онлайн редакторе EasyEDA. У них просто огромная база компонентов. Китайские микроконтроллеры разумеется тоже есть! В этом же редакторе я проектирую и печатную плату. Если ее заказывать в Китае, то редактор формирует отличный пакет документов в формате Gerber. А вот если делать прототип, то я перетаскиваю схему в SprintLayout, поскольку его мне проще подружить с программой CopperCam, которую я использую для построения программ фрезеровки печатных плат. Для фрезеровки, сверления отверстий и вырезания я использую небольшой станок CNC1610. Результат его работы выглядит так.
Правда на фото плата уже покрыта сплавом Розе. И еще. Это первая версия платы на которой я собирал прототип. Как раз на ней я 8-ю ногу назначил на вывод светодиода и получил "кирпич" после первой прошивки! В архиве, ссылка на который будет в конце статьи будет печатная плата уже скорректированная в соответствии со схемой, приведенной выше. Там все правильно.
В основном все детали для поверхностного монтажа. Резисторы и конденсаторы формата 0805. Панелька под чип и светодиоды расположены с обратной стороны. Без перемычек обойтись не удалось. Всего использовано 4 штуки из обмоточного провода диаметром 0,4 мм.
Вот так выглядит монтаж со стороны SMD-компонентов.
Еще раз хотел бы предупредить - это старая разводка в которой на 8-й ноге висит светодиод. Я потом перерезал дорожки и перепаивал перемычки и пару резисторов. У меня получилось так.
В архиве находится уже исправленный вариант платы!!!
Диоды расположены очень плотно. В местах их касания ободок следует немного сточить. Немного неудобно, но они легко точатся надфилем, а линейка получается более органичной!
Прежде всего проверяем работоспособность усилителя. Постоянный уровень на его выходе должен быть равен половине напряжения питания, а сигнал, если громко говорить в микрофон, должен входить в насыщение.
Больше никакой наладки устройство не требует.
Программирование индикатора уровня звука
Теперь самое интересное! Это мой первый код для CH32V003, поэтому в тексте программы будет очень много комментариев. Даже не столько комментариев, сколько заметок, которые рождались по ходу написания программы. Проект для PlatformIO также будет в архиве в конце статьи.
Полагаю, что из комментариев можно будет понять основную логику работы приложения, тем не менее обращу внимание на ее самые, на мой взгляд, важные особенности.
Для запуска АЦП с заданной периодичностью я решил использовать системный таймер. В обычном режиме он тикает с тактовой частотой и используется для измерения временных интервалов внутри программы или для организации задержек. В модуле system.h есть даже базовые функции для этого: DLY_us, DLY_ms. Но, изучать так изучать! Я решил использовать режим системного таймера при котором цикличность счета определяется не его разрядностью, а значением, записанным в регистр сравнения. Этот режим интересен тем, что по достижении значения сравнения таймер устанавливает специальный флаг, после чего счетчик сбрасывается и начинает счет снова. Значение этого флага можно отслеживать в основном теле программы и запускать какое-либо действие. Я буду запускать преобразование АЦП и дожидаться его окончания, после чего обрабатывать полученный отсчет. Кстати, флаг надо не забывать сбрасывать вручную. Таким образом мы обеспечим оцифровку сигнала с требуемой периодичностью.
Кто-то возразит - а как же прерывания? Правильно! Прерывания это святое! Тем более, что в нашем случае контроллер не будет выполнять полезной работы во время преобразования. Это плохо? В общем случае конечно плохо! Но для нашего случая значения не имеет. А прерывания и вообще рационализацию кода я оставлю для следующей серии...
И все бы было хорошо, если бы не один нюанс. Изменение режима работы системного таймера не позволяет использовать утилитарные функции задержек. Да бог бы с ними, подумал я, их использование все-равно дурной тон! Ан нет! Оказывается, эти утилиты могут использоваться некоторыми функциями инициализации. Например, функцией инициализации АЦП, описанной в библиотеке gpio.h - ADC_init.
Ладно, простим библиотечной функции некоторую вольность и переключим таймер в нужный нам режим (моя функция SYSTICK_init) после инициализации всей периферии.
В разделе определений вам наверняка бросится большое количество констант.
Не все они нужны непосредственно в программе. Большая их часть предназначена для того, чтобы сделать программу независимой от тактовой частоты и частоты дискретизации АЦП. Для программы важны по сути лишь несколько.
CAP_CHARGE_SPEED - задает скорость зарядки нашей емкости в единицах шкалы АЦП в микросекунду. По-умолчанию - 1 уровень в микросекунду. Это значит, что конденсатор заряжается от 0 до предела за 512 микросекунд (0,5 мс). Вся шкала АЦП содержит 1024 уровня. Уровень нуля - 512. Получается что максимальный уровень сигнала - 512 уровней вниз от нуля и вверх.
CAP_DISCHARGE_SPEED - скорость разряда емкости. По-умолчанию гораздо меньше - 0,001 уровней в микросекунду. То есть полная разрядка примерно за 0,5 секунды.
CAP_SCALE_GRADE - количество разрядов на которые сдвигаются значения сигнала при расчетах. Чтобы обойтись целочисленной арифметикой и не оперировать значениями типа 0,001. Сигнал умножается на некую постоянную величину путем его сдвига влево на соответствующее число разрядов. По-умолчанию 10 разрядов. Замечание. Если захотите использовать более медленную скорость разрядки конденсатора, например 0,0001, число разрядов сдвига нужно будет увеличить еще на 4, то есть до 14. Не хотите это на ATtiny13 провернуть!?
В программе константы CAP_CHARGE_SPEED и CAP_DISCHARGE_SPEED непосредственно не используются. Вместо них используются значения, приведенные к периоду дискретизации: CAP_CHARGE и CAP_DISCHARGE. Собственно из-за этого приведения и образовалось так много промежуточных констант.
Частота дискретизации выбрана равной 10 кГц (ADC_SAMLE_FREQ_HZ). Этого вполне достаточно. Да, громкость чистых высокочастотных звуков не оценить, но таких звуков в природе и не встречается!
АЦП работает в самом простом - программном режиме. Запускаем преобразование и ждем его окончания.
Оцифрованный сигнал подвергается двухполупериодному выпрямлению (детектированию), после чего его уровень используется для зарядки-разрядки емкости (переменная capacitor_voltage). В зависимости от значения этой переменной зажигаются светодиоды.
Объем скомпилированной программы составил 868 байт.
Это много. Можно уменьшить байт на 300...400 если не использовать стандартные библиотеки работы с GPIO и не инициализировать ненужную периферию, которая по-умолчанию запускается в модуле system.h. Но, пока такую задачу я перед собой не ставил.
Результат работы устройства на громком звуке выглядит так.
Конечно фотография не передает динамики. Для того, чтобы получить представление о работе устройства в реальном масштабе времени я подготовил небольшой ролик.
Устройство запитано от 3-вольтового элемента CR2032. Светит ярко, чувствительность высокая! Кто захочет просто повторить - не обязательно использовать колодку и переходник для чипа. Можно скорректировать печатную плату для более компактного варианта.
Файлы, необходимые для повторения индикатора уровня звука выложены в одном архиве.
Заключение
Первые шаги сделаны! Такие загадочные и непонятные китайские микроконтроллеры стали роднее и ближе! 32-битная архитектура пока проявляет себя с самой хорошей стороны. Немного удручает неполнота оригинальной документации. Не все описано, есть ошибки и несостыковки, например в обозначениях одних и тех-же регистров, но, пока ничего такого, что нельзя было бы преодолеть! К тому же многое постигается по аналогии с STM32.
А вот мощь, заключенная в маленьком корпусе потрясает и вдохновляет! Идей становится все больше, а вот ATtiny13 я скажу прощай и спасибо за чудесные часы, проведенные вместе!
Спасибо, что читаете-смотрите Terrabyte! Подписывайтесь, если вам интересна радиолюбительская тематика, микроконтроллеры, мини-ПК, необычные компьютерные решения и инновационные разработки! Спасибо всем, кто поддерживает нас с братом своими комментариями и лайками!
Наша группа ВК: https://vk.com/terrabyte
Наш канал на VK-Video: https://vk.com/video/@terrabyte/all
Наши разработки: