Русский
Русский
English
Статистика
Реклама

Шим

Из однобитной музыки мяубитную

27.03.2021 16:18:35 | Автор: admin
Прошлая статья, посвящённая обучающей плате Meowbit и реализациям Python для неё, завершалась упоминанием неспособности CircuitPython проигрывать музыку одновременно с игрой: писать на Python обработчики прерываний CircuitPython не позволяет, а без этого задержка на время перерисовки экрана (порядка 0.15 с) подвешивает звук. Тем не менее, фоновый звук бывает нужен достаточно часто, и для большинства поддерживаемых плат (100 из 189) CircuitPython включает модуль audioio либо audiopwmio, реализующий фоновый звук родными для платы способами. К сожалению, для Meowbit (и вообще для плат на основе STM32) не реализован ни тот ни другой модуль; но в opensource-проекте это дело поправимое :)


Найдите пасхалку в фото

Прежде всего: почему для проигрывания звука есть два разных модуля с полностью одинаковыми API, и на разных платах поддерживается либо тот, либо другой?

Вот как в аудиоредакторе (например Audacity) выглядит секунды обычного (16-битного)
WAV-файлa:


Значение плавно меняется в пределах примерно от 0.2 до +0.2 условной единицы. Если таким же образом менять напряжение, подаваемое на электродинамический громкоговоритель, то так же плавно будет колебаться мембрана примерно от 0.2 своего максимально возможного отклонения в одну сторону, до 0.2 отклонения в другую сторону. Модуль audioio реализует именно такое проигрывание звука через ЦАП плавно меняет напряжение на выводе, соединённом с динамиком.

Но в Meowbit вместо динамика стоит дешёвая пьезопищалка, неспособная отклонять мембрану в промежуточные положения: она из одного крайнего положения очень быстро переходит в другое крайнее, и остаётся там до следующего перехода. Это можно представить как звук с разрешением в один бит на сэмпл:


Таким способом невозможно передавать изменение громкости звука, но теоретически возможно передать все имеющиеся в нём гармоники если параллельно с 32768-кратным понижением разрешения увеличить во столько же раз (т.е. до сотен мегагерц) частоту дискретизации. Маловероятно, что мембрана пьезопищалки сможет колебаться с такой частотой; но это можно использовать и в свою пользу если научиться переключать напряжение на пищалке, когда мембрана на полпути, то можно издавать звуки промежуточной громкости! Поиск по патентам подтверждает, что люди действительно исследуют возможности использовать пьезопищалку таким образом. Мы в эти дебри углубляться не будем, и оставим обычную для WAV частоту дискретизации в десятки килогерц. Для музыки, где основные гармоники в районе килогерца, этого достаточно; речь, однако, превращается в едва разборчивый шум. Можете сравнить, как воспринимается использованный мной восьмисекундный образец звука, воспроизведённый на однобитной пьезопищалке: вначале оригинал, затем однобитная версия, затем запись Meowbit-а микрофоном.


Модуль audiopwmio реализует проигрывание звука через цифровой вывод посредством ШИМ: однобитная аудиозапись превращается в последовательность задержек между переключением
вывода на противоположное значение.

Итак, общий план реализации audiopwmio для Meowbit таков:

  1. Переводим переданную пользователем аудиозапись в ШИМ-формат (список задержек между переключениями);
  2. В обработчике прерывания от таймера переключаем вывод и настраиваем таймер на задержку до следующего переключения. Этот код можно с минимальными изменениями позаимствовать из стандартного модуля pulseio, реализующего в точности то, что нам нужно переключение вывода в соответствии с переданным списком задержек но не позволяющего коду на Python выполняться параллельно с переключением.

Не сразу было очевидно, что надо позаботиться ещё об одном аспекте реализации буферизации аудио. Мой тестовый восьмисекундный образец занимает 8220502 340 КБ это втрое больше, чем всё ОЗУ Meowbit; следовательно, загружать его в память придётся по кускам. Стандартная реализация audiocore.WaveFile загружает WAV-файл кусочками по 256 байт, что соответствует 128 сэмплам или 5.8 мс времени проигрывания. Это значит, что в среднем каждые 5.8 мс audiopwmio должен будет запрашивать повторное наполнение буфера; нет выхода, кроме как разместить этот вызов в том же самом обработчике прерывания от таймера иначе перерисовка экрана может задержать наполнение буфера на добрую сотню миллисекунд. Проблему это, однако, не решает до конца: что произойдёт, если прерывание от таймера случится во время перерисовки экрана? Экран Meowbit подключён через шину SPI, флеш-диск через неё же, значит обращение ко флешу во время перерисовки экрана всё равно невозможно!

В результате получилась реализация audiopwmio, способная воспроизводить аудиозаписи из памяти (или процедурно генерируемые) в максимально достижимом на Meowbit качестве; но аудиозаписи из файлов воспроизводятся только при отсутствии одновременных с этим обращений к экрану и ко флешу. Для звукового сопровождения несложных игр этого вполне хватит. PR с моей реализацией уже больше недели дожидается ревью, и когда audiopwmio для Meowbit появится в официальной версии CircuitPython неизвестно; но это не мешает любым желающим самостоятельно скомпилировать себе CircuitPython с моей добавкой.



Подробнее..

Простой способ отключения мерцания подсветки LCD мониторов и телевизоров, Flicker-Off

04.03.2021 16:23:55 | Автор: admin
''Свобода* лучше, чем несвобода*''
*от мерцания подсветки

Многие наверняка замечали, что за некоторыми мониторами глаза устают больше, чем за другими с такой же диагональю и разрешением.

Одной из причин этого может быть мерцание LED светодиодов подсветки монитора, вызванное ШИМ регулированием их яркости.

КДПВ

На habr.com уже было несколько статей, посвященных как описанию этого явления, например Пульсация экранов телевизоров и Увидеть пульсацию, так и методам борьбы с ним, например за счет увеличения частоты ШИМ подсветки Разгон подсветки монитора, или даже полной переделке подсветки (ссылки внутри статьи Как оценить пульсацию светодиодных ламп).

Очевидно, что подобные методы лечения требуют определенной прямизны рук, умения работать с напильником паяльником, и сопряжены с некоторым риском (и уж в любом случае потерей гарантии).

Однако иногда можно добиться вполне удовлетворительного результата, не вставая с дивана компьютерного кресла, ограничившись лишь подстройкой монитора, буквально отключив мерцание его подсветки (ну, или по крайней мере существенно его уменьшив). Так сказать,Flicker-Off.
Подробности под катом:

Сразу оговорюсь, что данный способ работает не во всех случаях, но Какое, право, дело вам до тех всех..., если именно вам* он сможет помочь!
  • Впрочем, с некоторыми оговорками это может помочь и в ряде других случаев (правда тоже не всех) об этом в конце статьи.

Сначала определимся Что такое не везет и как с ним бороться откуда вообще берется мерцание подсветки, и можно ли этого избежать, ориентируясь при выборе монитора на надпись Flicker-Free на его шильдике.

Причина использования ШИМ регулирования яркости


Дело в том, что при изменении величины тока через светодиод помимо его яркости, меняется и цветовая температура при снижении тока она обычно уходит в синеву (в отличие от ламп накаливания, которые при работе вполнакала уходят в красноту). Но, в отличие от весьма инерционных ламп накаливания, LED светодиоды* практически безынерционные, время их включения и выключения менее миллисекунды, что позволяет регулировать их яркость за счет Широтно-импульсной модуляции (ШИМ, PWM).
  • С CCFL лампами ситуация аналогичная, кроме того они нестабильно работают при пониженном напряжении.

Т.о. часть времени (в зависимости от скважности ШИМ) светодиод работает на 100% ярости*, (условно говоря в полный накал), а часть времени выключен.
  • Существуют светодиоды, в которых за счет технических ухищрений удаётся поддерживать стабильную цветовую температуру при снижении яркости в некоторых пределах, но такие светодиоды существенно дороже, да и рабочий диапазон регулирования их яркости не бесконечен, так что на малой яркости с ними тоже вынуждены использовать ШИМ.

Обычно в мониторах и телевизорах с LED подсветкой используется частота ШИМ от немногим более 100 Hz и выше, вплоть и до нескольких тысяч Hz.* Очевидно, что чем выше частота ШИМ, тем менее она заметна для глаз.
  • Чем руководствуются изготовители, в одном случае выбирая частоту ШИМ например 120 Hz, а в другом 6 kHz оставим за рамками данной статьи.

Отметим еще такой параметр ШИМ, важный для дальнейшего рассмотрения, как коэффициент заполнения (величина, обратная скважности).

ИМХО можно предположить, что при одинаковой рабочей яркости например 120 кд/м2, подсветка с частотой ШИМ 120 Hz и рабочей яркостью светодиодной подсветки 150 кд/м2 (коэффициент заполнения 80%) будет менее агрессивна для глаз, чем подсветка с частотой ШИМ 600 Hz и рабочей яркостью светодиодной подсветки 500 кд/м2 (коэффициент заполнения 25%).

Хотя визуально в первом случае мерцание можно будет заметить, а во втором скорее всего нет. Такая вот загогулина(с)

Теперь несколько слов о

Flicker-Free


Казалось бы тут все предельно ясно. Любой лингвист, читающий Шекспира в подлиннике, скажет, что flicker-free это немерцающий, свободный от мерцания. Ну а раз нет мерцания, то и нет проблемы мерцанием от ШИМ.

Однако с точки зрения техники это к сожалению не совсем так. Вернее так-то оно так, но не совсем, потому что иногда могут быть некоторые нюансы.

Например wikipedia нам сообщает кучу общих слов о Flicker-free, что богатым и здоровым быть лучше, чем бедным и больным, и что свобода* лучше, чем несвобода* (*имеется в виду свобода от мерцания), при этом единственное имеющееся примечание идет на страничку о ШИМ, которую мы только что рассматривали. Никаких ссылок на внешние источники, например на отраслевые или международные стандарты. На wikipedia это совсем непохоже.

Причина этого в том, что общепризнанных стандартов по Flicker-Free до сих пор попросту нет. ВООБЩЕ НЕТ, Карл!

Ну, а На нет и суда нет(с), и каждый производитель вправе так называть все, что сочтет нужным, главное не забывать клеить соответствующий шильдик на видное место
Flicker-Free
Можно пойти ещё дальше, и сделать свою фирменную технологию свой фирменный шильдик, например Flicker-Safe.

Видимо имеется в виду, что с Flicker-Free просто нет мерцания, а с Flicker-Safe мерцания вообще нет. Любой каприз за ваши деньги ;-)
Flicker-Safe

При этом рабочий монитор как правило эксплуатируется при невысокой яркости, а отсутствие ШИМ мерцания подсветки может гарантироваться нормироваться в каком-то диапазоне, например 100%-70% от максимальной.

Иногда изготовитель это указывает, хотя обычно забывает это сделать.
Иногда об этом можно узнать из тестов и обзоров.
В качестве примера цитата из такого обзора:
Обзор 31,5-дюймового 4K-монитора Samsung U32J590UQI
Это единственная из известных на просторах Интернета полноценная 10-битная (без применения FRC) *VA-панель с диагональю 31,5 дюйма, разрешением 3840x2160 пикселей (стандарт 4K) и особой BGR-LED-подсветкой без мерцания (Flicker-free)
в диапазоне яркости 31-100 % ШИМ у U32J590uQI не используется, а при 0-30 % её частота составляет 250-260 Гц...
Если учесть, что максимальная яркость у этого монитора 225 кд/м2, а многие пользователи предпочитают выставлять на рабочем мониторе рабочую яркость менее 100 кд/м2, то уже на грани.

А если изготовитель забыл указать другой диапазон рабочей яркости без ШИМ, например если мерцание отсутствует только при 100% подсветке это Flicker-Free?
А если до 60%?
А до 30%? Впрочем, по поводу последнего мы уже знаем, что это Flicker-Free :-)

Частота ШИМ 10kHz это Flicker Free?
А 1kHz?
А 240Hz?
А 120Hz?

Коэффициент заполнения ШИМ 25% при 1 kHz и яркости менее 50% это Flicker Free?
А 50%?
А 90%? И даже если это только при 100% подсветке и 120Hz?

И вообще, Сколько орехов должно быть в куче?(c)

Можно долго рассуждать об этом, а изготовитель лепить надпись по внутренним стандартам потому что он так видит.

И в то же время отсутствие этой надписи на старом мониторе ни о чем не говорит, например если монитор появился на рынке еще до начала моды на шильдики Flicker-Free, то у него такого шильдика не будет, впрочем как и мерцания может не быть* (а может и быть).
  • Безусловно, свобода* лучше, чем несвобода* (*от мерцания подсветки), и при выборе монитора надпись Flicker-Free, Flicker-Safe и т.п. дает больше шансов, что монитор не будет мерцать, или будет, но не так сильно, как без такой надписи.

Теперь перейдем от теории к практике, и для начала разберемся,

Как можно определить наличие ШИМ мерцания подсветки монитора


Конечно самые точные способы аппаратные, например с помощью спектрометров или хотя бы осциллографа с фотодиодом (об этом чуть ниже), некоторые из них описаны в по ссылкам в начале статьи.

Но достаточно точно определить наличие ШИМ пульсации можно и подручными методами, без использования специального оборудования.

Классический способ Карандашный тест мерцания экрана, см например,
Миф или правда: экраны современных телевизоров и мониторов не мерцают
Предварительно выставьте желаемую яркость и лучше, если на экране будет белая заливка или, по крайней мере, очень светлая картинка.
Взяв карандаш за кончик, поводите им перед светящимся экраном как веером. Если след от карандаша размывается, то мерцания нет, если же след разделяется (выглядит как набор теней от нескольких карандашей), то ваш монитор мерцает. Чем отчётливее контуры, тем мерцание сильнее...
Карандашный тест

Некоторые люди могут достаточно точно определять наличие мерцания на глаз, даже без карандаша. Я, например могу увидеть ощутить мерцание до 100-150 Hz периферийным зрением. Для этого нужно встать сбоку от светящегося экрана так, чтобы экран был практически за пределами зрения, а впереди был более темный фон. Если при этом возникает ощущение, что глаза вываливаются и как бы сами поворачиваются в сторону экрана, то значит есть ШИМ с высокой скважностью и частотой до сотни герц. Конечно этот способ крайне субъективный и неточный, но иногда он может помочь, например на выставке или в торговом зале, когда более ничего недоступно.

Но ИМХО самый простой, объективный и весьма точный способ (вплоть до оценки частоты и скважности ШИМ) это тест с помощью любой фотокамеры с CMOS матрицей и электронным затвором.

Такие фотокамеры есть практически у всех, поскольку камеры с CMOS матрицей и электронным затвором как правило используются в смартфонах и планшетах*.
  • Надо иметь в виду, что мыльницы с механическим затвором и любым типом матрицы, а также камеры с CCD матрицей для этого абсолютно не годятся.

Для того, чтобы убедиться, есть ли мерцание ШИМ подсветки у того или иного монитора не надо даже его фотографировать, достаточно просто направить камеру смартфона на экран монитора (желательно при этом включить на нем заливку белым, или просто вывести любую светлую картинку), и посмотреть на его дисплей.
image

Далее всё очень просто:
  • Если по изображению экрана монитора на дисплее смартфона бегут полосы, то значит его подсветка мерцает
  • Если изображение экрана монитора на дисплее смартфона без полос, то значит мерцания подсветки нет.
При желании можно сфотографировать экран монитора, тогда по фотографии можно оценить не только наличие или отсутствие мерцания подсветки, но и достаточно точно определить частоту ШИМ.

Samsung V32F390SIX 31.5 (2018)

Как это можно сделать я описывал в своей старой статье Метод самостоятельного определения времени отклика LCD экрана монитора или телевизора (статья посвящена определению времени отклика матрицы, но та же методика расчета годится и для определения частоты ШИМ его подсветки).

Ну а если при съемке экрана выставить минимально возможную выдержку (максимальное ISO), то помимо частоты ШИМ можно достаточно точно оценить и его скважность (коэффициент заполнения) по относительной ширине светлых и темных полос на снимке.
Чем короче будет выдержка, тем более четкими получатся границы полос на экране, и тем точнее будет определение скважности, ну а шум и другие артефакты в данном случае не важны.

Об этом мы поговорим ниже, на примере рассмотрения конкретного монитора.

Итак,

32" монитор-телевизор Samsung LT T32E310EX


Samsung LT T32E310EX

Несколько слов о причине, по которой был выбран 32" монитор-телевизор Samsung LT T32E310EX.

Тут всё просто на момент покупки в конце 2016 года это был единственный имеющийся в продаже 32" Full HD монитор*, который мне требовался, так что При всем богатстве выбора иной альтернативы не было (Рекламный слоган).
  • Правда был его сводный старший брат Smart-телевизор Samsung UE32J5500AU с такой же матрицей, но во первых Smart-TV функции мне были не нужны, а во вторых Samsung LT T32E310EX тогда позиционировался как монитор-телевизор (сейчас на сайте Samsung.ru эта страница протухла), а поскольку мне нужен был именно монитор, то я предпочел не рисковать, и взять именно его, а не чистый телевизор, рассчитывая, что свою титульную функцию монитор-телевизор будет выполнять лучше, чем просто телевизор.
    Хотя некоторые проблемы с его настройкой все-таки возникли, но их удалось успешно решить благодаря помощи зала.

Почему именно FullHD, а не 4k или хотя бы QHD (часто ошибочно называемый 2k)?

Конечно тут уже всё не столь однозначно, и есть широчайшее поле для HolyWar, чего мне хотелось бы избежать при обсуждении этой статьи.
Если это действительно интересно (или если в обсуждении возникнет HolyWar), то я могу написать об этом отдельную статью, для которой у меня достаточно материала (хотя там в обсуждении наверняка будет Holy War).

Ну а пока скажу лишь, что я прекрасно понимаю, что FullHD это уже прошлое, а будущее за 4k (а может и выше), однако мы живем в настоящем, на стыке прошлого и будущего, и иногда пока оказывается предпочтительнее старый-добрый FullHD.

