Аналоговый стабилизатор тока

Analog source of current, front panel

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

Analog source of current, inside

Внутренности стабилизатора/источника тока. К задней крышке крепятся, сверху вниз: аналоговая плата, микроконтроллер, бустер. На фронтовой панели органы управления и индикации

В моем заказном столе, сразу под столешницей, есть выдвижной ящик. Это промежуточный кэш, куда вначале попадает все что находится на столе, и потом перекочевывает на свои места в другие, менее приоритетные ящики. В последнее время я все время натыкался на свое изделие, а раз так, решил написать про него. Мне нравится, как я сделал аналоговый источник/стабилизатор тока, плюс предмет моей гордости — лаконичный интерфейс состоящий из дисплея на семисегментных индикаторах, крутилка — джойстик на потенциометре и всего две кнопки — Set, Start и Stop.

Аналоговыми вещами я занимался давно, и представился случай тряхнуть стариной — сделать  прототип  чего-либо  подобного.

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

Сразу оговорюсь, что я использую термин «ток» не в общеупотребительном смысле, а именно как физическую величину I, в отличие от напряжения U. Напоминаю, что в электротехнике «источник тока» это специальный термин, точно также как и «источник напряжения».

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

The device electric diagram

Электрическая принципиальная схема стабилизатора/источника тока

На схеме незримо присутствует микроконтроллер (МК) STM32F103, выдавая себя своими контактами. Само собой, на нем крутится программа, на работу которой мы будем иногда отвлекаться.

Начнем с электропитания. Аккумулятор форм-фактора Кроны BT1 подключается через защитный диод Шоттки D1. Не забываем, что косметологи это люди которым ничего не стоит переполюсовать батарею и засадить её против ее контактных площадок. Мы знаем своих клиентов!

Дальше пути 9 вольт расходятся (на самом деле 8.4 В, аккумулятор же, но пусть будет так). Одна линия идет на стабилизатор U1, который питает пищалку (есть и такое) на транзисторе Q1 и ЦАП на транзисторе Q4. Да — да, вы не ослышались, это цифро-аналоговый преобразователь. В нашем простейшем МК его нет, поэтому — сделай сам. К ЦАП мы сейчас вернемся, а пока завершим с электропитанием.

Другая линия 9 В идет на ключ, образованный Q2 и Q3, задача которого — по команде с МК подавать питание на бустер U2, который повышает напряжение до 30 В. Как вы помните, напряжение до 36 В считается относительно безопасным для человеческого организма. Это напряжение используется для формирования стабильного тока.

Вернемся к ЦАП. Его задача — сформировать управляющее напряжение на входе источника тока. ЦАП состоит из операционника U3A (вход 3) и транзистора Q5. Это напряжение будет полностью определять ток во внешней цепи электродов J5. Как работает этот ЦАП? На вход транзистора Q4 подается PWM сигнал от МК. Управление осуществляется изменением скважности PWM, что делает софт — устанавливая ее для заданных временных интервалов или вручную через крутилку. Q4 нагружен на RC цепочку — фильтр нижних частот R5, R6, C1. Постоянная времени фильтра выбрана большой; благодаря этому на выходе фильтра формируется постоянное напряжение, пропорциональное скважности импульсов PWM.

Транзистор Q4 работает в ключевом режиме. Для того чтобы время заряда и разряда конденсатора C1 было одинаковым, используется диод D2: заряд при запертом Q4 идет по цепи 5 В -> R5 -> D2, разряд при открытом Q4 идет по цепи R6 -> Q4. Сопротивления резисторов R5 и R6 равны. Таким образом, цифро-аналоговое преобразование выполняется через управление скважностью цифрового сигнала, с пропорциональным изменением постоянной составляющей которую выделяет RC фильтр.

В схеме есть измеритель выходного тока электродов для его отображения на индикаторе и коррекции. Он реализован на операционнике U3B, выход которого идет на АЦП МК PA1. Выходной ток проходит через токовый шунт номиналом 100 Ом и измерение напряжения (и значит тока) производится на нем.

Потенциометр / крутилка / джойстик подключен к другому АЦП МК PA0, кнопки — к GPIO. Пищалка получает звук от МК и сигнализирует об окончании процедуры. Дисплей подключается по последовательному интерфейсу и работает от МК.

