Подводные камни децентрализованной торговли

Всем привет! Меня зовут Павел Найданов. Начинал фронтенд-разработчиком, но вот уже несколько лет, как сменил специализацию: теперь работаю в отделе Solidity-разработки компании MetaLamp. На протяжении нескольких лет мы вместе с коллегами изучаем различные аспекты разработки DeFi приложений и собираем наши наработки и инсайты в статьи, которые помогут новичкам быстрее понять, как устроен web3.

Перед вами третий материал, который мы подготовили в рамках цикла статей о децентрализованных финансах. Поскольку DeFi занимают одно из ключевых мест в мире web3, рекомендую начать погружение в предметную область именно с изучения подобных материалов. А если вам нужен путеводитель по миру web3, можно совершенно бесплатно воспользоваться нашей картой развития, выложенной на GitHub: так изучение новой предметной области будет более системным и продуктивным. 

В этой статье я расскажу про несколько важных аспектов в работе DeFi приложений.  Рассмотрим на примерах:

  • проблему ценообразования при выполнении транзакций;

  • опасность потери прибыли для поставщиков ликвидности;

  • виды атак при выполнении транзакций

Знание этих вопросов поможет начинающим web3 разработчикам понять, с какими вызовами придётся столкнуться при работе с реальными проектами. Да и просто пользователям пригодится знание нюансов работы DeFi приложений.

Price slippage

Во время обмена токенов обычно происходит следующая история. Между ожидаемой ценой перед совершением транзакции и фактической ценой после ее выполнения  есть разница. Эту разницу мы называем проскальзыванием цены (slippage).

Неопытного пользователя может обескуражить это несовпадение. Но факт остаётся фактом: такая ситуация почти всегда сопутствует любому обмену токенов в DEX на базе AMM.

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

Пример проскальзывания цены

Предположим, у нас есть децентрализованный обменник, основанный на уже знакомом принципе AMM. В этом обменнике поставщики ликвидности создали ликвидную пару.

Представим, что Алиса и Боб хотят купить 1 ETH за USDT в нашем импровизированном обменнике. Стартовая цена 1 ETH на момент обмена — 1 400 USDT. Соответственно, и Алиса, и Боб отправляют в обменник 1 400 USDT.

Поскольку обменник децентрализованный и работает на основе блокчейн-технологии, то операции проводятся атомарно. Из-за пошагового  выполнения операций, транзакция Боба совершается раньше транзакции Алисы. Но транзакция не просто выполнилась раньше, она ещё и повлияла на цену ETH – он подорожал. После покупки Боба цена ETH подросла относительно его цены до момента покупки. Все по правилам AMM.

Как и почему это произошло? Цена актива зависит от соотношения количества токенов в пуле ликвидности. Если одного токена становится меньше, то его цена незначительно возрастает относительно другого токена.

Получается, что на момент исполнения транзакции Алисы, цена ETH уже изменилась и стала равна 1540 USDT. Но так как Алиса отправила USDT меньше, то эфира она получит тоже меньше. Другими словами, эфир стал для неё дороже. Это и есть то самое проскальзывание фактической цены относительно ожидаемой.

Важно! Проскальзывание часто выражается в процентах, которые указывают, насколько может изменится цена токена.

Фактическая цена обмена (то количество токена, которое ты получишь в результате) зависит от двух факторов: 

1) объёма токенов в ликвидной паре, 

 2) порядка транзакций на обмен в этой паре.

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

Почему возникает slippage?

Пример Алисы и Боба позволяет нам выделить две основные причины проскальзывания: ликвидность и волатильность.

  1. Волатильность – это изменчивость стоимости актива за определенный промежуток времени. Любой токен подвержен изменчивости.

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

    • Каждая покупка токена увеличивает его стоимость, поскольку его остаётся меньше в пуле. Каждая продажа токена уменьшает его стоимость, так как его в пуле станет больше.

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

На изображении ниже представлены три вида волатильности цены токена на временном таймлайне.

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

Давай предположим, что мы хотим приобрести 5 ETH.

Левый график. Ликвидность низкая. В пуле осталось 10 ETH. График показывает значительное смещение по кривой соотношения количества токенов. Покупка всего 5 ETHсильно изменит стоимость оставшихся 5 ETH в пуле. Согласно математике AMM, опустошить пул невозможно. Просто оставшиеся эфиры будут стоить очень и очень дорого.

Правый график. Ликвидность высокая. В пуле — 1000 токенов. Согласно графику видно, что покупка 5 ETH окажет гораздо меньшее влияние на количество эфира в пуле. Соответственно, стоимость ETH поменяется незначительно. То есть цена изменится меньше, если кто-то нас опередит и тоже купит 5 ETH.

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

Как Uniswap демонстрирует slippage?

С проскальзыванием работают следующие закономерности:

  1. Чем больше токенов участвует в сделке, тем больше она влияет на цену.

  2. Чем меньше токенов в пуле ликвидности, тем больше проскальзывание.

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

В дополнительных опциях свопа есть параметр «Price impact». Он наглядно показывает влияние цены сделки на стоимость покупаемого токена.

Посмотрим, что будет, если взять  какой-нибудь токен с очень ограниченным пулом ликвидности. Попробуем обменять токен USDT на ASM – токен, который поддерживает платформу Assemble.

В модальном окне свопа Uniswap заблаговременно предупредит о том, что сделка окажет критическое влияние на цену токена. Изменение цены превысит 91%, что нарушит баланс токенов в пуле ликвидности. В данном случае это сигнал того, что мы выкупим большую часть ликвидности.

О плюсах проскальзывания

Проскальзывание — это изменение цены токена в момент сделки. И интересно то, что оно может быть не только отрицательным.

Ожидаемая цена может измениться в обе стороны: быть ниже или выше ожидаемой. Соответственно и проскальзывания бывают двух видов: положительные и отрицательные.

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

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

Это работает как для покупки, так и для продажи токена.

Как Uniswap работает со slippage?

На некоторых обменниках можно в ручном режиме установить величину проскальзывания (0.5%, 1%, 5%). Так, децентрализованный обменник Uniswap в настройках свопа дает возможность указать slippage.

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

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

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

На следующем скриншоте я выставил высокое проскальзывание и Uniswap предупредил меня о потенциальной угрозе frontrun.

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

Impermanent loss

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

Когда мы предоставляем ликвидность в пул, цена внесённого актива может измениться. Это временное несоответствие между текущей ценой токенов в пуле и рыночной и называется непостоянными потерями. Эти потери возможные и незафиксированные. Постоянными и зафиксированными они станут только в момент вывода средств из пула.

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

Пример возникновения непостоянных потерь

Условие: Предположим, что я хочу стать поставщиком ликвидности для пула токенов USDT/ETH. Договоримся на берегу:

  • добавить токены нам будет необходимо в соотношении 1:1

  • цена 1 ETH равна 100 USDT

  • договоримся, что 1 ETH равен 100$

  • всего в пуле ликвидности на данный момент 10 ETH и 1000 USDT