По поводу видимой пиксельной решетки FullHD процитирую себя с другого форума
Мониторы для чувствительных глаз (часть 2), #4702
...Samsung LT T32E310EX С рабочего расстояния порядка 55-65см (вытянутой руки) пиксель 0.36 вижу, но не замечаю*...
  • Eсли приглядываться, то на 32" экране 2k QHD я тоже могу разглядеть пиксельную решетку (впрочем, ее я тоже естественно не замечаю).


Также отмечу, что выбор Samsung LT T32E310EX для меня оказался насколько удачным, что после приобретения своего кабинетного рабоче-развлекательного монитора (о нем я уже писал на habr-е в обсуждении статьи Кабинет на квадратном метре, если будет интерес, то у меня есть материал на отдельную статью на эту тему) я приобрел еще два таких же монитора второй сейчас используется как телевизор в спальне, а третий взял в запас в начале пандемии (чёрт его знает, чем все закончится, да и мониторы эти исчезают из продажи, так что пусть пока полежит).
На этом мне бы хотелось закрыть данный вопрос в рамках этой статьи.

Итак, возвращаемся к теме статьи:

Мерцание LED подсветки экрана и как с этим бороться


Сразу после покупки Samsung LT T32E310EX выяснилось, что LED подсветка его экрана мерцает при настройке рабочей яркости 100-120 кд/м2 (ИМХО оптимальная яркость для рабочего кабинетного монитора), причем весьма заметно я это вижу чувствую боковым зрением (см. выше).

Если для телевизора или мультимедийного монитора это могло бы быть приемлемо, то для рабочего монитора, за которым иногда проводишь больше половины суток, это ИМХО абсолютно недопустимо.

Впрочем, как я уже сказал выше, в данном случае это была не единственная проблема, вначале никак не удавалось разобраться с выводом изображения пиксель-в-пиксель (подробнее об этом в моей старой статье Тест для проверки цветового разрешения монитора или телевизора при подключении к компьютеру по цифровому видеоинтерфейсу)
444 vs 422

Я уже почти смирился с тем, что Samsung LT T32E310EX переедет в спальню в качестве телевизора, ну а к тому моменту, когда удалось решить проблему с интерфейсом, мне удалось разобраться и с ШИМ.

Поэтому сейчас монитор Samsung LT T32E310EX занял свое место на рабочем столе, а в спальню пришлось покупать еще один такой же телевизор Samsung LT T32E310EX :-)

work table

Как отказалось, у Samsung LT T32E310EX настройка режимов изображения несколько отличная от классической, идущей еще со времен CRT регулировки Яркости и Контраста.

В дополнение к регулировке Яркости и Контраста, здесь добавилась регулировка Подсветки.
picture

В общем-то это вполне логично если во времена CRT дисплеев настройка Яркости управляла максимальным анодным током (и т.о. уровнем белого), а Контрастность модуляцией на управляющем электроде, при этом непосредственно управлять уровнем черного не было технической возможности, то перешедшая по наследству в LCD традиционная регулировка Яркости управляет яркостью подсветки, а регулировка Контраста наклоном кривой вокруг какой-то средней точки, при этом глубину черного приходится подстраивать с помощью этих же регулировок.

В данном же случае настройка Подсветка регулирует яркость подсветки (вполне логично),

Яркость управляет глубиной черного (так сказать, яркостью черного),
Brightness

а Контраст регулирует уровень белого (да, именно так!)
Contrast

Вначале немного непривычно, но потом становится чрезвычайно удобно, и уже кажется вполне логичным*
  • Подобная трехдвижковая настройка параметров изображения используется не только в Samsung LT T32E310EX, но и в других мониторах и телевизорах.
    Хотя названия параметров могут быть другими. Например это может быть Яркость, Контраст и Глубина чёрного. Главное, чтобы общие принципы сохранялись.

Каждая из настроек занята своим делом, и не мешает друг другу.
Если я хочу отрегулировать глубину черного, то задействую только регулировку Яркость, не сбивая при этом регулировку уровня белого.
А если я хочу отрегулировать уровень и глубину белого, я могу использовать настройки Подсветка ИЛИ Контраст, не сбивая регулировку уровня черного.

Более того, благодаря ИЛИ в данном случае как раз и появляется возможность устранить ШИМ мерцание подсветки при невысокой рабочей яркости экрана!

БИНГО!!!


Чтобы не снижать коэффициент заполнения ШИМ подсветки менее 85-95% (об этом чуть ниже), мы просто не будем уменьшать настройку Подсветка ниже 18-20 эээ попугаев (20 попугаев в данном случае её максимальное значение), а требуемую для офисной работы невысокую рабочую яркость экрана 100-125 кд/м2 получим за счет существенного уменьшения настройки Контраст.

Очевидно, что неизбежной расплатой за это становится снижение Contrast Ratio между уровнем черного и уровнем белого, но из двух зол, по сравнению с мерцающей подсветкой, это ИМХО меньшее. Об этом чуть ниже.

Собственно говоря это все. Дальше

Настройка, тесты и замеры


Для начала проверим* уровень мерцания и коэффициент заполнения ШИМ подсветки при разных уровнях настройки Подсветка
  • На самом деле этот тест проводился много позже, через несколько лет после приобретения и настройки монитора, когда у меня появился осциллограф и фотодиод, и этот тест лишь подтвердил то, к чему я пришел интуитивно и эмпирически, но все-таки логичнее разместить его выше остальных настроек, поскольку они опираются на его выводы.

Для измерений использовались:
Осциллограф DSO-SHELL (DSO150)
-
фотодиод BPW34 DIP-2
(банан карта microSD для масштаба)
-
Фотографии тестов и примечания к результатам под спойлером
Исходная информация тестов
Уровень подсветки
0-из-20
(минимальное значение),
Коэффициент заполнения ШИМ 14,3%
(поскольку при этом яркость экрана очень небольшая, то несмотря на ISO 800 выдержка получилась очень длинной 1/120 сек, поэтому полосы от ШИМ не видны)
Уровень подсветки
6-из-20,
Коэффициент заполнения ШИМ 37,3%
(выдержка 1/241 уже достаточно короткая, чтобы были заметны полосы от ШИМ)
Уровень подсветки
10-из-20,
Коэффициент заполнения ШИМ 53,5%
Уровень подсветки
15-из-20,
Коэффициент заполнения ШИМ 73,1%
Уровень подсветки
18-из-20,
Коэффициент заполнения ШИМ 84,5%
(на фото полосы еще заметны, но карандашный тест мерцания уже не фиксирует)
Уровень подсветки
20-из-20
(максимальное значение),
Коэффициент заполнения ШИМ 91,3%
(несмотря на коэффициент заполнения чуть менее 100% мерцания практически нет, полосы на фото незаметны даже при короткой выдержке 1/323сек, карандашный тест уверено проходится)

Краткие результаты теста:

  • Уровень подсветки 0-из-20 (минимальное значение),
    Коэффициент заполнения ШИМ 14,3%
  • Уровень подсветки 6-из-20, Коэффициент заполнения ШИМ 37,3%
  • Уровень подсветки 10-из-20, Коэффициент заполнения ШИМ 53,5%
  • Уровень подсветки 15-из-20, Коэффициент заполнения ШИМ 73,1%
  • Уровень подсветки 18-из-20, Коэффициент заполнения ШИМ 84,5%
  • Уровень подсветки 20-из-20 (максимальное значение),
    Коэффициент заполнения ШИМ 91,3%

ИМХО можно считать, что при максимальном уровне подсветки экран практически не мерцает, и дальнейшую регулировку монитора будем производить при Подсветке 20-из-20 (хотя и 18-из-20 с Коэффициентом заполнения 85% тоже наверное вполне приемлемо)

Для начала произведем настройку

Уровни черного


Эту настройку можно произвести например по тестовой картинке Black level на сайте www.lagom.nl или другим аналогичным тестовым страничкам*
  • Я настраивался по тестовой картинке Настройка уровней на сайте www.mehanik99.ru, но сейчас этот сайт не работает из-за смерти его автора Александра Соколова. Скорее всего этот сайт умер вместе с ним, но оставлю ссылки на него как мемориальные

В отличие от большинства остальных настроек, проводимых или при рабочем освещении, или в полумраке, эту настройку необходимо производить в абсолютной темноте, при выключенном освещении, на пределе чувствительности глаз, с помощью регулировки Яркость добиваясь видимости градаций черного вплоть до Level 2 или хотя бы Level 3*
  • В идеале конечно хотелось бы увидеть и Level 1, но тут есть опасность вместе с водой выплеснуть и ребенка, и есть риск потерять максимально возможную для данного монитора глубину черного, поэтому лучше пожертвовать одним-двумя уровнями, и при этом получить максимально глубокий черный цвет.

Я ограничился хорошей различимостью Level 3 и видимостью на уровне ощущений Level 2*
  • Так получилось, что для моего экземпляра Samsung LT T32E310EX оптимальное значение регулировки Яркость совпало с его значением по умолчанию, равным 45 (т.е. у меня Уровень черного был идеально настроен из коробки), но на других экземплярах оптимальные значения конечно могут оказаться другими.

Black level

Еще фотографии настройки Black level здесь и здесь

После этого настраиваем рабочую

Яркость белого


Как я уже сказал, делать это будем только за счет регулировки Контраст, не трогая установленное максимальное значение Подсветка, равное 20-из-20.

Для рабочего монитора для снижения утомляемости глаз желательно иметь небольшое значение яркости белого поля порядка 100-150 кд/м2, некоторые пользователи предпочитают еще меньше до 80 кд/м2 и даже менее. Но тут уже все индивидуально.

При наличии калибратора с возможностью оперативного контроля ярости белого поля, можно было бы сразу выставить желаемую яркость, например 120 кд/м2. Поскольку у меня калибратора нет, пришлось это делать на глаз, проверяя получившееся значение с помощью online теста, например Contrast ratio test на сайте www.lagom.nl.

Сначала в условиях реального освещения рабочего места (этот вопрос также заслуживает отдельного разговора) включаем на мониторе заливку белым (можно просто открыть пустую страницу в браузере), и уменьшаем значение регулировки Контраст, снижая тем самым яркость экрана до тех пор, пока белый перестает выглядеть белым, и начинает казаться светло-серым. Оставляем пока это значение, и продолжаем работать за компьютером. Весьма вероятно, что через какое-то время светло-серый снова станет выглядеть белым, и можно попробовать еще немного уменьшить яркость экрана, уменьшив значение Контраст. Через какое-то время вы нащупаете свое оптимальное значение яркости экрана, и эту регулировку можно считать законченной.

Теперь можно проверить получившееся значение яркости экрана с помощью вышеупомянутого Contrast ratio test, ну а заодно и проверить, насколько сильно мы потеряли Contrast ratio из-за Подсветки, выставленной на 20-из-20.

Для полноценного проведения Contrast ratio test крайне желательна хорошая фотокамера, ну или хотя бы продвинутая мыльница с ручными регулировками, позволяющая сделать снимок экрана со светящейся заливкой черным в абсолютной темноте. При этом качество снимка и шумы не так важны, важна сама возможность сделать такую фотографию, чтобы на ней был хорошо различим светящийся черный экран. Мне для этого пришлось сделать несколько десятков снимков, чтобы выбрать из них несколько годных. Для этого даже пришлось сделать маску на камеру из черной картонки, чтобы убрать отражение фотокамеры в черном экране, подсвеченной этим самым черным экраном.

Впрочем, если вы не захотите делать полный Contrast ratio test, ограничившись лишь проверкой получившейся яркости белого поля, то можно всего этого и не делать, будет достаточно фотографии заливки белым, сделанной в соответствии с условиями теста (подробно описаны на его странице). Такую фотографию сделать конечно намного легче, чем с черным экраном.

Я все-таки сумел сделать требуемый комплект фотографий, и смог получить полноценный замер
Contrast ratio results for Samsung LT T32E310EX:
  • Contrast ratio 764:1 (edge: 916:1)
  • White Luminance 126 cd/m2

ИМХО вполне удовлетворительный результат для рабочего монитора. Яркость экрана 126 кд/м2 в рекомендуемых пределах, ну и не слишком большая потеря Contrast ratio из-за максимальной Подсветки.

Итого оптимальная рабочая настройка для моего экземпляра монитора Samsung LT T32E310EX:
  • Подсветка 20-из-20
  • Контраст 63%
  • Яркость 45%


Поставленная задача решена

Удалось добиться удовлетворительного значения рабочей яркости экрана при отсутствии мерцания его подсветки.

Осталось лишь проверить настройку

Уровни белого


В данном случае ничего настраивать не потребуется, поскольку у нас автоматически получился огромный запас по яркости, можно просто формально провести тест White saturation или другой аналогичный тест Уровней белого.

Потребуется лишь вывести изображение теста, и убедиться, что все уровни белого вплоть до Level 254 хорошо различимы.
White saturation

На этом грубую настойку монитора можно считать законченной, и нам остается посмотреть и при необходимости подрегулировать Баланс белого, Резкость, Gamma, Gamma Shift, ColourShift и другие параметры и тонкие настройки.
Впрочем, Это уже совсем другая история, выходящая за рамки этой статьи.
Ну и в заключении ложка дегтя, которая может оказаться в бочке мёда некоторых мониторов с описываемой в статье тройной регулировкой параметров изображения Яркость / Контраст / Подсветка, а также ложка мёда, которая может хорошо подсластить некоторые мониторы с мерцающей подсветкой и с классической двойной регулировкой Яркость / Контраст.

Как я уже упоминал выше, через некоторое время после покупки Samsung LT T32E310EX я начал присматриваться к претендентам для его замены в будущем.

Мое внимание привлек монитор-телевизор следующего поколения с изогнутым экраном Samsung V32F390SIX, и я уже почти собрался его покупать, однако решил сначала проверить его на мерцание подсветки.

К сожалению оказалось, у него подсветка мерцает в т.ч. и при максимальном значении сответствующей настройки.
Samsung V32F390SIX 31.5 (2018)

Не представляю, какое тут может быть рациональное объяснение.

Возможно, что Samsung не хочет создавать внутреннюю конкуренцию со своим чуть более дешевым чисто монитором (без телетюнера) с титульным Flicker Free Samsung C32F391FWI, у которого экран не мерцает в т.ч. и при снижении яркости подсветки.

В целом этот монитор мне тоже понравился, но к сожалению у него только один вход HDMI, а мне необходимо как минимум два с независимыми настройками параметров изображения*.
  • Да, я знаю о существовании HDMI-сплиттеров, и сам такой использую, однако при этом невозможно сделать различные настройки монитора для компьютерных (с невысокой рабочей яркостью) и мультимедийных (с более высокой яркостью) входов, а для меня это важно.

Правда у этого монитора есть еще вход Display Port, но у меня все источники сигнала только HDMI или DVI выходами, а переходников с HDMI на DP не существует (бывают переходники с DP на HDMI, но в данном случае они бесполезны).

Интересно, что раньше и Samsung LT T32E310EX, и Samsung V32F390SIX на сайте Samsung.ru обозначались, как монитор-телевизоры, да и маркировка у них мониторная, то сейчас на их сайте они обозначаются как просто телевизоры (вопреки собственно маркировке).

В общем маркетологи Samsung такие маркетологи

В результате я пока взял в запас еще один Samsung LT T32E310EX, и жду дальнейшего развития событий на мониторном рынке.
Ну а теперь обещанная ложка мёда для некоторых мониторов с мерцающей подсветкой и с классической двойной регулировкой Яркость / Контраст.

Если на вашем мониторе с такой регулировкой параметров изображения при максимальной настройке яркости подсветка не мерцает, однако яркость экрана при этом слишком высока, а при снижении яркости до приемлемого для Вас уровня начинает мерцать подсветка, то можно попробовать выставить значение яркости, при котором подсветка не мерцает, а яркость экрана снизить до необходимого уровня за счет соответствующей настройки драйвера видеокарты.
videodriver

Тут тоже важно не запутаться, что и куда двигать.

Для этого ИМХО стоит открыть в браузере упомянутые выше странички тестов Black level и White saturation, и в процессе изменения настройки следить, чтобы при этом не терялись максимальные и минимальные уровни черного и белого.

В моем случае (см screenshot выше) для снижения яркости нужно было сдвигать движок Контрастность влево, при этом изображение на экране становилась темнее (однако правый график при этом не изменялся).
Но на других видеокартах и других драйверах логика работы этой настройки может быть другой, так что следите за руками уровнями черного и белого.

Надо иметь в виду, что при таком способе регулировки мы расплачиваемся не только снижением Contrast ratio, как при предыдущем способе, но и теряем часть яркостного диапазона HDMI интерфейса, поэтому по возможности ИМХО лучше все же использовать способ с настройками монитора, а не драйвера.
В заключении

Несколько слов о недостатках


Но к сожалению за все приходится платить, и как сказала Фаина Раневская Всё приятное в этом мире либо вредно, либо аморально, либо ведет к ожирению