И теперь переходим к главному — собственно к источнику тока. Он реализован на p-n-p транзисторе Q5. Тип p-n-p потому что комфортней иметь общий провод ближе к земле, а не к питанию.

Идея схемы на Q5 — эмиттерный повторитель. Это значит, что благодаря обратной связи по напряжению повторитель держит стабильное напряжение на резисторе в эмиттерной цепи, практически не чувствуя нагрузки (естественно, в определенных пределах). Стабильное напряжение на резисторе — это значит стабильный ток в эмиттерной цепи и соответственно в коллекторной, поскольку это один и тот же ток. Вот так и выглядит дизайн источника/стабилизатора тока.

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

В такого типа устройствах, когда провода выходят наружу, первым делом возникает мысль — а что если их закоротить и тем самым сжечь прибор? Это как раз не наш случай. Режим короткого замыкания для источника тока — самый комфортный и естественный режим. Теоретически, для него разомкнутая цепь также катастрофична как и короткозамкнутая для источника напряжения.

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

Пока прототип ушел к своему заказчику, и не исключено что мы его запустим в серию. Но это уже про маркетинг )

Виртуальные функции C++. Следствие ведут Колобки

Двигаемся поэтапно, с самого начала.

1. Никаких виртуальных функций (пока что)

Используем struct вместо class чтобы каждый раз не писать public. В нашем примере не будет никаких приватных членов класса, а в остальном все тоже самое:

Функция f()  переопределена в дочернем классе Child (overriding, не путать с overloading). Вызов функций:

Пока никаких неожиданностей нет — все работает как должно.

Замечу, что если в дочернем классе не будет функции (2), то будет вызвана функция (1) родительского класса. Это происходит потому, что Child хранит гены родителя Parent и в силу наследования имеет доступ ко всем полям родительского класса. Но это так, к слову.

2. Все еще никаких виртуальных функций

Сделаем странную вещь — присвоим указателю на объект родительского класса объект производного класса и поразмышляем над тем, что получилось.

Указатель на Parent, а объект Child… нет ли здесь несоответствия или противоречия?

Рассуждаем так. Указатель на объект класса — это не просто адрес в памяти. Когда мы имеем дело с классами, фактически имеем набор указателей на все поля и функции класса — это если упростить (а упрощать мы любим). Выше я говорил, что Child хранит «гены» родителя Parent, в нашем случае — функцию (1) родительского класса. Поэтому указатель на Parent находит в Child то, про что он знает — отсылку на свою собственную функцию (1).

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

В ситуации наоборот

Указатель захочет сослаться на поля, которые есть только в Child, но в объекте Parent их быть не может!

3. Подозрительная конструкция

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

когда все прекрасно работает и без нее? Следствие, которое ведут Колобки, знает: встретили такую запись — значит где-то поблизости виртуальная функция (и может быть не одна).

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

Поэтому смотрим пока на то что «нет смысла», а потом переходим к заключительному полноценному примеру.

4. Бессмысленная виртуальная функция

Теперь функция f() стала виртуальной. Заведите хорошую привычку дописывать override в дочерних классах, чтобы во-первых было понятно что функция виртуальная (это здесь все видно, а если вы копаетесь в многочисленных листингах?), и во-вторых это страхует вас от ошибки в сигнатуре функции — без override компилятор все пропустит, но будет считать f() в дочернем классе уже не виртуальной, а с override — предупредит.

Повторяем наш эксперимент с самого начала.

Вы заметили какую — нибудь разницу по сравнению с первым сценарием без виртуальных функций? И я тоже нет.

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

5. Складываем все вместе

Объявление класса с виртуальными функциями:

Действие:

У меня для вас новость: объект родительского класса каким-то образом узнал про функцию в дочернем классе и вызвал именно ее. Как он это сделал?

Сейчас самое главное — не погрузиться в дебри новых понятий и не потерять прозрачность повествования. Новое понятие — это таблица виртуальных функций vtable класса Parent, которая содержит указатели на виртуальные функции. Отныне все вызовы виртуальных функций будут происходить не через указатель класса Parent* p, а через посредника — таблицу vtable.