Процесс:

  1. Я вношу в пул ликвидности 1 ETH и 100 USDT.

    Согласно договорённостям, стоимость 1 ETH и 100 USDT, внесённых в пул ликвидности, будет равна 200$.

    Высчитаем нашу долю внесённых токенов относительно всех токенов пула и получим 10%. Это значит, что мы будем получать 10% комиссий за обмены из нашего пула.

    Вспомним про механизм AMM с постоянным уравнением: X * Y = K, где X — количество ETH, Y — количество USDT, K — постоянное значение.

    В данном случае 10 ETH * 1000 USDT = 10 000. Это значение должно оставаться постоянным до и после любых обменов в пуле.

  1. Стоимость 1 ETH поднимается до 400$.

    Да, это произошло, но соотношение токенов в пуле также говорит, что цена эфира — 100 USDT. Арбитражные боты воспользуются этой возможностью. Они выявляют расхождения в ценах на разных обменниках и организуют процесс «купить в одном месте дешевле, продать в другом месте дороже». Прибыль кладут себе в карман. Подробнее об арбитражных ботах мы поговорим чуть позже.

  2. После работы ботов в пуле останется 5 ETH и 2000 USDT.

    Таким образом, сохранится наша постоянная K, которая равна 10 000. Также нивелируется разница в стоимости 1 ETH в пуле и за пределами обменника.

  3. А теперь пришло время забрать 10% вложенной нами ликвидности.

    Это 0,5 ETH и 200 USDT. В долларовом эквиваленте это — 400$. В два раза больше первоначально вложенной суммы! Также не стоит забывать про комиссионные сборы с пользователей за использование нашего пула ликвидности для обмена токенами. Предположим, что наше вознаграждение составило 20$.

Мы заработали 220$ в чистом виде! Приятно? Безусловно!

Однако, если бы мы просто держали свои ETH и USDT на кошельке, у нас был бы 1 ETH стоимостью 400$ и 100 USDT стоимостью 100$. Вместе это — 500$.

Разница между возможной стоимостью в 500$ и полученной стоимостью в 420$ и есть те самые непостоянные потери.

Заметим, что потери непостоянны и не зафиксированы, пока мы не выведем токены из пула. К тому же, комиссионные выплаты должны частично или полностью свести на нет потери, а в идеале даже вывести нас в плюс.

А есть ли способ избежать непостоянных потерь?

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

Для оценки непостоянных потерь при изменении цены токенов можно использовать графическое представление.

Согласно графику мы можем сделать вывод, что:

  • 1,25-кратное изменение цены — убыток 0,6%

  • 1,50-кратное изменение цены — убыток 2,0%

  • 1,75-кратное изменение цены — убыток 3,8%

  • 2-кратное изменение цены — убыток 5,7%

  • 3-кратное изменение цены — убыток 13,4%

  • 4-кратное изменение цены — убыток 20,0%

  • 5-кратное изменение цены — убыток 25,5%

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

Arbitrage. Mev. Gas auction

Давайте разберёмся с арбитражем и распространенными атаками ботов.

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

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

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

Арбитражные боты влияют на рыночные цены и на других участников рынка. Они уравновешивают курсы на обмен токенов по всему рынку, повышая его общую эффективность. Это благоприятно отражается на пользователях. В этом плане, арбитраж полезен, так как увеличивает объём торговли и доход от комиссии.

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

Чем опасны боты?

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

Отправленная транзакция в блокчейн исполняется не сразу. Она попадает в mempool или memory pool. Это такая небольшая база данных неподтверждённых или ожидающих выполнения транзакций. Когда транзакция подтверждается включением в блок, она удаляется из mempool.

Майнеры для составления блока выбирают транзакции из mempool на основе наибольшей выгоды, которую они могут получить. Для этого они могут включать, исключать и менять порядок транзакций на своё усмотрение в создаваемом блоке. Процесс извлечения такой прибыли называется Miner Extractable Value (MEV).

Вместе с майнерами за mempool следят и боты, так как содержимое mempool видно всем (специфика блокчейна). Боты могут успевать совершать собственные транзакции на опережение или после исполнения целевой транзакции.

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

  1. Провести покупку токенов по маленькой цене.

  2. Выждать транзакцию на ожидание.

  3. Продать токены по новой выросшей цене.

Провести свою транзакцию бот может путём искусственного повышения цены за газ. Из-за этого майнер будет брать из mempool эту транзакцию в первую очередь – из-за желания извлечь максимальный MEV.

Самое интересное здесь то, что ботов-злоумышленников может быть несколько. И тогда между ними начинается конкуренция по предложению наибольшей цены за газ. Такое явление называется войной за газ. Минус явления: такая война увеличивает комиссию для обычных пользователей.

Стоимость обмена токенов в DEX на AMM зависит от порядка транзакций. А этим порядком можно манипулировать, если предлагать большую плату за газ. Боты стараются направить свою транзакцию в точно определённое место среди других транзакций в ожидании. Таким образом создаются атаки, которые эксплуатируют майнеров.

Рассмотрим самые распространённые атаки ботов:

  1. Front-running

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

    Например, Алиса хочет приобрести много токенов. Это приведёт к повышению цены, так как токена останется в обороте меньше, а спрос на него останется прежним. Боту выгодно купить токен раньше Алисы. Бот это понимает и проводит атаку типа front-running.

    Согласно схеме срабатывает следующий сценарий:

    1. Алиса инициирует покупку токена и её транзакция попадает в mempool.

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

    3. Транзакция Алисы полностью подходит для бота. Он может на ней заработать, если купит токены раньше. Тогда бот начинает действовать. Он инициирует свою транзакцию на покупку токена, которая также попадает в mempool. Но бот предлагает майнеру цену за газ больше, чем предложила Алиса. Однако, он не предлагает слишком большую цену. Чтобы транзакция выполнилась очень быстро, ему нужно учитывать накладные расходы за свои действия и соблюдать баланс, иначе можно уйти в минус.

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

    5. Так как транзакция бота предлагает для майнера больше газа, логично, что он вставляет её в блок раньше транзакции Алисы. Это значит, что транзакция бота будет выполнена раньше транзакции Алисы.

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

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

  2. Back-running

    Атака похожа на front-running с той лишь разницей, что бот выполняет собственную транзакцию сразу после целевой транзакции.

    Например, бот отслеживает mempool на появление новых пулов ликвидности. Если он находит новый пул, то скупает как можно больше токенов. Однако скупает он не все из них, поскольку у пользователей должна оставаться возможность купить токены.

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

    Согласно этой схеме происходит следующий сценарий:

    1. Бот ищет новый, недавно появившийся токен и покупает его. Очень часто цена таких токенов низкая.

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

    3. Алиса инициирует покупку токена. Её транзакция попадает в mempool.

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

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

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

    Ты можешь спросить, в чём здесь опасность для Алисы? Ведь она купит раньше бота. Да, раньше, поэтому это не front-running, а back-running. После того, как Алиса купит свои токены, бот продаст свои. Помнишь, что для большей выгоды он скупил большую часть токенов?

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

  3. Sandwich