Необходимо упомянуть и о нескольких побочных явлениях, недостатках данного способа отключения мерцания:
  • Существенное снижение Contrast ratio монитора на рабочем режиме.
    Это ИМХО главный недостаток подобного способа борьбы мерцанием.
    Тут мне нечего возразить, я об этом уже несколько раз говорил выше.
    Например для моего монитора Samsung LT T32E310EX измеренные с помощью Contrast ratio test значения Т.е. выигрыш по уменьшению рабочей яркости практически равен потере Contrast ratio. Что в общем-то неудивительно подсветка в обоих случаях одинаковая (максимальная), глубина чёрного соответственно тоже, а белый во втором случае существенно притушен, и как следствие снижение динамического диапазона и потеря Contrast ratio.
    Это может быть особенно заметно, если у Вас монитор с изначально невысоким Contrast ratio, например 700:1 (некоторые типы IPS матриц), тогда в результате мы получим Contrast ratio всего около 300:1
    Допустимо ли это решать только Вам.
  • Ускоренная деградация LED светодиодов подсветки.
    При обсуждении меня периодически об этом спрашивают.
    Гм Не знаю Может быть
    С одной стороны изготовитель вроде не запрещает включение подсветки на 20-из-20, да и при настройке из коробки она включена почти на полную (ЕМНИП на 17-из-20), но с другой стороны любые девайсы при высокой нагрузке изнашиваются больше, чем при малой. Это относится и к процессорам, и к SSD, и к автомобилям, и к утюгам, ну и к светодиодам тоже.
    Другое дело насколько существенным будет снижение ресурса, и повлияет ли как-то на время эксплуатации. Не могу сказать
    Хотя на своем мониторе около года начал замечать, что в паре мест несколько увеличилась неравномерность заливки белым. Пока это видно только на сплошной заливке, и например в браузере или документе с текстом это практически незаметно. В этом мониторе используется задняя подсветка, и похоже, что парочка светодиодов несколько деградировала. Связано ли это с максимальной подсветкой не могу сказать. Во всяком случае на втором таком же мониторе, купленном менее чем через полгода после первого пока подсветка такая же равномерная, как и была вначале (тьфу^3), так что возможно попался такой экземпляр (хотя 5 лет..., 12+ часов в день...).
    В общем пока попробовал снизить уровень подсветки до 18-из-20 (коэффициент заполнения ШИМ 85%), неравномерность заливки за год вроде бы не увеличивалась, для глаз вроде тоже нормально.
  • Увеличенный расход электроэнергии
    Об этом меня тоже периодически спрашивают.
    Ну, давайте прикинем:
    У Samsung LT T32E310EX максимальная потребляемая мощность 69 Ватт.
    Предположим, что из них 50 Ватт потребляет подсветка, а 19 Ватт вся остальная электроника.
    Если бы мы включили подсветку монитора не на 20-из-20, а допустим на 10-из-20 (половину мощности), то мы бы сэкономили 25 Ватт потребляемой мощности.
    За год в режиме 12/365 (~4000 часов) получим лишнее потребление 100 кВтч или около 500 рублей.
    Много это или мало судить вам (тем более что для вашего монитора цифры наверняка будут другими).
    Ну а сколько было сожжено тонн баррелей нефти, и уничтожено сотен гектаров леса оставим Грете Тумберг.
    .$. У меня вместе с монитором и компьютером (который кстати тоже потребляет энергию) автоматичеки включается 10-ваттная лампа задней подсветки стены за рабочим местом, дополнительная 5-ваттная USB LED-лента на на верхней кромке монитора для дополнительного освещения стены, а также 10-ваттная лампа сверху чуть правее головы для освещения клавиатуры и рабочего места. Слева окно с жалюзи и еще одна лампа, включаемая вручную при необходимости. Ну и общее комнатное освещение пять 10-ваттных ламп.
    Ужас-ужас Только Грете Тумберг об этом не говорите...

В статье использованы материалы обсуждений на forum.ixbt.com, в которых я принимал участие

.$.

Некоторые аспекты, касающиеся выбора монитора, настройки параметров его изображения, а также организации освещения рабочего места я затронул лишь вскользь, в той их части, где это непосредственно касается темы данной статьи.

Это безусловно заслуживает отдельного разговора.

Но это уже совсем другая история...
Подробнее..

Как я переделываю недиммируемые светодиодные светильники в диммируемые. Статья вторая

10.06.2021 04:21:04 | Автор: admin

Учтя всю критику и советы в первой статье, я решил питать светодиодные ленты в светильниках от специализированного драйвера. В качества драйвера был выбран драйвер фирмы Mean Well LDH-45B-350. Этот драйвер можно питать напряжением в широких пределах от 18 до 32 вольт. Я для питания приобрёл блок той же фирмы что и сами драйверы Mean Well LRS-100-24.

Небольшое отступление.

Скажу, что я немного разочаровался в этих блоках питания, да и драйверах, думал солидная фирма, всё должно быть так сказать по красоте и работать без нареканий, но на деле всё оказалось вообще не так. Блоки покупал в интернет магазине Чип и Дип, думал что никаких проблем не будет, солидный магазин, солидная фирма, должно всё работать идеально долгие годы. Но по факту первый звоночек я получил ещё когда собирался питать светодиодные ленты постоянным стабилизированным напряжением используя для этого 2 минвеловских блока питания соединённых последовательно LRS-50-24 и LRS-50-36.
Уже тогда я заметил, что эти блоки гудят при определённой частоте ШИМ. То есть регулировку яркости ленты я делал с помощью ШИМ и полевого транзистора IRF520N. Думал, может что-то не так рассчитал, не такой транзистор поставил и проблема в моём ШИМ, который как-то влияет на блок и заставляет его пищать. Но оказалось ни фига подобного, когда я уже потом к одному блоку, а именно к LRS-50-24 подключил указанный выше светодиодный драйвер, при определённой яркости этот блок всё равно начинал пищать. Тогда я решил купить блок помощнее на 100 ватт, вот он уже с этими драйверами вроде как перестал пищать или пищит, но гораздо тише чем 50-ти ваттный. В общем хочу всех предостеречь и посоветовать покупать блоки помощнее, c двойным, а то и тройным запасом по мощности. Я может даже куплю и поставлю блок на 150 ватт LRS-150-24. Хотя 3 моих светодиодных светильника потребляют в сумме 60 ватт. Но блок придётся покупать с более чем двойным запасом, чтобы не пищал и не гудел. Причём как уже сказал, пищат оба моих 50-ти ваттных блока и на 24 вольта и на 36 вольт. То есть, вряд ли это мне 1 такой из тысячи попался который гудит. А вот 100 ваттный вроде как и не гудит, или гудит, но гораздо тише. В общем даже если он и гудит, то этот уровень гудения меня устраивает, в отличии от 50-ти ваттных, они гудят довольно сильно.

Второй косяк который всплыл оказался в светодиодном драйвере. При уменьшении яркости с помощью ШИМ, 2 светильника переставали светить, а вот 1 светил. Проблема оказалось в том, что один из драйверов выдавал большее напряжение на выходе чем остальные. Естественно такая ерунда меня не устраивала, когда 2 светильника не горят, а один ещё светит, пусть и очень-очень тускло, я хочу сделать всё идеально, поэтому решил его обменять, но я уже впаял его в плату, пришлось выпаивать, а я ненавижу (хотя и могу) выпаивать детальки у которых больше чем 4 ножки, и ножки которых разнесены на несколько сантиметров друг от друга. Выпаять я конечно выпаял, аккуратно, не перегрев и не повредив ни плату, ни драйвер, но удовольствие такое занятие мне не доставляет, и покупая драйверы этой фирмы, я даже не подозревал что мне придётся их когда либо выпаивать, и уж тем более не думал, что придётся выпаивать сразу же, потому что один оказался бракованный. Причём он в принципе работал хорошо, до определённого понижения яркости, и перед впаиванием в плату, я как человек ОЧЕНЬ подозрительный их всё же проверял, но не на всём диапазоне яркости, и не очень досконально. Вот и оказалось, что при очень низкой яркости, когда уже все светильники не светили, один все же светил. Так что опять хочу предостеречь тех, кто будет покупать такие драйверы, и тем более платочный вариант. Лучше перед впаиванием сразу досконально проверить каждый драйвер, померить напряжение которое он выдаёт на максимуме и минимуме, ну и в середине диапазонов, в идеале вообще проверять всё со всеми подключаемыми светильниками которые должны работать синхронно.

Ну и ещё одна проблема прежде чем вернусь к самой теме статьи. Знаете сколько мне менял ЧИП ДИП драйвер который стоит 10$ ? 1/12 ГОДА! 1 месяц! Охренеть конечно сервис, а если бы это ни дача была, которую я только делаю, где могу месяц и подождать, тем более у меня в комнате 3 таких драйвера и соответственно 3 светильника, и 2 светят, то есть свет в комнате есть, а например в квартире, и светильников в комнате было бы не 3 как у меня, а один. Драйвера нет, получается освещения в комнате нет. Если всё заточено под драйвер, и человек не предусмотрел возможности быстренько переделать и пустить по этим проводам 220 вольт и поставить обычную лампочку в случае чего. Сиди месяц без света, пока ЧИП ДИП будет проводить экспертизу твоего драйвера, цена которому 10$. В общем это печально, такого от ЧИП ДИП я не ожидал.

В общем вот такие проблемы с блоками и драйверами. Подытожу:
1) некоторые (скорее всего маломощные) блоки питания Mean Well гудят (ну или могут гудеть);
2) из 6 драйверов 1 оказался бракованный, ЧИП ДИП это признал и поменял на новый;
3) экспертиза и обмен товара в ЧИП ДИП может занять около 1 месяца и даже немного больше 30 дней, может в самой Москве это было бы и быстрее, но из РБ города Гомель, отправка на экспертизу в Москву, потом отправка обратно нового товара. В общем я ждал более 30 дней прежде чем получил новый драйвер!

Ну да ладно, а теперь самое интересно.

Плата LED драйверов

Для светодиодных драйверов LDH-45B-350 была изготовлена плата со следующей схемой:

Схема соединения LED драйверовСхема соединения LED драйверовДизайн платы в EagleДизайн платы в Eagle

Шаблон (маска) платы размерами 186 х 76 мм:

Шаблон (маска) платыШаблон (маска) платы

Ну и фото того что получилось в итоге:

Плата LED драйверовПлата LED драйверовПлата LED драйверовПлата LED драйверовПлата LED драйверов (обратная сторона)Плата LED драйверов (обратная сторона)

Чёрная гребёнка подключается к моей универсальной плате управления со 128 мегой о которой я писал в этой статье . А вот с помощью синих разъёмов, выводы PWM DIM соединяются вместе, и через резистор на 10К подключаются к выводам DIM -. Почему я не стал это соединение и сам резистор делать сразу на плате на постоянно, да потому что в будущем, возможно, я сделаю независимое включение/выключение каждого светильника, а может даже и независимую регулировку яркости, если придумаю простой интерфейс управления всем этим великолепием. Если всё будет очень сложно управляться, то колхоз с кучей кнопок и выключателей я делать конечно же не буду. Пока все светильники включаются синхронно и яркость регулируется также синхронно, но заложена возможность дальнейшей переделки на полностью независимое включение/выключение светильников, и может даже независимую регулировку яркости. В общем посмотрим. Независимое включение/выключение каждого светильника точно сделаю, даже уже придумал простой интерфейс управления, чтобы на стене не нужно было делать кучу выключателей. Будет только 1 выключатель и энкодер для управления яркостью, а поскольку на стене будет ещё и дисплей из матриц, показывающий яркость освещения, вот с помощью него и энкодера и будет сделан интерфейс для возможность выключения каждого светильника по отдельности.

Плата матриц

Ещё я решил сделать около выключателя индикатор отображения уровня яркости освещения в процентах. Дисплей решил изготовить из белых светодиодных матриц, вот таких:

МатрицыМатрицы

Схема платы с матрицами:

Схема соединения матрицСхема соединения матрицДизайн платы матриц в EagleДизайн платы матриц в Eagle

Шаблон (маска) платы матриц размерами 105 х 62 мм:

Шаблон (маска) платы матрицШаблон (маска) платы матриц

Ну и фото того что получилось:

Плата матрицПлата матрицПлата матриц (обратная сторона)Плата матриц (обратная сторона)Плата матрицПлата матриц

Плата управления матрицами

Ну и для управления платой матриц, надо изготовить плату управления матрицами.
Схема её следующая:

Схема платы управления матрицамиСхема платы управления матрицамиДизайн платы управления матрицами в EagleДизайн платы управления матрицами в Eagle

Шаблон (маска) платы управления матрицами, размерами 105 х 52 мм:

Шаблон (маска) платы управления матрицамиШаблон (маска) платы управления матрицами

Ну и то фото того что получилось:

Плата управления матрицамиПлата управления матрицамиПлата управления матрицами (обратная сторона)Плата управления матрицами (обратная сторона)

Плата матриц с платой управления

Плата матриц с платой управленияПлата матриц с платой управленияПлата матриц с платой управления (обратные стороны)Плата матриц с платой управления (обратные стороны)Плата матриц с платой управления обратные стороныПлата матриц с платой управления обратные стороны

Демонстрация работы дисплея

Поскольку мой дисплей состоит из 3 матриц, а каждая матрица 8 на 8 точек, то общая длинна дисплея получается 24 столбца. Максимальное количество символов на экране 4, это когда будет светиться 100%. Поэтому каждый символ будет занимать 6 столбцов (5 столбцов на сам символ + пустой столбец для отделения символов друг от друга).

Как многие поняли из видео, регулировка яркости у меня будет ступенчатая с шагом в 5%. Изменяться она будет энкодером. Всего будет 20 фиксированных значений яркости от 0 до 100%.

Осталось собрать всё это воедино и готово!

Подробнее..

Программирование устройств на основе модуля ESP32

09.10.2020 14:15:38 | Автор: admin
Микроконтроллер это интегральная схема, способная выполнять программы. Сегодня на рынке представлено множество таких моделей от самых разных производителей. Цены на эти устройства продолжают падать. Однокристальные чипы находят широкое применение в самых разнообразных сферах: от измерительных приборов до изделий развлечений и всевозможной домашней техники. В отличие от персональных компьютеров микроконтроллер сочетает в одном кристалле функции процессора и периферийных устройств, содержит оперативную память и постоянное запоминающее устройство в для хранения кода и данных, однако обладает значительно мешьшими вычислительными ресурсами. ESP32 это микроконтроллер, разработанный компанией Espressif Systems. ESP32 представляют собой систему на кристалле с интегрированным Wi-Fi и Bluetooth контроллерами. В серии ESP32 используется ядро Tensilica Xtensa LX6. Платы с ESP32 обладают хорошей вычислительной способностью, развитой периферией при этом весьма популярны ввиду низкой цены в диапазоне 7$ 14$: Aliexpress, Amazon.

Данная статья не претендует на роль исчерпывающего руководста, скорее представляет собой сборник источников материала и рекомендаций. В статье я хочу затронуть вопросы с которыми мне пришлось столкнуться при выборе программных средств для разработки проекта, а также некоторые кейсы практического применения модулей ESP32. В следующей статье я хочу показать наглядный пример использования ESP32 в качестве котроллера управления для небольшой двухколесной мобильной платформы. Поэтому здесь рассмотрим такие детали как:

  • Выбор среды разработки;
  • Настройка рабочего окружения, компиляция и загрузка проекта ESP-IDF;
  • Обработка сигналов ввода/вывода GPIO;
  • Широтно-импульсная модуляция с использованием модуля MCPWM;
  • Аппартный счетчик PCNT;
  • Подключение к WI-Fi и MQTT.


Обзор модуля ESP32-WROOM-32E


Согласно datasheet модуль содержит:

MCU
ESP32-D0WD-V3 embedded, Xtensa dual-core 32-bit LX6 microprocessor, up to 240 MHz
448 KB ROM for booting and core functions
520 KB SRAM for data and instructions
16 KB SRAM in RTC

Wi-Fi
802.11b/g/n
Bit rate: 802.11n up to 150 Mbps
A-MPDU and A-MSDU aggregation
0.4 s guard interval support
Center frequency range of operating channel: 2412 ~ 2484 MHz

Bluetooth
Bluetooth V4.2 BR/EDR and Bluetooth LE specification
Class-1, class-2 and class-3 transmitter
AFH
CVSD and SBC

Hardware
Interfaces: SD card, UART, SPI, SDIO, I 2 C, LED PWM, Motor PWM, I 2 S, IR, pulse counter, GPIO, capacitive touch sensor, ADC, DAC
40 MHz crystal oscillator
4 MB SPI flash
Operating voltage/Power supply: 3.0 ~ 3.6 V
Operating temperature range: 40 ~ 85 C
Dimensions: See Table 1

Certification
Bluetooth certification: BQB
RF certification: FCC/CE-RED/SRRC
Green certification: REACH/RoHS

image
Функциональная блок-диаграмма

Более подробно с особенностями микроконтроллера можно ознакомится на википедии .

В основе модуля лежит микросхема ESP32-D0WD-V3 *. Встроенный чип разработан с учетом возможности масштабирования и адаптации. Центральный процессор содержит два ядра, которыми можно управлять индивидуально, а тактовая частота ЦП регулируется от 80 МГц до 240 МГц. Чип также имеет сопроцессор с низким энергопотреблением, который можно использовать вместо ЦП для экономии энергии при выполнении задач, не требующих больших вычислительных мощностей, таких как мониторинг состояния пинов. ESP32 объединяет богатый набор периферийных устройств, начиная от емкостных сенсорных датчиков, датчиков Холла, интерфейса SD-карты, Ethernet, высокоскоростного SPI, UART, IS и IC.
Техническая документация представлена на официальном ресурсе

Информацию про распиновку модуля ESP-WROOM-32 можно легко найти на просторах сети, как здесь

Выбор среды разработки


Arduino IDE


Микроконтроллеры семейства AVR, а затем и платформа Arduino появились задолго до ESP32. Из ключевых особенностей Arduino является относительно низкий порог вхождения, позволяющий практически любому человеку создать что-то быстро и легко. Платформа внесла важный вклад в open source hardware сообщество и позволила приобщиться огромному числу радиолюбителей. Среду разработки Arduino IDE можно свободно скачать с оффсайта . Не смотря на очевидные ограничения по сравнению с профессиональной средой разработки, Arduino IDE покрывает 90% из того, что требуется достичь для любительских проектов. В сети также имеется достаточное количество статей на тему установки и настройки Arduino IDE для программирования модулей ESP32, например: Arduino core for the ESP32, habr.com, voltiq.ru и randomnerdtutorials.com.

Программируя ESP32 в среде Arduino необходимо учитывать распиновку, как указано на странице arduino-esp32

image
Распиновка модуля ESP32

Основное преимущество данного подхода разработки заключается в быстром вхождении и легкости создания проектов, используя те же принципы, как и для Arduino. А также использование многих библиотек, как и для Arduino. Еще одной хорошей особенностью является возможность совмещать библиотеки и принципы разработки Arduino с оригинальным ESP-IDF фреймворком.

PlatformIO


Как сказано на официальном ресурсе : Cross-platform PlatformIO IDE and Unified Debugger Static Code Analyzer and Remote Unit Testing. Multi-platform and Multi-architecture Build System Firmware File Explorer and Memory Inspection Другими словами PlatformIO это экосистема для разработки встроенных устройст, поддерживающая множество платформ, включая Arduino и ESP32. В качестве IDE используется Visual Studio Code или Atom. Установка и настройка достаточно простая после установки редактора кода выбираем PlatformIO из списка плагинов и устанавливаем. Опять же в сети много материалов на данную тему, начиная от официального источника здесь и здесь, и продолжая статьями с подробными иллюстрациями здесь и здесь.
PlatformIO по сравнению с Arduino IDE оладает всеми качествами современной среды разработки: организация проектов, поддержка плагинов, автодополнение кода и много другое.
Особенностью разработки на PlatformIO является унифицированная структура проекта для всех платформ