Принципиально важным является то, что содержимое этой таблицы не задается жестко во время компиляции, а может меняться по ходу выполнения программы (это то, что вас на собеседовании спрашивали про позднее связывание или dynamic binding). Если мы поймем, как и когда эта таблица заполняется и меняется, мы поймем все про виртуальные функции. Поехали!

6. Gdb, vtbl

Ехать будем с помощью отладчика gdb — это наиболее удобный способ посмотреть расположение vtable и виртуальных функций в памяти. Поменяем наш пример, чтобы с отладчиком было удобнее работать:

Компилируемся, запускаем отладку:

Замечу что b = d еще не выполнялось: мы остановились до этой строчки.

Для успокоения души проверим адреса, которые нам выдал отладчик, например для b:

Все в порядке, vtable содержит адреса Base::base_and_derived() и Base::base() (доверяй, но проверяй!)

Теперь посмотрим на таблицы, на какие виртуальные функции они указывают. С классом Base все понятно, это пара собственных виртуальных функций base_and_derived(), base(). С дочерним классом Derived немного интереснее: его таблица содержит переопределенную виртуальную функцию базового класса base_and_derived(), собственно виртуальную функцию base() которая принадлежит базовому классу (родитель!) и собственная виртуальная derived().

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

Продолжаем программу и выполняем наше легендарное присвоение указателя производного класса указателю базового класса:

 

Произошли существенные изменения, и главное из них — мы фактически потеряли виртуальную таблицу базового класса (что еще можно было ожидать от присваивания?) Точнее, базовый и производный класс сейчас содержат только одну виртуальную таблицу расположенную по адресу 0x555555557d28, которая по сути таблица класса Derived оставленная без изменений —.

Поскольку класс Base теперь пользуется виртуальной таблицей класса Derived, предыдущая запись
0x5555555552a0 <Base::base_and_derived()>

Поменялась на запись
0x5555555552a0 <Base::base_and_derived()>

какой она и была для Derived раньше.

Это означает: все вызовы виртуальной функции base_and_derived выполненные с помощью указателя b->base_and_derived() приведут не к вызову виртуальной функции базового класса (как можно было ожидать, ведь b — указатель на Base), а к вызову виртуальной функции произвольного класса.

Что и требовалось доказать.

7. Послесловие

Вот и все. Приведу пример, когда это бывает нужно в реальной жизни. Например, клиент знает только про класс Interface и пользуется им:

У нас есть возможность подсовывать различные варианты реализации интерфейса, оставляя клиента в неведении:

Этот пример можно воспринимать по другому: например, Implementation_1 это боевая реализация, а Implementation_2 это mock заглушка для тестирования. Или Interface это класс прокси или адаптера. В общем все шаблоны C++ которые только существуют базируются на этой записи:

и это все, что вам нужно знать про полиморфизм в C++.

Микрофонный усилитель на LM358

Понимаю, тема несколько выбивается из ряда… но разнообразие прекрасно, полезно и бодрит, поэтому продолжаем. В последние два с половиной года я был сильно погружен коммерческую разработку на  C++ , мой стол набитый комплектацией простаивал без дела, и вот пришло время размяться на аналоговом поле.

На самом деле микрофонный усилитель — это не просто так, а часть моего проекта, который я завершу — когда нибудь, надеюсь. Задача усилителя — завести голос с микрофона гарнитуры на АЦП STM32. Определенное время я развлекался, знакомясь с ужасными схемотехническими решениями, которые кочуют с сайта на сайт (высокоимпедансный микрофон на вход схемы с общим эмиттером), и решил дать простое решение с подробными разъяснениями (фокус с последующим разоблачением).

Итак, смотрим чертеж.

Microphone amplifier LM358

Микрофонный усилитель на LM358 / Microphone amplifier LM358

Слева подключаем микрофон, справа подаем выход усилителя на АЦП микроконтроллера. Пусть обозначения цветов вас не смущают — это маркировка проводов в моем монтаже.

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

Поэтому пусть будет универсальное решение — вход нашего усилителя будет высокоомным, поэтому подаем сигнал с микрофона на (+) вход операционного усилителя — ОУ. И давайте примем, что сопротивление входа (+) ОУ будет бесконечным, поэтому входную нагрузку будут определять параллельно включенные R2 и R3 (да — да, вы не ослышались —  для сигнала что питание что земля все едино, поэтому микрофон получает нагрузку 50 кОм).