Эта атака комбинирует в себе front-running и back-running атаки.

Перейдём сразу к примеру. Алиса хочет купить токен на децентрализованной бирже, которая использует модель автоматизированного маркет-мейкера.

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

Согласно этой схеме происходит следующий сценарий:

  1. Стратегия бота заключается в поиске транзакции, которая повысит стоимость токена достаточно для заработка.

  2. Алиса инициирует покупку токена. Транзакция попадает в mempool.

  3. Бот, который постоянно мониторит mempool, видит транзакцию Алисы и применяет свою стратегию заработка.

  4. Транзакция Алисы полностью подходит для бота: она поднимет стоимость и бот сможет на ней заработать. Тогда бот инициирует две транзакции. Первая на покупку токена — бот выставляет цену газа больше Алисы. Вторая на продажу токена — бот выставляет цену газа меньше Алисы.

  5. Обе транзакции также попадают в mempool.

  6. Все это время майнер занимается своими прямыми обязанностями и составляет блок с транзакциями. Из-за правильно предложенных цен за газ первая транзакция бота будет выполнена майнером раньше транзакции Алисы, а вторая после.

    В данном случае неприятность для Алисы — это проскальзывание цены. То есть фактическая цена покупки для Алисы будет больше, а токена в таком случае она купит меньше, чем ожидала.

Бот смог заработать на разнице покупки и продажи. Он купил первой транзакцией токен дешевле. Второй транзакцией продал дороже, чем купил. Профит положил себе в карман.

Вывод

На этом все! Мы изучили много особенностей DEX. Почему цена за токены может проскальзывать и как можно на это повлиять, опасности добавления ликвидности в пул, связанные с непостоянной потерей. Отдельно поговорили о том, как работают арбитражеры и какие основные типы атак бывают.

Ты, конечно же, хочешь спросить, для чего мне может понадобится эта теория?

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


ссылка на оригинал статьи https://habr.com/ru/articles/752846/

Разработка сервопривода с BLDC мотором

Открытый проект серво‑контроллера MC50 продолжает развиваться. На этот раз поговорим о создании сервопривода.

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

Предыдущие статьи по теме проекта MC50

В прошлой статье было показано как заставить BLDC мотор крутиться с помощью периферии микроконтроллера S5D9 семейства Renesas Synergy™

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

Конструкция

Конструкция состоит из мотор‑редуктора, дополнительного редуктора 29/40, простого узла с серво‑потенциометром для контроля за скоростью выходного вала и термистором для контроля за температурой мотора. Как видно, внешний редуктор даёт удобно разместить серво‑потенциометр. Дополнительно он увеличивает момент силы 1.3 раза Такая конструкция в разных масштабах встречается во множестве сервоприводов.

Конструкция сервопривода

Конструкция сервопривода

Схема подключения

Плата серво‑контроллера рассчитана на питание от внешнего источника напряжением от 20 до 30 В. Но силовой драйвер продолжает функционировать при понижении напряжения до 7 В. При отсутствии внешнего источника напряжения плата может питаться от USB, но силовой драйвер при этом функционировать не будет.

Схема подключения серво-контроллера MC50

Схема подключения серво-контроллера MC50

О частоте вращения BLDC мотора

Частота вращения BLDC двигателя определяется приложенным к нему напряжением при условии постоянной нагрузки на валу (считаем что 6-шаговая коммутация в это время работает безукоризненно, напряжение меняем ШИМ‑ом). Эта частота записывается в даташите на мотор. Если момент силы нагрузки будет больше или меньше, то частота будет разная при неизменном напряжении. Но мы не знаем точно момент силы нагрузки и он скорее всего будет переменным.

Для идеального двигателя без нагрузки, без трения и без сопротивления обмоток частоту вращения при определенном напряжении можно узнать вращая мотор внешним приводом до достижения заданного напряжения. Частота вращения в этот момент и будет искомой частотой. Но тут важна форма напряжения. Применив 6-шаговую коммутацию к мотору с синусоидальной обратной ЭДС, не получится получить ту же частоту.

Идеальный BLDC двигатель будет крутиться с бесконечной частотой при подаче на него бесконечного напряжения. У реального же BLDC двигателя в какой‑то момент сначала наступит насыщение магнитопровода, резко повысится ток через обмотки, обмотки раскалятся и спустя некоторое время мотор загорится. Поэтому частота вращения и допустимый момент силы ещё ограничиваются температурой мотора.

Разработка архитектуры управления.

Если для для управления голым двигателем архитектура достаточно ясна, то для управления сервоприводом с неизвестной динамикой нагрузки все несколько сложнее.

Для начала отметим, что частота вращения ротора не равна частоте вращения сервопривода, а в динамике с реальной механикой с люфтами и упругими реакциями даже не пропорциональна.

Для управления частотой вращения самое лёгкое решение — это просто менять скважность широтно‑импульсной модуляции (ШИМ).

Простейшая модель сервопривода в Simulink. Здесь реализована 6-шаговая коммутация и вращение только в одном направлении

Простейшая модель сервопривода в Simulink. Здесь реализована 6-шаговая коммутация и вращение только в одном направлении

Файл модели здесь

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

Осциллограмма сигналов  при вращении груза в вертикальной плоскости

Осциллограмма сигналов при вращении груза в вертикальной плоскости

Но как только момент силы начнёт меняться частота вращения начнёт пульсировать, как показано на видео ниже.

Непостоянство частоты вращения при отсутствии автоматического регулирования

Непостоянство частоты вращения при отсутствии автоматического регулирования

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

Такая реализация есть в среде MATLAB Simulink в виде модели конвертируемой в исходники на C. Ниже представлена модель в виде открытой расширяемой подсистемы.

Файлы модели здесь.

Модель универсального PID регулятора

Модель универсального PID регулятора

Модель повторяет функциональность встроенного PID компонента Simulink, но при этом выводит все коэффициенты PID как входные аргументы в одной шине. Аргументы можно менять и в стандартном компоненте используя символьные обозначения переменных из рабочего пространства, но это влечёт за собой неявные зависимости приводящие к ошибкам. А шина обеспечивает генерацию аргумента как передаваемой структуры. Это уменьшает объем кода при интеграции модели в embedded проект и упрощает рефакторинг моделей.

Диалог настройки PID регулятора в Simulink

Диалог настройки PID регулятора в Simulink

Для предотвращения бесконечного нарастания интегральной составляющей (anti‑windup) применён метод clamping.

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

Сгенерированные из модели исходники находятся в директории MATLAB_PID.

Выбор одноконтурного или двухконтурного управления скоростью вращения серво-привода.