project_dir lib    README platformio.ini src     main.cpp


Каждый проект PlatformIO содержит файл конфигурации с именем platformio.ini в корневом каталоге проекта. platformio.ini имеет разделы (каждый из которых обозначен [заголовком]) и пары ключ / значение внутри разделов. Строки, начинающиеся с символа точка с зяпятой ; игнорируются и могут использоваться для комментариев. Параметры с несколькими значениями можно указать двумя способами:
  1. разделение значения с помощью , (запятая + пробел);
  2. многострочный формат, где каждая новая строка начинается как минимум с двух пробелов.


Следующей фичей разработки для ESP32 является возможность выбора фреймворка: Arduino или ESP-IDF. Выбирая Arduino в качестве фреймворка мы получаем ранее описанные преимущества разработки.

image

В составе PlatformIO входит удобный инструментарий билда, загрузки и отладки проектов

image

Espressif IoT Development framework


Для ESP32 компания Espressif разработала фреймворк под названием IoT Development Framework, известный как ESP-IDF. Его можно найти на Github здесь. Проект содержит очень хорошую документацию и снабжен примерами, которые можно брать за базу. Установка и настройка среды окружения хорошо описана в разделе Get Started. Имеется несколько вариантов установки и работы с фреймворком.

Клонирование проекта из репозитория и ручная установка утилит.

Клонирование проекта из Github

mkdir -p ~/espcd ~/espgit clone --recursive https://github.com/espressif/esp-idf.git


Для Windows установка утилит разработки возможна с помощью инсталлятора или с помощью скриптов для командной строки:

cd %userprofile%\esp\esp-idfinstall.bat


Для PowerShell

cd ~/esp/esp-idf./install.ps1


Для Linux и macOS

cd ~/esp/esp-idf./install.sh


Следующим шагом является настройка переменных окружений среды. Если установка инструментов разработки была выполнена на Windows с помощью инсталлятора, то ярлык на командную консоль добавляется в меню и на рабочий стол, после чего можно открывать командную оболочку и работать с проектами. Альтернативный спрособ запуска командной оболочки для Windows:

%userprofile%\esp\esp-idf\export.bat

или Windows PowerShell:
.$HOME/esp/esp-idf/export.ps1


Linux и macOS:
. $HOME/esp/esp-idf/export.sh

Следует обратить внимание на пробел между точкой и путем к скрипту

Далее в руководстве рекомендуется добавить алиас на скрипт настройки переменных окружения в пользовательский профиль если работа производитя в системе Linux или macOS. Для этого необходимо скопировать и вставить следующую команду в профиль своей оболочки (.profile, .bashrc, .zprofile, и т.д.):
alias get_idf='. $HOME/esp/esp-idf/export.sh'


Вызывая команду get_idf в консоль экспортируются необходимы переменные окружения. В моем случае также необходимо было прописать алиас на запуск виртуального окружения python

alias esp_va=source $HOME/.espressif/python_env/idf4.2_py2.7_env/bin/activate

и добавить его в следующий алиас
alias get_idf='esp_ve && . $HOME/esp/esp-idf/export.sh'


Для создание нового проекта с нуля можно склонировать исходники с github.com или скопировать из каталога с примерами esp-idf/examples/get-started/hello_world/.

Информация о структуре проекта, утилитах компиляции, загрузки, настройки и др. Находиться здесь

Проект представляет собой каталог со следующей структурой:

- myProject/             - CMakeLists.txt             - sdkconfig             - components/ - component1/ - CMakeLists.txt                                         - Kconfig                                         - src1.c                           - component2/ - CMakeLists.txt                                         - Kconfig                                         - src1.c                                         - include/ - component2.h             - main/       - CMakeLists.txt                           - src1.c                           - src2.c             - build/


Конфигурация проекта содержится в файле sdkconfig в корневом каталоге. Для изменения настроек необходимо вызвать команду idf.py menuconfig (или возможно idf.py.exe menuconfig в Windows).
В одном проекте обычно создаются два приложения project app (основной исполняемый файл, т.е. ваша кастомная прошивка) и bootloader app (программа начального загрузчика проекта).
components это модульные части автономного кода, которые компилируются в статические библиотеки (файлы .a) и связаны с приложением. Некоторые из них предоставляются самой ESP-IDF, другие могут быть получены из других источников.
Утилита командной строки idf.py предоставляет интерфейс для простого управления сборками проекта. Ее расположение в Windows %userprofile%\.espressif\tools\idf-exe\1.0.1\idf.py.exe. Она управляет следующими инструментами:

  • CMake настраивает проект для сборки
  • Консольный сборщик проекта: Ninja, либо GNU Make)
  • esptool.py для прошивки модулей.

Каждый проект имеет один файл CMakeLists.txt верхнего уровня, который содержит параметры сборки для всего проекта. Минимальная конфигурация файла включает следующие необходимые строчки:
cmake_minimum_required(VERSION 3.5)include($ENV{IDF_PATH}/tools/cmake/project.cmake)project(myProject)


Проект ESP-IDF можно рассматривать как совокупность компонентов, в котором каталог main является главным комонентом, запускающим код. Поэтому в данной директории также содержится файл CMakeLists.txt. Чаще всего его структура подобна:
idf_component_register(SRCS "main.c" INCLUDE_DIRS ".")

Где указывается, что исходный файл main.c необходимо зарегистрировать для компонента, а файлы заголовков содержаться в текущем каталоге. При необходимости можно переименовать каталог main устаовив EXTRA_COMPONENT_DIRS в прокте CMakeLists.txt. Подробно можно ознакомиться здесь

Помимо этого в каталоге находится исходный main.с (имя может быть любое) файл с точкой входа функцией void app_main(void).
Кастомые компоненты создатся в каталоге components. Подробнее процесс описан в разделе Component Requirements.

Подключение модуля ESP32 компьютеру в большинстве случаев проиводится с помощью USB-кабеля подобно платам Arduino за счет имеющегося бутлоадера. Подробнее процесс описан здесь . Едиственное, что необходимо это наличие драйвера конвертора USB to Uart в системе, который можно скачать по ссылкам из приведенного источника. После установки драйвера, необходимо определить номер COM-порта в системе для загрузки скомпилированной прошивки в модуль.
Конфигурирование. В большинстве случаев подходят настройки по умолчанию. Но для вызова консольного интервейса меню необходимо перейти в каталог проекта и в командной строке набрать:

idf.py menuconfig


image
Меню с конфигурационными настройками

После вызова этой команды файл sdkconfig будет создан, если его ранее не было или заново сконфигурирован. В более ранних туториалах можно встретить команды make menuconfig, что являются уставревшими.
Добавление кастомных настроек в файл sdkconfig возможно вручную, например:

## WiFi Settings кастомное меню #CONFIG_ESP_HOST_NAME="имя точки"CONFIG_ESP_WIFI_SSID="название точки доступа"CONFIG_ESP_WIFI_PASSWORD="пароль"


Но предпочтительным является способ с помощью дополнительного конфигурационного файла Kconfig.projbuild, который необходимо располагать в каталоге с компонентом. Содержимое файла может быть следующим:

# put here your custom config valuemenu "Example Configuration"config ESP_WIFI_SSID    string "Keenetic"    default "myssid"    help    SSID (network name) for the example to connect to.config ESP_WIFI_PASSWORD    string "password"    default "mypassword"    help    WiFi password (WPA or WPA2) for the example to use.endmenu


После вызова команды idf.py menuconfig в файле sdkconfig автоматически добавиться дополнительный раздел. Вызов команды idf.py menuconfig возможен и в проекте PlatformIO, однако нужно учитывать факт отличия структура проекта PlatformIO от классического ESP-IDF, из-за чего файл sdkconfig с может заново сгенериться и потярять кастомные настройки. Тут возможны вышеупомянутые варианты правка файла руками, временнное переименование каталога src в main, или настройка файла CMakeLists.txt

Компиляция и загрузка проекта.
Для билда проекта необходимо набрать команду

idf.py build


Эта команда скомпилирует приложение и все компоненты ESP-IDF, а затем сгенерирует загрузчик, таблицу разделов и двоичные файлы приложения.

$ idf.py buildRunning cmake in directory /path/to/hello_world/buildExecuting "cmake -G Ninja --warn-uninitialized /path/to/hello_world"...Warn about uninitialized values.-- Found Git: /usr/bin/git (found version "2.17.0")-- Building empty aws_iot component due to configuration-- Component names: ...-- Component paths: ...... (more lines of build system output)[527/527] Generating hello-world.binesptool.py v2.3.1Project build complete. To flash, run this command:../../../components/esptool_py/esptool/esptool.py -p (PORT) -b 921600 write_flash --flash_mode dio --flash_size detect --flash_freq 40m 0x10000 build/hello-world.bin  build 0x1000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.binor run 'idf.py -p PORT flash'


Следует учитывать, что первоначальный процесс компиляции даже простого проекта занимает время, так, в отличие от Arduino фреймворка компилируются многие дополнительные модули ESP-IDF. Дальнейшее изменение исходников приводит только к компиляции этих же файлов. Исключение составляет изенения конфигурации.

Для загрузки скомпилированных двоичных файлов (bootloader.bin, partition-table.bin и hello-world.bin) на плату ESP32 необходимо запустить команду

idf.py -p PORT [-b BAUD] flash


где PORT мы заменяет на тот, что нам нужно (COM1, /dev/ttyUSB1), а также опционально можем изменить скорость загрузки, указав необходимое значения для BAUD

Для остлеживания загруженной программы можно использовать любую утилиту мониторинга com-порта, как HTerm , CoolTerm , или использовать утилиту мониторинга IDF Monitor, для ее запуска необходимо ввести команду:

idf.py -p PORT monitor


ESP-IDF Eclipse Plugin



Документация по установки и настройки плагина находится здесь

image

Предустановки для использования:

  • Java 11 and above; (хотя и на java 8 работает, возможно из-за этого глюки);
  • Python 3.5 and above;
  • Eclipse 2020-06 CDT;
  • Git;
  • ESP-IDF 4.0 and above ;


Плагин довольно неплохо интегрирован в среду разработки, автоматизирует львиную долю функционала. Но, к сожалению, не без ложки дегтя. В eclipse версиях позже 2019-09 в esp-idf проектах в Windows до сих пор присутствует баг с индексированием исходных файлов

image

Кроме этого наблюдаются и другие глюки, когда проект просто не билдится по непонятным причинам. Помогает только зарытие проекта и перезагрузка eclipse.

ESP-IDF Visual Studio Code Extension


И последний, на мой взгляд самый интересный вариант это официальный плагин для Visual Studio Code.
Как и PlatformIO легко устанавливается из раздела расширений. Установка и настройка ESP-IDF фреймворка в этом расширении представлена в качестве меню onboarding, о чем также говорится в описании. Загрузка и установка всех компонентов происходит автоматически в процессе прохождения этапов меню. Можно привести все скрины процесса, но они интуитивно понятны, и практически не требуют пояснения. В пользу PlatformIO можно отметить более удобный инструментарий билда, загрузки и мониторинга проекта. В отличие от этого esp-idf плагин управляется с помощью меню команд, что можно вызвать с помощью клавиши F1, или сочетании клавишь, описанных в мануале.

image
Первоначальная настройка плагина

Преимущество использования плагина в том, что соблюдается классическая структура проекта, нет необходимости как-то еще шаманить с настройками (в PlatformIO такая необходимость возникает). Есть один нюанс, если мы хотим открыть ранее созданных проект в visual studio code с esp-idf плагином, то нам всего лишь необходимо скопировать в корень с проектом каталог .vscode, который можно получить сгенерировав хотя бы один раз шаблонный проект с помощью esp-idf плагина.

image
Меню команд

FreeRTOS



Согласно википедии FreeRTOS многозадачная операционная система реального времени (ОСРВ) для встраиваемых систем.. FreeRTOS обеспечивает мультизадачность за счет совместного использования процессорного времени всеми потоками, или в терминологии ОС задачами (task). На мой взгляд наиболее полное и внятное руководство по FreeRTOS на русском находится здесь . На языке оригинала мануалы можно изучить из официального источника. Я лишь приведу рисунок состояния задач

image

FreeRTOS была портирована на широкий спектр аппаратных платформ, включая процессоры Xtensa, используемые в ESP32. Подробнее можно ознакомиться в документации

GPIOs


GPIO или универсальный ввод/вывод это возможность дискретного управления пина сигналом 1 или 0.

Как видно из самого названия такие пины имеют два рабочих режима ввод или вывод. В первом случая мы читаем значение, во втором записываем. Еще одним важным фактором при работе с GPIO является уровень напряжения. ESP32 это устройство с напряжением 3,3 В. Поэтому следует быть осторожным при работе с другими устройствами, которые имеют 5В и выше. Также важно понимать, что максимальный ток, которым можно нагружать вывод GPIO, составляет 12 мА. Для использования функций GPIO, предоставляемых ESP-IDF нам необходимо подключить заголовок driver/gpio.h. Затем можно вызвать gpio_pad_select_gpio (), чтобы указать функцию данного вывода. На ESP32 доступно 34 различных GPIO. Они обозначены как:

  • GPIO_NUM_0 GPIO_NUM_19
  • GPIO_NUM_21 GPIO_NUM_23
  • GPIO_NUM_25 GPIO_NUM_27
  • GPIO_NUM_32 GPIO_NUM_39

Следующая нумерация не входит в число пинов 20, 24, 28, 29, 30 и 31.
Таблицу с распиновком можно посмотреть здесь
Следует обратить внимание, что пины GPIO_NUM_34 GPIO_NUM_39 используют только режим ввода. Их нельзя использовать для вывода сигнала. Кроме того, контакты 6, 7, 8, 9, 10 и 11 используются для взаимодействия с внешней флеш-картой по SPI, не рекомендуется их использовать для других целей, но если очень хочется, то можно. Тип данных gpio_num_t это перечисление со значениями, соответствующими номерам пинов. Рекомендуется использовать эти значения, а не числа. Направление пина устанавливается с помощью функции gpio_set_direction (). Например, для установки пина как выход:

gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT);


Для установки пина в качестве входа:

gpio_set_direction(GPIO_NUM_17, GPIO_MODE_INPUT);


Если мы настроили GPIO как выход, то можем установить его значение равным 1 или 0, вызывая gpio_set_level ().

Следующий пример переключает GPIO раз в секунду:

gpio_pad_select_gpio(GPIO_NUM_17);gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT);while(1) {    printf("Off\n");    gpio_set_level(GPIO_NUM_17, 0);    vTaskDelay(1000 / portTICK_RATE_MS);    printf("On\n");    gpio_set_level(GPIO_NUM_17, 1);    vTaskDelay(1000 / portTICK_RATE_MS);}


В качестве альтернативы настройке всех атрибутов отдельных пинов мы можем установить свойства одного или нескольких контактов с помощью вызова функции gpio_config (). Она принимает в качестве входных данных структуру gpio_config_t и устанавливает направление, pull up, pull down и настройки прерывания для всех выводов, представленных в битовой маске. Например:

gpio_config_t gpioConfig;gpioConfig.pin_bit_mask = (1 << 16) | (1 << 17);gpioConfig.mode = GPIO_MODE_OUTPUT;gpioConfig.pull_up_en = GPIO_PULLUP_DISABLE;gpioConfig.pull_down_en = GPIO_PULLDOWN_ENABLE;gpioConfig.intr_type = GPIO_INTR_DISABLE;gpio_config(&gpioConfig);


Pull up и pull down настройки
Обычно читается, что входной пин GPIO имеет сигнал высокого или низкого уровня. Это означает, что он подключен к источнику питания или к земле. Однако, если пин не подключен ник чему, то он находится в плавающем (floating) состоянии. Часто необходимо установить начальный уровень неподключенного пина как высокий или низкий. В таком случае производится аппаратная (подключение c помощью резисторов) или программная поддяжка вывода соответственно к +V pull up или к 0 pull down. В ESP32 SDK мы можем определить GPIO как pull up или pull down с помощью функции gpio_set_pull_mode (). Эта функция принимает в качестве входных данных номер контакта, который мы хотим установить, и режим поддяжки, связанный с этим контактом. Например:
gpio_set_pull_mode (21, GPIO_PULLUP_ONLY);


GPIO обработка прерывания
Чтобы обнаружить изменение входного сигнала на пине мы можем периодически опрашивать его сотояние, однако это не лучшее решение по ряду причин. Во-первых мы должны циклически делать проверку, тратя процессорное время. Во-вторых в момент опроса состояние пина может быть уже не актуальное за счет задержки и можно пропустить входные сигналы. Решением этих проблем является прерывание. Прерывание похоже на дверной звонок. Без звонка нам придется периодически проверять, есть ли кто-нибудь у двери. В исходном коде мы можем определить функцию обратного вызова прерывания, которая будет вызываться, когда вывод изменяет значение своего сигнала. Мы также можем определить, что является причиной вызова обработчика, установив следующие параметры:
  • Disable не вызывать прерывание при изменении сигнала;
  • PosEdge вызов обработчика прерывания при изменении с низкого на высокий;
  • NegEdge вызов обработчика прерывания при изменении с высокого на низкий;
  • AnyEdge вызов обработчика прерывания либо при изменении с низкого на высокий, либо при изменении с высокого на низкий;


image

Обработчик прерывания может быть отмечен для загрузки в ОЗУ во время компиляции. По умолчанию сгенерированный код находится во флэш-памяти. Если предварительно пометить его как IRAM_ATTR, он будет готов к немедленному выполнению из опреативной памяти.

void IRAM_ATTR my_gpio_isr_handle(void *arg) {...}


image

Кто работал с микроконтроллерами, знает, что опработка входных сигналов от кнопок сопровождается дребезгом контактов. Что может интерпретироваться, как серия переходов, и следовательно, серия событий обработчика прерывания. Для этого мы должны внести в код обработку дребезга контактов. Для этого нам надо считать исходное событие, дождаться, пока вибрации не утихнут, а затем выполнить повторную выборку входного состояния.

image