Лирическое отступление — почему на (+), а не на (-)? Ведь входы ОУ равноценны и образуют дифференциальный транзисторный каскад. Все дело в отрицательной обратной связи через R6, благодаря которой вход (+) становится высокоомным. Как это работает — рассказал на примере эмиттерного повторителя .

Давайте сразу про обратную связь. Она весьма двулична и ведет себя по-разному по постоянному и переменному току. По постоянному току играет R6 (и в этом варианте он вообще не нужен — мы могли бы накоротко замкнуть выход ОУ и вход (-)). Обратная связь получается 100 — процентной, поэтому коэффициент усиления каскада по постоянному току — единица, и средняя точка, которую устанавливает делитель R2/R3, будет такой же на выходе операционника, то есть — половина напряжения питания.

Теперь про обратную связь по переменному току. Здесь играет конденсатор C3, который закорачивает нижний конец резистора R4 на землю, в результате чего в цепи обратной связи появляется делитель R6/R4. Тут уже резистор R6 выполняет свою роль и совместно с R4 устанавливает коэффициент усиления каскада по переменному току, равный 100.

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

Поскольку режимы по постоянному току для микрофона и входа (+) ОУ — разные, изолируем эти цепи конденсатором C1. Надеюсь, не надо объяснять, что он проницаем для входного сигнала, и неожиданно маленькая емкость — 0.1 мкф совсем не большое препятствие, потому что опять таки — вход высокоомный.

Как вы наверное догадались, линия в верхней части, уходящая вдаль направо — это питание 3.3 В. Столько я рассчитываю получить с USB смартфона (ну вот, проговорился про еще про один кусок будущего проекта).

Для LM358 такое напряжение — нормально. Заметим, что выходной сигнал будет меняться не относительно земли (разделительного конденсатора на выходе ОУ нет), а между землей и 3.3 В — что АЦП и надо.

Для чего нужна цепочка R5C2? Практикующие инженеры знают, что мусор в цепях питания — обычное дело. На чувствительном входе каскада ОУ он нам совсем не нужен, и поэтому фильтр R5C2 будет прибивать все что по частоте выше постоянного тока (в идеале).

Пара электролит — керамика для сброса мусора на землю также стоит в правой части, которая не видна. Зачем керамический конденсатор маленькой емкости в параллель с электролитическим? Последние имеют неприятное свойство — паразитную индуктивность. Поэтому с ростом частоты они будут не очень хорошим конденсатором.

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

В результате экспериментов выяснилось, что усилитель держит неплохую полосу от 100 Гц до 10 кГц и негромких песен вполне достаточно до раскачки выхода от 0.5 В до 2.5 В.

PyTorch: fast array computations on GPU

When using numpy at the beginning of a project we don’t think about performance. This is because it is more important for us to get solution that works. And only then, with real data, observing how our computer begins to slow down, we have to find answer on how to avoid this. And then we remember the GPU inside our computer and think: can it help us?

Yes, sure. PyTorch can utilize GPU and perform all computations on it. PyTorch uses Tensor primitives like numpy arrays. Unlike numpy, once declared tensors reside on GPU, and are calculated on it. Due to a lot of the parallel working GPU inits, computing performance arises dramatically.

We will take the matrix multiplication algorithm for the example.

First, let’s make sure the multiplication computations are performed correctly.

 

Output:

 

Everything is as expected.

Now, tensor comes into play. At this point, all the necessary modules and drivers have already been installed:

 

Output:

 

Let’s allow PyTorch to multiply matricies:

 

Output:

 

The result is the same as for the numpy.

Take a larger matrices and see how much time it takes to calculate in each of the three modes:

1) PyTorch in «cpu» (numpy — like) CPU only mode
2) PyTorch in «cuda» (GPU) mode
3) Numpy CPU mode

 

Output for M=100

 

Output for M=1000

 

Output for M=10000

 

For small matrices (M=100) numpy looks like a performance champion. Even torch in cpu mode loses to it. The worst results are observed for the GPU.

For a medium-sized matrices (M=1000) the results are leveled out. Numpy and torch in cpu mode are the same and only torch in gpu mode is far behind.