Чтобы сделать выбор архитектуры петли управления проведём эксперимент. Сделаем в среде FreeMaster две записи сигналов в разных режимах управления мотором. Записывать будем одновременно четыре сигнала:

  • сигнала включения,

  • тока протекающего через мотор,

  • состояния датчиков Холла, отражающих скорость вращения ротора,

  • скорость выходного вала привода.

Первая запись ниже сделана во время скачкообразного включения вращения мотора в режиме 6-step с максимальным напряжением.

Вторая запись ниже сделана во время скачкообразного включения вращения мотора в режиме 6-step с удержанием постоянного тока через мотор с помощью петли управления током.

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

Очевидно в первом случае практически трудно будет проводить регулировку скорости регулируя алгоритмом PID скважность ШИМ. Во всяком случае с интервалом регулирования меньшим 0.1 сек. Поскольку скорость откликается совершенно не похожим на линейную систему образом. А классический PID хорош только для линейных систем.

Во втором случае отклик гораздо более линейный и PID здесь подойдёт. Вернее тут подойдёт простой PI контур, поскольку отклик демонстрирует инерциальную нагрузку без упругости (осцилляций). Но надо помнить — тут уже применён высокоскоростной контур поддержки заданного тока. т. е. в данном случае у нас получится 2-контурная система: быстрый PI контур управления током через ШИМ и медленный PI для управления скоростью через задание тока.

В итоге пришли к следующей архитектуре в составе модели Simulink :

Архитектура с двумя петлями управления скоростью вращения сервопривода в среде Simulink

Архитектура с двумя петлями управления скоростью вращения сервопривода в среде Simulink

Файл модели здесь

Модель включает мотор-редуктор с планетарной передачей и датчиками Холла, внешний редуктор, серво сенсор с моделью измерения скорости включая шумы измерения, 3-фазный инвертер с CMOS транзисторами, ШИМ модулятор и два контура управления: по скорости и по току. Цветами на модели выделяются участки работающие с разной частотой тактирования. Контур тока работает на частоте 16 Кгц, контур скорости на частоте 100 Гц.

Измерения частоты вращения ротора датчиками Холла

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

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

Используется логика захвата таймеров GPT3211 и GPT3212 (согласно хидеру из SSP они имеет имена R_GPTB3 и R_GPTB4). Таймера имеют по два модуля захвата, поэтому для сигналов с трех датчиков нужно два таймера. Таймера тактируются абсолютно синхронно благодаря команде одновременного запуска (все таймера в Synergy можно запустить синхронно). 

Здесь фрагмент того как производится измерение в обработчике прерываний от АЦП:

  st = R_GPTB3->GTST;  // Читаем флаги состояния capture таймера  GPT3211    if (st & BIT(0))     // Проверяем флаг TCFA. Input Capture/Compare Match Flag A   {     // Здесь если событие захвата произошло      R_GPTB3->GTST = 0;      // Очищаем флаг захвата и остальные флаги.                             // За другие флаги захвата не беспокоимся, поскольку они при штатной работе не могут быть взведены в этот момент.      val = R_GPTB3->GTCCRA;  // Читаем регистр с захваченным состоянием счетчика      // Здесь рассчитываем разницу по отношению к предыдущему захваченному значению на том же угле поворота ротора, т.е. для полюса с номером в переменной hall_u_cnt     if (hall_u_prev_arr[hall_u_cnt] > 0)     {       if (val > hall_u_prev_arr[hall_u_cnt])       {         hall_u_capt_arr[hall_u_cnt] = val - hall_u_prev_arr[hall_u_cnt];       }       else       {         // Корректируем если было произошло переполнение счетчика между захватами         hall_u_capt_arr[hall_u_cnt] =  0x7FFFFFFF -(hall_u_prev_arr[hall_u_cnt] - val -1);       }        hall_u_capt = hall_u_capt_arr[hall_u_cnt]; // Сохраняем разницу в промежуточную переменную. Эта переменная используется при отладке через FreeMaster       // .......................................................       one_turn_period = hall_u_capt;             // Записываем длительность полного оборота ротора в глобальную переменную для дальнейшего использования остальными задачами       // .......................................................     }     hall_u_prev_arr[hall_u_cnt] = val;         // Сохраняем текущее захваченное значение в переменную предыдущего значения     hall_u_cnt++;                              // Ведем счет полюсов. Для каждого полюса сохраняется своя измеренная величина     if (hall_u_cnt >= 8) hall_u_cnt = 0;      // Определяем направление вращения     h = R_IOPORT5->PCNTR2 & 0x7;  // Читаем сигналы датчиков Холла здесь снова, несмотря на то что они уже были прочитаны в обработчике прерывания                                   // Это нужно поскольку capture логика может сработать уже после того как в ISR были прочитаны состояния датчиков     if ((h == 0b100) || (h == 0b011))      rotating_direction = 1; // Направление вращения определяем по паттернам сигнало с датчиков сразу после текущего фронта     else if ((h == 0b010) || (h == 0b101)) rotating_direction = -1;      R_GPTB5->GTCLR_b.CCLR13 = 1;  // Сброс счетчика отслеживающего остановку вращения     R_GPTB5->GTST           = 0;  // Сброс флагов счетчика отслеживающего остановку вращения   }    if (st & BIT(1))   {     // Input capture/compare match of GTCCRB occurred     R_GPTB3->GTST = 0;     val = R_GPTB3->GTCCRB;     if (hall_v_prev_arr[hall_v_cnt]>0)     {       if (val > hall_v_prev_arr[hall_v_cnt])       {         hall_v_capt_arr[hall_v_cnt] = val - hall_v_prev_arr[hall_v_cnt];       }       else       {         hall_v_capt_arr[hall_v_cnt] =  0x7FFFFFFF -(hall_v_prev_arr[hall_v_cnt] - val -1);       }       hall_v_capt = hall_v_capt_arr[hall_v_cnt];       one_turn_period = hall_v_capt;     }     hall_v_prev_arr[hall_v_cnt] = val;     hall_v_cnt++;     if (hall_v_cnt >= 8) hall_v_cnt = 0;      h = R_IOPORT5->PCNTR2 & 0x7;  // Читаем сигналы датчиков Холла здесь чтобы они были прочитаны не раньше чем стработает capture логика     if ((h == 0b110) || (h == 0b001))      rotating_direction = 1;     else if ((h == 0b011) || (h == 0b100)) rotating_direction = -1;      R_GPTB5->GTCLR_b.CCLR13 = 1;  // Сброс счетчика отслеживающего остановку вращения     R_GPTB5->GTST           = 0;   }     st = R_GPTB4->GTST;    if (st & BIT(0))   {     // Input capture/compare match of GTCCRA occurred     R_GPTB4->GTST = 0;     val = R_GPTB4->GTCCRA;     if (hall_w_prev_arr[hall_w_cnt]>0)     {       if (val > hall_w_prev_arr[hall_w_cnt])       {         hall_w_capt_arr[hall_w_cnt] = val - hall_w_prev_arr[hall_w_cnt];       }       else       {         hall_w_capt_arr[hall_w_cnt] =  0x7FFFFFFF -(hall_w_prev_arr[hall_w_cnt] - val -1);       }       hall_w_capt = hall_w_capt_arr[hall_w_cnt];       one_turn_period = hall_w_capt;     }     hall_w_prev_arr[hall_w_cnt] = val;     hall_w_cnt++;     if (hall_w_cnt >= 8) hall_w_cnt = 0;      h = R_IOPORT5->PCNTR2 & 0x7;  // Читаем сигналы датчиков Холла здесь чтобы они были прочитаны не раньше чем стработает capture логика     if ((h == 0b101) || (h == 0b010))      rotating_direction = 1;     else if ((h == 0b110) || (h == 0b001)) rotating_direction = -1;      R_GPTB5->GTCLR_b.CCLR13 = 1;  // Сброс счетчика отслеживающего остановку вращения     R_GPTB5->GTST           = 0;   } 