Следующий пример демонстрирует обработку прерывания входных сигналов. Настоятельно рекомендую ознакомится с управлением очередями во FreeRTOS для дальнейшего понимания кода, если вы еще с этим не знакомы. В примере показаны две задачи:
test1_task, которая разблокируется при наступлении события прерывания при активации сигнала на пине 25, и в консоль единократно выводится сообщение Registered a click;
test2_task периодически опрашивается, и при активации сигнала на пине 26 каждые 100 мсек в консоль выводится сообщение GPIO 26 is high!.
В примере также установлен программный таймер xTimer, он не является обязательным для данного случая, скорее как пример асинхронной задержки.
Антидребезг производится с помощью функции timeval_durationBeforeNow, которая проверяет, длится ли нажатие более 100 мсек. Есть и другие программные шаблоны против дребезга, но смысл примерно тот же. В составе ESP-IDF также имеется пример работы GPIO

Обработка входных сигналов
#include <stdio.h>#include "freertos/FreeRTOS.h"#include "freertos/task.h"#include "driver/gpio.h"#include "esp_log.h"#include "freertos/queue.h"#include "c_timeutils.h"#include "freertos/timers.h"static char tag[] = "test_intr";static QueueHandle_t q1;TimerHandle_t xTimer;#define TEST_GPIO (25)static void handler(void *args) {    gpio_num_t gpio;    gpio = TEST_GPIO;    xQueueSendToBackFromISR(q1, &gpio, NULL);}void test1_task(void *ignore) {    struct timeval lastPress;    ESP_LOGD(tag, ">> test1_task");    gpio_num_t gpio;    q1 = xQueueCreate(10, sizeof(gpio_num_t));    gpio_config_t gpioConfig;    gpioConfig.pin_bit_mask = GPIO_SEL_25;    gpioConfig.mode = GPIO_MODE_INPUT;    gpioConfig.pull_up_en = GPIO_PULLUP_DISABLE;    gpioConfig.pull_down_en = GPIO_PULLDOWN_ENABLE;    gpioConfig.intr_type = GPIO_INTR_POSEDGE;    gpio_config(&gpioConfig);    gpio_install_isr_service(0);    gpio_isr_handler_add(TEST_GPIO, handler, NULL);    while(1) {        //ESP_LOGD(tag, "Waiting on queue");        BaseType_t rc = xQueueReceive(q1, &gpio, portMAX_DELAY);        //ESP_LOGD(tag, "Woke from queue wait: %d", rc);        struct timeval now;        gettimeofday(&now, NULL);        if (timeval_durationBeforeNow(&lastPress) > 100) {            if(gpio_get_level(GPIO_NUM_25)) {                ESP_LOGD(tag, "Registered a click");                if( xTimerStart( xTimer, 0 ) != pdPASS ) {                    // The timer could not be set into the Active state.                }            }        }        lastPress = now;    }    vTaskDelete(NULL);}void test2_task(void *ignore) {    gpio_set_direction(GPIO_NUM_26, GPIO_MODE_INPUT);    gpio_set_pull_mode(GPIO_NUM_26, GPIO_PULLDOWN_ONLY);    while(true) {        if(gpio_get_level(GPIO_NUM_26)) {            ESP_LOGD(tag, "GPIO 26 is high!");            if( xTimerStart( xTimer, 0 ) != pdPASS ) {                    // The timer could not be set into the Active state.                }        }        vTaskDelay(100/portTICK_PERIOD_MS);    }}void vTimerCallback( TimerHandle_t pxTimer ) {    ESP_LOGD(tag, "The timer has expired!");}void app_main(void){    xTaskCreate(test1_task, "test_task1", 5000, NULL, 8, NULL);    xTaskCreate(test2_task, "test_task2", 5000, NULL, 8, NULL);    xTimer = xTimerCreate("Timer",       // Just a text name, not used by the kernel.                            2000/portTICK_PERIOD_MS,   // The timer period in ticks.                            pdFALSE,        // The timers will auto-reload themselves when they expire.                            ( void * ) 1,  // Assign each timer a unique id equal to its array index.                            vTimerCallback // Each timer calls the same callback when it expires.                        );}



PCNT (Pulse Counter)



Модуль PCNT (счетчик импульсов) предназначен для подсчета количества нарастающих и / или спадающих фронтов входного сигнала. Каждый блок модуля имеет 16-битный регистр со знаком и два канала, которые можно настроить для увеличения или уменьшения значения счетчика. Каждый канал имеет входной сигнал, который фиксирует изменение сигнала, а также вход управления, который можно использовать для включения или отключения счета. Входы имеют дополнительные фильтры, которые можно использовать для устранения нежелательных выбросов сигнала.

Счетчик PCNT имеет восемь независимых счетных единиц (units), пронумерованных от 0 до 7. В API они указаны с помощью pcnt_unit_t. Каждый модуль имеет два независимых канала, пронумерованных 0 и 1 и указанны с помощью pcnt_channel_t.
Конфигурация предоставляется отдельно для каждого канала устройства с помощью pcnt_config_t и охватывает:

  • Номер юнита и номер канала к которому относится эта конфигурация;
  • Номера GPIO импульсного входа и входа стробирующего импульса;
  • Две пары параметров: pcnt_ctrl_mode_t и pcnt_count_mode_t для определения того, как счетчик реагирует в зависимости от состояния управляющего сигнала и того, как выполняется подсчет положительного / отрицательного фронта импульсов.
  • Два предельных значения (минимальное / максимальное), которые используются для установления точек наблюдения и запуска прерываний, когда счетчик импульсов достигает определенного предела.


Затем настройка конкретного канала выполняется путем вызова функции pcnt_unit_config () с указанной выше конфигурационной структурой pcnt_config_t в качестве входного параметра.
Чтобы отключить импульс или управляющий вход в конфигурации, нужно указать PCNT_PIN_NOT_USED вместо номера GPIO.

После выполнения настройки с помощью pcnt_unit_config () счетчик сразу же начинает работать. Накопленный значение счетчика можно проверить, вызвав pcnt_get_counter_value ().

Следующие функции позволяют управлять работой счетчика: pcnt_counter_pause (), pcnt_counter_resume () и pcnt_counter_clear ()

Также возможно динамически изменять ранее установленные режимы счетчика с помощью pcnt_unit_config (), вызывая pcnt_set_mode ().

При желании контакт импульсного входа и контакт входа управления можно изменить на лету с помощью pcnt_set_pin ().

Модуль PCNT имеет фильтры на каждом из импульсных и управляющих входов, добавляя возможность игнорировать короткие выбросы в сигналах. Длина игнорируемых импульсов предоставляется в тактовых циклах APB_CLK с помощью вызова pcnt_set_filter_value (). Текущие настройки фильтра можно проверить с помощью pcnt_get_filter_value (). Цикл APB_CLK работает на частоте 80 МГц.
Фильтр запускается / приостанавливается вызовом pcnt_filter_enable () / pcnt_filter_disable ().
Следующие события, определенные в pcnt_evt_type_t, могут вызвать прерывание. Событие происходит, когда счетчик импульсов достигает определенных значений:

  • Минимальные или максимальные значения счетчика: counter_l_lim или counter_h_lim, указанные в pcnt_config_t;
  • Достижение уставки 0 или уставки 1, что устанавливаются с помощью функции pcnt_set_event_value ().
  • Количество импульсов = 0


Чтобы зарегистрировать, включить или отключить прерывание для указанных выше событий, необходимо вызвать pcnt_isr_register (), pcnt_intr_enable (). и pcnt_intr_disable (). Чтобы включить или отключить события при достижении пороговых значений, также потребуется вызвать функции pcnt_event_enable () и pcnt_event_disable ().
Чтобы проверить, какие пороговые значения установлены на данный момент, нужно использовать функцию pcnt_get_event_value ().
Пример из ESP-IDF представлен здесь

Я использовать счетчик PCNT для вычислени скорости вращения колеса. Для этого необходимо считать количество импульсов на оборот, после чего обнлять счетчик.

Пример кода
typedef struct {      uint16_t delay; //delay im ms      int pin;      int ctrl_pin;      pcnt_channel_t channel;      pcnt_unit_t unit;      int16_t count;} speed_sensor_params_t;esp_err_t init_speed_sensor(speed_sensor_params_t* params) {      /* Prepare configuration for the PCNT unit */    pcnt_config_t pcnt_config;    // Set PCNT input signal and control GPIOs    pcnt_config.pulse_gpio_num = params->pin;    pcnt_config.ctrl_gpio_num = params->ctrl_pin;    pcnt_config.channel = params->channel;    pcnt_config.unit = params->unit;    // What to do on the positive / negative edge of pulse input?    pcnt_config.pos_mode = PCNT_COUNT_INC;   // Count up on the positive edge    pcnt_config.neg_mode = PCNT_COUNT_DIS;   // Keep the counter value on the negative edge    pcnt_config.lctrl_mode = PCNT_MODE_REVERSE; // Reverse counting direction if low    pcnt_config.hctrl_mode = PCNT_MODE_KEEP;    // Keep the primary counter mode if high    pcnt_config.counter_h_lim = INT16_MAX;    pcnt_config.counter_l_lim = - INT16_MAX;     /* Initialize PCNT unit */    esp_err_t err = pcnt_unit_config(&pcnt_config);    /* Configure and enable the input filter */    pcnt_set_filter_value(params->unit, 100);    pcnt_filter_enable(params->unit);    /* Initialize PCNT's counter */    pcnt_counter_pause(params->unit);    pcnt_counter_clear(params->unit);    /* Everything is set up, now go to counting */    pcnt_counter_resume(params->unit);    return err;}int32_t calculateRpm(speed_sensor_params_t* params) {    pcnt_get_counter_value(params->unit, &(params->count));    int32_t rpm = 60*(1000/params->delay)*params->count/PULSE_PER_TURN;    pcnt_counter_clear(params->unit);    return rpm;}



Широтно-импульсная модуляция (ШИМ) с использованием модуля MCPWM


Информация о модуле представлена здесь
В сети имеется много статей на тему ШИМ, особенно если искать применительно к Arduino.
Википедия дает короткое и емкое определение Широтно-импульсная модуляция (ШИМ, англ. pulse-width modulation (PWM)) процесс управления мощностью методом пульсирующего включения и выключения прибора. Принцип регулирования с помощью ШИМ изменение ширины импульсов при постоянной амплитуде и частоте сигнала.

image

Частота ШИМ Ардуино 488,28 Гц., разрешение 8 разрядов (0255), и имеется возможность использования шести аппаратных выводов 3, 5, 6, 9, 10, 11. Однако, используя настройки регисторов микроконтроллера AVR можно добиться и других значений частоты ШИМ.
Микроконтроллер ESP32 имеет в своем арсенале отдельный модуль MCPWM, а точнее два модуля, каждый из которых имеет три пары ШИМ выводов

image

Далее в документации выходы отдельного блока помечены как PWMxA / PWMxB.
Более подробная блок-схема блока MCPWM представлена ниже. Каждая пара A / B может синхронизироваться любым из трех таймеров Timer 0, 1 и 2. Один и тот же таймер может использоваться для синхронизации более чем одной пары выходов ШИМ. Каждый блок также может собирать входные данные, такие как сигналы синхронизации, обнаруживать сигналы тревог, такие как перегрузка по току или перенапряжение двигателя, а также получать обратную связь с помощью сигналов захвата, например, на положение ротора.

image

Объем конфигурации зависит от типа двигателя, в частности, от того, сколько выходов и входов требуется, и какой будет последовательность сигналов для управления двигателем.
В нашем случае опишем простую конфигурацию для управления щеточным двигателем постоянного тока, который использует только некоторые из доступных ресурсов MCPWM. Пример схемы показан ниже. Он включает в себя H-мост для переключения поляризации напряжения, подаваемого на двигатель (M), и обеспечения достаточного тока для его управления.

image

Конфигурация включает следующие шаги:

  • Выбор блока MPWn, который будет использоваться для привода двигателя. На плате ESP32 доступны два модуля, перечисленных в mcpwm_unit_t.
  • Инициализация двух GPIO в качестве выходных сигналов в выбранном модуле путем вызова mcpwm_gpio_init (). Два выходных сигнала обычно используются для управления двигателем вправо или влево. Все доступные параметры сигналов перечислены в mcpwm_io_signals_t. Чтобы установить более одного вывода за раз, нужно использовать функцию mcpwm_set_pin () вместе с mcpwm_pin_config_t.
  • Выбор таймера. В устройстве доступны три таймера. Таймеры перечислены в mcpwm_timer_t.
  • Установка частоты таймера и начальной загрузки в структуре mcpwm_config_t.
  • Вызов mcpwm_init () с указанными выше параметрами.


Способы управления ШИМ следующие:

  • Мы можем установить высокое или низкое значение для конкретного сигнала с помощью функции mcpwm_set_signal_high () или mcpwm_set_signal_low (). Это заставит двигатель вращаться с максимальной скоростью или остановиться. В зависимости от выбранного выхода A или B двигатель будет вращаться вправо или влево.
  • Другой вариант подать на выходы сигнал ШИМ, вызвав mcpwm_start () или mcpwm_stop (). Скорость двигателя будет пропорциональна режиму ШИМ.
  • Чтобы изменить режим ШИМ, необходимо вызвать mcpwm_set_duty () и указать значение режима в ролцентах. При желании можно вызвать mcpwm_set_duty_in_us (), чтобы устанавливать значение в микросекундах. Проверить текущее установленное значение можно с помощью вызова mcpwm_get_duty (). Фазу сигнала ШИМ можно изменить, вызвав mcpwm_set_duty_type (). Коэффициент заполнения устанавливается индивидуально для каждого выхода A и B с помощью mcpwm_generator_t. Значение коэффициента заполнения относится к длительности высокого или низкого выходного сигнала. Это настраивается при вызове mcpwm_init (), как описано выше, и выборе одной из опций в mcpwm_duty_type_t.


Пример кода для коллекторного двигателя находится здесь

В своем проекте я практически использовал код из примера, немного подкорректировав его и добавил управление вторым двигателем. Для независимого управления ШИМ-каналами необходимо каждый из них настроить отдельным таймером, например MCPWM_TIMER_0 и CPWM_TIMER_1:

Пример кода
void mcpwm_example_gpio_initialize(void){    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT);    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, GPIO_PWM0B_OUT);    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1A, GPIO_PWM1A_OUT);    mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM1B, GPIO_PWM1B_OUT);    //mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_SYNC_0, GPIO_SYNC0_IN);    mcpwm_config_t pwm_config;    pwm_config.frequency = 1000;    //frequency = 500Hz,    pwm_config.cmpr_a = 0;    //duty cycle of PWMxA = 0    pwm_config.cmpr_b = 0;    //duty cycle of PWMxb = 0    pwm_config.counter_mode = MCPWM_UP_COUNTER;    pwm_config.duty_mode = MCPWM_DUTY_MODE_0;    mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);    //Configure PWM0A & PWM0B with above settings    mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_1, &pwm_config);    //Configure PWM0A & PWM0B with above settings          // deadtime (see clock source changes in mcpwm.c file)    mcpwm_deadtime_enable(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_BYPASS_FED, 80, 80);   // 1us deadtime    mcpwm_deadtime_enable(MCPWM_UNIT_0, MCPWM_TIMER_1, MCPWM_BYPASS_FED, 80, 80);  }



Подключение к WI-Fi и работа с MQTT


Тема протокола Wi-FI достаточно обширная. Для описания протокола понадобиться серия отдельных статей. В официальном руководстве можно ознакомиться с разделом Wi-Fi driver. Описание программного API находится здесь . Примеры кода можно посмотреть здесь

Библиотеки Wi-Fi обеспечивают поддержку для настройки и мониторинга сетевых функций ESP32 WiFi. Следующие доступные конфигурации:

  • Режим станции (также известный как режим STA или режим клиента WiFi). ESP32 подключается к точке доступа.
  • Режим AP (он же режим Soft-AP или режим точки доступа). Станции подключаются к ESP32.
  • Комбинированный режим AP-STA (ESP32 одновременно является точкой доступа и станцией, подключенной к другой точке доступа).
  • Различные режимы безопасности для вышеперечисленных (WPA, WPA2, WEP и т. Д.)
  • Поиск точек доступа (активное и пассивное сканирование).
  • Смешанный режим для мониторинга пакетов WiFi IEEE802.11.


MQTT протокол. Ознакомится с темой можно здесь или здесь . Руководство ESP-IDF с примерами находится здесь .

Для настройки MQTT в коде сначала необходимо подключиться к сети Wi-Fi. После чего установить подключение к брокеру. Обработка сообщения производится в коллбеке в качестве параметра которого выступает esp_mqtt_event_handle_t event. Если тип события равен MQTT_EVENT_DATA, значит можно парсить топик и данные. Можно настроить различное поведение в результате успешного подключения, отключение и подписки на топики.

Пример подключени к Wi-Fi:
tcpip_adapter_init();    wifi_event_group = xEventGroupCreate();    ESP_ERROR_CHECK(esp_event_loop_create_default());    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &ip_event_handler, NULL));    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();    ESP_ERROR_CHECK( esp_wifi_init(&cfg) );    ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );    wifi_config_t sta_config = {        .sta = {            .ssid = CONFIG_ESP_WIFI_SSID,            .password = CONFIG_ESP_WIFI_PASSWORD,            .bssid_set = false        }    };    ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &sta_config) );    ESP_LOGI(TAG, "start the WIFI SSID:[%s] password:[%s]", CONFIG_ESP_WIFI_SSID, "******");    ESP_ERROR_CHECK( esp_wifi_start() );    ESP_LOGI(TAG, "Waiting for wifi");    xEventGroupWaitBits(wifi_event_group, BIT0, false, true, portMAX_DELAY);    //MQTT init    mqtt_event_group = xEventGroupCreate();    mqtt_app_start(mqtt_event_group);