At the moment, everything looks sad for GPU. It makes a real breakthrough for large-sized (M=10000) matrices. Here the gap in execution speed is huge: 37 milliseconds for GPU versus 12 seconds for numpy.

Simultaneous computing is good

 

Дружище C.Elegans и его нейрокомпьютер

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

Печально, но на вопрос «почему так» судя по всему отвечать некому. Подробно об этом написано в замечательной книге Доккинза «Слепой часовщик», а если коротко — то не было никакого Главного конструктора, который создавал живые организмы в тиши своего кабинета. Эволюция за сотни миллионов лет сама сделала эту работу, непрерывно экспериментируя с мутациями, оставляя жизнеспособные решения и безжалостно уничтожая ошибочные. Нам осталось строить предположения, наблюдая конечный результат.

Читать дальше…

Автоматическое обнаружение радиолокационных станций визуальной подсистемой БЛА

По сравнению с такими шустрыми средствами, как противорадиолокационные ракеты (Anti Radar Missile) беспилотник выглядит сущим недоразумением. Однако, это всего лишь следствие инерции мышления, когда на самом деле главный недостаток БЛА по сравнению с ракетой — низкая скорость, становится преимуществом в следующих случаях:

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

Читать дальше…

Обработка потока с видеокамеры средствами ПЛИС/FPGA. OV7670 + Zedboard

В статье маркеры визуальной системы автоматической посадки БЛА была описана  обработка видео-данных реального времени для системы оптического типа — VBLS. Это была хоть и шустрая, сделанная на C++, но модель. Теперь, как было замечено в конце этой статьи, пришло время показать систему в боевом варианте, где обработка видеопотока параллелится в ПЛИС.

В данной системе для экспериментов я выбрал весьма удобную отладочную плату  Zedboard. Чип Zynq-7000 основан на SoC архитектуре ARM+FPGA, однако на этом этапе функционал ARM использовать не будем и ограничиваемся работой только с ПЛИС. В качестве камеры выступает популярный модуль OV7670 разрешением 640х480 (хвала Алиэкспрессу!). Почему такое скромное разрешение? А потому что в Zedboard ресурсы ограничены, в частности критичным для нас является размер встроенной быстродействующей памяти BRAM (не путать с DDR). Результирующее видео выводится на дисплей через разъем VGA платы.

Читать дальше…

Маркеры визуальной системы автоматической посадки БЛА

Визуальная автоматическая посадка БЛА может выполняться не только по характерным точкам местности, но и по специально подготовленным и известным изображениям — маркерам. Собственно сама стандартная разметка ВПП состоит из таких маркеров, только исторически они предназначались не для обработки компьютером, а для человека — пилота. Поэтому нет ничего удивительного в том, что искусственный интеллект дрона потребует учесть его интересы и установить те маркеры, с которыми ему удобнее работать )

Visual Based Automatic Landing System, VBLS, Marker, Marker, UAV, Drone, Автоматическая посадка БПЛА, runway, DinaPrim 2700

Стандартизированная разметка ВПП. Маркеры обозначают тип полосы, точку касания и отклонение от этой точки

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

Читать дальше…

Каким быть военному радиопеленгатору

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

Мой рассказ — об отечественном военном аэродромном пеленгаторе. Именно аэродромном, поскольку АРП другого назначения, в том числе для радиоразведки, поиска и спасения находятся за рамками моего повествования. Да и тема аэродромной радиопеленгации для меня самая близкая, в которой я работаю с 1983 года.

Читать дальше…

STM32: добавляем Ethernet

В предыдущей статье я рассказывал о том, как хорошо работать с микроконтроллерами STM32 без операционной системы. Однако может наступить момент, когда вам понадобится наладить взаимодействие с вашим МК по сети. Например, что-нибудь типа умного дома или передача отладочной и диагностической информации на web страницу. Наличие Ethernet интерфейса — это конечно сильный аргумент для того, чтобы бросить STM32 и перейти на полноценный ARM процессор под управлением Linux. Однако, этот аргумент парируется очень просто: на рынке есть компактные Ethernet модули, которые можно задействовать в наших STM32 проектах. Речь идет о чипах семейства W5500 WIZnet, на основе которых эти модули и собраны. Добавляем к нашему проекту этот модуль (хвала Али Экспрессу, как обычно!), и получаем функциональность Ethernet в МК STM32.

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

 

stm32, stm32f103, Ethernet, W5500

Микроконтроллер STM32F103 и модуль Ethernet W5500 в сборе

Работать с модулем W5500 — это сущий ад. В нем куча регистров, в которые нужно заносить и считывать информацию. Когда я раскрыл мануал этого чипа, то первое что я сделал — это сразу закрыл его и начал искать библиотеку, в которой вся эта регистровая часть уже реализована и которая предоставляет такие привычные и понятные интерфейсы: socket(), listen() и так далее. И я ее нашел.

Как работать с библиотекой я расскажу дальше, а пока поработаем с нашим фото.

Пройдемся по макетке слева направо и посмотрим, что я там разместил.

Макетная сборка

Модуль W5500

Слева, как вы уже догадались, Ethernet модуль W5500. В этом варианте исполнения его пины смотрят вниз, что удобно для размещения на макетной плате. Есть вариант и пинами вверх, что будет удобно если вы решите просто соединять контакты напрямую кабелями. Собственно чип W5500 распаян под зеленой платкой, из-за чего создается впечатление что кроме Ethernet разъема больше ничего нет. На самом деле это не так. Чип берет на себя всю работу по поддержке сетевых протоколов, а работать с ним нужно по интерфейсу SPI. Поэтому красные и темные проводки — это и есть соединения SPI с микроконтроллером.

Кто уже работал с SPI, сейчас испытывают небольшой диссонанс. Этот протокол вообще-то говоря принято использовать для внутриплатных соединений, и выводить его наружу считается моветоном. Пришлось поступиться принципами ради демонстрационных целей, потому что нужна скорость, которую другие популярные интерфейсы — I2C и UART обеспечивать не могут. Если же вы распаяете модуль W5500 на своей собственной плате, то правила поведения в приличном обществе будут соблюдены.

UART

Смещаем взгляд правее и видим вертикально воткнутую платку. Это адаптер UART-USB, с которым мы познакомились раньше. Его мы используем исключительно для отладочных целей, для того чтобы выводить сообщения  нашей программы на экран PC.

От МК этой плате требуется только одна линия: Tx (отдаем строчки), которая на адаптере будет обозначаться уже как Rx (принимаем строчки). Ну и само собой кабель, который вы видите на торце платы, подключен к USB разъему PC.

Переключатель на плате устанавливаем в положение 3.3В: это будут уровни линий передачи данных, с которыми работает адаптер.

МК STM32F103 Blue Pill

По центру — наш микроконтроллер, плата МК STM32F103C8T6. Здесь неожиданностей нет, единственное я использовал компактную версию под названием Blue Pill. Есть еще Black Pill, Red Pill но честно говоря не знаю чем все они принципиально отличаются друг от друга. Работать будут все.

В комплекте с этими платами идут линейки пинов, которые я распаял снизу. Это позволяет вставить пины контроллера в макетную плату. Если вы используете Pill’ы в своих устройствах, запаивать пины нет необходимости — можно сразу паять соединения на ламели по краям платы. В нашей конфигурации используются контакты соответствующие UART1 и SPI2.

К разъему JTAG в торце платы МК как обычно подключен программатор/отладчик ST-LINK V2. Его мы видим сверху. Его место — также в USB разъеме PC.

USB разъем для электропитания не используется — напряжение подается непосредственно на пины МК.

Электропитание

На правой стороне макетной платы расположен весьма удобный модуль электропитания, который обеспечивает независимые напряжения 5В или 3.3 В по обеим линиям питания макетной платы (синие и красные линии). Своими пинами модуль ложится в аккурат на эти линии, плюс к этому есть возможность подключиться еще сверху. Входное напряжение для модуля — 12В.

Модуль питания сконфигурирован джамперами на выходное напряжение 3.3В, отбор питания идет по внешним линиям модулями W5500 и МК STM32, которым требуется 3.3В. На фото видны эти желтые и коричневые короткие перемычки, которые идут рядом.

Нюанс: вы пожете подключить к МК вместо 3.3В 5В к соответствующему (другому естественно!) контакту. Эти 5В будут преобразованы в 3.3В.

Внимание! Не подключайте к плате внешнее питание 3.3В и 5В от USB одновременно. Можете потерять МК ) Также по этой причине провод +5В для JTAG от USB PC болтается в воздухе, как может заметить внимательный читатель )

Модуль UART требует электропитание 5В, которое мы обеспечиваем внешним проводком который подключается к пину +5В модуля питания. Есть подозрение, что он прекрасно будет работать и без этого проводка от USB, когда тот подключен (и работает на самом деле).

Ситуация когда к одной цепи питания модуля подключаются два источника питания — не совсем здоровая. Картинку портит поступающие 5В от USB, которые преобразуются в 3.3В и эта цепь потенциально может конфликтовать с внешней линией питания 3.3В. В таких случаях обычно ставят диод Шоттки, который защищает цепи питания 3.3В от реверсного тока. Будем надеяться, что так оно и есть )

Зачем вообще нужен модуль питания, если раньше мы прекрасно запитывали микроконтроллер от USB? Не забываем, что теперь у нас появился серьезный потребитель по напряжению 3.3В — это чип W5500, который не в лучшие для нас моменты готов принять до 185мА. Внутренний преобразователь уровня 5/3.3В микроконтроллера на такой подвиг не способен, если нам в голову придет мысль взять напряжение 3.3В оттуда.

Конфигурация

Как и раньше, для создания проекта воспользуемся услугами STM32CubeMX. По традиции создаем проект на основе Makefile, чтобы не городить огород с визуальными системами разработки (помните наш проект hardcore? Только Makefile и vim!). Как обычно, включаем пункт Debug:Serial Wire в меню SYS, чтобы иметь возможность прошивки и отладки. Сразу включатся пины PA13, PA14: через них STLINK будет общаться с МК.

Осталось сконфигурировать интерфейсы и все. Включаем USART1: Mode Asynchronous (Куб задействует пины PA9, PA10) и включаем SPI2: Mode Full-Duplex Master. На вкладке конфигурации для SPI2 нужно будет подправить параметр Prescaler: установить его в значение 4. Напоминаю еще раз, что UART нужен только для отладки, чтобы получать на нашем компе логи из программы контроллера.

Почему SPI2 а не SPI1? Так было в библиотеке поддержки чипа W5500, я не стал менять интерфейс. Видимо, разработчикам было удобно работать с пинами именно с этой стороны модуля микроконтроллера ) В нашем случае Куб выдаст для SPI пины PB13 — PB15.

Нам еще понадобится управлять пином PB12 как выходным (забегая вперед — библиотека использует его для включения чипа W5500). Поэтому кликаем на него и делаем GPIO_Output. И точно также, сразу делаем выходным пин PC13: к нему подключен светодиод МК, и грех не воспользоваться возможностью поморгать светодиодом в нужных местах.

Создаем проект и переходим к следующему шагу.

Соединения

Поскольку распиновка модулей W5500 и UART-USB и так известна, а распиновку Blue Pill мы уже получили с помощью Куба, займемся соединениями. Работа приятная, медитативная, навевает мысли о тщете всего сущего, хорошо заниматься этим перед сном, глубокое погружение гарантировано 🙂

Начнем с модуля W5500. Все, что нам от него нужно — это цепи SPI и питания. С подключением питания все понятно — задействуем цепи 3.3В и GND, самое главное — правильно соединить линии интерфейса SPI. Поскольку на модуле нет нумерации пинов, на схеме соединений слева будем указывать SPI обозначение пина по версии W5500, справа — номер пина и SPI обозначение по версии Куба:

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


 

Памятка по SPI:

  • MISO — Master In, Slave Out: если мы в режиме мастер — принимаем данные, в режиме периферии — передаем;
  • MOSI — Master Out, Slave In: в режиме мастер передаем данные, в режиме периферии — принимаем.

В нашей конфигурации W5500 будет периферийным модулем, который будет выбираться сигналом SCNn с МК. Поэтому по линии MISO данные будут идти от W5500 к МК, по линии MOSI — в противоположном направлении. Обратите внимание, что нет необходимости переполюсовки Tx/Rx как в UART: назначение вывода (вход или выход) меняется в зависимости от того, в каком режиме — Master или Slave работает модуль.

С модулем UART-USB все просто. Нужна только одна линия передачи данных:

И в заключение — подключаем программатор/отладчик ST-LINK V2:

Цепь 3.3В остается неподключенной!

Не забудьте соединить «земли» всех модулей с землей модуля питания и подать 3.3В на модули W5500 и Blue Pill. Модуль UART-USB, как я говорил до этого, подключается к пину 5В модуля питания.

W5500 Library

Наконец наступил долгожданный момент — подключение библиотеки модуля W5500, чтобы с ним можно было работать вменяемым образом. Саму библиотеку можно скачать здесь. На самом деле, это не совсем библиотека, а набор файлов которые нужно включить в наш проект. Они точно также будут компилиться и собираться, как и наши собственные файлы. Поэтому по структуре это не library, а дополнительные к нашему проекту кусочки. Но я все равно буду называть это библиотекой — по существу. Кому нравится — могут называть это также драйвером; сама WizNet вообще использует наименование Library_Driver.

Подобные продукты третьих сторон я храню отдельно от своего проекта, потому как не нужно дублировать исходники в каждом отдельном проекте и тем более включать их в систему контроля версий. Для этого у меня специальная директория — /home/user/bin, куда и скачаем библиотеку WizNet она же драйвер W5500.

Распаковываем архив и меням имя на ioLibrary_Driver. Далее, нужно настроить библиотеку на определенную версию чипа. Для этого в файле Ethernet/wizchip_conf.h ищем строчку вида

И меняем W5500 на модель чипа, которая у вас в наличии. Само собой у меня менять ничего не пришлось.

Теперь нужно как-то интегрировать исходники библиотеки в наш проект. Это делается через Makefile. Добавляем в него строки следующим образом (будьте внимательны: показаны также существующие строки, чтобы вам было проще ориентироваться по make-файлу):

Все достаточно просто, не правда ли? Наступил момент запуска библиотеки. Для этого ее нужно инициализировать и выполнить тестовый пример — запустить TCP сервер который будет принимать входящее соединение и отвечать чем-то в стиле «Hello World».

Software

Как вы наверное уже догадались, все это будет жить в файле tcp.c. Но вначале библиотеку нужно инициализировать. Код инициализации разместим в main.c, поскольку так ему будет проще доступаться к интерфейсным переменным.

Первым делом нужно приготовить для библиотеки наши callback функции, которые она будет дергать когда ей понадобится доступ к SPI для обмена с чипом WZ5500:

Теперь библиотеке нужно как-то сообщить о нашей проделанной работе. Регистрация наших callback-функций в библиотеке выполняется следующей парой строк:

Само собой, это уже вызовы функций библиотеки. Дальнейшая инициализация:

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

Ну а теперь можно вызывать нашу функцию tcp_server(), которая будет делать всю полезную работу. Функцию разместим в файле Src/tcp.c. Не забудем объявить ее в заголовке main.h:

Так уж и быть, выложу полное содержание tcp.c. Как все это работает, описал в комментах. Для тех, кто знаком с логикой создания входящих tcp соединений, трудностей не будет никаких.

Проверяем все это так. Соединяем патчкордом модуль W5500 и наш комп. Конфигурируем проводное соединение, у меня на PC Ubuntu это адрес 192.168.77.5 (заметьте, что это также адрес шлюза для W5500). С нашей машины пингуем модуль: ping 192.168.77.6, убеждаемся что пинги проходят.

Дальше, достаем швейцарский нож хакера — программу netcat, коннектимся к модулю:

и получаем в ответ долгожданное «Hello World», а в экране minicom наблюдаем логи, которые посылает нам trace через модуль UART-USB.

Некоторые размышления по структуре программы. В ожидании входящих соединений, МК видимо будет выполнять полезную работу. Например обрабатывать поток радиолокационных данных ) Возникает проблема одновременной поддержки двух процессов. В Linux среде это решается просто — системным вызовом select(), который может зависать на нескольких событиях. В STM32 Линукса нет, но зато для STM32 есть простая операционная система реального времени FreeRtos. И она умеет работать с потоками, то есть содержит простейший планировщик заданий. Даю наводку, а как вы ей распорядитесь — ваше дело )

Про прошивку МК и отладку я подробно написал в предыдущей статье. Поэтому здесь описание этих процессов опущено.