Здесь мы получаем значение частоты вращения c девиацией меньшей 0.3%, но с гораздо большей задержкой чем могли бы получить измеряя длительности между соседними фронтами. Но в последнем случае нам пришлось бы применить фильтр, который внёс бы ещё большую задержку.

Экстраполяция частоты вращения при внезапной остановке

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

  // Блок линейно понижающий скорость в случает отсутствия сигналов с датчиков Холла   if (one_turn_period != 0)   {     if (R_GPTB5->GTST & BIT(6))  // Проверяем флаг TCFPO. Overflow Flag     {       // В случае переполения тамера сразу отмечаем скорость как нулевую.       // Поскольку переполение таймера происходит с периодом в две секунды, то такую низкую скрость принимаем как нулевую       one_turn_period = 0;     }     else     {       // За один оброт мотора мы имеем 8 полюсов * 3 датчика = 24 события сброса таймера R_GPTB5       // И если таймер R_GPTB5 смог набрать 2/3 (16/24) длительности одного оборота и не был сброшен значит скорость упала       // и можно начинать снижать оценку текущей скорости       uint32_t no_edge_period = R_GPTB5->GTCNT*16;       if (no_edge_period > one_turn_period)       {         one_turn_period = no_edge_period;       }     }   } 

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

Влияние способа коммутации силовых транзисторов на качество управления и энергопотребление

В прошлой статье о 6-шаговом управлении коммутацией были показаны несколько способов переключения транзисторов при ШИМ модуляции. В нашей программе оставлено два способа. Между ними можно выбирать с помощью настройки параметра Enable soft commutation.

На нашем мотор‑редукторе эти два способа показали только незначительные различия. При одинаковой частоте вращения ток потребления был идентичным. Но при мягкой коммутации вращение мотора начиналось при меньшем значении коэффициенте заполнения. Дело в том, что мотор из‑за трения не начинает крутиться до того момента как коэффициент заполнения ШИМ не превысит некий порог. Этот порог в случае жёсткой коммутации в нашем случае был в районе 55%, а в случае мягкой коммутации в районе 30%. т. е. мягкая коммутация даёт больший диапазон регулирования ШИМ — 70%, в противовес жёсткой — 45%. Это отражается потом и на качестве управления частотой вращения. При мягкой коммутации качество управления несколько лучше.

Способ коммутации выбирается параметром en_soft_commutation.

Регулировка тока затворов силовых транзисторов

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

Типичная схема драйвера на TMC6200

Типичная схема драйвера на TMC6200

У этой микросхемы есть возможность через интерфейс SPI задавать уровень выходных токов на затворы. Есть 4 уровня: от самого слабого 400…600 мА до самого сильного 1200…1800 мА.

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

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

Управлять настройками драйвера TMC6200 можно с помощью ряда параметров. Настройки загружаются при старте основной задачи управления мотором.

Калибровка смещения токовых усилителей драйвера TMC6200

Микросхема TMC6200 имеет встроенные усилители напряжения с токовых шунтов расположенных в фазах серво драйвера. Наличие встроенных усилителей упрощает схему. Есть возможность менять коэффициент усиления через интерфейс SPI.

Однако недостатком этих усилителей является довольно большое напряжение смещения. Ещё хуже, то что оно сильно зависит от температуры микросхемы. Но мало того, оно разное для каждого из 3 усилителей и может меняться во времени даже при постоянной внешней температуре. В нашей программе используется самый чувствительный режим с усилением в 20 раз. При таком усилении смещение на выходе усилителей достигает 240 мВ и более.

Решение проблемы найдено в том что перед каждым включением мотора производится перекалибровка смещения нуля для каждого из 3 усилителей. Перекалибровка длится около 15 мсек и существенного влияния на быстродействие не оказывает.

Что делать с обратной ЭДС? 

Как только мотор начинают крутить внешние силы вместо нашего драйвера он превращается в генератор. В генератор мотор также превращается на короткие промежутки времени когда происходит торможение. Если частота вращения вызванная внешними силами превышает частоту вращения на номинальном напряжении при холостом ходе, то повышается напряжение на шине питания. Ниже на осциллограмме показаны скачки напряжения на шине питания когда сервопривод вращает груз на рычаге с постоянным значением ШИМ. В моменты опускания груза напряжение на шине питания повышается на 5.5 В. Это происходит потому что частота вращения ротора в эти моменты превышает максимальную частоту вращения на номинальном напряжении по даташиту.

Осциллограмма выброса обратной ЭДС на шине питания

Осциллограмма выброса обратной ЭДС на шине питания

Если бы мы управляли скоростью и не давали разгоняться сервоприводу, то таких выбросов напряжения не было бы. Однако скачки напряжения могут возникнуть и при выключенном состоянии сервопривода. Тогда стратегия такая. Когда напряжение достигнет уровня достаточного для включения процессора, он сразу подаёт сигнал включения на все нижние транзисторы. Транзисторы закорачивают обмотки мотора и напряжение спадает.

Проблема определения скорости вращения серво-потенциометра

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

Вид сигнала с серво-потенциометра

Вид сигнала с серво-потенциометра
Модель Simulink для тестирования вариантов фильтрации сигнала скорости вращения выходного вала сервопривода.

Модель Simulink для тестирования вариантов фильтрации сигнала скорости вращения выходного вала сервопривода.

Поскольку фильтр вносит фазовую задержку мы не можем на основе такого отфильтрованного сигнала управлять как угодно быстро и точно.

Для того чтобы узнать задержку в результате фильтрации измерим её на модели

Посмотрим разницу во времени между сигналами Serv.speed и Meas.serv.speed. Это два разных значения скорости вращения выходного вала в градусах в секунду. Первая величина реальная, снятая непосредственно с вала, а вторая оценочная, после фильтрации дифференциала сигнала позиции вала.

Вид окна Data Inspector в MATLAB сигналов скорости вращения выходного вала в градусах в сек

Вид окна Data Inspector в MATLAB сигналов скорости вращения выходного вала в градусах в сек

При коэффициенте экспоненциального фильтра (на модели обозначен как Speed exp.filter1) равного 1-1/2048 (0.99951) получаем задержку 83 мс.

Вид окна Data Inspector в MATLAB сигналов скорости вращения выходного вала в градусах в сек

Вид окна Data Inspector в MATLAB сигналов скорости вращения выходного вала в градусах в сек

При коэффициенте экспоненциального фильтра равного 1-1/256 (0.9961) получаем задержку 16 мс.

Задержки существенные. Они ограничивают такт регулятора скорости величиной не более 10…50 Гц.

Как видно, простая комбинация FIR и IIR фильтров не слишком хороший способ получить чистое значение скорости. Но пока останавливаемся на этом.

К вопросу о применении магнитных сенсоров вместо серво-потенциометра

Можно слышать мнение о лучших свойствах магнитных сенсоров по сравнению с серво-потенциометром. Был проведён эксперимент с GMR сенсорами угла поворота TLI5012B.

Была изготовлена отладочная плата с этим сенсором, написан софт для обмена с FreeMaster. Непосредственно над сенсором закрепили диаметрально поляризованный магнит. Магнит повернут так чтобы сенсор показывал угол близкий к нулю. И были проведены замеры показаний при смене ориентации платы относительно магнитного поля земли

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

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

Вот результат изменения показаний сенсора при повороте платы в поле земли:

Как видно присутствие рядом мощного магнита не спасло сенсор от чувствительности к полю земли. Изменение показаний достигают 10%.

Шум серво-потенциометра достигает 5 сэмплов на любом угле поворота при 12-битном АЦП. На середине диапазона это будет равно 5/2048 = 0.24%. У GMR сенсора TLI5012 шум составляет 0.025 %

Однако деформации платы с сенсором на доли миллиметра приводили к искажениям показаний на 0.06 %. Сенсор очень чувствителен к изменению расстояния до магнита и очень чувствителен к вибрациям.

Изменение дистанции между осями сенсора и магнита на 1 мм приводит к изменению значения угла на 1 градус.

Появление на расстоянии 50 см магнита аналогичного приложенного к сенсору также вызывает флуктуации приблизительно в 1 градус.

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

Разработка приложений для MC50

Программный фреймворк серво контроллера MC50 выполнен на базе Azure RTOS. На данном этапе он содержит в себе практически весь состав промежуточного программного обеспечения Azure RTOS с драйверами периферии. Поверх слоя промежуточного ПО находится несколько прикладных задач: управления мотором, управления микросхемой 3-фазного драйвера мотора, измерения, GUI, управление выходами и другие.

Грубая структура программного обеспечения

Грубая структура программного обеспечения

В текущей версии софта реализовано управление рычагом на заданные углы с заданной скоростью. С помощью ручного энкодера производится калибровка углов, настройка скорости поворота и номер узла на шине CAN. Все остальные параметры можно настроить через терминал VT100 через USB VCOM или через интернет, как это было показано в предыдущих статьях. Команды сервоприводу могут подаваться с ручного энкодера, по шине CAN и через протокол FreeMaster через USB.

Разработка продолжается…


ссылка на оригинал статьи https://habr.com/ru/articles/752842/

React Custom Hook: useMediaQuery

In this article series, we embark on a journey through the realm of custom React hooks, discovering their immense potential for elevating your development projects. Our focus today is on the «useMediaQuery» hook, one of the many carefully crafted hooks available in the collection of React custom hooks.

Github: https://github.com/sergeyleschev/react-custom-hooks

import { useState, useEffect } from "react" import useEventListener from "../useEventListener/useEventListener"  export default function useMediaQuery(mediaQuery) {     const [isMatch, setIsMatch] = useState(false)     const [mediaQueryList, setMediaQueryList] = useState(null)     useEffect(() => {         const list = window.matchMedia(mediaQuery)         setMediaQueryList(list)         setIsMatch(list.matches)     }, [mediaQuery])     useEventListener("change", e => setIsMatch(e.matches), mediaQueryList)     return isMatch }

The useMediaQuery hook allows you to dynamically update your UI based on a given media query. Simply pass in the desired media query as a parameter, and the hook will return a boolean value indicating whether the media query matches the current viewport size.

One of the key advantages of this custom hook is its simplicity and reusability. With just a few lines of code, you can effortlessly implement responsive behavior throughout your application. Whether you need to conditionally render components, apply specific styles, or trigger different functionality based on screen size, useMediaQuery has got you covered.

import useMediaQuery from "./useMediaQuery"  export default function MediaQueryComponent() {     const isLarge = useMediaQuery("(min-width: 200px)")     return <div>Large: {isLarge.toString()}</div> }

This hook is not limited to specific use cases; it can be utilized in a variety of scenarios. For instance, you can use it to dynamically adjust the layout of a navigation menu, hide or show certain elements based on screen size, or even optimize the loading of data based on the available space. The possibilities are endless, and the useMediaQuery hook empowers you to deliver a seamless user experience across different devices and screen sizes.

Full Version | React Custom Hooks: https://habr.com/en/articles/746760/


ссылка на оригинал статьи https://habr.com/ru/articles/752808/

React Custom Hook: useStateWithHistory

In this article series, we embark on a journey through the realm of custom React hooks, discovering their immense potential for elevating your development projects. Our focus today is on the «useStateWithHistory» hook, one of the many carefully crafted hooks available in the collection of React custom hooks.

Github: https://github.com/sergeyleschev/react-custom-hooks

import { useCallback, useRef, useState } from "react"  export default function useStateWithHistory(     defaultValue,     { capacity = 10 } = {} ) {     const [value, setValue] = useState(defaultValue)     const historyRef = useRef([value])     const pointerRef = useRef(0)     const set = useCallback(         v => {             const resolvedValue = typeof v === "function" ? v(value) : v             if (historyRef.current[pointerRef.current] !== resolvedValue) {                 if (pointerRef.current < historyRef.current.length - 1) {                     historyRef.current.splice(pointerRef.current + 1)                 }                 historyRef.current.push(resolvedValue)                 while (historyRef.current.length > capacity) {                     historyRef.current.shift()                 }                 pointerRef.current = historyRef.current.length - 1             }             setValue(resolvedValue)         },         [capacity, value]     )     const back = useCallback(() => {         if (pointerRef.current <= 0) return         pointerRef.current--         setValue(historyRef.current[pointerRef.current])     }, [])     const forward = useCallback(() => {         if (pointerRef.current >= historyRef.current.length - 1) return         pointerRef.current++         setValue(historyRef.current[pointerRef.current])     }, [])     const go = useCallback(index => {         if (index < 0 || index > historyRef.current.length - 1) return         pointerRef.current = index         setValue(historyRef.current[pointerRef.current])     }, [])     return [         value,         set,         {             history: historyRef.current,             pointer: pointerRef.current,             back,             forward,             go,         },     ] }

Advantages of useStateWithHistory:

  1. Automatic history tracking: useStateWithHistory automatically keeps track of the values you set, allowing you to access the complete history whenever you need it.

  2. Efficient memory usage: The hook utilizes a capacity parameter, ensuring that the history doesn’t grow indefinitely. You can define the maximum number of historical values to keep, preventing excessive memory consumption.

  3. Time-travel functionality: With back(), forward(), and go() functions, you can seamlessly navigate through the recorded history. Travel back and forth between previous states or jump directly to a specific index, enabling powerful undo/redo or step-by-step functionality.

Where to use useStateWithHistory:

  1. Form management: Simplify the process of handling form inputs by providing an easy way to track changes, revert to previous values, or redo modifications.

  2. Undo/Redo functionality: Implement undo/redo functionality in your application with ease. Track state changes and allow users to navigate back and forth through their actions effortlessly.

  3. Step-by-step navigation: Use useStateWithHistory to build interactive guides or tutorials where users can navigate between different steps while preserving their progress.

import { useState } from "react" import useStateWithHistory from "./useStateWithHistory"  export default function StateWithHistoryComponent() {     const [count, setCount, { history, pointer, back, forward, go }] =         useStateWithHistory(1)     const [name, setName] = useState("Sergey")     return (         <div>             <div>{count}</div>             <div>{history.join(", ")}</div>             <div>Pointer - {pointer}</div>             <div>{name}</div>             <button onClick={() => setCount(currentCount => currentCount * 2)}>                 Double             </button>             <button onClick={() => setCount(currentCount => currentCount + 1)}>                 Increment             </button>             <button onClick={back}>Back</button>             <button onClick={forward}>Forward</button>             <button onClick={() => go(2)}>Go To Index 2</button>             <button onClick={() => setName("John")}>Change Name</button>         </div>     ) }

Throughout this article series, we focused on one of the gems from the collection of React custom hooks – «useStateWithHistory«. This hook, sourced from the «react-custom-hooks» repository, revolutionizes how we work in our React applications.

Full Version | React Custom Hooks: https://habr.com/en/articles/746760/


ссылка на оригинал статьи https://habr.com/ru/articles/752826/

Пора улучшать согласование

Я работаю в фирме, которая разрабатывает устройства для умных домов на частотах 869, 915 МГц. Это маломощные устройства с антеннами из провода, PCB-антеннами и внешними штыревыми антеннами. Для работы в квартире антенна должна быть всенаправленной. Заранее не известно, где будет расположено устройство и как сориентировано. Некоторые ключевые устройства мы относили к сторонним специалистам для настройки антенны, потом использовали эти согласования в других устройствах. Какое-то время этого хватало. У нас большой парк устройств, плюс одни и те же устройства работают в разных корпусах. Это требует настройки антенны для каждого типа продукта. Обращаться каждый раз к специалистам слишком дорого, поэтому несколько лет пытаемся научиться делать сами. Далее расскажу о процессе настройки антенны для одного из наших устройств с антенной из провода (ground plane).

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

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

  • Векторный анализатор цепей SV4401A — клон NanoVNA.
  • Спектроанализатор DSA815TG для относительной оценки излучаемой мощности.
  • Антенны Linx на 869 МГц и 915 МГц.
  • Китайскую оценочную плату для калибровки векторного анализатора с разъёмами u.fl.
  • Магазин подстроечных конденсаторов и индуктивностей.
  • Программу Atyune для моделирования согласования.

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

▍ Цели и задачи

Цель работы: увеличить дальность работы устройства. Этого можно добиться двумя путями:

  • увеличить мощность излучения антенны,
  • сделать антенну направленной.

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

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

▍ Определение уровня идеального согласования

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

У меня есть антенны на 869 и 915 МГц. Из диаграммы Вольперта-Смита видно, что каждая антенна лучше работает в своём диапазоне (более низкий КСВ).

Linx 868 МГц

Linx 915 МГц

Антенну подключаю через u.fl пигтейл. Плата имеет перемычку, через которую можно выбрать направление для радиосигнала.

Подключение антенны к «живому» устройству

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

Безэховой камеры у меня нет, поэтому наблюдаю дисперсию сигнала из-за отражения от стен, пола и потолка. Принимающая антенна неподвижна. Передающую антенну двигаю в квадрате 20х20 см, и немного меняю положение своего тела, пока не добьюсь максимально возможного показания на спектроанализаторе.

Исходные устройства имели энергию -32 и -31 дБм для частот 869 и 915 МГц соответственно. Устройства с эталонными антеннами: -28 и -25 дБм соответственно.

▍ Ход работы

Согласовывать антенну буду с помощью п-фильтра.

П-фильтр

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

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

Вот пример:

Типичный пример подключения ВАЦ к исследуемой антенне

Напаивать компоненты тяжелее, чем подключать эталонные нагрузки, поэтому я припаял u.fl разъём к началу радиотракта. Теперь калибровку можно делать на оценочной плате.

Китайская оценочная плата

Доработанная плата

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

▍ Подготовка ВАЦ к измерениям

На результат измерений оказывают сильное влияние окружающие предметы, особенно металлические, а также изменение изгиба кабеля. Лучше всего использовать жёсткие кабели, или полужёсткие. У меня есть только гибкий кабель. Поэтому приклеиваю скотчем к деревянному столу ВАЦ и кабель.

Китайская оценочная плата

  • Диапазон частот от 869 и 915 МГц.
  • Минимальное количество точек сканирования (101), потому что диапазон частот очень маленький.
  • Усреднение результата 4. Чтобы уменьшить уровень шума и повысить стабильность графиков.
  • Калибровка по трём эталонным сопротивлениям (SOL).

Прибор готов к измерениям. Подключаю печатную плату с напаянным u.fl разъёмом без согласующих элементов. На картинке показана схема измерения с припаянным коаксиальным кабелем, но проблема с плоскостью калибровки такая же, как и у меня.

Схема измерений с переносом плоскости калибровки

Сейчас плоскость калибровки находится где-то после u.fl разъёма. Чтобы продолжить согласование антенны, нужно или перенести плоскость калибровки к последовательному компоненту п-фильтра, или учесть это в программе моделирования. Двигать плоскость калибровки можно по коаксиальному кабелю, копланарной линии, если волновое сопротивление равно 50 Ом. Первый метод для меня сложнее. Кусочек копланарной линии между плоскостью калибровки и компонентами п-фильтра оказывает влияние на измерения. Чем он длиннее, тем сильнее будет поворачиваться кривая вокруг центра на диаграмме Вольперта-Смита. Сигнал проходит этот кусочек дважды, и у него увеличивается фаза. На картинке можно увидеть, как дополнительный кусочек коаксиального кабеля смещает кривую. Исходный график красного цвета, жёлтый — после добавления длины.

Китайская оценочная плата

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

В дорогих современных ВАЦ есть функции настройки порта, но в моём дешёвом есть одна функция «E-delay». Дальнейшая методика не требует расчётов и взята из обучающего ролика.
Согласующих компонентов нет. Открываю график зависимости фазы от частоты.

Фаза без коррекции

Добавляю задержку, пока фаза не станет нулевой.

Фаза с коррекцией

Считаю, что после этого плоскость калибровки передвинулась на последовательный элемент, середину п-фильтра.

Ставлю перемычку 0 Ом на место последовательного элемента и припаиваю антенну заведомо большей длины. Антенна называется «четвертьволновый штырь». Длина четверти волны 86 мм для частоты 869 МГц. Расчёт сделан для скорости электромагнитной волны в вакууме. Пусть будет 120 мм. Отрезаю понемногу кончик провода и смотрю, как изменяется согласование антенны. Выбираю самое лучшее согласование, в котором КСВ на обеих частотах минимально. К сожалению, картинок и подробных протоколов эксперимента не сохранилось. В таблице последние три эксперимента, когда уже знал, где искать.

№ опыта

Длина антенны

869 МГц 915 МГц
КСВ Z КСВ Z
1 95 3,9 21R 48pF 2,5 33R 5pF
92,5 3,7 21R 5pF 2,75 26R 5,8pF
2 95 3 28R .. 2,4 34R ..
93 3,1 27R .. 2,4 32R ..
92 3,3 24R .. 2,4 30R ..
90 3,5 2,6
3 95 3,2 26R .. 2,45 32R ..

Выбрал длину провода 95 мм с самым низким КСВ для двух частот. Данные результаты противоречат моим знаниям. Я и все в моём окружении уверены, что длина антенны четвертьволнового штыря должна быть равна или немного меньше четверти длинны волны из-за коэффициента укорочения. Но наверняка не всё так просто.

Диаграмма Вольперта-Смита антенны без согласования

Сохраняю измерения ВАЦ в файл и открываю их в программе Atyune. Выбираю следующий вариант согласования.

Вариант согласования двумя индуктивностями

Нажимаю «auto matching», кривая оказывается возле центра диаграммы. Картинка будет ниже.
Посмотрю по шагам, что происходит c кривой при добавлении элементов.

Оставлю только последовательную катушку в программе.

Согласование из последовательной катушки

А это показания прибора с напаянной катушкой.

Видно, что диаграмма сдвинулась в том же направлении, но недостаточно.

Попробую припаять индуктивность побольше. Это показания с катушкой 8.2 нГн.

Кривая ушла дальше, чем в программе, и КСВ стал приемлемым. Может, этого достаточно?
Напаиваю согласование на рабочий узел, спектроанализатор показывает -30 дБм. Стало лучше, но ещё не идеал. Продолжаю.

Это кривая с катушкой 7.5 нГн.

Стало более похоже на показания программы. Постараюсь запомнить эту катушку, а пока продолжу с тем согласованием, которое предложила программа, 6.8 нГн.

Это кривая в программе после добавления шунтирующей катушки.

Идеальное согласование в программе

И показания ВАЦ.

Гораздо хуже, чем предсказала программа. Но маркер 2 имеет КСВ 1,17. Это очень хороший результат. Напаиваю согласование на рабочий узел. Спектроанализатор показывает -26 дБм для частоты на маркере 2 и -32 дБм на второй частоте. Одну частоту удалось согласовать. К сожалению, то, что я описываю, происходило в течение недели, и я забыл про последовательную катушку 7.5 нГн. Сейчас, когда собрал все данные вместе, мой следующий шаг выглядит случайным и необдуманным. В процессе согласования не понимал, почему мои действия не приводят к ожидаемым результатам, и пробовал получить результат эмпирически. Насколько понимаю, это основной метод работы с антеннами. Ведь в программе идеальная картинка, потому что не учитываются паразитные свойства согласующих компонентов и расстояния между ними. Сам ВАЦ измеряет отражённый сигнал на модифицированной плате. На реальной плате вместо разъёма для ВАЦ будет стоять микроконтроллер, и это далеко не все пункты, вносящие рассогласование и погрешности. Поэтому показания ВАЦ для меня ориентир, а доверяю я только показаниям спектроанализатора.

Решил посмотреть, как изменятся показания спектроанализатора с другими шунтирующими катушками. Вдруг мне повезёт и КСВ второй частоты удастся понизить, не испортив хороший КСВ.

В таблице результаты измерений.

Шунтирующая индуктивность, нГн 869 МГц, дБм 915 МГц, дБм
15 -28 -28
12 -28 -26
10 -33 -26
8,2 -31 -28
6,8 -32 -30

Лучший результат для 12 нГн. Это практически уровень эталонных антенн.

Осталось провести сравнение в «боевых» условиях. Место проведения — офис с длинным коридором. Методика: приёмник закреплён в комнате, я хожу с передатчиками по коридору и ищу максимальное расстояние, на котором передаются команды. К сожалению, коридора оказалось мало. На картинке представлены результаты сравнения для частоты 869 МГц.

Сравнение дальности работы антенн

На частоте 915 МГц дальность связи выше, и мне приходилось уходить по лестнице на другой этаж. Приводить эти данные не буду. В обоих случаях тенденция сохранялась. Худшей была исходная антенна, на втором месте антенна 95 мм и «немного» лучше антенна после согласования. Провести измерения на улице не получилось. Качество радиосвязи заметно хуже. Предполагаю, из-за большого уровня шумов. Сейчас в планах найти тихое, уединённое место возле озера, и не спеша в хорошей компании провести «измерения выходного дня».

▍ Выводы

После настройки антенны излучаемая энергия увеличилась в среднем на 5 дБм. Это соответствует увеличению излучаемой мощности примерно в три раза. Дальность связи значительно возросла.

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

Конечно, мне не хватает как теоретических знаний, так и практических. Нашёл интересный обучающий курс по разработке, оптимизации и тюнингу антенн. Он проходит в заочном формате на базе ТУСУР (томский университет). Чтобы курс начался, нужно собрать 6 человек. Пока нас двое. Приглашаю всех желающих (ссылка на страницу курса). Там же можно запросить подробную программу курса. Цена около 70 000 р. Никакого отношения к ТУСУР не имею, хочу понимать немного больше в работе антенн и писать более осмысленные статьи.

▍ Список использованной литературы:

  1. Antenna Impedance Measurement and Matching.
  2. Help-файл программы Atyune.
  3. Многочисленные видеоролики на YouTube авторов: w2aew, «Не влезай — Убьёт!!! Канал про электричество», Andreas Spiess, EEVblog.
  4. Джоэль П. Дансмор «Измерения параметров СВЧ устройств», Техносфера.

Telegram-канал с розыгрышами призов, новостями IT и постами о ретроиграх 🕹️


ссылка на оригинал статьи https://habr.com/ru/articles/752816/