Подключение к MQTT брокеру
void mqtt_app_start(EventGroupHandle_t event_group){    mqtt_event_group = event_group;    const esp_mqtt_client_config_t mqtt_cfg = {        .uri = "mqtt://mqtt.eclipse.org:1883",    //mqtt://mqtt.eclipse.org:1883        .event_handle =  mqtt_event_handler,        .keepalive = 10,        .lwt_topic = "esp32/status/activ",        .lwt_msg = "0",        .lwt_retain = 1,    };    ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());    client = esp_mqtt_client_init(&mqtt_cfg);    esp_mqtt_client_start(client);



Обработчик MQTT
esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event){    esp_mqtt_client_handle_t client = event->client;    int msg_id;    command_t command;    // your_context_t *context = event.context;    switch (event->event_id) {        case MQTT_EVENT_CONNECTED:             xEventGroupSetBits(mqtt_event_group, BIT1);            ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");            msg_id = esp_mqtt_client_subscribe(client, "esp32/car/#", 0);            msg_id = esp_mqtt_client_subscribe(client, "esp32/camera/#", 0);            ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);            break;        case MQTT_EVENT_DISCONNECTED:            ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");            break;        case MQTT_EVENT_SUBSCRIBED:            ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);            msg_id = esp_mqtt_client_publish(client, "esp32/status/activ", "1", 0, 0, 1);            ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);            break;        case MQTT_EVENT_UNSUBSCRIBED:            ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);            break;        case MQTT_EVENT_PUBLISHED:            ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);            break;        case MQTT_EVENT_DATA:            ESP_LOGI(TAG, "MQTT_EVENT_DATA");            printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);            printf("DATA=%.*s\r\n", event->data_len, event->data);            memset(topic, 0, strlen(topic));            memset(data, 0, strlen(data));            strncpy(topic, event->topic, event->topic_len);            strncpy(data, event->data, event->data_len);            command_t command = {                .topic = topic,                .message = data,            };            parseCommand(&command);            break;        case MQTT_EVENT_ERROR:            ESP_LOGI(TAG, "MQTT_EVENT_ERROR");            break;        default:            break;    }    return ESP_OK;}



На этом я завершаю свое повествование об использовании модуля ESP32. В статье были рассмотрены примеры на ESP-IDF, как фреймворка максимально использующего ресурсы модуля. Программирование с использованием других платформ, как javaScript, MicroPython, Lua можно найти на соответствующих ресурсах. В следующей статье, как уже упоминалось, я приведу практический пример использовании микроконтроллера. Сравню программный подход Arduino и ESP-IDF.
Подробнее..

Платформа с web-камерой на ESP32

13.10.2020 00:06:12 | Автор: admin
Идея собрать мобильную платформу с web-камерой на борту появилась практически спонтанно. Мне хотелось иметь в арсенале скромной домашней автоматизации что-то вроде IP-камеры. И тут даже не столько вопрос в цене или в качестве, скорее это можно назвать творческим экспериментом. Материалом для вдохновения были различные статьи DIY и проекты вроде этого

В сборе конструкция выглядит так

image

Комплектующие


В качестве базы выступает мобильная двухпалубная робо-платформа Car Chassis 2WD Mini Kit

image

Размеры платформы: 135 мм х 135 мм х 80 мм

Приводом являются два стандартных мотор-колеса с редуктором и двигателем постоянного тока с растровыми дисками для датчиков скорости:

  • номинальный ток: 250 мА макс. при напряжении 3,6 В
  • крутящий момент 800 г/см (при напряжении 6В)
  • напряжение питания: 6 8 В
  • скорость вращения без нагрузки: 170 об/мин (при напряжении 3,6 В)
  • передаточное число редуктора: 1: 48
  • оси выходят с двух сторон
  • диаметр осей: 5 мм
  • размеры: 64x20x20 мм
  • вес: 26 г


В качестве драйвера электродвигателя выбран модуль MX1508
Почитать про модуль можно здесь

image

Технические параметры:

  • Напряжение питания: 2 10 В
  • Рабочий драйвера на один канал: 1.5 А (пиковый ток 2.5 А, не более 10 секунд)
  • Входной сигнал логика: 5 В
  • Габариты: 24,7 х 21 х 0,5 мм


Для горизонтального и вертикального перемещения IP камеры выбраны популярные серводвижки SG90 2кг

image

На сайте производителя представлена следующая спецификация:

  • Weight: 9g
  • Dimension: 2312.2x29mm
  • Stall torque: 1.8kg/cm(4.8v)
  • Gear type: POM gear set
  • Operating speed: 0.1sec/60degree(4.8v)
  • Operating voltage: 4.8v
  • Temperature range: 0_ 55
  • Dead band width: 1us
  • Power Supply: Through External Adapter
  • servo wire length: 25 cm
  • Servo Plug: JR (Fits JR and Futaba)


Для web-камеры был выбран держатель FPV Bracket Kit

image

Описание держателя в интернет-магазине: FPV позволит ориентировать FPV-камеру в 3-х плоскостях. Простое подключение и управление позволит быстро собрать и подключить платформу к контроллеру или полетному контроллеру. Используется вместе с сервоприводами EMAX 9g ES08A Mini Servo или сервами SG90 (с некоторыми доработками).
С некоторыми доработками следует учитывать, набор пришлось дорабатывать напильником в прямом смысле. Но для DIY за $0.36 вполне себе ничего. Некоторые жаловались, что даже доработка не помогла, и сервы не подошли по размерам, в моем же случае все норм. Используется два движка SG90 для горизонтального и вертикального перемещения камеры. Вариант спроектировать и распечатать на 3D-принтере тоже рассматривался, но остановился пока на этом держателе.

IP Камера на базе ESP32 CAM

image

Согласно описанию: The I2S subsystem in the ESP32 also provides a high speed bus connected directly to RAM for Direct Memory Access. Putting it simply, you can configure the ESP32 I2S subsystem to send or receive parallel data under hardware control.
Т.е. можно настроить интерфейс I2S ESP32 для отправки или получения параллельных данных под аппаратным управлением, что и реализовано для подключения камеры. Разработкой данной платы занимается компания Seeed Studio, здесь представлена цена $9.90, но в наших радиомагазинах продают за $8, видимо не только Seeed Studio их может производить.

Технические данные:

  • The smallest 802.11b/g/n Wi-Fi BT SoC Module
  • Low power 32-bit CPU,can also serve the application processor
  • Up to 160MHz clock speedSummary computing power up to 600 DMIPS
  • Built-in 520 KB SRAM, external 4MPSRAM
  • Supports UART/SPI/I2C/PWM/ADC/DAC
  • Support OV2640 and OV7670 cameras,Built-in Flash lamp.
  • Support image WiFI upload
  • Support TF card
  • Supports multiple sleep modes.
  • Embedded Lwip and FreeRTOS
  • Supports STA/AP/STA+AP operation mode
  • Support Smart Config/AirKiss technology
  • Support for serial port local and remote firmware upgrades (FOTA)


Источник питания


Управление платформы от автономного питания длительное время без подзарядки не предусматривалось. Поэтому в качестве источника был выбран модуль питания 2А на 18650 с USB-выходом с одним слотом.

image

Характеристики:
  • Тип аккумулятора: 18650 Li-Ion (без защиты)
  • Напряжение зарядного устройства: от 5В до 8В
  • Выходные напряжения:
  • 3В непосредственно с аккумулятора через защитное устройство
  • 5В через повышающий преобразователь.
  • Максимальный выходной ток:
  • Выход 3В 1А
  • Выход 5В 2А
  • Максимальный ток зарядки: 1А
  • Тип входного разъёма: micro-USB
  • Тип выходного разъёма: USB-A
  • Потребителей 5В от перегрузки и короткого замыкания
  • Габаритные размеры:
  • Печатная плата: 29,5 х 99,5 х 19 мм
  • Всего устройства: 30 х 116 х 20 мм


В качестве основного контроллера выбран ESP-WROOM-32

Ранее я описывал характеристики ESP32 более детально. Здесь приведу базовые характеристики модуля:
  • 32-битный двуядерный микропроцессор Xtensa LX6 до 240 МГц
  • Флеш-память: 4 МБ
  • Беспроводная связь Wi-Fi 802.11b/g/n до 150 Мб/c, Bluetooth 4.2 BR/EDR/BLE
  • Поддержка STA/AP/STA+AP режимов, встроенный стек TCP/IP
  • GPIO 32 (UART, SPI, I2C, I2S интерфейсы, ШИМ, SD контроллеры, емкостные сенсорные, АЦП, ЦАП и не только
  • Питание: через microUSB разъем (CP2102 преобразователь) или выводы
  • Шаг выводов: 2.54 мм (можно вставить в макетную плату)
  • Размер платы: 5.2 х 2.8 см


В качестве датчиков скорости применены два оптических энкодеров Noname для подсчета импульсов вращения растровых дисков мотор-колеса

image

Характеристики:
  • Напряжение питания: 3,3В 5В
  • Ширина паза датчика: 6 мм;
  • Тип выхода: аналоговый и цифровой
  • Индикатор: cостояние выхода


Для измерения расстояния применен популярный ультразвуковой дальномер HC-SR04

image

Характеристики:
  • Напряжение питания: 5 В
  • Потребление в режиме тишины: 2 мА
  • Потребление при работе: 15 мА
  • Диапазон расстояний: 2400 см
  • Эффективный угол наблюдения: 15
  • Рабочий угол наблюдения: 30


Программные реализации


Первым шагом стало знакомство и прошивка модуля ESP32 CAM.
Описание работы с модулем представлены на Харбе, здесь, здесь и на других ресурсах.
В основном статьи описывают простой процесс прошивки с помощью Arduino IDE. В большинстве случает этого достаточно, и меня по-началу этот вариант тоже устраивал.

image

В радиомагазинах модули ESP32-CAM продаются с камерой OV2640, поэтому в скетче необходимо сделать небольшое изменение:

// Select camera model//#define CAMERA_MODEL_WROVER_KIT//#define CAMERA_MODEL_ESP_EYE//#define CAMERA_MODEL_M5STACK_PSRAM//#define CAMERA_MODEL_M5STACK_WIDE#define CAMERA_MODEL_AI_THINKER


А также указать SSID и пароль к точке доступа Wi-Fi

const char* ssid = "REPLACE_WITH_YOUR_SSID";const char* password = "REPLACE_WITH_YOUR_PASSWORD";


Одним из условий работы web-камеры в моем случае является возможность передавать видеопоток через прокси-сервер Keenetic. Я использую домашний роутер Keenetik Viva. Сервис KeenDNS предоставляет доменное имя домашнему web-ресурсу. Но к моему удивлению первая попытка завершилась неудачей. При попытке удаленного доступа через интернет я получил ошибку Header fields are too long for server to interpret. С подобной проблемой я не первый столкнулся. Решением данной проблемы является изменение конфигурации CONFIG_HTTPD_MAX_REQ_HDR_LEN, например

#define CONFIG_HTTPD_MAX_REQ_HDR_LEN 2048


В случае с Arduino IDE ESP32 модули уже скомпилированны и представлены в виде статических библиотек, которые располагаются в Windows по пути %userprofile%\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\tools\sdk\
Просто изменение параметра в заголовке ничего не даст.
То есть для изменения конфигурации нам необходимо перекомпилировать библиотеки ESP-IDF.
Решением стало клонирование проекта github.com/espressif/esp-who. В каталоге с примерами находим проект camera_web_server, делаем изменение параметра максимальной длины заголовка, ну а также не забываем указать настройки подключения к Wi-Fi

image

Для того, чтобы проект скомпилировался, пришлось установить еще один чек-бокс Support array rtc_gpio_desc for ESP32

image

После успешной компиляции и загрузки проекта переходим по соответствующему IP адресу в браузере и попадаем на страницу с интерфейсом нашей web-камеры.

image

Интерфейс похож на Arduino примеры, но добавлен некоторый функционал.

Я внес небольшие изменения в исходный файл app_httpd.c для управления сигналом вывода GPIO_NUM_2 с web-интерфейса. Хотя в описании модуля говориться об использовании пинов для нужд SD-карты, но я ее не использую, поэтому могу задействовать данные пины.

void app_httpd_main(){gpio_set_direction(GPIO_NUM_2, GPIO_MODE_OUTPUT);static esp_err_t cmd_handler(httpd_req_t *req){.......//строчка 736else if(!strcmp(variable, "gpio2")) {    if (val == 0)                gpio_set_level(GPIO_NUM_2, 0);            else                gpio_set_level(GPIO_NUM_2, 1);    }


Для дистанционного управления я наверстал незамысловатую панель на Node-Red, что крутиться на Raspberry pi.

image

Изображение видеопотока удалось встроить в ноду template:

<iframe     src="http://personeltest.ru/away/192.168.1.61"    width="300" height="300"></iframe>


Тут важен один момент: необходимо встраивать именно http, в случае https будут проблемы с Content-Security-Policy. Если же и в этом случае будут возникать проблемы, то можно попробовать добавить заголовки:

<script>    var meta = document.createElement('meta');    meta.httpEquiv = "Content-Security-Policy";    meta.content = "default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';";document.getElementsByTagName('head')[0].appendChild(meta);</script>


Для управления пином GPIO_NUM_2 модуля ESP32-CAM после внесения изменений в прошивку следует выполнять следующий http GET запрос:

http://192.168.1.61/control?var=gpio2&val=1 //или 0


На интерфейсе панели это переключатель wakeup, в рабочем потоке это выглядит так

image

где функция request:

var newMsg = {}var i = msg.payload ? 1 : 0;newMsg.query = "control?var=gpio2&val=" + inode.send(newMsg)


Настройки ноды http request:

image

Остальные параметры и статусы передаются по MQTT

Подключение к Wi-Fi и MQTT


Я приведу примеры, используя Arduino фреймворк, так как я на нем также экспериментировал. Но в итоге рабочее приложение у меня на ESP-IDF.

Подключение заголовка #include <WiFi.h>

Функция подключения к Wi-Fi для Arduino фреймворка
void setup_wifi(){  Serial.println("Starting connecting WiFi.");  delay(1000);  for (int8_t i = 0; i < 3; i++)  {    WiFi.begin(ssid, password);    uint32_t start = millis();    while (WiFi.status() != WL_CONNECTED && ((millis() - start) < 4000))    {      Serial.print(".");      delay(500);    }    if (WiFi.status() == WL_CONNECTED)    {      Serial.println("WiFi connected");      Serial.println("IP address: ");      Serial.println(WiFi.localIP());      return;    }    else    {      Serial.println("Connecting Failed");      //WiFi.reconnect(); // this reconnects the AP so the user can stay on the page    }  }}



В функции присутствует цикл на три итерации, т.к. с первой попытки часто не подключается, а затем бесконечно ждет статуса WL_CONNECTED. Может как-то по-другому еще можно решить, но так точно работает.

Подключение к MQTT для Arduino фреймворка выполняется с помощью библиотеки github.com/knolleary/pubsubclient.git.

Для использования библиотеки необходимо подключить заголовок #include <PubSubClient.h>

Функция подключения к MQTT
bool setup_mqtt(){  if (WiFi.status() == WL_CONNECTED)  {    if (!client.connected())    {      client.setServer(mqtt_server, 1883);      client.setCallback(callback);    }    Serial.print("Connecting to MQTT server ");    Serial.print(mqtt_server);    Serial.println("...");    String clientId = "ESP32_car_client";    if (client.connect(clientId.c_str()))    {      Serial.println("Connected to MQTT server ");      //subscribing to topics      client.subscribe("esp32/car/#");      client.subscribe("esp32/camera/#");      return true;    }    else    {      Serial.println("Could not connect to MQTT server");      return false;    }  }  return false;}



Вначале мы проверяем, что подключены к Wi-Fi, затем подключаемся к брокеру client.setServer(mqtt_server, 1883);

И устанавливаем коллбек функцию client.setCallback(callback);

MQTT коллбек функция
void callback(char *topic, byte *payload, unsigned int length){  Serial.println("Message arrived ");  memset(payload_buf, 0, 10);  for (int i = 0; i < length; i++)  {    payload_buf[i] = (char)payload[i];  }  command_t mqtt_command = {      .topic = topic,      .message = payload_buf};  xQueueSend(messageQueue, (void *)&mqtt_command, 0);}



В случае успешного подключения подписываемся на топики

client.subscribe("esp32/car/#");client.subscribe("esp32/camera/#");


Были замечены случаи отваливания MQTT соединения, поэтому была добавлена проверка в периодическую задачу опроса.

Периодическая задача опроса
void pollingTask(void *parameter){  int32_t start = 0;  while (true) {    if (!client.connected()) {      long now = millis();      if (now - start > 5000) {        start = now;        // Attempt to reconnect        if (setup_mqtt()) {          start = 0;        }      }    }    else {      client.loop();      int val = digitalRead(WAKEUP_PIN);      if (val == LOW) {        Serial.println("Going to sleep now");        esp_deep_sleep_start();      }    }    vTaskDelay(100 / portTICK_PERIOD_MS);  }  vTaskDelete(NULL);}



Пример подключения к Wi-FI и MQTT c помощью ESP-IDF был описан в предыдущей статье

В случае использования ESP-IDF глюков при подключении к Wi-Fi и MQTT не наблюдалось. Один нюанс при обработке данных из MQTT топика в функции esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event): при типе события MQTT_EVENT_DATA следует учитывать параметры event->topic_len и event->data_len и брать имя топика и данные именно соответствующей длины, в противном случае мы получим мусор. Для этого мы можем создать буферные массивы или выделить память динамически (затем освободить ее), и скопировать данные, например так

strncpy(topic, event->topic, event->topic_len);strncpy(data, event->data, eventdata_len);


Отправка данных в топик производится с помощью функции esp_mqtt_client_publish

esp_mqtt_client_publish(client, topics[i], topic_buff[i], 0,0,0);


Обработка данных ультразвукового датчика HC-SR04


HC-SR04 дешевый и популярный датчик для проектирования устройств с микроконтроллерами. Как обычно в сети куча материала на эту тему: здесь и здесь. Описание также можно посмотреть здесь, а краткий даташит здесь.
Если коротко, то для начала измерения расстояния необходимо подать высокий сигнал длительностью 10 s на пин Trig. Это инициирует передачу сенсором 8 циклов ультразвукового импульса с частотой 40 кГц и ожидания отраженного ультразвукового импульса. Когда датчик обнаруживает ультразвуковой сигнал от приемника, он устанавливает для вывода Echo высокий уровень и задержку на период (ширину), пропорциональный расстоянию. Чтобы вычислить расстояние необходимо вычислить формулу:

distance = duration * 340 м/с = duration * 0.034 м/мкс, где

340 м/с скорость распространения звука в воздухе.

image

В Arduino фреймворке функция pulseIn позволяет узнать длительность импульса в s
Для ESP-IDF есть проект ESP-IDF Components library, в котором также есть компонент ultrasonic для HC-SR04.

Пример кода
esp_err_t ultrasonic_measure_cm(const ultrasonic_sensor_t *dev, uint32_t max_distance, uint32_t *distance){    CHECK_ARG(dev && distance);    PORT_ENTER_CRITICAL;    // Ping: Low for 2..4 us, then high 10 us    CHECK(gpio_set_level(dev->trigger_pin, 0));    ets_delay_us(TRIGGER_LOW_DELAY);    CHECK(gpio_set_level(dev->trigger_pin, 1));    ets_delay_us(TRIGGER_HIGH_DELAY);    CHECK(gpio_set_level(dev->trigger_pin, 0));    // Previous ping isn't ended    if (gpio_get_level(dev->echo_pin))        RETURN_CRITICAL(ESP_ERR_ULTRASONIC_PING);    // Wait for echo    int64_t start = esp_timer_get_time();    while (!gpio_get_level(dev->echo_pin))    {        if (timeout_expired(start, PING_TIMEOUT))            RETURN_CRITICAL(ESP_ERR_ULTRASONIC_PING_TIMEOUT);    }    // got echo, measuring    int64_t echo_start = esp_timer_get_time();    int64_t time = echo_start;    int64_t meas_timeout = echo_start + max_distance * ROUNDTRIP;    while (gpio_get_level(dev->echo_pin))    {        time = esp_timer_get_time();        if (timeout_expired(echo_start, meas_timeout))            RETURN_CRITICAL(ESP_ERR_ULTRASONIC_ECHO_TIMEOUT);    }    PORT_EXIT_CRITICAL;    *distance = (time - echo_start) / ROUNDTRIP;    return ESP_OK;}



В комментариях присутствует объяснение к алгоритму. Измерение длительности импульса происходит в цикле while пока уровень сигнала высокий на Echo пине (после // got echo, measuring) после чего расстояние измеряется

*distance = (time - echo_start) / ROUNDTRIP

Коэффициент для получения расстояния в сантиметрах ROUNDTRIP = 58.

В Arduino фреймворке это выглядит еще проще

Пример кода
#include "ultrasonic.h"portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;#define PORT_ENTER_CRITICAL portENTER_CRITICAL(&mux)#define PORT_EXIT_CRITICAL portEXIT_CRITICAL(&mux)Ultrasonic::Ultrasonic() {  pinMode(GPIO_NUM_33, OUTPUT);  pinMode(GPIO_NUM_26, INPUT);}uint32_t Ultrasonic::calculateDistance() {    PORT_ENTER_CRITICAL;    digitalWrite(GPIO_NUM_33, LOW);    delayMicroseconds(2);    digitalWrite(GPIO_NUM_33, HIGH);    delayMicroseconds(10);    digitalWrite(GPIO_NUM_33, LOW);    duration = pulseIn(GPIO_NUM_26, HIGH);    PORT_EXIT_CRITICAL;    distance = duration / 58;    return distance;}uint32_t Ultrasonic::getDistance() {    return distance;}



Была попытка использования библиотеки ultrasonic ESP-IDF для Arduino проекта ESP32, но работает это дело до первого глюка датчика. Почему так, точно выяснить не удалось. Глюк датчика это периодически просчет в импульсах и выдача ложных показаний, в вычисленных цифрах это выглядит, как расстояние более 20000 см. На форумах пишут, что это из-за некачественного датчика (китайская копия).

Измерение скорости с помощью оптических датчиков


Оптический модуль считывания импульсов создан на основе компаратора LM393 и щелевого датчика. Предназначен для использования с растровыми дисками, которые одеваются на вал редуктора или электродвигателя.

Как обычно на эту тему уже есть статьи: digitrode.ru, mirrobo.ru, и arduino-kit.ru.

Схема датчика:

image

В Arduino фреймворке мы вычисляем скорость следующим образом:
определяем переменную (структуру) счетчика, например
typedef struct {  int encoder_pin = ENCODER_PIN; // pulse output from the module  unsigned int rpm = 0; // rpm reading  volatile byte pulses = 0; // number of pulses  unsigned long timeold = 0;  unsigned int pulsesperturn = 20;} pulse_t;


Затем в функции setup мы должны зарегистрировать входной пин и прерывание на него

pinMode(pulse_struct.encoder_pin, INPUT);attachInterrupt(pulse_struct.encoder_pin, counter, FALLING);


Далее вычисляется количество оборотов в минуту

pulse_struct.rpm =         (60 * 1000 / pulse_struct.pulsesperturn )/         (1000)* pulse_struct.pulses;


Пример кода
void pulseTask(void *parameters) {  sensor_data_t data;  data.sensor = OPTICAL_SENSOR;  portBASE_TYPE xStatus;   while (true) {      //Don't process interrupts during calculations      detachInterrupt(0);      pulse_struct.rpm =         (60 * 1000 / pulse_struct.pulsesperturn )/         (1000)* pulse_struct.pulses;      pulse_struct.pulses = 0;      data.value = pulse_struct.rpm;      //Restart the interrupt processing      attachInterrupt(0, counter, FALLING);      Serial.print("optical: ");      Serial.println(data.value);     //Sending data to sensors queue    xStatus = xQueueSend(sensorQueue, (void *)&data, 0);    if( xStatus != pdPASS ) {     printf("Could not send optical to the queue.\r\n");    }    taskYIELD();    vTaskDelay(1000 / portTICK_PERIOD_MS);   }}



В ESP-IDF для этих целей можно использовать аппаратный счетчик PCNT, который был описан в предыдущей статье.

Пример кода обрабатываемой задачи
typedef struct {      uint16_t delay; //delay im ms      int pin;      int ctrl_pin;      pcnt_channel_t channel;      pcnt_unit_t unit;      int16_t count;} speed_sensor_params_t;void pulseTask(void *parameters) {  sensor_data_t data_1;  sensor_data_t data_2;  data_1.sensor = OPTICAL_SENSOR_1;  data_2.sensor = OPTICAL_SENSOR_2;  portBASE_TYPE xStatus;  speed_sensor_params_t params_1 = {      .delay = 100,      .pin = ENCODER_1_PIN,      .ctrl_pin = GPIO_NUM_0,      .channel = PCNT_CHANNEL_0,      .unit = PCNT_UNIT_0,      .count = 0,  };    ESP_ERROR_CHECK(init_speed_sensor(&params_1));    speed_sensor_params_t params_2 = {      .delay = 100,      .pin = ENCODER_2_PIN,      .ctrl_pin = GPIO_NUM_1,      .channel = PCNT_CHANNEL_0,      .unit = PCNT_UNIT_1,      .count = 0,  };    ESP_ERROR_CHECK(init_speed_sensor(&params_2));    while(true) {        data_1.value = calculateRpm(&params_1);        data_2.value = calculateRpm(&params_2);        sensor_array[OPTICAL_SENSOR_1] = data_1.value;        sensor_array[OPTICAL_SENSOR_2] = data_2.value;        printf("speed 1 = %d\n", data_1.value);        printf("speed 2 = %d\n", data_2.value);        xStatus = xQueueSend(sensorQueue, (void *)&data_1, 0);        xStatus = xQueueSend(sensorQueue, (void *)&data_2, 0);        if( xStatus != pdPASS ) {        printf("Could not send optical to the queue.\r\n");        }        vTaskDelay(100 / portTICK_PERIOD_MS);}}



ШИМ управление


Про управление сервоприводами на Arduino можно почитать на developer.alexanderklimov, wiki.amperka.ru.
Как сказано в источнике выше: сервопривод это механизм с электромотором, который может поворачиваться в заданный угол и удерживать текущее положение. Практически мы имеем дело с широтно-импульсной модуляцией, где от ширины импульса сигнала зависит угол поворота привода.

image

Для ESP32 на Arduino фреймворке можно использовать библиотеку ESP32Servo

Для этого мы подключаем заголовок

#include <ESP32Servo.h>


Создаем объект

Servo servo_horisontal;


Указываем выходной пин

 servo_horisontal.attach(SERVO_CAM_HOR_PIN);


После этого можем записывать необходимое значение величины поворота

servo_horisontal.write(value);


ШИМ управление для других типов устройств на Arduino фреймворке производится с помощью библиотеки esp32-hal-ledc.h
Микроконтроллеры ESP32 не поддерживают стандартную функцию Arduino analogWrite() для ШИМ. Вместо их прдусмотрены функции:
ledcSetup(channel, freq, resolution_bits) указываются канал, частота и разрешение
ledcAttachPin(GPIO, channel) указываются порт и канал
ledcWrite(channel, dutycycle) указываются канал и коэффициент заполнения ШИМ-сигнала
Примеры можно увидеть
Как видно из названия, изначально функции проектировались для управления светодиодными модулями, но их также используют и для других целей.

В ESP-IDF фреймворке управление серво-приводом осуществляется так же, как и управление коллекторным с использованием модуля MCPWM, как описано в предыдущей статье. Пример MCPWM servo motor control можно посмотреть здесь

Пример кода
static uint32_t servo_per_degree_init(uint32_t degree_of_rotation){    uint32_t cal_pulsewidth = 0;    cal_pulsewidth = (SERVO_MIN_PULSEWIDTH + (((SERVO_MAX_PULSEWIDTH -          SERVO_MIN_PULSEWIDTH) * (degree_of_rotation)) / (SERVO_MAX_DEGREE)));    return cal_pulsewidth;}void mcpwm_example_servo_control(void *arg){    uint32_t angle, count;    //1. mcpwm gpio initialization    mcpwm_example_gpio_initialize();    //2. initial mcpwm configuration    printf("Configuring Initial Parameters of mcpwm......\n");    mcpwm_config_t pwm_config;    pwm_config.frequency = 50;    //frequency = 50Hz, i.e. for every servo motor time period should be 20ms    pwm_config.cmpr_a = 0;    //duty cycle of PWMxA = 0    pwm_config.cmpr_b = 0;    //duty cycle of PWMxb = 0    pwm_config.counter_mode = MCPWM_UP_COUNTER;    pwm_config.duty_mode = MCPWM_DUTY_MODE_0;    mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);    //Configure PWM0A & PWM0B with above settings    while (1) {        for (count = 0; count < SERVO_MAX_DEGREE; count++) {            printf("Angle of rotation: %d\n", count);            angle = servo_per_degree_init(count);            printf("pulse width: %dus\n", angle);            mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, angle);            vTaskDelay(10);     //Add delay, since it takes time for servo to rotate, generally 100ms/60degree rotation at 5V        }    }}



Т.е. нам необходимо инициализировать модуль с помощью функции mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);
А затем задавать значение угла
mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, angle);

C примерами использования модуля MCPWM для разных типов привода можно ознакомиться на github.
Пример управления коллекторным двигателем также был представлен в предыдущей статье.

Хочется отметить, что подобная платформа представляет собой дифференциально-управляемую неголономную систему. Двигатели имеют разброс в характеристиках, поэтому приходится задавать программное смещение для одного их них для обеспечения равномерной скорости. Ознакомиться с теорией можно на сайте robotosha.ru robotosha.ru/robotics/robot-motion.html. Для оптимального управления мотор-редукторами применен PID-алгоритм с обратной связью в виде оптических датчиков. Описание алгоритма представлено здесь и здесь.
Описание уравнений движения, а также алгоритмов управления выходит за рамки данной статьи. Дифференциальная кинематика еще не реализованы в коде.

Sleep Modes


Согласно документации, а также описания в статье, ESP32 может переключаться между различными режимами питания:
  • Active mode
  • Modem Sleep mode
  • Light Sleep mode
  • Deep Sleep mode
  • Hibernation mode


В таблице приведены различия потребления тока в разных режимах.

image

Я задействовал режим Deep Sleep mode в случае отсутствия высокого сигнала на пине GPIO_NUM_13

gpio_set_direction(WAKEUP_PIN, GPIO_MODE_INPUT);esp_sleep_enable_ext0_wakeup(WAKEUP_PIN,1); //1 = High, 0 = Low


В случае отсутствия внешнего воздействия я подтянул вход 10к резистором к 3.3 В, хотя можно и программно. А в задаче периодического опроса проверяю состояние сигнала входа

if(!gpio_get_level(WAKEUP_PIN)) {         printf("Going to sleep now\n");        esp_deep_sleep_start();    }


На этом буду завершать описание. Был показан практический пример использования модулей ESP32 с различной периферией. Также затронуты некоторые вопросы программной реализации и сравнение подходов ESP-IDF и Arduino.
Подробнее..

Создание терминала для СКУД и УРВ

21.06.2021 12:17:59 | Автор: admin

Вступление

Ранее, в предыдущих статьях, я рассказывал о бизнес-процессах, которые можно закрыть различными решениями нашего программного обеспечения, однако настало время рассказать о разработке железа, а именно терминале, который способен служить 2 целям - контролю доступа и учету рабочего времени в связке с нашей системой учета рабочего времени.

Историю можно начать с того, что наша компания очень долгое время сотрудничает со всемирно известной сетью фастфудов - KFC (на территории Беларуси и Украины). Головной болью такой сферы, как HoReCa, был и будет учет рабочего времени сотрудников. Учитывая огромную текучку кадров, в том числе и обычных студентов, которые пришли подработать на непродолжительное время, становится сложно проконтролировать, сколько часов отработано тем или иным сотрудником. Плюс немаловажным моментом стало то, что сотрудники часто перемещаются с ресторана на ресторан, а это требует дополнительного контроля. Как же быть?

Нашей компанией было принято решение разработать терминал учета рабочего времени, который бы регистрировал рабочие (или не рабочие) интервалы рабочего времени сотрудников с привязкой к конкретному месту работы. При этом нужна была поддержка нескольких видов идентификаторов - биометрия по отпечатку пальцев, RFID и PIN с возможностью фотофиксации событий доступа.

Поэтому был придумал предельно простой и быстрый сценарий действий на терминале:

  1. Прийти на рабочее место, подойти к терминалу и пройти идентификацию (приложить палец, карту к считывателю или ввести PIN)

  2. Выбрать на тачскрине Работа, после чего отправиться на свое рабочее место и приступить к работе

  3. При необходимости перерыва подойти к терминалу, пройти идентификацию, выбрать Перерыв

  4. При возвращении на рабочее место - подойти к терминалу, пройти идентификацию, выбрать Работа

  5. По окончанию рабочей смены подойти к терминалу, пройти идентификацию и выбрать Завершить работу.

То есть все события должны были фиксироваться на терминале, после чего все данные залетали бы на облако системы рабочего времени TARGControl. И там, административным персоналом, формировались табели и отчеты по объектам (ресторанам), что позволило бы корректно начислять заработную плату сотрудникам.

Разработка

Имея на руках техническое задание, была разработана структурная и функциональная схема. Далее пошло самое интересное: мы начали рассматривать различные варианты одноплатных компьютеров, которые бы смогли обеспечить нужный функционал терминала. Среди вариантов были следующие компьютеры: Banana Pi M4, Orange Pi PC+, ODROID-C4, NanoPi M4 и Raspberry PI Computer Module 3+. Произведем небольшое сравнение данных моделей.

Banana Pi M4

Orange Pi PC+

ODROID-C4

NanoPi M4

Raspberry PI CM3+

Память

Слот MicroSD с поддержкой расширения до 256 ГБ и флэш-память eMMC 8 ГБ с поддержкой до 64 ГБ

TF-карта (макс. 32ГБ) / слот для карты eMMC

8 ГБ флэш-память EMMC

1x разъем EMMC (доступно 8/16/32/64 ГБ)

1 слот Micro SD

нет встроенной eMMC, но есть разъем eMMC,
1 слот для MicroSD до 128 GB

8 GB eMMc + поддержка 1 слота microSD

RAM

1 GB DDR4 (опционально 2 GB)

1GB DDR3

4GB DDR4

Двухканальный 2GB DDR3-1866

1GB LPDDR2 SDRAM

CPU

Realtek RTD1395 ARM Cortex-A53 Quad-Core 64 Bit 1.8 GHz

H3 Quad-coreCortex-A71.2 GHz

Amlogic S905X3 Quad-Core Cortex-A55 ARMv8.2-A 64-bit 1.5GHz

RK3399- Cortex-A72 + Quad Core Cortex-A531.8 GHz

Broadcom BCM2837B0 с четырьмя ядрами Cortex A53 1.2 GHz

GPU

Mali 470 MP4 GPU OpenGL ES 1.1/2.0

Mali400MP2 GPU 600MHz
с поддержкой OpenGL ES 2.0

Mali-G31, поддержка OpenGL ES 3.2 и API Vulkan последнего поколения

Mali-T864поддержка OpenGL ES1.1/2.0/3.0/3.1, OpenCL, DX11 и AFBC

Broadcom VideoCore IV

Сеть

Ethernet 10/100/1000 Мбит / с
Опциональный USB-ключ Wi-Fi. Поддержка PoE

10/100 Ethernet RJ45

RJ45 Ethernet порт (10/100/1000)

Порт Gbps Ethernet

10/100 для подключения маршрутизатора или коммутатора с функцией PoE

После детального изучения и анализа цены (все модели находились в примерно одном ценовом диапазоне на момент их анализа - 2019 год), мы все же пришли к выводу, что лучше всего подойдет Raspberry PI Computer Module 3+. Почему Raspberry ? Да, некоторые характеристики уступают конкурентам, однако главным преимуществом стало то, что по Raspberry банально больше поддерживаемых библиотек и лучше техническая поддержка, т.к Raspberry на рынке с 2012 года и вокруг него сформировалось активное комьюнити.

Решение со встроенной памятью eMMC, предусмотренное в CM3, позволяет не использовать флеш-карту в качестве носителя ОС. Большое количество циклов перезаписи eMMC повышает надежность и срок службы памяти по сравнению с флеш-картами. При этом мы зарезервировали разъем для SD карт. Сразу можно сказать, что заявленной памяти для терминала хватает с лихвой, ибо сохраняемые события весят от силы пару килобайт. Что касается хранения фотографий, то здесь все сложнее: программно мы поставили ограничение в 5000 фото, но так как у нас зарезервирована флеш-карта, то данный лимит можно расширить до приемлемого значения.

Разработку управляющей платы мы начали с организации необходимых питающих напряжений. На нашей плате нам необходимо было обеспечить 3 значения напряжений: 5.0В, 3.3В и 1.8В. Внешний источник питания у нас 12В 3А. Для получения 5В и 3.3В мы использовали схему на основе широтной импульсной модуляции. Для источника питания 1.8В мы задействовали линейный понижающий преобразователь. Это выглядит, примерно, следующим образом.

Схема питания терминалаСхема питания терминала

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

Отметим также и схему защиты питания внешних считывателей, представленную ниже.

Схема защиты питания считывателя от диверсийСхема защиты питания считывателя от диверсий

То есть если закоротить питание, считыватель уйдет в защиту (питание считывателя отключится), а терминал продолжит работу. Номиналы резисторов R51 и R52 задают выходной ток. Изменяя номинал резистора R51 можно менять ток ограничения в значительных пределах (у нас стоят заданные на схеме номиналы исходя из рассчитанной нами нагрузки).

Питание мы организовали, самое время перейти к периферии.

К слову, Raspberry поддерживает UART-интерфейс и 2 USB версии 2.0. Через один из доступных USB мы решили организовать доступ к Ethernet с помощью микросхемы LAN9514 . Второй USB используется для подключения периферии, например индикатор алкоголя. Также задействовав GPIO для подключения кнопок, электромагнитных замков, защёлок, алкостестера в дискретном режиме работы и картаприемников.

Схема реализации Ethernet и USBСхема реализации Ethernet и USB

У CM3 на борту всего 2 UART. Один нам пригодится для организации интерфейса RS-485, а второй - debug. Поэтому мы использовали микросхему FT4232HL, для увеличения количества интерфейсов. У нее есть входной интерфейс USB, который поддерживает связь с LAN9514, он же в свою очередь коннектится с CM3.

Схема расширения количества UART-овСхема расширения количества UART-ов

Вот теперь у нас стало больше на целых 4 UARTa (задействуем всего 2). Один используется для подключения биометрического модуля отпечатков пальцев от южнокорейского производителя Suprema-SFM6020-OP6-8M/16M (8M - 5К отпечатков, 16М- 25К отпечатков).

Второй для подключения карточного модуля 7941D (поддерживает 2 частоты Emarine (125 кГц) и Mifare (13,56 МГц).

Suprema-SFM6020-OP6-8MSuprema-SFM6020-OP6-8M

Один из выделенных UART, как и планировали, мы задействовали под RS-485, позволяющий подключать внешние карточные или биометрические считыватели, а также передавать информацию во внешние системы при необходимости интеграции. Схема 485-го представлена ниже.

RS-485RS-485

В этой схеме мы прибегли к некоторым хитрым схемотехническим решениям, объединив и подключив линии RE и DE через транзистор, что позволило нам автоматизировать на аппаратном уровне переключение режимов приема/передачи.

Что касается интеграции с другими системами традиционным для СКУД способом, для передачи минимальной информации у нас организован выходной Wiegand. Есть и два входных Wiegand'а для подключения внешних считывателей. Ниже представлены их электрические принципиальные схемы.

Входной WiegandВходной WiegandВыходной WiegandВыходной Wiegand

Немаловажным моментом будет, что терминал имеет 2 реле для управления замком, турникетом или шлагбаумом. Рассмотрим данный момент на примере подключения турникета (стандартная ситуация для СКУД). Есть два режима управления турникетом, потенциальный режим и импульсный. При потенциальном режиме управления для разблокировки турникета в направлении А срабатывает выход L1 OUT (в направлении В выход L2 OUT). При окончании данного времени или при совершении прохода выходной сигнал возвращается в исходное состояние.

В импульсном режиме для разблокировки выхода L1 OUT и L2 OUT срабатывают кратковременно, посылая управляющий импульс на турникет (обычно 0,2-0,3 секунды). При получении импульса турникет разблокируется в соответствующем направлении на время 5 секунд либо пока не будет совершен проход в данном направлении.

Для контроля прохода в направлении А или направлении В используются две линии, на которые контроллер турникета выдает импульсные сигналы при совершении прохода в том либо другом направлении. Данные импульсные сигналы подключаются к входам SENS1 для прохода в направлении А и SENS2 для прохода в направлении В.

Например, для работы с турникетами PERCo в контроллере должен быть установлен импульсный режим управления. Для этого время срабатывания сигналов L1 OUT и L2 OUT должно быть установлено в пределах от 0,2 до 1 секунды.

Подключение турникета PERCoПодключение турникета PERCo

Как описано в самом начале статьи, по нашему техническому заданию, на терминале должна была быть организована фотофиксация, поэтому решили выбрать камеру от Raspberry, которая в терминалах имеет 2 исполнения:

  1. RPi D - 72.4 градуса обзор, 5 mpx, размер камеры 25x24 мм (старое исполнение).

  2. RPi G - 160 градусов обзор, 5 mpx, размер камеры 25x24 мм (теперь используем только этот вариант).

Схема организации интерфейсов DSI и CSIСхема организации интерфейсов DSI и CSI

Выбирая дисплей, выбор снова пал на знакомый бренд - 7-ми дюймовый touch-screen от Raspberry с разрешением 800x480 и DSI интерфейсом.

Исходя из размеров дисплея, считывателя и общей компоновки плат, были определены габариты корпуса для терминала - 210 x 173 x 44. Корпус решено было сделать цельнометаллическим алюминиевыми с гальваническим покрытием, что обеспечивало хотя бы минимальную вандалоустойчивость. Дисплей, конечно же, так не защищен.

Корпус терминалаКорпус терминала

Собрав все воедино мы получили терминал данного вида.

Знакомьтесь, терминал D1!Знакомьтесь, терминал D1!

Немного отклоняясь от темы, можно сказать, что мы были обмануты Raspberry, когда они в 4-ом поколении Compute Module поменяли форм-фактор и мы уже не смогли его просто заменить в нашей текущей плате и были вынуждены переходить на новое поколение путем разработки новой платы, но об этом в следующих статьях.

На данный момент с 2019 года нашей компаний было продано более 500 терминалов данной модели, а это показывает, что решение оказалось весьма успешным и надежным. В данной статье я коснулся лишь одной части - железа, однако большая работа была проделана и над прошивкой терминала. В следующей статье я расскажу, как реализована программная часть терминала. В случае возникновения вопросов или критических замечаний, прошу последовать в комментарии для бурного обсуждения!

Подробнее..

Из песочницы Разбираемся с модулем ШИМ на tms320

07.11.2020 20:18:47 | Автор: admin
Добрый день. Какое-то время назад я прочёл, что какому-то человеку захотелось изучить поглубже вопрос про ePWM модуль на мк tms320f28xxx, поэтому я решил, почему бы и мне не написать статью на эту тему, в которой я постараюсь подробно разжевать этот модуль на примере tms320f28335.

Возможности модуля ePWM


  • Выходы epwmA epwmB могут работать как:
    • single-edge operation
    • dual-edge symmetric operation
    • dual edge asymmetric opperation
  • Может быть сконфигурировано мертвое время
  • Может быть сконфигурирован TZ эвент и настроено логическое состояние выхода как HI так и LO.
  • Может быть настроенно событие прерывания или событие SOC для ADC.

Давайте приблизимся к основным блокам: из чего состоит ePWM модуль и к чему он подключен.



Как видно из рисунка, не так много и блоков находится в модуле ePWM. Поэтому, имеет смысл рассмотреть за что отвечает каждый модуль и начнём с сигналов.

  • EPWMxA и EPWMxB сигналы наверное, самый очевидный выходной сигнал. Обычное логическое состояние либо HI, либо LO, в зависимости от того, как сконфигурировано выходное воздействие
  • TZ1 TZ6 сигналы тут уже не совсем очевидные входные сигналы. Для тех, кто обладает около нулевыми знаниями в этой теме, как и я в своё время, подробно опишу это. Эти сигналы обычно используются для генерирования аварийных реакций, а именно вывода EPWMxA и EPWMxB выходов в определённое состояние. Зачастую, у драйверов силовых ключей есть возможность генерировать аварию по ключам, иногда схемотехники делают аппаратную защиту по уровню какого-то значения некой величины, например тока, при достижении которого генерируется некий логический уровень. Это мы сейчас говорили о физической реализации. Кроме физического получения этого сигнала, его можно генерировать и программно.
  • EPWMxSYNCI и EPWMxSYNCO сигналы тут мало что можно рассказать, это сигналы сихронизации, которые могут быть входом или выходом.
  • EPWMxSOCA и EPWMxSOCB сигналы тут всё более чем понятно из названия. Эти события могут задать события SOC для АЦП.
  • EPWMxTZINT и EPWMxINT сигналы тут событие прерываний на TZ и на события связанные с самим ШИМ, например генерирование прерывания по периоду ШИМ.

Теперь перейдём к модулям

Time base (TB) модуль отвечает за время события каждого модуля ePWM. Не будем сильно вдаваться во все настройки этого модуля, думаю достаточно обратить внимание на то, что есть 3 режима работы счётчика:

  • Up-Down-Count Mode
  • Up-Count Mode
  • Down-Count Mode



А так же есть настройка синхронности таймера через выставление бита TBCLKSYNC

Counter compare (CC) модуль через него мы как-раз таки и задаём нашу скважность.
Action-Qualifier (AQ) модуль через него можно настроить состояние на событие. А для выходов можно настроить следующие действия:

  • Выставить в состояние HI
  • Выставить в состояние LO
  • Произвести инверсию состояния
  • Ничего не делать

Dead-Band Submodule (DB) модуль с помощью этого модуля можно настроить зоны нечувствительности для каналов ШИМа. Ни для кого не будет секретом то, что ключи транзисторов переключаются не мгновенно и для того, чтобы не было ситуации когда верхний ключ полумоста не успел закрыться, а нижний уже открыт, выставляют задержку на переключение в состояние HI и более раннее включение в состояние LO.

Trip-Zone Submodule (TZ) модуль как говорилось выше, этот модуль связан с отработкой аварийных состояний. Тут мы можем выбрать 1 из 4 действий.

  • Выставить в состояние HI
  • Выставить в состояние LO
  • Выставить High-impedance состояние
  • Ничего не делать

Событие, вызывающее действие TZ модуля может быть вызвано как программно, так и аппаратно. Кроме этого, предусмотрен вызов прерывания.

Теперь перейдем от слов к практике


Для начала необходимо настроить GPIO на альтернативную функцию epwm

EALLOW;// Включение pull-upGpioCtrlRegs.GPAPUD.bit.GPIO0 = 0x000;  GpioCtrlRegs.GPAPUD.bit.GPIO1 = 0x000;  // Настройка GPIO как  EPWM1AGpioCtrlRegs.GPAMUX1.bit.GPIO0 = 0x001;   GpioCtrlRegs.GPAMUX1.bit.GPIO1 = 0x001;  EDIS;

Далее, когда мы уже настроили GPIO, мы можем приступать к разным настройкам. Для настройки работы ШИМа надо определиться с тем, что мы хотим получить. Начнём с частоты TBCLK. Она определяется по формуле:

$$display$$ TBCLK = SYSCLKOUT / (HSPCLKDIV CLKDIV)$$display$$


Тут необходимо обратить внимание на то, что CLKDIV по умолчанию равен 1, с HSPCLKDIV всё иначе, по умолчанию он равен 2. Это надо держать в уме, поскольку бывают случаи, когда люди об этом забывают. При загрузке программы в RAM часто HSPCLKDIV = 1, соответственно, эту проблему замечают не сразу.

С частотой тактирования TBCLK мы определились. Но надо бы выбрать, то, как у нас будет работать счётчик. По спаду, по нарастающей, а может и так и эдак, для этого надо настроить соответствующий регистр, например:

 EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UP; 

В дальнейшем, чтобы никто не пугался макросам, определимся откуда они вообще появляются. Эти дефайны определены в файле с названием DSP2833x_EPwm_defines.h.

После чего необходимо определиться с тем, как будут реагировать наши GPIO на достижение определённых значений TBCTR. Вариантов действий более чем достаточно. Они приведены в таблице ниже:



Затем необходимо определиться, какого поведения мы хотим от портов А и В, а именно, мы хотим, чтобы они были связаны друг с другом или же могли работать независимо. Если мы хотим, чтобы порт А был ведущим, то мы просто записываем для него действия, например (случай для отсчёта по нарастающей):

    EPwm1Regs.AQCTLA.bit.CAU = AQ_SET;     EPwm1Regs.AQCTLA.bit.CAD = AQ_CLEAR;

если же хотим независимости и для второго порта, то добавляем:

    EPwm1Regs.AQCTLA.bit.ZRO = AQ_SET;    EPwm1Regs.AQCTLA.bit.CAU = AQ_CLEAR;    EPwm1Regs.AQCTLB.bit.ZRO = AQ_SET;    EPwm1Regs.AQCTLB.bit.CBU = AQ_CLEAR;

По подробнее о настройках необходимо обратиться к изображению выше за одним лишь дополнением, регистров AQCTLA чуть больше, чем изображено на таблице, сильно картину это не меняет, а лишь вносится конкретика касательно того, в каком случае достиг нужного значения счётчик, например, при отсчете в верх или отсчёте в низ. Более кратко с битами можно ознакомиться в вырезке из системного .h файла

struct AQCTL_BITS {            // bits   description    Uint16 ZRO:2;              // 1:0    Action Counter = Zero    Uint16 PRD:2;              // 3:2    Action Counter = Period    Uint16 CAU:2;              // 5:4    Action Counter = Compare A up    Uint16 CAD:2;              // 7:6    Action Counter = Compare A down    Uint16 CBU:2;              // 9:8    Action Counter = Compare B up    Uint16 CBD:2;              // 11:10  Action Counter = Compare B down    Uint16 rsvd:4;             // 15:12  reserved};

Если у нас 2 порта ePWM работают зависимо и мы хотим выставить мёртвое время, то необходимо выставить регистр в нужное состояние, например:

EPwm1Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE;

Теперь, когда с описанием периферии определились, можно перейти к конкретным примерам.

Настройка ePWM в режиме отсчёта по нарастающей


Тут пример без мёртвого времени и порт A, и порт В работают зависимо. Когда A активен, B не активен.

EPwm1Regs.TBPRD = 150000 / 5; // т.к частота 150Мгц / 5000 Гц    // Выставляем скважность 50%    EPwm1Regs.CMPA.half.CMPA = EPwm1Regs.TBPRD / 2;    EPwm1Regs.TBPHS.half.TBPHS = 0;     EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UP;     EPwm1Regs.TBCTL.bit.PHSEN = TB_DISABLE;     EPwm1Regs.TBCTL.bit.PRDLD = TB_SHADOW;    EPwm1Regs.TBCTL.bit.SYNCOSEL = TB_SYNC_DISABLE;    EPwm1Regs.TBCTL.bit.HSPCLKDIV = 0;    EPwm1Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW;    EPwm1Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW;    EPwm1Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO;     EPwm1Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO;     EPwm1Regs.AQCTLA.bit.PRD = AQ_CLEAR;    EPwm1Regs.AQCTLA.bit.CAU = AQ_SET;    EPwm1Regs.AQCTLB.bit.PRD = AQ_SET;    EPwm1Regs.AQCTLB.bit.CAU = AQ_CLEAR;

На осцилограмме можно увидеть полученный результат:



Теперь можно попробовать добавить мёртвое время, для этого добавляем:

    EPwm1Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE;    EPwm1Regs.DBCTL.all = BP_ENABLE + POLSEL_ACTIVE_HI_CMP;// Настройка db     EPwm1Regs.DBFED = 300;// Мертвое время = 150Мгц * 2мкс = 300    EPwm1Regs.DBRED = 300; 

Отсчёт мёртвого времени производится аналогично частоте, по формуле:

$$display$$DB = TBCLK * deadTime;$$display$$


А теперь мы получили мёртвое время таким, каким мы и хотели



А что делать, если нам надо развязать порт А и порт В? Такое тоже имеет место быть. Тут всё просто. Возвращаемся к первому примеру и удаляем последние 4 строчки, и записываем каждому скважность в следующие регистры.

    EPwm1Regs.AQCTLA.bit.ZRO = AQ_SET;    EPwm1Regs.AQCTLA.bit.CAU = AQ_CLEAR;    EPwm1Regs.AQCTLB.bit.ZRO = AQ_SET;    EPwm1Regs.AQCTLB.bit.CBU = AQ_CLEAR;    EPwm1Regs.CMPA.half.CMPA = EPwm1Regs.TBPRD / 2;// Выставляем скважность 50% порта А    EPwm1Regs.CMPB= EPwm1Regs.TBPRD / 3;// Выставляем скважность 33% порта В

Теперь мы имеем вот такую вот картину. Можно задавать скважность каждому каналу по отдельности.



Для режима по спаду всё примерно аналогично. Есть отличие с отсчётом в режиме up-down. Тут частота шима рассчитывается уже по формуле:

$$display$$TBPRD = TBCLK / ( 2 * Fpwm)$$display$$


Примерно так же и для dead Time.

Наверное, единственно важное, что не было рассмотрено, так это настройка TZ, ну что ж, теперь остановимся на этом модуле чуть чуть по подробнее.

Для программного вызова события аварии достаточно настроить следующие регистры:

    EPwm1Regs.TZCTL.bit.TZA = TZ_FORCE_LO;    EPwm1Regs.TZCTL.bit.TZB = TZ_FORCE_LO;

Вызов и обнуление аварии ШИМа можно осуществить с помощью следующих команд:

    //Вызов аварии    EALLOW;        EPwm1Regs.TZFRC.bit.OST = 0x001;    EDIS;    //Обнуление сигнала аварии    EALLOW;EPwm1Regs.TZCLR.bit.OST = 0x0001;    EDIS;

Если же мы хотим аппаратно вызвать сигнал TZ, то тут всё ещё проще, через регистр TZSEL выставляем нужный нам TZ, но вдобавок к этому необходимо настроить GPIO на TZ.

Заключение


Если кому-то покажется данная статья интересной, то могу в более менее ускоренном порядке написать ещё пару статей. В планах имеется рассмотреть can модуль, хотелось бы dma, а еще может быть напишу небольшую статью по IQMath от ti с их библиотеками.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru