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

Embedded

Фитнес-трекер для скейта

08.02.2021 02:17:53 | Автор: admin
Прототипы на микроконтроллерах nRF52 (в WLCSP корпусе, чем я только думал?), NXP QN9080 и STM32WB55. Ничего не бывает с первого раза, эти ревизии работали лишь частично.Прототипы на микроконтроллерах nRF52 (в WLCSP корпусе, чем я только думал?), NXP QN9080 и STM32WB55. Ничего не бывает с первого раза, эти ревизии работали лишь частично.

Основная идея

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

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

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

Что-то подобное уже существует?

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

RideBlock (c) IGGRideBlock (c) IGG

1) RideBlock. Проект скейтборд-трекера с мобильным приложением. Использует IMU (9DoF) для сбора данных. Был запущен в инкубаторе в Лос-Анджелесе 4-5 лет назад, в 2017 году не собрал нужную сумму на Indiegogo, с тех пор признаков жизни не подаёт.

Syrmo (c) DigitalTrendsSyrmo (c) DigitalTrends

2) Syrmo. Аналогичный проект из Долины, с более удачным местом установки на подвеске. Размещение сбоку под осью уберегает устройство от возможных ударов и не препятствует выполнению ряда трюков. Для измерения высоты прыжков использует высокоточный барометр, для остального - 6DoF IMU. Последние новости о проекте датируются 2015 годом.

Провалились ли эти проекты? Я считаю, что да.

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

Можно предположить, что:

  • Целевая аудитория (скейтеры) не захотели тратить деньги на устройство к скейту, когда на эту же сумму можно купить новую доску или кеды, ведь у скейтеров это дорогой расходный материал, который нужен всегда

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

  • Не хватило капитала на реализацию идеи: денежного либо человеческого

  • Оба проекта были мошенничеством в том или ином виде и служили лишь целью быстрого получения денег

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

Скейт-трюки

Дальше по тексту на глаза могут попасться названия трюков, которые могут быть не знакомы тем из читателей, кто не катается или не играл в THPS или Skate 3. Под спойлером небольшой раздел с илюстрацией трюков.

Скейтборд-ликбез

Хотя катание на скейте и несёт в себе креативный и развлекательный аспекты, некая система всё же присутствует. Известные трюки имеют устоявшиеся названия, вот самые базовые из них (в исполнении вашего покорного слуги):

Kickflip
Доска в прыжке совершает полный оборот вокруг продольной оси с помощью носка ноги, как будто её пинают. Отсюда и слово kick в названииДоска в прыжке совершает полный оборот вокруг продольной оси с помощью носка ноги, как будто её пинают. Отсюда и слово kick в названии
Heelflip
Доска в прыжке делает полный оборот вокруг продольной оси с помощью пятки, в сторону противоположную kickflip-у, отсюда и heel в названииДоска в прыжке делает полный оборот вокруг продольной оси с помощью пятки, в сторону противоположную kickflip-у, отсюда и heel в названии
Pop shove-it
Доска вращается в воздухе по горизонтальной оси на 180 с помощью задней ногиДоска вращается в воздухе по горизонтальной оси на 180 с помощью задней ноги

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

Требования к железу

  • Компактность. На скейтборде не так много места, куда можно было бы закрепить устройство так, чтобы оно не пострадало

  • Питание от батареи. Батарея должна быть перезаряжаемая и компактная, но достаточной ёмкости, чтобы протянуть минимум одну типичную скейт-сессию (у меня это 2-3 часа)

  • Необходимые датчики: акселерометр, гироскоп, магнитометр и др.

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

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

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

  • (Желательно) Отделяемый блок батареи, чтобы не заряжать доску

Требования к прошивке

Сформулируем некий минимум того, что мы хотим от прошивки устройства:

  • Опрос датчиков и сбор данных. Собираем и обрабатываем данные с гироскопа, акселерометра, и т.д.

  • Обеспечение работы BLE, формирование пакетов данных

  • Отслеживание состояния и уровня заряда батареи. Общаемся с контроллером заряда, оцениваем когда батарея заряжена и сколько осталось заряда

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

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

Подбираем компоненты

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

Микроконтроллер

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

Выбирал между:

  1. Nordic Semi nRF52x зарекомендовавшие себя на рынке системы на кристалле с Cortex-M4F и отличной поддержкой BLE/Bluetooth 5. Преимущества этих чипов известны: очень продуманная и простая в использовании периферия, возможность назначать её на любые выводы, что освобождает руки при разводке платы. Проверенная временем экосистема драйверов, документации и примеров. В том числе и на языке Rust.

  2. ST STM32WBx5 относительная новинка на рынке беспроводных SoC.

STM32WB55 в QFN-48 корпусеSTM32WB55 в QFN-48 корпусе

Отличительные особенности:
1) Два ядра: Cortex-M4F как основное и Cortex-M0+ как ядро для радио-стека
2) Периферия унаследована от STM32L4, можно переиспользовать код и опыт
3) Высокочастотный кварц можно подключать без нагрузочных конденсаторов, они встроены в чип
4) Есть поддержка USB, у Nordic она только в самом продвинутом nRF52840 и супер-новом двухъядерном (2 x Cortex-M33) nRF5340

Изначально выбрал Nordic, но как только в широкой продаже появился STM32WB, решил попробовать его. В отличие от нордика, под STM32 чип не было экосистемы драйверов и библиотек и пришлось написать свои1,2 и получить тонну опыта. Собственно, ради нового опыта я этот чип и выбрал, хотя и nRF52 всем устраивает.

Помимо этих двух чипов, довелось попробовать в работе чип NXP QN908x. Это довольно интересный чип с уникальными фишками типа встроенного сопроцессора (FSP) для sensor fusion и машинного обучения, USB, встроенных конденсаторов для кварцев, подключения внешней FLASH-памяти по (Q)SPI. Было дело, что я даже развёл и спаял плату под этот чип. Но столкнулся с сырой документацией и примерами, странными багами с отладочной платой и, видимо, отсутствие опыта с чипами NXP дало о себе знать.

Решил ставить STM32WB55. Сперва в исполнении с 1 МБ FLASH и 512 КБ RAM. Затем можно будет оптимизировать размер памяти.

Инерциальные датчики (IMU)

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

Изначально выбор пал на Bosch Sensortec BNO055. Это SiP из акселерометра, гироскопа и магнитометра, а также Cortex-M0+ микроконтроллера. Чип идёт с проприетарной прошивкой, которая обеспечивает опрос датчиков и sensor fusion, что позволяет сильно разгрузить основной микроконтроллер. При этом размеры корпуса 5.2 x 3.8 мм. В ходе работы над драйвером обнаружился ряд несовершенств прошивки этого чипа (например, медленная скорость обновления данных либо кривые углы Эйлера после fusion-а).

Чип BNO08x на типичной breakout-платеЧип BNO08x на типичной breakout-плате

Затем вышел более новый Hillcrest/CEVA BNO08x на базе того же железа, что и BNO055, но с более совершенной прошивкой и я решил использовать уже его. Разница между BNO080 и BNO085 лишь в версии прошивки, идущей в чипе.

Отдельно стоит упомянуть InvenSense/TDK ICM-20948, преемника очень популярного MPU9250 (который ныне NRND). Чип с хорошими характеристиками и тоже имеет встроенный sensor fusion (Motion Engine), но документация на него очень непрозрачная либо под NDA, что для хобби-проекта неприемлемо.

Поэтому ставим BNO08x, даже если это может быть немного оверкилл.

Контроллер питания, заряда батареи

Девайс будет работать от батареи. При этом хотелось бы, чтобы устройство:

  1. Заряжало литиевую батарею

  2. Мгновенно переключалось между источниками питания: от шнура при зарядке и от батареи, когда шнур отключен (power path selection)

  3. Отслеживало, сколько в батарее осталось заряда (gas gauge)

  4. Включало-выключало элементы схемы по команде главного контроллера

  5. Обладало режимами вкл/выкл

  6. Имело один или несколько преобразователей напряжения для питания остальных частей

Все желаемые функции так или иначе можно сделать из дискретных компонентов: микросхем заряда, gas gauge чипов, регуляторов напряжения, watchdog-чипов с кнопкой, транзисторов. Но из-за требований к компактности, идеальным вариантом было бы получить все перечисленные возможности, добавив лишь одну микросхему. И такие варианты есть, они входят в категорию микросхем под названием PMIC.

Что предлагает рынок?

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

  1. AMS AS3701 PP, 1 DC/DC, 2 LDO, WLCSP-20, charger, GPIO и кнопка (5/6)

  2. Maxim MAX20353 PP, 3 DC/DC, 2 LDO, корпус WLP-56, charger, gas gauge, GPIO, кнопка (6/6)

  3. Dialog DA9070 PP, 1 DC/DC, 3 LDO, WLCSP-42, charger, gas gauge (ADC), watchdog и кнопка (6/6)

  4. TI TPS6572x PP, 1 DC/DC, 1 LDO, WQFN-32, DSBGA-25, charger, GPIO и кнопка (4/6)

  5. Qorvo (Active Semi) ACT81460 PP, 4 DC/DC, 3 LDO, 1 HVB, WLCSP-49, charger, 4 GPIO, кнопка (5/6)

  6. Microchip MCP73871 PP, QFN-20, charger. Просто маленький зарядник + power path контроллер (2/6)

  7. X-Powers AXP173 PP, 2 DC/DC, 4 LDO, QFN-32, charger, кнопка, gas gauge (6/6)

AXP173, QFN-32AXP173, QFN-32

Из массы имеющихся вариантов, я сделал довольно странный, но по-итогу вполне логичный выбор это китайский чип X-Powers AXP173. Это оказался единственный чип, который включает всё что нужно и при этом представлен в QFN-корпусе. Наличие такого корпуса важно по причине требований к ударостойкости самого устройства, корпусы типа BGA/WLCSP известны низкой стойкостью к ударам и подверженностью возникновения трещин в шариках.

И это только если не задумываться о том, что WLCSP-корпуса с шагом 0.4/0.5мм сильно повышают требования к печатной плате, а также требуют инспекции во время производства (например, просвет рентгеном).

После гугл-перевода китайского даташита, оказалось вполне понятно как с AXP173 работать по I2C шине, был написан драйвер (Rust). Микросхему легко достать и в Китае она стоит мало, проблем с ней не возникало. Кстати, чипы этой фирмы используются почти во всех устройствах на базе решений Allwinner (планшеты, одноплатник Banana Pi и т.п.).

Хоть чип и предлагает несколько DC/DC, для экономии пространства на плате я ограничился использованием лишь трёх LDO: два на главный микроконтроллер и один на IMU.

Все LDO и DC/DC преобразователи позволяют регулировать их выходное напряжение через соответствующие регистры. Чип имеет несколько встроенных АЦП для измерения напряжения батареи, протекающего в/из батареи тока, интегратор тока (coulomb counter), который помогает отслеживать уровень заряда. Чип так же можно использовать с кнопкой, и настраивать реакцию на короткое и длинное нажатие. Например, выключать или включать устройство по длинному нажатию. На каждое нажатие кнопки чип выдаёт прерывание и указывает в одном из регистров, какое было нажатие. Выдаются прерывания и по началу/окончанию зарядки, подключению/отключению зарядного устройства и много чему ещё.

Ставим AXP173.

Батарея

За батареями можно ходить на Aliexpress, я заказывал максимально приближенный к размерам будущей платы литиевый элемент 301430 на 120 mAh.

Фото батареи
Батарея 3мм х 14 мм х 30мм на 120 мАчБатарея 3мм х 14 мм х 30мм на 120 мАч

Радио

Следуя рекомендациям для STM32WB55 мы берём чип-фильтр гармоник ST MLPF-WB55-01E3:

2.4GHz фильтр MLPF-WB55-01E32.4GHz фильтр MLPF-WB55-01E3

Простоты ради я решил так же использовать маленькую и проверенную чип-антенну Johanson 2450AT18B100:

Чип-антенна на 2.4GHz 2450AT18B100Чип-антенна на 2.4GHz 2450AT18B100

SoC имеет встроенный балун, поэтому он нам не нужен.

Светодиоды

В текущей ревизии устройства два светодиода:

  1. Светодиод-чип желтого цвета American Bright BL-HKC37A-AV-TRB размера 0402 для индикации процесса зарядки. Он подключается напрямую к PMIC AXP173 и управляется ею же

  2. Маленький чип RGB-светодиода 1x1mm Foshan NationStar FC-B1010RGBT-HG. Подключается к микроконтроллеру и используется для отладки и индикации процесса работы

Пожалуй, самый миниатюрный RGB-светодиод 1х1мм, FC-B1010RGBT-HG. Фото от LCSCПожалуй, самый миниатюрный RGB-светодиод 1х1мм, FC-B1010RGBT-HG. Фото от LCSC

Схемотехника

Теперь мы можем сформировать целостную картину основных блоков устройства:

Система-на-кристалле STM32WB5x отвечает за BLE связь и управляет по двум разным IC шинам контроллером питания (PMIC) и инерциальными датчиками (IMU). Светодиоды и кнопка образуют минимальный интерфейс для взаимодействия с устройством, а USB порт позволяет заряжать батарею и, например, обновлять прошивку, загружать лог данныхСистема-на-кристалле STM32WB5x отвечает за BLE связь и управляет по двум разным IC шинам контроллером питания (PMIC) и инерциальными датчиками (IMU). Светодиоды и кнопка образуют минимальный интерфейс для взаимодействия с устройством, а USB порт позволяет заряжать батарею и, например, обновлять прошивку, загружать лог данных

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

Схема рисовалась в онлайн-EDA Upverter (by Altium), ссылка на дизайн.

USB-порт, питание, батарея

Схема подсистемы питанияСхема подсистемы питания

На схеме видно, что MicroUSB-порт (J3) и батарея (J2) подключены к PMIC AXP173 (U3).

Выходы регуляторов напряжения LDO1 и LDO2 через сдвоенный диод Шоттки питают основной микроконтроллер. Диоды нужны, чтобы оба LDO не перетягивали друг на друга одеяло и регулировали только свой выход.

Почему вообще два LDO на один чип? Чтобы обеспечить достаточный ток, так как LDO1 очень маломощный и рекомендуется производителем только для питания домена RTC. При этом LDO1 включен вообще всегда, а LDO2 включается программно, но почти сразу же.

LDO3 питает IMU и по-умолчанию выключен. Его будем включать только когда нужны данные с датчиков.

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

IMU

Схема подключения инерциальных датчиков (IMU)Схема подключения инерциальных датчиков (IMU)

BNO08x (U2) питается от VCC_IMU и сидит на I2C шине (IMU_SCL, IMU_SDA). Также стоит внешний кварц на 32.768 Гц (Y3) с нагрузочными конденсаторами (C17, C18) на 22 пФ.

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

От IMU микроконтроллер через I2C шину получает данные, а кроме этого есть линия запроса на прерывание IMU_INT. По отдельным линиям микроконтроллер также управляет сбросом чипа и режимом входа в загрузчик, если вдруг когда-нибудь можно будет обновлять прошивку чипа IMU (такая возможность есть, но она под NDA).

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

Отдельно на схеме стоят также подтяжки (R21, R22) для I2C шины, на которой висит IMU. Эти подтяжки подключены к питанию IMU и неактивны (не потребляют ток) когда IMU обесточен путём отключения LDO3 PMIC, который питает VCC_IMU. Отключая подтяжки вместе с чипом, мы экономим некоторую часть общего бюджета энергопотребления.

Почему для обмена данными с IMU выбрана шина I2C вместо гораздо более быстрой SPI? Всё просто: сэкономил ножки микроконтроллера. Две (SCL, SDA) против четырёх (MOSI, MISO, nCS, CLK). Микроконтроллер, чип IMU и подтяжки на 4.7 кОм позволяют шине I2C разгоняться до 400 кГц без особых проблем.

Теперь перейдём к мозгу устройства.

Микроконтроллер, радио, DC/DC (SMPS), внешние подтяжки I2C

Мозг устройстваМозг устройства

Обвязка микроконтроллера STM32WB55 (U1) согласно даташиту. Благодаря встроенным нагрузочным конденсаторам, кварцевый резонатор на 32 МГц Y2 подключается напрямую к чипу. В то время как низкочастотный кварц Y1 на 32.768 Гц подключается с нагрузочными конденсаторами на 12 пФ.

В плане подключения 2.4 GHz антенны для BLE всё тривиально, никаких диаграмм Смита пока не потребовалось. Антенна ANT1 подключена через фильтр FL1. Выход SoC RF1, фильтр и антенна рассчитаны на импеданс 50 Ом, поэтому согласующая цепь не применяется, достаточно соединить всё напрямую.

L1, L2, C1 внешние элементы для встроенного DC/DC преобразователя SMPS. Его использование позволяет снизить энергопотребление SoC при работающем радио и получить более чистое питание, уменьшив шум.

Подтяжки для шины I2C (R2, R3), к которой подключен PMIC, не подключаются сразу к линии питания. Вместо этого они подключены к выходу SoC I2C_PULLUPS. Подтяжки включаются только при подаче на этот выход высокого лог. уровня и выключаются при переводе выхода в High-Z (высокоимпедансное, третье) состояние. Таким образом подтягивающие резисторы не потребляют энергию когда не нужны.

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

Разводка платы

Так выглядит трассировка одного из сигналов в UpverterТак выглядит трассировка одного из сигналов в Upverter

Разводка платы точно так же выполнялась в EDA Upverter. В основном я использовал готовые футпринты компонентов из встроенной в Upverter библиотеки на базе Octopart, но для парочки деталей приходилось редактировать футпринт либо рисовать свой.

Процесс

Обычно я развожу платы по примерно такому плану:

  1. Рисуем желаемую форму и задаём габариты платы

  2. Задаём нужные слои, у нас их точно будет 4: верхний, земля, питание, нижний. Такой четырёхслойный стек является рекомендуемым1,2 для плат с RF, при этом слой земли должен быть следующим после слоя, на котором будет разведена RF часть. Кроме этого, работать с 4 слоями удобнее, особенно когда это касается подключения питания компонентов. Когда два внутренних слоя выделены для питания и земли, для запитывания чипов часто достаточно просто поставить переходное отверстие на соответствующий слой

  3. Задаём автоматические правила проверки (DRC) согласно возможностям выбранного производителя плат (у меня это PCBWay): 5/5 mil (0.127 мм) проводник/зазор, 10 mil (0.254 мм) ширина отверстия, 4 mil внешнее кольцо переходного отверстия. При этом это минимально допустимые правила. Для большей надёжности я ставлю по-умолчанию минимальные размер дорожки и зазор в 6 mil (0.1524 мм)

  4. Расставляем монтажные отверстия для крепления платы в корпусе. Лучше сделать это в самом начале, чем потом, когда места под них уже не останется

  5. Расставляем те компоненты, которые точно должны быть с краю платы: разъёмы и антенну

  6. Ставим на плату все микросхемы (U1 U3) и их обвязку и двигаем-вращаем их, пока:

    1. Не станет как можно меньше пересечений воздушных линий (ratsnest) между компонентами. Это уменьшает число переходов на другие слои и в целом даёт идею об оптимальности взаимного расположения

    2. Длина воздушных линий не станет минимальной (это уменьшает длину печатных проводников и помогает сэкономить площадь платы)

  7. Расставляем/двигаем обвязочные компоненты по ходу разводки

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

  9. Cтараемся держать все SMD компоненты на одном слое платы. Это позволит удешевить автоматический монтаж, потому что pick-and-place автомат сможет расставить все компоненты за один подход

Результат

Разводку удалось уместить в 35 х 15 мм, что вполне удовлетворяет нашим требованиям к компактности.

Скриншот платы (без полигонов, вид сверху)

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

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

Заказ производства

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

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

Подготовка

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

  • Gerber-файлы, содержащие рисунок платы + drill файл с координатами отверстий

  • BOM, т.е. список компонентов с точным артикулом (part number) детали, количеством

  • XYRS-файл (centroid), содержащий координаты центра и ориентацию всех монтируемых компонентов

В Upverter есть специальный раздел для экспорта этих файлов, заходим туда и скачиваем. Все файлы, кроме BOM, не требуют никакой обработки и готовы к отправке. А вот BOM нужно будет подогнать под стандартный формат (.xlsx), предлагаемый PCBWay.

Пример BOM в формате PCBWay

В каждой строке количество деталей идёт в расчёте на одно устройство. При закупке компонентов это количество сотрудники PCBWay умножат на число собираемых устройств.

Заказываем платы

Заходим на сайт PCBWay и переходим в раздел Quote & Order. Там вводим основные параметры платы:

  • Режим Всё Включено (Turnkey): платы, трафареты, закупка, монтаж, всё делают они

  • Размер: 35 х 15 мм

  • Сторона монтажа SMD-компонентов: только верх

  • Количество плат: 5 (это минимум плат в заказе, сам монтаж можно сделать и на меньшем количестве)

  • Число слоёв: 4

  • Цвет паяльной маски: чёрный

  • Финиш поверхностей: HASL со свинцом (хороший вариант когда не надо самому паять)

Остальные параметры я оставил по-умолчанию.

Загружаем герберы, указываем, какой файл отвечает за какой слой, загружаем сверловку и XYRS, загружаем BOM. Отправляем и ждём ответа.

Через некоторое время в личном кабинете статус заказа меняется на In Audit и если всё хорошо, то заказ переходит в Audit Passed и начинается производство плат. Одновременно с этим на почту пишет сотрудник компании и уточняет наличие позиций в BOM, примерное время ожидания доставки компонента. Таблица дополняется столбцом стоимости каждой позиции умноженной на число устройств, а так же итоговой стоимостью компонентов. На этом этапе можно заменить отсутствующие на складе детали на аналоги, чтобы сократить время ожидания. Так же можно попросить специалиста подобрать компонент запросами типа поставьте самый дешевый светодиод 0402 желтого цвета или самый доступный кварц в этом корпусе с точностью 50 ppm.

По моему опыту, проходит несколько рабочих дней от отправки файлов до первого email с уточнениями по BOM.

Производство

Хорошим признаком того, что всё идёт хорошо является тишина. Если производитель больше ничего не пишет, значит процесс идёт и проблем не возникло. Сам процесс можно отслеживать поэтапно в личном кабинете:

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

Когда производитель смонтировал самую первую плату (я так понял, что они делают это поштучно, а не панелями), наступает час Икс, когда на почту приходит письмо с фотографиями готовой платы и просьбой внимательно посмотреть и указать на недочёты:

Предварительная плата выглядела вполне себе ничего:

Точно лучше, чем если бы я паял вручнуюТочно лучше, чем если бы я паял вручную

Фото платы в более высоком разрешении: клик.

У производителя возник вопрос по поводу полярности футпринта RGB-светодиода, ведь у них не было схемы, чтобы сверить, только герберы:

Фото светодиода с предположением о полярности

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

Доставка

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

До адреса в России фирмой EMS посылка шла где-то неделю.

Фото посылки я не делал, но сделал фото того, что оказалось внутри:

Одно из пяти готовых устройств я уже подключил для тестов. К устройствам также приложили подарок: 5 таких же плат, но без компонентовОдно из пяти готовых устройств я уже подключил для тестов. К устройствам также приложили подарок: 5 таких же плат, но без компонентов

Экономика и цены

Удовольствие не из дешёвых, но затраты компенсируются огромной экономией времени и нервов. Вот, что получилось у меня (курс доллара на 2021 год):

  • Платы: 5 штук за $97 (7,200 ) в сумме

  • Монтаж: 5 устройств за $186 (13,800 ) в сумме. При стоимости монтажа в $30 (2,238 ), львиная доля стоимости это компоненты:

    • SoC: $11.5 x 5 = $55.75 (4,160 ) за 5 штук

    • IMU: $11.9 x 5 = $59.85 (4,466 ) за 5 штук

    • USB разъем molex: $1.2 x 5 = $6.3 (470 )

    • Цены остальных компонентов меркнут по сравнению с первой тройкой

  • Доставка EMS: $28 (2,000 )

Очевидно, что есть куда удешевлять. Вот некоторые идеи и low-hanging fruit по снижению стоимости:

  • Взять менее тонкий техпроцесс плат, наприме 6/6 mil вместо 5/5 mil, взять стандартный цвет маски (зелёный)

  • Снижение оверкиллов в дизайне: использовать младшие модели SoC с меньшим объемом памяти и периферией. Взять более дешевый IMU BNO080 вместо BNO085

  • Использовать USB разъем подешевле

Прошивка и софт

Hello, world!Hello, world!

Что-ж, вот железо лежит на столе, подключенное к программатору и ждёт, когда в него вдохнут жизнь.

Приступив к программной части, я сформулировал следующий план действий:

  1. Язык прошивки будет Rust. Исключительно на нём я пишу последние годы и уже порядком подзабыл Си, да и возвращаться к нему после раста совсем не хочется

  2. Надо собрать в кучу или дописать драйверы для периферии, BLE-стека, IMU и PMIC

  3. Помигать светодиодом

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

  5. Написать программу для визуализации кватернионов положения

  6. Написать прошивку посложнее, которая будет слать все данные с IMU через BLE

  7. Написать программу на ПК для опроса по BLE и адаптировать визуализацию, добавив графики

Сбор данных

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

Ориентация осей относительно скейтборда и желаемое положение устройства под доскойОриентация осей относительно скейтборда и желаемое положение устройства под доской

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

Мне удалось добиться стабильной передачи данных по BLE с частотой 15 Гц через характеристику с оповещением (Notify), несущую пакет данных из 20 байт. Если пытаться выставить частоту обновления выше, то перестаёт выдаваться прерывание от радио-сопроцессора. Особо глубоко пока не копал, но предполагаю, что боттлнеком является процедура обновления характеристики где-то в BLE стеке, работающем на сопроцессоре. Есть идея, например, в одно обновление характеристики упаковывать несколько пакетов, два или три за раз. Это нужно только на этапе сбора данных. В дальнейшем, все основные расчёты планируется вести на самом устройстве, поэтому канал BLE будет существенно разгружен.

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

Делаю Ollie, данные регистрируются с частотой где-то 15 ГцДелаю Ollie, данные регистрируются с частотой где-то 15 Гц

На графике акселерометра (слева снизу) видно, когда доска отрывается от земли и когда она приземляется. Значительно возрастает ускорение по вертикальной оси Z (зелёная). Используя эти данные и время, можно придумать алгоритм расчета высоты прыжка. На графике справа показаны данные с гироскопа. Т.к. при ollie значительных вращений вокруг осей не происходит, график гироскопа здесь выглядит скучновато.

Heelflip. Из-за вращения доски график гироскопа выглядит интереснее. Из-за низкого FPS визуализации, кажется, что доска не сделала полный оборот вдоль оси Y, но на видео он был, а также на гироскопе видно, что вращение точно было.Heelflip. Из-за вращения доски график гироскопа выглядит интереснее. Из-за низкого FPS визуализации, кажется, что доска не сделала полный оборот вдоль оси Y, но на видео он был, а также на гироскопе видно, что вращение точно было.Ещё пример
Постоянное вращение показано на графике гироскопа как ровная линия выше нуля, при этом на акселерометре по оси X видно значительное ускорение (но со знаком минус)Постоянное вращение показано на графике гироскопа как ровная линия выше нуля, при этом на акселерометре по оси X видно значительное ускорение (но со знаком минус)

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

Например, kickflip это +360 вдоль оси Y, а heelflip это -360. Меняем знаки местами если стойка скейтера меняется с regular на goofy. А pop shove-it будет выглядеть как вращение на 180 вдоль оси Z.

Заключение

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

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

  • Корпус! Пожалуй, это самая сложная часть проекта и источник прокрастинации для меня. Но корпус придётся делать, не приматывать же устройство скотчем каждый раз

  • Собрать ещё больше примеров данных с более высокой частотой, дабы иметь представление о тех вещах, которых не видно при 15 Гц дискретизации

  • Обратиться к литературе по алгоритмам определения высоты по акселерометру, машинному обучению и классификации по данным с IMU, например: 1, 2, 3, 4

  • Выполнять расчёты на самом устройстве, по BLE передавать лишь результаты

  • Оптимизация энергопотребления устройства

  • Мобильное приложение

  • Подумать над влаго- и ударо- защитой устройства. Это скорее относится к корпусу, но и с точки зрения электроники тоже можно попытаться на что-то повлиять. На ум приходит conformal coating платы, убрать, где возможно кварцевые, резонаторы, водонепроницаемый разъем для зарядки

Спасибо за внимание, с нетерпением жду ваших вопросов и комментариев!

Ссылки

  1. Репозиторий с кодом прошивки, программ и документация

  2. Схема и разводка платы на Upverter

Подробнее..

Edge платы для домашнего Computer Vision

13.04.2021 06:09:41 | Автор: admin

Я люблю делать всякие странные штуки с Computer Vision. Из того, что я выкладывал на Хабре - умная кормушку для птиц и камера для слежения за ребенком. По работе примерно тем же занимаюсь. Так что слежу за актуальным рынком embedded устройств для ComputerVision. Прошлый обзор я делал полтора года назад. Для Embedded это долго. В этом я сосредоточусь на устройствах которые вышли недавно + некоторый анализ что из этих устройств можно использовать дома/для хобби.

Рассказ будет построен следующим образом:

  • Продуктовые железки которые стали классикой продакшна / железки которые почти доросли до таких.Их можно взять и запустить из коробки. Большие OpenSource комьюнити/персональные фреймворки. Время развертывания обученной сети на такой железке в 1-2 дня.

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

  • Железки которые выглядят интересно, но нет ни гайдов по ним, ни историй успехи, ни подробной документации.

  • Железка есть информации почти нет/нельзя получить без запросов. На рынке нет истории использования/успеха.

Сразу несколько дисклеймеров, чтобы не возвращаться к ним:

  • Далеко не все из перечисленного я лично использовал/тестировал

  • Далеко не все перечислено. Я уверен что забыл/не знаю многое. И очень надеюсь что в комментарии накидаете чего-нибудь интересного

  • Я фокусируюсь на устройствах где есть GPU/NPU или прочие ускорители инференса. Безусловно, есть всякие FPGA, и прочее, но я не считаю их применимыми для хоббийных проектов. (что такое NPU GPU TPU и другие аббревиатуры - можно прочитать в этой замечательной статье)

Часть 1. Ближе всего к продукту

С одной стороны, весь этот раздел можно было бы свести к простому перечислению:

  • Jetson

  • Intel

  • Coral

  • Android телефоны

  • Прочие Embedded, устройства с хорошим процессором, без NPU/GPU

И в этом списке за последние 2 года появился бы лишь Coral. Но, в реальности, все сильно интереснее. Появились не только и не столько новые архитектуры, как имплементации/доработки старых. О чем мы и поговорим.

В мире Jetsonов новинок нет. Сейчас в продаже:

  • jetson nano

  • jetson xavier nx

  • jetson agx

  • jetson tx2

12ого началась конференция GTC от NVIDIA, но ничего нового на ней не объявили, так что, скорее всего, на следующий год ничего нового не будет.

Встречаются имплементации Jetson'а, под другие экосистемы. Самое известное - AWS Panorama. Jetson находящийся внутри экосистемы Амазона.

PanoramaPanorama

Jetson, безусловно, одна из самых удобных плат для хобби. Есть разводка GPIO, много кода который работает из коробки. Нейронные сети можно никуда не конвертировать, используя в оригинальном фреймворке.
Cтоит понимать, что из всех четырех Jetson'ов для хобби лучше всего подходит Nano. Он стоит лишь 100$, что значительно меньше следующего в серии NX, стоящего 400$. В теории, TX2в середине, но его почти нет в продаже + менее удобная плата. Проектов на Jetson очень много. Например из того что было в медийном пространстве - 1, 2. Вот тут есть неплохая подборка.
Лично я участвовал где-то в 5-7 проектах где Jetson был основной платформой. 2-3 из них переросли в полноценные продукты. Но, вынужден сказать, что для хобби его не использовал ни разу. Почему? Какая-то совокупность факторов всегда. Nano у меня был первой серии, там были баги по питанию. Иногда была не нужна производительность дополнительная. Иногда хотелось опробовать чего-то нового.

В отличие от Jetson, на базе Movidius появляются интересные штуки. В первую очередь это M.2 и mPCIe карты. Какие-то даже уже были, когда я писал прошлый обзор: 1, 2, 3.
Сейчас их очень много от разных производителей.

Удобны ли ли они для каких-нибудь прототипов и хобийных проектов? Мне кажется, что ниша есть, но очень узкая:

  • Когда надо много производительности (есть сборки где есть несколько мовидиусов)

  • Когда USB соединение слишком нестабильно, но M.2/PCIe хватит (переносные устройства)

Во вторую очередь - это ряд устройств от luxonis. Они устраивали большую компанию на кикстартере про OAK и OAK-D. OAK это платы где movidius воткнут не на материнскую плату, а на плату с камерой. Кроме того, у них есть несколько устройств с movidius когда он стоит на плате 1, 2. Я не буду вдаваться подробнее тут, кому интересно - про них я делал более подробный обзор у себя в блоге/на Youtube:

Кому лень читать/смотреть - вот краткая выдержка:

  • + Минус одно USB соединение + к стабильности - для части проектов полезно

  • - От USB все равно не уйти - до прода скорее всего не дойдет

  • + Неплохой дизайн, неплохой корпус

  • - Заменили хороший OpenVino на какой-то мутный DepthAI

  • - Дорого. Дороже чем собрать такое с оригинальным Movidius'ом, дороже чем с Jetson Nano

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

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

Кроме luxonis до movidius'а в камере догадался FLIR, достаточно крупный производитель камер. Они выпустили FireFly DL, который явно на порядки более продуктовый чем OAK, а стоит только на 100$ дороже (+объектив).

Чтобы закончить с Movidius окончательно. По простоте использования/наличию документации/комментариям и поддержке - на мой взгляд Movidius - это один из самых разумных ускорителей для своих проектов на нейронных сетях. Его минус - сложный переход с продакшну, и необходимость конвертации сети. То что вышло за последние годы - расширяет возможности использования. Но все же мне не хватает плат сравнимых с RPi3 где мовидиус бы стоял напрямую на плате.

Google Coral. Вот тут много нового. В своем прошлом обзоре я был крайне недоволен им - очень ограниченные платы, очень бажные. Но прогресс. Баги пофикшены, выпущена новая линейка. Есть почти все то же самое что и в movidius, только напрямую от производителя - отдельные платы, стики, M.2, pci-e, чистые чипы, и.т.д..

В качестве основного фреймворка - специальный tflite, где уже сильно меньше потерь на конвертации. Но, как видно, слоев все равно сильно меньше чем в том же ONNX.
Из плюсов, про которые не сказал - на базе Coral уже сторонние производители создают свои решения. Например Asus. Из минусов - считанные разы слышал что его использовали в продакшене. И, обычно, для каких-то простых задач.
Для себя я понимаю почему я избегаю Coral - при прочих равных не хочу трогать TensorFlow. А по текущим характеристикам он нигде не превосходит вариантов выше.

Телефоны. Так же, стоит отметить, многие телефоны получили поддержку из плат сопроцессоров для нейронных сетей. Есть несколько фреймфорков для инференса моделей на них. Например тут и тут описывал. Зачастую телефон стал удобнее чем embedded для пилота и хобби. Основной минус - отсутствие периферии. Второй серьезный минус для меня - внутренняя инфраструктура приложений. Конечно, Unity и Flutter упрощают логику использования. Но все же, лично для меня, телефоны сильно сложнее чем Linux-системы.
С другой стороны, в телефоне уже есть и камера и акселерометр, и интернет.

Прочее. Под "прочим" я в первую очередь подразумеваю системы где заход в нейронные сети идет со стороны процессора. Например в процессорах Intel за счет OpenVino можно сильно оптимизировать сети. На некоторых процессорах, например на RaspberryPI есть оптимизация под инструкции Neon. RPi4 вполне может справляться с какими-то задачами детекции и трекинга в реальном времени. Так что если вам нужно с помощью машинного зрения раз в день проверять рассаду - думаю подойдет.

Часть 2. Работает, но мало информации

Есть такая забавная штука. В ML сейчас 90% знаний открыто. Есть статьи, большая часть публикаций с OpenSource. Подробнейшие фреймворки на Nvidia и Intel. Но рынок аппаратных платформ был исторически не такой. Хотите подключить камеру по csi-mpi к своей платформе? Будьте добры купите дорогущий мануал по протоколу. Хотите разрабатывать на нашей платформе? Для этого вам нужно специальное программное обеспечение которое просто так вы не скачаете. И много фирм по производству железа по-другому и не мыслят. Как результат мы имеем полтора гайда на платформу до её покупки. Невозможность протестировать до покупки. Отсутствие форумов по теме. И проблему с каждой функцией где что-то пошло не так.

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

  • RockChip

  • Gyrfalcon

Обе платформы я видел в проде. Даже немного экспериментировал. Но у обоих платформ крайне неудобный фреймворк переноса сетей/использования.

RochChip. Основная платформа на которой все делается - Rockchip 3399Pro. Самая популярная реализация, наверное - Firefly, RockPiN10 или Toybrick.

Что забавно, у того же ASUS есть версия не только на базе Google Coral, но и на базе RockChip.
Сейчас разрабатывается новая версия - 1, 2.
В целом, плюс RockChip'а - это плата которую любят все разработчики железа. Есть референсный дизайн, комплектующие, и.т.д. Собрать продукт проще и быстрее чем на Jetson.
Но перенос сети весьма непредсказуем. Документация куцая и полукитайская. Как поддерживается - не понятно. Я видел несколько проектов где сети все же перенесли. Но, гарантировать что это всегда можно, нельзя.

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

Забавно, что платы на его базе в продаже почти отсутствуют. Что-то из того что есть: 1, 2, 3 .

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

Делать ли свои проекты на базе этих платформ? Подходят ли они для хобби? В целом, такое мне кажется возможным. Главное чтобы были простые сетки. Классификация/базовая детекция, и.т.д.
По цене такие платформы достаточно дешевы и сравнимы с OpenVino|Jetson вариантами.
Но надо серьезно понимать зачем так делать. Это может быть:

  • желание сделать продукт из своей разработки

  • желание распаять свою систему

  • нехватка в Jetson|RPi каких-то возможностей

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

Часть 3. Внешне все выглядит неплохо, есть какая-то документация, но примеров нет

Пожалуй, сюда я отнесу только одну плату Khadas VIM3 . Много кто о ней знает, но не видел ни одного человека который бы что-то на ней сделал. Есть в открытой продаже, просто купить. Заявлены неплохие параметры по скорости.

Судя по документации перенос моделей достаточно простой. Но, так как никто не пробовал/не тестировал, - не понятны ограничения. Сама плата собрана на базе процессора Amlogic A311D, который содержит NPU модуль. Amlogic многие позиционируют как конкурент RockChip, сравнивая их. Но сравнений именно NPU модулей - нет.

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

Часть 4. Железки есть, информации нет

Большая часть плат тут имеет очень слабую документацию. Часть плат - это отладочные платы для мобильных платформ. Другая часть - платы специально под ML, но по которым очень мало информации и данных в интернете.

BeagleV. Плата которая пока не вышла, но выглядит неплохо. Разработана на базе процессора U74 от SiFive. Используется RISC-V архитектура.

BeagleBoard - странная плата с комьюнити вокруг. Именно вокруг этого комьюнити частично построена плата BeagleV. Плата сделана на базе NPU модуля от Texas Instruments. Вроде даже какие-то репозитории есть:

  1. Фреймворк от TI для обучения нейронных сетей. Аж 55 звезд на гитхабе.

  2. Репозиторий платы. Аж 88 звезд.

Считать ли это "популярным" решением - не уверен. Но, возможно, какие-то базовые сети запустить будет можно.

Пример настоящего Edge (минимальное использование ЦПУ и энергоэффективности) это - Sipeed Maixduino и Grove AI Hat. Но, разработка на них, судя по отзывам, которые я слышал, ужасна. Сети плохо поддерживаются, мало производительности. Вот тут пример использования. Ну, по сути все проблемы обычного Arduino.
Я видел людей которые делали и адекватные проекты на их базе, и хоббийные. Но я не готов:)

Глобально, Qualcomm - это, конечно, производитель процессоров для мобильных. Но, на их же базе, есть какое-то количество именно embedded платформ. При этом, Qualcomm - имеет свой SDK, и свою платформу для исполнения нейронных сетей. Года 2.5 назад я сталкивался с ней. И тогда там была жесть. Простейший слой сложения не поддерживался. Что там говорить про сплиты или объединения. По сути работал лишь VGG на трехканальный вход.
Сейчас, судя по слухам все лучше. К тому же, должен нормально работать Tensorflow lite.
Но вот с платами под эмбеддед у Qualcomm плохо. Есть вот такое (но стоит почти 500 баксов). Или вот такое (мало информации, но выглядит прикольно, за 300 баксов камера + корпус + ускоритель = неплохо).

Примерно так же себя ведет Huawei. Есть фреймворк. Не пользовался, и не знаю. Есть какое-то количество плат. Должен работать Tensorflow lite.
Но, мне сложно придумать где такая плата будет иметь смысл на использование.

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

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

P.S.

Про девайсы которые попадают мне в руки/про которые я читаю - иногда пишу у себя в блоге (telegramm, vk). Наверное, через год-два проапдейчу тут что накопится.
Прошлый апдейт делал на ютубе.

Подробнее..

Использование UDB в микроконтроллерах PSOC 4 и 5LP Infineon (Cypress) для управления светодиодами WS2812

21.04.2021 22:10:18 | Автор: admin

Светодиоды типа WS2812 очень популярны, ими удобно управлять, передавая по одному проводу команды для сотен светодиодов. Они имеют, с одной стороны, очень простой протокол, а с другой стороны, в микроконтроллерах нет аппаратных интерфейсов для этого протокола и его приходится формировать программно управляя выводом микроконтроллера. В этой статье я расскажу, как с помощью UDB микроконтроллеров серии PSOC 4 и PSOC 5LP компании Infineon сделать периферийный модуль для управления этими светодиодами.

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

На данном рисунке показана блок-схема микроконтроллера PSOC 5LP Family.

На рисунке виден блок Universal Digital Block Array, который связан через внутренние шины Digital Interconnect и System Bus со всеми компонентами микроконтроллера.

Для того чтобы начать разработку, необходимо скачать и установить PSOC Creator. Эта IDE абсолютно бесплатна, работает как с компилятором GCC, так и Keil.

После установки и запуска вы увидите стартовую страницу.

Нажимаем File->New->Project и попадаем в окно создания нового проекта.

В этом окне необходимо выбрать целевое устройство. Это может быть оценочная плата, например, CY8CKIT-059. Но чтобы эта плата появился в списке, необходимо скачать и установить соответствующий пакет.

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

Следует заметить, что микроконтроллеры PSOC 4 и PSOC 5LP могут работать в диапазоне напряжений питания от 1.7 до 5.5 вольт. Таким образом, мы сможем напрямую подключить микроконтроллер к светодиоду WS2812 без дополнительного буфера.

Выбираем оценочную плату CY8CKIT-059, жмем Next>. В следующем окне надо выбрать то, с чего мы начнем разработку. Это может быть один из примеров, пустой проект или один из предварительно сохраненных шаблонов. Выбираем пустой проект.

Жмем Next>, в следующем окне надо выбрать имя проекта, место его расположения и имя рабочего пространства.

Нажимаем кнопку Finish и попадаем на закладку схемы проекта.

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

Нажав SHIFT+F6 можно скомпилировать проект, компиляция должна пройти без ошибок.

Для создания нового компонента на вкладке слева жмем закладку Components.

Затем щелкаем правой клавишей на строчке Project и выбираем пункт меню Add Component Item.

Первое, что нам нужно создать, это символ компонента. Поэтому выбираем пункт Symbol Wizard, прописываем имя компонента, Cypress рекомендует включать номер версии в имя компонента таким образом Имя_компонента_v1_0, цифры 1_0 в дальнейшем можно изменять в зависимости от версии компонента.

Нажимаем Create New и попадаем в мастер создания символа компонента, где создаем четыре вывода: вход тактового сигнала, асинхронного сброса, выход для управления светодиодами и выход сигнала прерывания или DMA, который будет устанавливаться в 1 при опустошении буфера FIFO. Выводимые данные мы будем записывать в оперативную память, поэтому на схеме нет выводов для входных данных.

Жмем OK и попадаем на вкладку с созданным символом. Щелкаем на пустом месте листа правой клавишей мыши и выбираем пункт PROPERTIES.

Здесь нам нужно ввести два имени Doc.ApiPrefix и Doc.DefaultInstanceName.

Жмем OK, затем снова щелкаем правой клавишей мыши на пустом месте листа символа и выбираем пункт меню Generate Verilog:

Тут просто нажимаем кнопку Generate.

Двойным щелчком открываем Verilog файл.

Затем выбираем пункт меню Tools->DataPath Config Tool.

В DataPath Config Tool открываем только что созданный Verilog файл.

Теперь нам нужно добавить DataPath в наш Verilog файл. Выбираем пункт меню Edit->New DataPath.

В этом окне нам нужно ввести имя Datapath и выбрать разрядность DataPath, 8 бит нам будет достаточно.

Мастер создал Datapath, заполненный значениями по умолчанию:

Область 1 это 8 команд, которые может выполнять DataPath. Область 2 это маски. Область 3 настройка режима работы элементов Datapath.

Настал момент, когда нам надо разобраться с тем, как устроен и работает этот самый DataPath. UDB состоит: из блока тактовых сигналов, асинхронного сброса, регистра Status для чтения состояния и регистра Control для записи управляющих сигналов UDB.

Также в него входят две PLD матрицы на 12 входов и 4 выхода.

И сердцем UDB является DataPath. Можно сказать, что это очень примитивный микроконтроллер. У него есть: ALU, память программ из восьми ячеек, четыре регистра общего назначения, два буфера глубиной четыре байта организованных как FIFO, сдвиговый регистр, две маски, а также два блока сравнения и два блока проверки на равенство 0x00 и 0xFF регистров A0, A1. Следует также добавить, что эти блоки можно объединять и организовывать 16, 24, 32 битную обработку данных. Микроконтроллер может писать и читать, как в регистры A0,A1,D0,D1, так и в FIFO.

В datasheet к светодиоду WS2812 указаны следующие требования к временных характеристикам сигнала.

Старший бит посылается первымСтарший бит посылается первым

Теперь попробуем описать, что мы хотим от нашего модуля. Если FIFO пуст, модуль находится в режиме ожидания. Как только мы записываем первый байт в FIFO, наш модуль начинает формирование импульса сброса. После окончания импульса сброса, читает байт из FIFO и отправляет его. И так все четыре байта, до тех пор, пока FIFO не опустеет. Флаг пустого FIFO мы будем использовать для формирования сигнала прерывания или DMA. Если очередная порция данных будет записана до того, как закончится передача взятого из FIFO байта, то импульс сброса формироваться не будет.

Рисуем блок схему для нашего модуля состоящую из 8 состояний:

Выбираем длительность такта 200ns, такой интервал будет удовлетворять временным ограничениям указанным в datasheet WS2812B. Длительность импульса сброса в таком случае составит 255*0.2s= 51s. Заполним необходимые поля в DataPath Configuration Tool.

Первая команда это состояние ожидания записи данных в FIFO. Одновременно мы обнуляем операцией Исключающее ИЛИ регистр A1. Функция выбрана XOR, источник A и B регистр A1, запись регистра A1 выбрана из ALU.

На вторую команду мы переходим по сигналу FIFO empty == 0. То есть в FIFO поступили данные, в этот же момент мы устанавливаем выходной сигнал в 0. Во второй команде мы увеличиваем на единицу A1 до тех пор, пока он не примет значение 0xFF. После этого переходим к команде три, устанавливая выходной сигнал в 1.

В команде три мы загружаем байт из FIFO в A0 и загружаем счетчик бит из D1 в A1, и переходим к команде четыре.

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

В команде семь происходит ветвление по трем адресам. Если счетчик бит A1 != 0, то мы переходим к команде восемь, чтобы сформировать еще один такт выходного сигнала 1. Если A1 == 0 и FIFO пуст, переходим к первой команде ожидания поступления данных в FIFO. Если FIFO не пуст, то переходим к команде три, загрузка данных из FIFO в A0 и счетчика бит A1 из D1.

Нам также надо включить маску 0 и присвоить ей значение 0x80. При проверке на равенство регистра A0 с маской 0x80, регистру D0 равному 0x80 это даст значение передаваемого бита. FIFO у нас по умолчанию сконфигурированы на ввод данных в DataPath.

В завершении пропишем значения по умолчанию для регистров D0 (маска старшего бита) = 8'h80 и D1 (счетчик бит) = 0'h08, используя пункт меню View->Initial Register Values.

Сохраняем (Ctrl+S) и закрываем Datapath Configuration Tool. Нам осталось написать логику работы машины состояний в Verilog файле. Сначала присвоим сигналам модуля имена используемых нами регистров и флагов.

udb8(        /*  input                   */  .reset(rst),//Входной сигнал сброса        /*  input                   */  .clk(clk),//Входной тактовый сигнал        /*  input   [02:00]         */  .cs_addr(state),//Регистр адреса команды DataPath        /*  input                   */  .route_si(1'b0),        /*  input                   */  .route_ci(1'b0),        /*  input                   */  .f0_load(1'b0),        /*  input                   */  .f1_load(1'b0),        /*  input                   */  .d0_load(1'b0),        /*  input                   */  .d1_load(1'b0),        /*  output                  */  .ce0(send_bit),//Значение отправляемого бита        /*  output                  */  .cl0(),        /*  output                  */  .z0(),        /*  output                  */  .ff0(),        /*  output                  */  .ce1(),        /*  output                  */  .cl1(),        /*  output                  */  .z1(z_count_bit),//Флаг равенству 0 счетчика бит        /*  output                  */  .ff1(end_ws2812_reset),//Флаг равенству 0xFF регистра A1        /*  output                  */  .ov_msb(),        /*  output                  */  .co_msb(),        /*  output                  */  .cmsb(),        /*  output                  */  .so(),        /*  output                  */  .f0_bus_stat(),        /*  output                  */  .f0_blk_stat(fifo_empty),//Флаг пустого FIFO        /*  output                  */  .f1_bus_stat(),        /*  output                  */  .f1_blk_stat());

Первые два сигнала это вход асинхронного сброса и вход тактового сигнала.

Следующий это трех-битный вход адреса памяти команд DataPath. Мы присваиваем этому входу регистр состояний state. Далее идут выходные сигналы UDB: ce0 это значение выводимого бита. При создании конфигурации DataPath мы включили маску 0 и присвоили ей значение 0x80, получается операция send_bit=(A0 & 0x80==D0) 1 : 0;

Флаг z1, проверяет на равенство 0 регистра A1, это у нас счетчик отправляемых бит, присваиваем ему сигнал z_count_bit.

Следующий флаг ff1, он устанавливается в 1 при равенстве регистра A1 - 0xFF. Этот флаг мы используем при формировании импульса сброса для WS2812, присваиваем ему имя сигнала end_ws2812_reset.

И последний флаг f0_blk_stat, он устанавливается в 1, когда FIFO 0 пуст. Присваиваем ему имя сигнала fifo_empty.

Нам осталось объявить используемые регистры и флаги, и прописать машину состояний.

    localparam IDLE = 3'h0;    localparam WS2812_RESET = 3'h1;    localparam LOAD_A0 = 3'h2;    localparam CHECK_BIT = 3'h3;    localparam SEND_BIT = 3'h4;    localparam DEC_BIT_CNT = 3'h5;    localparam SHIFT_DATA = 3'h6;    localparam NOP = 3'h7;    reg [2:0]state;//Адрес команды    reg out;//Регистр выходного сигнала    reg send_tic;//Регистр дополнительного такта    wire fifo_empty;//Флаг пустого FIFO    wire send_bit;//значение отправляемого бита    wire end_ws2812_reset;//Флаг равенства 0xFF регистра A1    wire z_count_bit;//Флаг равенства 0 счетчика бит    assign irq=fifo_empty;//Присваиваем выходу прерывания флаг пустого FIFO    assign ws2812=out;//Присваиваем выходной сигнал регистру выходного сигналаalways @(posedge clk or posedge rst )//beginif (rst)begin       // Асинхронный сбросstate <= IDLE;            out<=1'b1;endelsebegincase (state)IDLE://Ожидание поступления данных в FIFObeginif(fifo_empty==1'b0)beginstate <= WS2812_RESET;//Если данные в FIFO поступили,                     out<=1'b0;//переходим к команде формирования импульса сброса, выходной сигнал в 0endelsebegin                    out<=1'b1;//Если данных в FIFO нет, выходной сигнал в 1                endendWS2812_RESET://Формирование импульса сбросаbegin                if(end_ws2812_reset)//Ждем равенства 0xFF регистра A1                begin                    state <= LOAD_A0;//Если A1=0xFF, переходим к команде загрузки данных из FIFO                    out<=1'b1;//Выход в 1                endendLOAD_A0://Загрузка байта из FIFObegin                state <= CHECK_BIT;//Переходим к команде проверки значения выводимого битаendCHECK_BIT://Команда проверки значения выводимого битаbegin                send_tic <= 1'b0;//Обнуляем регистр дополнительного такта                state <= SEND_BIT;//Переходим к команде отправки бита данных                if(send_bit==1'b0)                begin                    out <= 1'b0;//Если выводимый бит 0, устанавливаем выход в 0                endendSEND_BIT://Команда отправки бита данныхbegin                if(send_tic)//Если дополнительный такт уже был                begin                    state <= DEC_BIT_CNT;//Переходим к команде декремента счетчика бит                    out <= 1'b0;//Устанавливаем выходной сигнал в 0                end                else                begin                    send_tic <= 1'b1;//Если дополнительного такта не было, устанавливаем флаг дополнительного такта                endendDEC_BIT_CNT://Команда декремента счетчика битbegin                state <= SHIFT_DATA;//Переходим к команде сдвига выводимого байта влевоendSHIFT_DATA://Команда сдвига выводимого байта влевоbegin                out<=1'b1;//Выходной сигнал в 1                if(z_count_bit)//Если счетчик выведенных бит равен 0                begin                    if(fifo_empty == 1'b0)//Если FIFO не пуст                    begin                        state <= LOAD_A0;//Переходим к загрузке нового байта                    end                    else                    begin                        state <= IDLE;//Если пуст переходим в режим ожидания прихода данных в FIFO                    end                end                else                begin                    state <= NOP;//Если счетчик бит не равен 0, переходим к формированию дополнительного такта                endendNOP://Команда дополнительного тактаbegin                state <= CHECK_BIT;//Переходим к команде проверки значения выводимого битаendendcaseendend

Железную часть мы закончили, осталось написать небольшое API, чтобы микроконтроллер мог взаимодействовать с нашим модулем. Сохраняем и закрываем Vetilog файл.

Создадим заголовочный файл, щелкаем правой клавишей мыши на имени нашего компонента и выбираем Add Component Item.

Ищем API Header File, вписываем имя файла и нажимаем Create.

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

#include "cytypes.h"#define `$INSTANCE_NAME`_SHIFT_MASK 0x80#define `$INSTANCE_NAME`_NUM_SHIFT_BITS 8#define `$INSTANCE_NAME`_FIFO_LEVELS 4#define `$INSTANCE_NAME`_bit_cnt (*(reg8 *)`$INSTANCE_NAME`_udb8_u0__D1_REG)#define `$INSTANCE_NAME`_shift_mask (*(reg8 *)`$INSTANCE_NAME`_udb8_u0__D0_REG)#define `$INSTANCE_NAME`_data_fifo (*(reg8 *)`$INSTANCE_NAME`_udb8_u0__F0_REG)#define `$INSTANCE_NAME`_actl (*(reg8 *)`$INSTANCE_NAME`_udb8_u0__DP_AUX_CTL_REG)void `$INSTANCE_NAME`_Start(void);

Здесь следует разъяснить, что такое $INSTANCE_NAME. Это то, что мы вводили в свойство Doc.DefaultINstanceName при создании символа компонента. В нашем случае, будет автоматически формироваться имя ws2812_1_bit_cnt, где 1 после ws2812 это автоматическая нумерация компонентов на схеме. И да, на схеме это имя можно поменять на любое другое. Хитрую кавычку можно ввести так: <ALT>+<96>. Имя регистра, генерируемого при компиляции, состоит из трёх частей, $INSTANCE_NAME это имя компонента, udb8 это имя DataPath, которое мы указали при создании нового DataPath и u0__D1_REG это имя регистра. В случае ошибки, можно посмотреть эти имена в файле cyfitter.h после компиляции.

Функцию Start можно было не создавать, так как после сброса микроконтроллера в регистры D0 и D1, загружаются значения по умолчанию. Также мы сможем в программе, в любой момент, записать необходимые данные в регистр простой записью:

ws2812_1_bit_cnt=8;

Запись байта в FIFO выглядит так:

ws2812_1_data_fifo=0xAA;

Но мы сделаем это с заделом на будущее. Снова щелкаем правой клавишей мыши на имени компонента и выбираем Add Component Item. Затем выбираем API C File, вводим имя и нажимаем кнопку Create.

Двойным щелчком на имени файла, на панели слева открываем созданный файл и вводим в него следующий текст.

#include "`$INSTANCE_NAME`.h"void `$INSTANCE_NAME`_Start(void){    `$INSTANCE_NAME`_shift_mask=`$INSTANCE_NAME`_SHIFT_MASK;    `$INSTANCE_NAME`_bit_cnt=`$INSTANCE_NAME`_NUM_SHIFT_BITS;}

Нажимаем Ctrl+Shift+S, чтобы сохранить все изменения. Теперь мы можем добавить созданный компонент на схему проекта. Переключаемся на вкладку Source слева.

Двойным щелчком по файлу TopDesign.cysh открываем схему проекта, справа мы видим набор поставляемых с Psoc Creator компонентов.

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

Перетаскиваем его на схему.

Щелкаем справа на вкладку Cypress, затем выбираем в папке Systems компонент Clock и перетаскиваем его на схему так, чтобы квадратик вывода clk компонента ws2812_1 совпал с квадратиком вывода компонента Clock_1, тогда выводы соединяться.

Щелкаем правой клавишей на компоненте Clock_1 и в выпавшем меню выбираем пункт Configure. В этом же меню можно выбрать пункт Open Datasheet и посмотреть datasheet на этот компонент.

Вписываем частоту 5MHz.

Затем в папке Digital библиотеки компонентов берем компонент Ligic Low и перетаскиваем его на схему, и размещаем рядом с выводом rst компонента ws2812_1. Затем нажимаем клавишу W и соединяем вывод rst и Logic Low проводом.

Из папки Ports and Pins берем компонент Digital Output Pin и подсоединяем его к выводу ws2812 нашего компонента, затем щелкаем правой клавишей по компоненту Pin_1, выбираем пункт меню Configure и изменяем имя компонента с Pin_1 на ws2812_port.

Присвоим сигнал ws2812_port порту P1(7), для этого, двойным щелчком по строчке Pins в папке Design Wide Resourse, открываем вкладку назначения выводов и на панели справа, выбираем Port - P1(7).

На вкладке Clocks, двойным щелчком на строчке IMO, открываем страничку конфигурации тактовых частот.

И устанавливаем частоту IMO - 3MHz, так как при такой частоте точность составляет 1%. Частоту PLL ставим 79MHz. На 80MHz IDE будет ругаться, так как с учетом отклонений, частота будет превышать предельно допустимые 80MHz для данного микроконтроллера.

Так как пост уже затянулся, автоматическую загрузку FIFO компонента через DMA я делать не буду, будем отправлять данные по прерыванию. Следовательно из папки System добавляем на схему компонент Interrupt.

Вызываем меню Configure для компонента isr_1, и даем ему имя isr_ws2812.

Нам еще понадобится компонент Timer, возьмём его в папке Digital->Function. К выводу interrupt подключим еще один компонент Interrupt, который переименуем в isr_timer. Также удалим у компонента Timer тактовый генератор и подключим тактовый вход таймера к компоненту Clock_1, переместив предварительно компонент Clock_1 чуть влево.

Теперь вызовем меню Configure для компонента Timer_1, выберем 16 битный режим, поставим период 14999 для прерывания каждые 3ms и поставим галочку напротив прерывания по переполнению. Почему я выбрал период обновления 3ms? Пересылка одного байта занимает 8*1,2s=9,6s всего 100 светодиодов по 3 байта на точку, получаем 2880s+51s на импульс сброса. Получаем 2931s, следовательно, 3ms нам вполне достаточно.

Как видно, компонент таймер может быть создан на базе аппаратного таймера или синтезирован на базе UDB блоков, мы оставим Fixed Function реализацию.

И нам осталось написать небольшой Hello Habr, кстати, нажав Shift +F6 можно скомпилировать проект, компиляция должна завершиться без ошибок.

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

Кадры формируются по сигналу прерывания модуля Timer_1, картинка рисуется в предварительном буфере и по флагу окончания передачи данные копируются в основной буфер. Для увеличения скорости, копирование идет 32 битными словами.

int main(void){    ws2812_struct.buffer_ptr=ws2812_struct.buffer;    fillScreen(0);    CyGlobalIntEnable; /* Enable global interrupts. */    Timer_1_Start();    ws2812_1_Start();    isr_ws2812_StartEx(WS2812_HANDLER);    isr_timer_StartEx(TIMER333HZ_HANDLER);        uint16 delay=0;    for(;;)    {        if((delay++)==20)        {            delay=0;            scrollStr(text, sizeof(text), color , BACKGROUND);        }        while(ws2812_struct.wait_tx==0);        ws2812_struct.wait_tx=0;        copyBuffer((uint32*)ws2812_struct.draw_buffer,(uint32*)ws2812_struct.buffer,sizeof(ws2812_struct.buffer)/4);                }}

В заключении приведу screenshot измерителя ресурсов для данного проекта:

Мы израсходовали 4,4% ресурсов UDB. Это означает, что в данном микроконтроллере мы сможем разместить около 20 модулей ws2812. В микроконтроллерах PSOC 4200, например, CY8C4245PVI-482 скорей всего удастся поместить три таких модуля.

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

Импульс СбросаИмпульс СбросаПередача 1 и 0Передача 1 и 0

Проект размещен на Github.

Подробнее..

Разработка firmware на С словно игра в бисер. Как перестать динамически выделять память и начать жить

07.04.2021 10:06:45 | Автор: admin

C++ is a horrible language. It's made more horrible by the fact that a lotof substandard programmers use it, to the point where it's much mucheasier to generate total and utter crap with it.

Linus Benedict Torvalds

Собеседование шло уже второй час. Мы наконец-то закончили тягучее и вязкое обсуждение моей скромной персоны, и фокус внимания плавно переполз на предлагаемый мне проект. Самый бойкий из трех моих собеседников со знанием дела и без лишних деталей принялся за его описание. Говорил он быстро и уверенно явно повторяет весь этот рассказ уже не первый раз. По его словам, работа велась над неким чрезвычайно малым, но очень важным устройством на базе STM32L4. Потребление энергии должно быть сведено к минимуму... USART... SPI... ничего необычного, уже неоднократно слышал подобное. После нескольких убаюкивающих фраз собеседник внезапно подался чуть вперед и, перехватив мой сонный взгляд, не без гордости произнес:

А firmware мы пишем на C++! мой будущий коллега заулыбался и откинулся в кресле, ожидая моей реакции на свою провокативную эскападу.

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

У вас есть какие-то опасения? поспешил спросить он с искренней озабоченностью в голосе.

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

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

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

И у нас есть код ревью! встрепенувшись, поспешили добавить хором двое других невероятно квалифицированных члена команды.

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

IAR

Так уж получилось, что мы впервые встретились на этом проекте. "Ну, это же специальный компилятор для железок", наивно думал я, "сработаемся". Не скажу, что я жестоко ошибся и проклял тот день, но использование именно этого компилятора доставляет определенный дискомфорт. Дело в том, что в проекте уже начали внедрение относительно нового стандарта С++17. Я уже потирал потные ладошки, представляя, как перепишу вон то и вот это, как станет невероятно красиво, но IAR может охладить пыл не хуже, чем вид нововоронежской Аленушки.

Новый стандарт реализован для нашего любимого коммерческого компилятора лишь частично, несмотря на все заверения о поддержке всех возможностей новейших стандартов. Например, structured binding declaration совсем не работает, сколько не уговаривай упрямца. Еще IAR весьма нежен и хрупок, какая-нибудь относительно сложная конструкция может довести его до истерики: компиляция рухнет из-за некой внутренней ошибки. Это самое неприятное, поскольку нет никаких подсказок, по какой причине все так неприятно обернулось. Такие провалы огорчают даже сильнее финала Игры престолов.

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

SIL

Для некоторых классов устройств существует такое понятие, как стандарты SIL. Safety integrity level уровень полноты безопасности, способность системы обеспечивать функциональную безопасность.

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

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

std::exception

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

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

__cxa_allocate_exception

Название у нее уже какое-то нехорошее, и действительно, выделяет память для объекта исключения и делает это весьма неприятным образом прямо в куче. Вполне возможно эту функцию подменить на собственную реализацию и работать со статическим буфером. Если не ошибаюсь, то в руководстве для разработчиков autosar для с++14 так и предлагают делать. Но есть нюансы. Для разных компиляторов реализация может отличаться, нужно точно знать, что делает оригинальная функция, прежде чем грубо вмешиваться в механизм обработки. Проще и безопаснее от исключений отказаться вовсе.Что и было сделано, и соответствующий флаг гордо реет теперь над компилятором! Только вот стандартную библиотеку нужно будет использовать вдвойне осторожнее, поскольку пересобрать ее с нужными опциями под IAR возможности нет.

std::vector

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

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

template <class T, std::size_t Size>class StaticArray { using ssize_t = int;public: using value_type = T; template <class U> struct rebind {   using other = StaticArray<U, Size>; }; StaticArray() = default; ~StaticArray() = default; template <class U, std::size_t S> StaticArray(const StaticArray<U, S>&); auto allocate(std::size_t n) -> value_type*; auto deallocate(value_type* p, std::size_t n) -> void; auto max_size() const -> std::size_t;};

Ключевые функции, конечно, allocate и deallocate. Передаваемый им параметр n это не размер в байтах, а размер в попугаях, которые хранятся в векторе. Функция max_size используется при проверке вместимости аллокатора и возвращает максимально возможное теоретически число, которое можно передать в функцию allocate.

Тут очевиднейший пример использования аллокатора
std::vector<int, StaticArray<int, 100>> v;    v.push_back(1000);std::cout<<"check size "<<v.size()<<std::endl;    v.push_back(2000);std::cout<<"check size "<<v.size()<<std::endl;

Результат выполнения такой программы (скомпилировано GCC) будет следующий:

max_size() -> 100

max_size() -> 100

allocate(1)

check size 1

max_size() -> 100

max_size() -> 100

allocate(2)

deallocate(1)

check size 2

deallocate(2)

std::shared_ptr

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

Конечно, контролировать управление памятью путем использования кастомных аллокаторов вполне возможно. В стандартной библиотеке есть замечательная функция std::allocate_shared, которая создаст разделяемый объект именно там, где мы укажем. Указать же можно самолепным аллокатором примерно такого вида:

template <class Element,           std::size_t Size,           class SharedWrapper = Element>class StaticSharedAllocator {  public:  static constexpr std::size_t kSize = Size;  using value_type = SharedWrapper;  using pool_type = StaticPool<Element, kSize>;  pool_type &pool_;  using ElementPlaceHolder = pool_type::value_type;  template <class U>  struct rebind {    using other = StaticSharedAllocator<Element, kSize, U>;  };  StaticSharedAllocator(pool_type &pool) : pool_{pool} {}  ~StaticSharedAllocator() = default;  template <class Other, std::size_t OtherSize>  StaticSharedAllocator(const StaticSharedAllocator<Other, OtherSize> &other)     : pool_{other.pool_} {}  auto allocate(std::size_t n) -> value_type * {    static_assert(sizeof(value_type) <= sizeof(ElementPlaceHolder));    static_assert(alignof(value_type) <= alignof(ElementPlaceHolder));    static_assert((alignof(ElementPlaceHolder) % alignof(value_type)) == 0u);      return reinterpret_cast<value_type *>(pool_.allocate(n));  }  auto deallocate(value_type *p, std::size_t n) -> void {    pool_.deallocate(reinterpret_cast<value_type *>(p), n);  }};

Очевидно, Element тип целевого объекта, который и должен храниться как разделяемый объект. Size максимальное число объектов данного типа, которое можно создать через аллокатор. SharedWrapper это тип объектов, которые будут храниться в контейнере на самом деле!

Конечно, вы знаете, что для работы shared_ptr необходима некоторая дополнительная информация, которую нужно где-то хранить, лучше прямо с целевым объектом вместе. Поэтому для этого аллокатора очень важна структура rebuild. Она используется в недрах стандартной библиотеки, где-то в районе alloc_traits.h, чтобы привести аллокатор к виду, который необходим для работы разделяемого указателя:

using type = typename _Tp::template rebind<_Up>::other;

где _Tp это StaticSharedAllocator<Element, Size>,

_Up это std::_Sp_counted_ptr_inplace<Object, StaticSharedAllocator<Element, Size>, __gnu_cxx::_S_atomic>

К сожалению, это верно только для GCC, в IAR тип будет немного другой, но общий принцип неизменен: нам нужно сохранить немного больше информации, чем содержится в Element. Для простоты тип целевого объекта и расширенный тип должны быть сохранены в шаблонных параметрах. Как вы уже догадались, SharedWrapper и будет расширенным типом, с которым непосредственно работает shared_ptr.

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

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

Еще немного кода для иллюстрации

Сам пул объектов основан на StaticArray аллокаторе. А чего добру пропадать?

template <class Type, size_t Size>struct StaticPool {  static constexpr size_t kSize = Size;  static constexpr size_t kSizeOverhead = 48;  using value_type = std::aligned_storage_t<sizeof(Type)+kSizeOverhead,                                             alignof(std::max_align_t)>;  StaticArray<value_type, Size> pool_;    auto allocate(std::size_t n) -> value_type * {    return pool_.allocate(n);  }  auto deallocate(value_type *p, std::size_t n) -> void {    pool_.deallocate(p, n);  }};

А теперь небольшой пример, как это все работает вместе:

struct Object {  int index;};constexpr size_t kMaxObjectNumber = 10u;StaticPool<Object, kMaxObjectNumber> object_pool {};StaticSharedAllocator<Object, kMaxObjectNumber> object_alloc_ {object_pool};std::shared_ptr<Object> MakeObject() {  return std::allocate_shared<Object>(object_alloc_);}

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

std::function

Универсальная полиморфная обертка над функциями или функциональными объектами. Очень удобная штука. Точно была бы полезна в embedded проекте, хотя бы для каких-нибудь функций обратного вызова (callbacks).

Чем мы платим за универсальность?

Во-первых, std::function может использовать динамическую аллокацию памяти.

Небольшой и несколько искусственный пример:

int x[] = {1, 2, 3, 4, 5};    auto sum = [=] () -> int {      int sum = x[0];      for (size_t i = 1u; i < sizeof(x) / sizeof(int); i++) {        sum += x[i];      }      return sum;    };        std::function<int()> callback = sum; 

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

Дело в том, что в классе нашей универсальной обертки содержится небольшой участок памяти (place holder), где может быть определена содержащаяся функция.

Любая функция в С++ может быть определена с помощью двух указателей максимум. Для свободных функций или функторов достаточно одного указателя, если нужно вызвать метод класса, то нужен указатель на объект и смещение внутри класса. Собственно, у нас есть небольшое укромное местечко для пары указателей. Конечно, небольшие функциональные объекты можно хранить прямо на месте этих указателей! Если размер лямбды, например, не позволяет целиком запихать ее туда, то на помощь снова придет динамическая аллокация.

Для GCC

Опции -specs=nano.specs уже не будет хватать для std::function.

Сразу появится сообщения подобного вида:

abort.c:(.text.abort+0xa): undefined reference to _exit

signalr.c:(.text.killr+0xe): undefined reference to _kill

signalr.c:(.text.getpidr+0x0): undefined reference to _getpid

Правильно, ведь пустая функция должна бросать исключение.

Нужна другая опция -specs=nosys.specs, где включены все необходимые заглушки для всяких системных функций.

Соберем небольшую прошивку, чтоб проверить как повлияет включение std::function на потребление памяти различных видов. Прошивка стандартный пример от ST для подмигивающего светодиода. Изменения в размере секций файла-прошивки в таблице:

text

data

bss

67880

2496

144

Невооруженным взглядом видно, что секция .text выросла просто фантастически (на 67Кб!). Как одна функция могла сделать такое?

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

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

00000440cplus_demangle_operators0000049e__gxx_personality_v0000004c4 d_encoding000004fed_exprlist00000574_malloc_r0000060cd_print_mod000007f0d_type00000eec_dtoa_r00001b36_svfprintf_r0000306cd_print_comp

Много функций с префиксом d_* функции из файла cp-demangle.c библиотеки libiberty, которая, как я понимаю, встроена в gcc, и не так просто выставить ее за дверь.

Также имеются функции для обработки исключений (bad_function_call, std::unexpected, std::terminate)

_sbrk, malloc, free функции для работы с динамическим выделением памяти.

Результат ожидаемый флаги -fno-exceptions и -fno-rtti не спасают.

Внедрим второй подобный функциональный объект в другой единице трансляции:

text

data

bss

67992

2504

144

Вторая std::function обошлась не так уж и дорого.

Показательно также то, сколько объектных файлов и из каких библиотек мы используем для этих случаев.

Для случая без std::function список короткий
libc_nano.alibg_nano.alibg_nano.a(lib_a-exit.o)libg_nano.a(lib_a-exit.o) (_global_impure_ptr)libg_nano.a(lib_a-impure.o)libg_nano.a(lib_a-init.o)libg_nano.a(lib_a-memcpy-stub.o)libg_nano.a(lib_a-memset.o)libgcc.alibm.alibstdc++_nano.a
Для случая с std::function список гораздо длиннее
libc.alibg.alibg.a(lib_a-__atexit.o)libg.a(lib_a-__call_atexit.o)libg.a(lib_a-__call_atexit.o) (__libc_fini_array)libg.a(lib_a-__call_atexit.o) (atexit)libg.a(lib_a-abort.o)libg.a(lib_a-abort.o) (_exit)libg.a(lib_a-abort.o) (raise)libg.a(lib_a-atexit.o)libg.a(lib_a-callocr.o)libg.a(lib_a-closer.o)libg.a(lib_a-closer.o) (_close)libg.a(lib_a-ctype_.o)libg.a(lib_a-cxa_atexit.o)libg.a(lib_a-cxa_atexit.o) (__register_exitproc)libg.a(lib_a-dtoa.o)libg.a(lib_a-dtoa.o) (_Balloc)libg.a(lib_a-dtoa.o) (__aeabi_ddiv)libg.a(lib_a-exit.o)libg.a(lib_a-exit.o) (__call_exitprocs)libg.a(lib_a-exit.o) (_global_impure_ptr)libg.a(lib_a-fclose.o)libg.a(lib_a-fflush.o)libg.a(lib_a-findfp.o)libg.a(lib_a-findfp.o) (__sread)libg.a(lib_a-findfp.o) (_fclose_r)libg.a(lib_a-findfp.o) (_fwalk)libg.a(lib_a-fini.o)libg.a(lib_a-fputc.o)libg.a(lib_a-fputc.o) (__retarget_lock_acquire_recursive)libg.a(lib_a-fputc.o) (__sinit)libg.a(lib_a-fputc.o) (_putc_r)libg.a(lib_a-fputs.o)libg.a(lib_a-fputs.o) (__sfvwrite_r)libg.a(lib_a-freer.o)libg.a(lib_a-fstatr.o)libg.a(lib_a-fstatr.o) (_fstat)libg.a(lib_a-fvwrite.o)libg.a(lib_a-fvwrite.o) (__swsetup_r)libg.a(lib_a-fvwrite.o) (_fflush_r)libg.a(lib_a-fvwrite.o) (_free_r)libg.a(lib_a-fvwrite.o) (_malloc_r)libg.a(lib_a-fvwrite.o) (_realloc_r)libg.a(lib_a-fvwrite.o) (memchr)libg.a(lib_a-fvwrite.o) (memmove)libg.a(lib_a-fwalk.o)libg.a(lib_a-fwrite.o)libg.a(lib_a-impure.o)libg.a(lib_a-init.o)libg.a(lib_a-isattyr.o)libg.a(lib_a-isattyr.o) (_isatty)libg.a(lib_a-locale.o)libg.a(lib_a-locale.o) (__ascii_mbtowc)libg.a(lib_a-locale.o) (__ascii_wctomb)libg.a(lib_a-locale.o) (_ctype_)libg.a(lib_a-localeconv.o)libg.a(lib_a-localeconv.o) (__global_locale)libg.a(lib_a-lock.o)libg.a(lib_a-lseekr.o)libg.a(lib_a-lseekr.o) (_lseek)libg.a(lib_a-makebuf.o)libg.a(lib_a-makebuf.o) (_fstat_r)libg.a(lib_a-makebuf.o) (_isatty_r)libg.a(lib_a-malloc.o)libg.a(lib_a-mallocr.o)libg.a(lib_a-mallocr.o) (__malloc_lock)libg.a(lib_a-mallocr.o) (_sbrk_r)libg.a(lib_a-mbtowc_r.o)libg.a(lib_a-memchr.o)libg.a(lib_a-memcmp.o)libg.a(lib_a-memcpy.o)libg.a(lib_a-memmove.o)libg.a(lib_a-memset.o)libg.a(lib_a-mlock.o)libg.a(lib_a-mprec.o)libg.a(lib_a-mprec.o) (_calloc_r)libg.a(lib_a-putc.o)libg.a(lib_a-putc.o) (__swbuf_r)libg.a(lib_a-readr.o)libg.a(lib_a-readr.o) (_read)libg.a(lib_a-realloc.o)libg.a(lib_a-reallocr.o)libg.a(lib_a-reent.o)libg.a(lib_a-s_frexp.o)libg.a(lib_a-sbrkr.o)libg.a(lib_a-sbrkr.o) (_sbrk)libg.a(lib_a-sbrkr.o) (errno)libg.a(lib_a-signal.o)libg.a(lib_a-signal.o) (_kill_r)libg.a(lib_a-signalr.o)libg.a(lib_a-signalr.o) (_getpid)libg.a(lib_a-signalr.o) (_kill)libg.a(lib_a-sprintf.o)libg.a(lib_a-sprintf.o) (_svfprintf_r)libg.a(lib_a-stdio.o)libg.a(lib_a-stdio.o) (_close_r)libg.a(lib_a-stdio.o) (_lseek_r)libg.a(lib_a-stdio.o) (_read_r)libg.a(lib_a-strcmp.o)libg.a(lib_a-strlen.o)libg.a(lib_a-strncmp.o)libg.a(lib_a-strncpy.o)libg.a(lib_a-svfiprintf.o)libg.a(lib_a-svfprintf.o)libg.a(lib_a-svfprintf.o) (__aeabi_d2iz)libg.a(lib_a-svfprintf.o) (__aeabi_dcmpeq)libg.a(lib_a-svfprintf.o) (__aeabi_dcmpun)libg.a(lib_a-svfprintf.o) (__aeabi_dmul)libg.a(lib_a-svfprintf.o) (__aeabi_dsub)libg.a(lib_a-svfprintf.o) (__aeabi_uldivmod)libg.a(lib_a-svfprintf.o) (__ssprint_r)libg.a(lib_a-svfprintf.o) (_dtoa_r)libg.a(lib_a-svfprintf.o) (_localeconv_r)libg.a(lib_a-svfprintf.o) (frexp)libg.a(lib_a-svfprintf.o) (strncpy)libg.a(lib_a-syswrite.o)libg.a(lib_a-syswrite.o) (_write_r)libg.a(lib_a-wbuf.o)libg.a(lib_a-wctomb_r.o)libg.a(lib_a-writer.o)libg.a(lib_a-writer.o) (_write)libg.a(lib_a-wsetup.o)libg.a(lib_a-wsetup.o) (__smakebuf_r)libgcc.alibgcc.a(_aeabi_uldivmod.o)libgcc.a(_aeabi_uldivmod.o) (__aeabi_ldiv0)libgcc.a(_aeabi_uldivmod.o) (__udivmoddi4)libgcc.a(_arm_addsubdf3.o)libgcc.a(_arm_cmpdf2.o)libgcc.a(_arm_fixdfsi.o)libgcc.a(_arm_muldf3.o)libgcc.a(_arm_muldivdf3.o)libgcc.a(_arm_unorddf2.o)libgcc.a(_dvmd_tls.o)libgcc.a(_udivmoddi4.o)libgcc.a(libunwind.o)libgcc.a(pr-support.o)libgcc.a(unwind-arm.o)libgcc.a(unwind-arm.o) (__gnu_unwind_execute)libgcc.a(unwind-arm.o) (restore_core_regs)libm.alibnosys.alibnosys.a(_exit.o)libnosys.a(close.o)libnosys.a(fstat.o)libnosys.a(getpid.o)libnosys.a(isatty.o)libnosys.a(kill.o)libnosys.a(lseek.o)libnosys.a(read.o)libnosys.a(sbrk.o)libnosys.a(write.o)libstdc++.alibstdc++.a(atexit_arm.o)libstdc++.a(atexit_arm.o) (__cxa_atexit)libstdc++.a(class_type_info.o)libstdc++.a(cp-demangle.o)libstdc++.a(cp-demangle.o) (memcmp)libstdc++.a(cp-demangle.o) (realloc)libstdc++.a(cp-demangle.o) (sprintf)libstdc++.a(cp-demangle.o) (strlen)libstdc++.a(cp-demangle.o) (strncmp)libstdc++.a(del_op.o)libstdc++.a(del_ops.o)libstdc++.a(eh_alloc.o)libstdc++.a(eh_alloc.o) (std::terminate())libstdc++.a(eh_alloc.o) (malloc)libstdc++.a(eh_arm.o)libstdc++.a(eh_call.o)libstdc++.a(eh_call.o) (__cxa_get_globals_fast)libstdc++.a(eh_catch.o)libstdc++.a(eh_exception.o)libstdc++.a(eh_exception.o) (operator delete(void*, unsigned int))libstdc++.a(eh_exception.o) (__cxa_pure_virtual)libstdc++.a(eh_globals.o)libstdc++.a(eh_personality.o)libstdc++.a(eh_term_handler.o)libstdc++.a(eh_terminate.o)libstdc++.a(eh_terminate.o) (__cxxabiv1::__terminate_handler)libstdc++.a(eh_terminate.o) (__cxxabiv1::__unexpected_handler)libstdc++.a(eh_terminate.o) (__gnu_cxx::__verbose_terminate_handler())libstdc++.a(eh_terminate.o) (__cxa_begin_catch)libstdc++.a(eh_terminate.o) (__cxa_call_unexpected)libstdc++.a(eh_terminate.o) (__cxa_end_cleanup)libstdc++.a(eh_terminate.o) (__gxx_personality_v0)libstdc++.a(eh_terminate.o) (abort)libstdc++.a(eh_throw.o)libstdc++.a(eh_type.o)libstdc++.a(eh_unex_handler.o)libstdc++.a(functional.o)libstdc++.a(functional.o) (std::exception::~exception())libstdc++.a(functional.o) (vtable for __cxxabiv1::__si_class_type_info)libstdc++.a(functional.o) (operator delete(void*))libstdc++.a(functional.o) (__cxa_allocate_exception)libstdc++.a(functional.o) (__cxa_throw)libstdc++.a(pure.o)libstdc++.a(pure.o) (write)libstdc++.a(si_class_type_info.o)libstdc++.a(si_class_type_info.o) (__cxxabiv1::__class_type_info::__do_upcast(__cxxabiv1::__class_type_info const*, void**) const)libstdc++.a(si_class_type_info.o) (std::type_info::__is_pointer_p() const)libstdc++.a(tinfo.o)libstdc++.a(tinfo.o) (strcmp)libstdc++.a(vterminate.o)libstdc++.a(vterminate.o) (__cxa_current_exception_type)libstdc++.a(vterminate.o) (__cxa_demangle)libstdc++.a(vterminate.o) (fputc)libstdc++.a(vterminate.o) (fputs)libstdc++.a(vterminate.o) (fwrite)

А что IAR?

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

text

ro data

rw data

2958

38

548

О, добавилось всего-то каких-то жалких 3Кб кода! Это успех. Фанат GCC во мне заволновался, почему так мало? Смотрим, что же добавил нам IAR.

Добавились символы из двух новых объектных файлов:

dlmalloc.o 1'404 496

heaptramp0.o 4

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

Естественно, никаких выделений в куче нет, но IAR приготовился: видно, что он создал структуру gm (global malloc: a malloc_state holds all of the bookkeeping for a space) и некоторые функции для работы с этой структурой.

Объектный файл того юнита, в котором была добавлена функция, тоже ощутимо располнел:

до

main.cpp.obj 3'218 412 36'924

после

main.cpp.obj 4'746 451 36'964

Файл прибавил более 1Кб. Появилась std::function, ее сопряжение с лямбдой, аллокаторы.

Добавление второго такого функционального объекта в другую единицу трансляции дает нам очередной прирост:

text

ro data

rw data

3 998

82

600

Прибавили более 1Кб. Т.е. каждая новая функция добавляет нам по килобайту кода в каждой единице трансляции. Это не слишком помогает экономить: в проекте не один и не два колбэка, больше десятка наберется. Хорошо, что большинство таких функций имеют сигнатуру void(*)(void) или void(*)(uint8_t *, int), мы можем быстро накидать свою реализацию std::function без особых проблем. Что я и сделал.

Убедившись, что моя примитивная реализация function работает и не требует много памяти, перенес ее в проект, заменив все std::function, до которых смог дотянуться. С чувством выполненного долга я отправился на набережную любоваться закатом.

Дома меня поджидало письмо от коллеги, преисполненное благодарности. Он писал, что благодаря отказу от богомерзких std::function проект сильно схуднул, мы все молодцы! Сочившееся из меня самодовольство брызнуло во все стороны. Прилагался также классический рекламно-наглядный график до-после, вопивший об уменьшении размера отладочной версии прошивки аж на 30 процентов. В абсолютных величинах цифра была еще страшнее, это, на минуточку, целых 150 килобайт! Что-о-о-о? Улыбка довольного кота медленно отделилась от лица и стремительным домкратом полетела вниз, пробивая перекрытия. В коде просто нет столько колбэков, чтоб хоть как-то можно было оправдать этот странный феномен. В чем дело?

Смотря на сонное спокойствие темной улицы, раскинувшейся внизу, я твердо решил, что не сомкну глаз, пока не отыщу ответ. Проснувшись утром, в первую очередь сравнил два разных elf-файла: до и после замены std::function. Тут все стало очевидно!

В одном забытом богом и кем-то из разработчиков заголовочном файле были такие строчки:

using Handler = std::function<void()>;static auto global_handlers = std::pair<Handler, Handler> {};

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

Понятно, чего хотел добиться неизвестный мне автор, и это вполне могло получиться. Начиная с 17-го стандарта, в заголовочном файле можно разместить некие глобальные объекты, которые будут видны и в других единицах трансляции. Достаточно вместо static написать inline. Это работает даже для IAR. Впрочем, я не стал изменять себе и просто все убрал.

Вот тут я все же не удержатся от объяснения очевидных вещей

Если у вас несколько единиц трансляции и создание глобального объекта вынесено в заголовочный файл, то при сборке проекта вы неизбежно получите ошибку multiple definition. Ежели добавить static, как сделал неизвестный мне разработчик, то все пройдет гладко, но в итоге будет занято несколько участков памяти и от глобальности ничего не останется.

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

// a.h

#pragma once

int a();

// a.cpp

#include "a.h"

#include "c.hpp"

int a() { return cglob * 2; }

// b.h

#pragma once

int b();

// b.cpp

#include "b.h"

#include "c.hpp"

int b() { return cglob * 4; }

// main.cpp

#include "a.h"

#include "b.h"

int main() { return a() + b(); }

// c.hpp

#pragma once

int c_glob = 0;

Пробуем собрать наш небольшой и бесполезный проект.

$ g++ a.cpp b.cpp main.cpp -o test

/usr/lib/gcc/x8664-pc-cygwin/10/../../../../x8664-pc-cygwin/bin/ld: /tmp/cccXOcPm.o:b.cpp:(.bss+0x0): повторное определение cglob; /tmp/ccjo1M9W.o:a.cpp:(.bss+0x0): здесь первое определение

collect2: ошибка: выполнение ld завершилось с кодом возврата 1

Неожиданно получаем ошибку. Так, теперь меняем содержимое файла c.hpp:

static int c_glob = 0;

Вот теперь все собирается! Полюбуемся на символы:

$ objdump.exe -t test.exe | grep glob | c++filt.exe

[ 48](sec 7)(fl 0x00)(ty 0)(scl 3) (nx 0) 0x0000000000000000 c_glob

[ 65](sec 7)(fl 0x00)(ty 0)(scl 3) (nx 0) 0x0000000000000010 c_glob

Вот и второй лишний символ, что и требовалось доказать.

А ежели изменить c.hpp таким образом:

inline int c_glob = 0;

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

Вывод будет весьма банален: нужно понимать, что делаешь... и соответствовать стандартам SIL!

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

Всем спасибо, всем удачи!

Подробнее..

Опыт написания IDL для embedded

24.02.2021 08:04:01 | Автор: admin

Предисловие

Я при работе с микроконтроллерами часто сталкивался с бинарными протоколами. Особенно, когда есть несколько контроллеров. Или же используется bluetooth low energy и необходимо написать код для обработки бинарных данных в характеристике. Помимо кода всегда требуется понятная документация.

Всегда возникает вопрос - а можно ли описать как-то протокол и сгенерировать на все платформы код и документацию? В этом может помочь IDL.

1. Что такое IDL

Определение IDL довольно простое и уже представлено на wikipedia

IDL, илиязык описания интерфейсов(англ.Interface Description LanguageилиInterface Definition Language)язык спецификацийдля описанияинтерфейсов, синтаксически похожий на описание классов в языкеC++.

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

Бонус также является - генерация документации, структур, кода.

2. Мотивация

В процессе работы я попробовал разные кодогенераторы и IDL. Среди тех, что попробовал были - QFace (http://personeltest.ru/aways/github.com/Pelagicore/qface), swagger (Это не IDL, а API development tool). Также существует коммерческое решение проблемы: https://www.protlr.com/.

Swagger больше подходит к REST API. Поэтому сразу был отметён. Однако его можно использовать если применяется cbor (бинарный аналог json с кучей крутых фич).

В QFace давно не было коммитов, хотелось некоторых "наворотов" для применения в embedded, возникли сложности при написании шаблона. Он не ищет символы сам, не умеет считать поля enum-ов.

Бесплатные решения было найти сложно, чтобы можно было комфортно использовать при разработке бинарных протоколов.

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

2.1 Обзор QFace

IDL, которая являлась источником вдохновения, имеет простой синтаксис:

module <module> <version>import <module> <version>interface <Identifier> {    <type> <identifier>    <type> <operation>(<parameter>*)    signal <signal>(<parameter>*)}struct <Identifier> {    <type> <identifier>;}enum <Identifier> {    <name> = <value>,}flag <Identifier> {    <name> = <value>,}

Для генерации используется jinja2. Пример:

{% for module in system.modules %}    {%- for interface in module.interfaces -%}    INTERFACE, {{module}}.{{interface}}    {% endfor -%}    {%- for struct in module.structs -%}    STRUCT , {{module}}.{{struct}}    {% endfor -%}    {%- for enum in module.enums -%}    ENUM   , {{module}}.{{enum}}    {% endfor -%}{% endfor %}

Концепция интересная. Можно было просто "подпилить" для комфорта "напильником", что конечно и сделал мой коллега. Но мне показалось интересным взять библиотеку sly и просто написать IDL с нужными фичами.

3. Обзор sly

Почему именно sly - библиотека очень проста для описания грамматики.

Сначала надо написать лексер. Он токенизирует код чтобы далее было проще обрабатывать парсером. Код из документации:

class CalcLexer(Lexer):    # Set of token names.   This is always required    tokens = { ID, NUMBER, PLUS, MINUS, TIMES,               DIVIDE, ASSIGN, LPAREN, RPAREN }    # String containing ignored characters between tokens    ignore = ' \t'    # Regular expression rules for tokens    ID      = r'[a-zA-Z_][a-zA-Z0-9_]*'    NUMBER  = r'\d+'    PLUS    = r'\+'    MINUS   = r'-'    TIMES   = r'\*'    DIVIDE  = r'/'    ASSIGN  = r'='    LPAREN  = r'\('    RPAREN  = r'\)'

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

Парсер - делает работу по преобразованию набора токенов по определенным правилам. С помощью его и осуществляется основная работа. В случае компиляторов - преобразование в байт-код/объектный файл итд. Для интерпретаторов - можно сразу выполнять вычисления. При реализации кодогенератора - можно преобразовать в дерево классов.

Также парсер задается очень простым способом (пример из документации):

class CalcParser(Parser):    # Get the token list from the lexer (required)    tokens = CalcLexer.tokens    # Grammar rules and actions    @_('expr PLUS term')    def expr(self, p):        return p.expr + p.term    @_('expr MINUS term')    def expr(self, p):        return p.expr - p.term    @_('term')    def expr(self, p):        return p.term    @_('term TIMES factor')    def term(self, p):        return p.term * p.factor    @_('term DIVIDE factor')    def term(self, p):        return p.term / p.factor    @_('factor')    def term(self, p):        return p.factor    @_('NUMBER')    def factor(self, p):        return p.NUMBER    @_('LPAREN expr RPAREN')    def factor(self, p):        return p.expr

Каждый метод класса отвечает за парсинг конкретной конструкции. В декораторе @_ указывается правило, которое обрабатывается. Имя метода sly распознает как название правила.

В этом примере сразу происходят вычисления.

Подробнее можно прочитать в официальной документации: https://sly.readthedocs.io/en/latest/sly.html

4. Процесс создания

В самом начале программа получает yml файл с настройками. Затем при помощи sly преобразовывает код в древо классов. Далее выполняются вычисления и поиски объектов. После вычисления - передается в jinja2 шаблон и дерево символов.

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

Вначале определили, что модуль состоит из списка термов:

    @_('term term')    def term(self, p):        t0 = p.term0        t1 = p.term1        t0.extend(t1)        return t0

Затем определим, что терм состоит из определений структуры, энумератора или интерфейса разделенные символом ";"(SEPARATOR):

   @_('enum_def SEPARATOR')    def term(self, p):        return [p.enum_def]    @_('statement SEPARATOR')    def term(self, p):        return [p.statement]    @_('interface SEPARATOR')    def term(self, p):        return [p.interface]    @_('struct SEPARATOR')    def term(self, p):        return [p.struct]

Здесь терм сразу паковался в массив для удобства. Чтобы список термов (term term правило) работал уже сразу с листами и собрал в один лист.

Ниже представлен набор правил для описания структуры:

    @_('STRUCT NAME LBRACE struct_items RBRACE')    def struct(self, p):        return Struct(p.NAME, p.struct_items, lineno=p.lineno)    @_('decorator_item STRUCT NAME LBRACE struct_items RBRACE')    def struct(self, p):        return Struct(p.NAME, p.struct_items, lineno=p.lineno, tags=p.decorator_item)    @_('struct_items struct_items')    def struct_items(self, p):        si0 = p.struct_items0        si0.extend(p.struct_items1)        return si0    @_('type_def NAME SEPARATOR')    def struct_items(self, p):        return [StructField(p.type_def, p.NAME, lineno=p.lineno)]    @_('type_def NAME COLON NUMBER SEPARATOR')    def struct_items(self, p):        return [StructField(p.type_def, p.NAME, bitsize=p.NUMBER, lineno=p.lineno)]    @_('decorator_item type_def NAME SEPARATOR')    def struct_items(self, p):        return [StructField(p.type_def, p.NAME, lineno=p.lineno, tags=p.decorator_item)]    @_('decorator_item type_def NAME COLON NUMBER SEPARATOR')    def struct_items(self, p):        return [StructField(p.type_def, p.NAME, bitsize=p.NUMBER, lineno=p.lineno, tags=p.decorator_item)]

Если описать простым языком правила - структура (struct) содержит поля структур (struct_items). А поля структур могут определяться как:

  • тип (type_def), имя (NAME), разделитель (SEPARATOR)

  • тип (type_def), имя, двоеточие (COLON), число (NUMBER - для битфилда, означает количество бит), разделитель

  • список декораторов (decorator_item), тип, имя, разделитель

  • список декораторов, тип, имя, двоеточие (COLON), число (NUMBER - для битфилда), разделитель

Новшество относительно QFace (однако есть в protlr) - была введена возможность описывать специальные условные ссылки на структуры. Было решено назвать эту фичу - alias.

    @_('DECORATOR ALIAS NAME COLON expr struct SEPARATOR')    def term(self, p):        return [Alias(p.NAME, p.expr, p.struct), p.struct]

Это было сделано чтобы поддерживалась следующая конструкция:

enum Opcode {    Start =  0x00,    Stop = 0x01};@alias Payload: Opcode.Startstruct StartPayload {...};@alias Payload: Opcode.Stopstruct StopPayload {...};struct Message {    Opcode opcode: 8;    Payload<opcode> payload;};

Данная конструкция обозначает, что если opcode = Opcode.Start (0x00) - payload будет соответствовать структуре StartPayload. Если opcode = Opcode.Stop (0x01) - payload будет иметь структуру StopPayload. То есть создаем ссылку структуры с определенными условиями.

Следующее что было сделано - отказался от объявления модуля. Показалось это избыточным так как - имя файла уже содержит имя модуля, а версию писать бессмысленно так как есть git. Хороший протокол имеет прямую и обратную совместимость и в версии нуждаться не должен. Был выкинут тип flag так как есть enum, и добавил возможность описания битфилдов. Убрал возможность определения сигналов так как пока что низкоуровневого примера, демонстрирующего пользу, не было.

Была добавлена возможность python-подобных импортов. Чтобы можно было импортировать из другого модуля только конкретный символ. Это полезно для генерации документации.

Для вычислений был создан класс - Solvable. Его наследует каждый объект, которому есть что посчитать. Например, для SymbolType (тип поля класса или интерфейса). В данном классе этот метод ищет по ссылке тип, чтобы добавить его в поле reference. Чтобы в jinja можно было сразу на месте обратиться к полям enum или структуры. Класс Solvable должен искать во вложенных символах вычислимые и вызывать solve. Т.е. вычисления происходят рекурсивно.

Пример реализации метода solve для структуры:

    def solve(self, scopes: list):        scopes = scopes + [self]        for i in self.items:            if isinstance(i, Solvable):                i.solve(scopes=scopes)

Как видно, в методе solve есть аргумент - scopes. Этот аргумент отвечает за видимость символов. Пример использования:

struct SomeStruct {i32someNumber;@setter: someNumber;void setInteger(i32 integer);};

Как видно из примера - это позволяет производить поиск символа someNumber в области видимости структуры, вместо явного указания SomeStruct.someNumber.

Заключение

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

В папке examples/uart - находится пример генерации заголовков, кода и html документации. Пример иллюстрирует типичный uart протокол с применением новых фич. Подразумевается, что функции типа put_u32 итд - определит сам пользователь исходя из порядка байт и архитектуры MCU.

Ознакомиться подробнее с реализацией можно по ссылке: https://gitlab.com/volodyaleo/volk-idl

P.S.

Это моя первая статья на Хабр. Буду рад получить отзывы - интересна ли данная тематика или нет. Если у кого-то есть хорошие примеры кодо+доко-генераторов бинарных протоколов для Embedded, было бы интересно прочитать в комментариях. Или какая-то успешная практика внедрения похожих систем для описания бинарных протоколов.

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

Подробнее..

Rust сохраняем безразмерные типы в статической памяти

11.06.2021 14:22:51 | Автор: admin

Не так давно в качестве хобби я решил погрузиться в изучение embedded разработки на Rust и через какое-то время мне захотелось сделать себе логгер, который бы просто писал логи через UART, но при этом не знал какая конкретно реализация используется. Вот тут я быстро осознал, именно в этом конкретном случае я не могу полагаться на статический полиморфизм и мономорфизацию, ведь компилятор не знает сколько нужно памяти выделять под конкретную реализацию. Фактически это означает, что нам нужно как-то уметь сохранять в памяти типы, размер которых неизвестен на этапе компиляции. Такой способностью обладает типBox, но он использует для этого динамическое выделение памяти из кучи. В итоге возникла идея написать свой аналог данного типа, но хранящий объект в предоставленном пользователем буфере, а не в глобальной куче.

А почему бы просто не взять какой-нибудьlinked_list_allocatorот Фила, дать ему пару килобайт памяти и воспользоваться обычнымBoxтипом, или даже взять какой-нибудь простейший bump аллокатор, ведь мы хотим использовать его лишь для того, чтобы создать несколько глобальных объектов, но есть множество сценариев, когда куча не используется принципиально? Это и дополнительная зависимость от целогоallocкрейта и дополнительные риски, что использование кучи выйдет за рамки строго детерминированных сценариев, что будет приводить к трудноуловимым ошибкам.

С другой стороны, мы можем просто принимать&'static dyn Traitи таким образом переложить заботу о том, как получить такую ссылку, на конечного пользователя, но чтобы обеспечить потом доступ к этой ссылке, нам необходимо использовать примитивы синхронизации или же воспользоваться unsafe кодом, с другой стороны, конечный пользователь тоже должен воспользоваться ими, чтобы создать такую ссылку. В конечном итоге у нас получается или двойная работа или unsafe в публичном API, что довольно плохо. Да и в целом, Box обладает гораздо более широкой областью применения, например, его можно использовать для организации очереди задач в очередном futures executor.

Что же такое безразмерные типы?

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

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

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

#[repr(C)]pub(crate) union PtrRepr<T: ?Sized> {    pub(crate) const_ptr: *const T,    pub(crate) mut_ptr: *mut T,    pub(crate) components: PtrComponents<T>,}#[repr(C)]pub(crate) struct PtrComponents<T: ?Sized> {    pub(crate) data_address: *const (),    pub(crate) metadata: <T as Pointee>::Metadata,}

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

pub struct DynMetadata<Dyn: ?Sized> {    vtable_ptr: &'static VTable,    phantom: crate::marker::PhantomData<Dyn>,}/// The common prefix of all vtables. It is followed by function pointers for trait methods.////// Private implementation detail of DynMetadata::size_of etc.#[repr(C)]struct VTable {    drop_in_place: fn(*mut ()),    size_of: usize,    align_of: usize,}

Таким образом, в текущей реализации, размер&dyn Displayна x86_64 составляет 16 байт, а когда мы пишем такой вот код:

let a: u64 = 42;let dyn_a: &dyn Display = &a;

Компилятор генерирует объектVTableи сохраняет его где-то в статической памяти, а обычную ссылку заменяет на широкую, содержащую кроме адреса еще и указатель на таблицу виртуальных функций. Ссылка на таблицу виртуальных функций статическая и не зависит от места расположения значения, таким образом, для того, чтобы создать желаемыйBox<dyn Display>из искомого значенияa, нам необходимо извлечь метаданные из ссылки наdyn_aи все это вместе скопировать в заранее приготовленный для этого буфер. Чтобы все это сделать, нам необходимо использовать nightly features:unsizeиptr_metadata.

Для получения&dyn Tиз&Valueиспользуется специальный маркерный трейтUnsize, который выражает отношение междуSizedтипом и его безразмерным альтер-эго. То есть,TэтоUnsize<dyn Trait>в том случае, еслиTреализуетTrait.

А чтобы работать с метаданными указателя используется функцияcore::ptr::metadataи типажPointee, который связывает тип указателя и тип его метаданных, в случае с безразмерными типами метаданные имеют типDynMetadata<T>, гдеTэто искомый безразмерный тип.

#[inline]fn meta_offset_layout<T, Value>(value: &Value) -> (DynMetadata<T>, Layout, usize)where    T: ?Sized + Pointee<Metadata = DynMetadata<T>>,    Value: Unsize<T> + ?Sized,{    // Get dynamic metadata for the given value.    let meta = ptr::metadata(value as &T);    // Compute memory layout to store the value + its metadata.    let meta_layout = Layout::for_value(&meta);    let value_layout = Layout::for_value(value);    let (layout, offset) = meta_layout.extend(value_layout).unwrap();    (meta, layout, offset)}

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

Обратите внимание, что мы беремLayoutот ссылки на метаданные, а неDynMetadata<Dyn>::layout, последний описывает размещениеVTable, но нас интересует размещение самогоDynMetadata, будьте внимательны!

Пишем свой Box

Вот, теперь у нас есть все необходимое, чтобы написать нашBox, его код довольно простой:

Конструктор, который копирует данные и метаданные в предоставленный буфер, используя довольно удобное API указателя.

impl<T, M> Box<T, M>where    T: ?Sized + Pointee<Metadata = DynMetadata<T>>,    M: AsRef<[u8]> + AsMut<[u8]>,{    pub fn new_in_buf<Value>(mut mem: M, value: Value) -> Self    where        Value: Unsize<T>,    {        let (meta, layout, offset) = meta_offset_layout(&value);        // Check that the provided buffer has sufficient capacity to store the given value.        assert!(layout.size() > 0);        assert!(layout.size() <= mem.as_ref().len());        unsafe {            let ptr = NonNull::new(mem.as_mut().as_mut_ptr()).unwrap();            // Store dynamic metadata at the beginning of the given memory buffer.            ptr.cast::<DynMetadata<T>>().as_ptr().write(meta);            // Store the value in the remainder of the memory buffer.            ptr.cast::<u8>()                .as_ptr()                .add(offset)                .cast::<Value>()                .write(value);            Self {                mem,                phantom: PhantomData,            }        }    }}

А вот и код, который собирает байты назад в&dyn Trait:

    #[inline]    fn meta(&self) -> DynMetadata<T> {        unsafe { *self.mem.as_ref().as_ptr().cast() }    }    #[inline]    fn layout_meta(&self) -> (Layout, usize, DynMetadata<T>) {        let meta = self.meta();        let (layout, offset) = Layout::for_value(&meta).extend(meta.layout()).unwrap();        (layout, offset, meta)    }    #[inline]    fn value_ptr(&self) -> *const T {        let (_, offset, meta) = self.layout_meta();        unsafe {            let ptr = self.mem.as_ref().as_ptr().add(offset).cast::<()>();            ptr::from_raw_parts(ptr, meta)        }    }    #[inline]    fn value_mut_ptr(&mut self) -> *mut T {        let (_, offset, meta) = self.layout_meta();        unsafe {            let ptr = self.mem.as_mut().as_mut_ptr().add(offset).cast::<()>();            ptr::from_raw_parts_mut(ptr, meta)        }    }

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

impl<T, M> Deref for Box<T, M>where    T: ?Sized + Pointee<Metadata = DynMetadata<T>>,    M: AsRef<[u8]> + AsMut<[u8]>,{    type Target = T;    #[inline]    fn deref(&self) -> &T {        self.as_ref()    }}impl<T, M> DerefMut for Box<T, M>where    T: ?Sized + Pointee<Metadata = DynMetadata<T>>,    M: AsRef<[u8]> + AsMut<[u8]>,{    #[inline]    fn deref_mut(&mut self) -> &mut T {        self.as_mut()    }}
running 8 teststest tests::test_box_dyn_fn ... oktest tests::test_box_nested_dyn_fn ... oktest tests::test_box_in_provided_memory ... oktest tests::test_box_trait_object ... oktest tests::test_box_move ... oktest tests::test_drop ... oktest tests::test_layout_of_dyn ... oktest tests::test_box_insufficient_memory ... ok

Miri

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

cargo miri test   Compiling static-box v0.0.1 (/home/aleksey/Projects/opensource/static-box)    Finished test [unoptimized + debuginfo] target(s) in 0.40s     Running unittests (target/x86_64-unknown-linux-gnu/debug/deps/static_box-e2c02215f3157959)running 8 teststest tests::test_box_dyn_fn ... error: Undefined Behavior: accessing memory with alignment 1, but alignment 8 is required   --> /home/aleksey/.rustup/toolchains/nightly-2021-04-25-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:886:9    |886 |         copy_nonoverlapping(&src as *const T, dst, 1);    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ accessing memory with alignment 1, but alignment 8 is required    |    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

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

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

        // Construct a box to move the specified memory into the necessary location.        // SAFETY: This code relies on the fact that this method will be inlined.        let mut new_box = Self {            align_offset: 0,            mem,            phantom: PhantomData,        };        let raw_ptr = new_box.mem.as_mut().as_mut_ptr();        // Compute the offset that needs to be applied to the pointer in order to make        // it aligned correctly.        new_box.align_offset = raw_ptr.align_offset(layout.align());

Вот собственно и все, после этого Miri больше не показывает ошибок выравнивания.

cargo miri test   Compiling static-box v0.1.0 (/home/aleksey/Projects/opensource/static-box)    Finished test [unoptimized + debuginfo] target(s) in 0.30s     Running unittests (target/x86_64-unknown-linux-gnu/debug/deps/static_box-ce23f69c165cf930)running 11 teststest tests::test_box_dyn_fn ... oktest tests::test_box_in_provided_memory ... oktest tests::test_box_in_static_mem ... oktest tests::test_box_in_unaligned_memory ... oktest tests::test_box_insufficient_memory ... oktest tests::test_box_move ... oktest tests::test_box_nested_dyn_fn ... oktest tests::test_box_trait_object ... oktest tests::test_drop ... oktest tests::test_layout_of_dyn_split_at_mut ... oktest tests::test_layout_of_dyn_vec ... oktest result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out   Doc-tests static-boxrunning 2 teststest src/lib.rs - (line 24) ... oktest src/lib.rs - (line 48) ... oktest result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s

Хочу еще сказать несколько слов относительно типаLayout, в нем содержится два поляsize, которое содержит размер памяти в байтах, необходимый для размещения объекта, иalign- это число (причем всегда степень двойки), которому должен быть кратен указатель на объект данного типа. И таким образом, чтобы починить выравнивание, мы просто вычисляем сколько нам нужно прибавить к адресу начала буфера, чтобы получить адрес кратныйalign. Дополнительно довольно доступно написано про выравнивание уу Фила.

Заключение

Ура, теперь мы можем писать вот такой вот код!

use static_box::Box;struct Uart1Rx {    // Implementation details...}impl SerialWrite for Uart1Rx {    fn write(&mut self, _byte: u8) {        // Implementation details    }}let rx = Uart1Rx { /* ... */ };SOME_GLOBAL_WRITER.init_once(move || Box::<dyn SerialWrite, [u8; 32]>::new(rx));// A bit of code later.SOME_GLOBAL_WRITER.lock().unwrap().write_str("Hello world!");

Итак, мы при помощи unsafe и некоторого количества nightly фич смогли написать тип, позволяющий размещать полиморфные объекты на стеке или в статической памяти без использования кучи, что может быть полезным во многих случаях. Хотя, конечно, каждый раз при получении ссылки на объект приходится дополнительно вычислять адрес метаданных и значения, но мы не можем просто так взять и сохранить эти адреса как поля структуры, в этом случае она станет самоссылающиеся, что довольно неприятно в Rust контексте, это не работает с семантикой перемещения. В целом, если воспользоваться pin API, и сделать нашBox неперемещаемым, то можно будет позволить себе эту оптимизацию, а заодно и обеспечить возможность работать с любыми Future типами.

Хочу еще сказать напоследок, что не стоит бояться писать низкоуровневый unsafe код, но стоит 10 раз подумать над его корректностью и обязательно использовать Miri вCIтестах, он отлавливает довольно много ошибок, а разработка низкоуровневого кода требует очень большой внимательности к деталям всевозможным граничным случаям. В конечном счете, именно знания того, как в реальности реализована та или иная языковая абстракция, позволяет перестать воспринимать её как черную магию. Часто все намного проще и очевиднее, чем кажется, стоит просто копнуть чуть поглубже.

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

Ссылка на крейт

Подробнее..

Zephyr в embedded опыт использования на STM32F7-Discovery

15.06.2020 18:18:41 | Автор: admin
image

История о моем опыте использования операционной системы реального времени (ОСРВ) Zephyr для устройства на базе микроконтроллера STM32F7-Discovery.


В статье:


  • Что такое Zephyr и при чем тут Linux?
  • Запуск проекта на STM32. Интересные моменты по работе с драйверами.
  • Фишки этой ОС. Что понравилось, а что нет.

Привет, Хабр, меня зовут Илья. Я студент выпускного курса университета и параллельно прохожу стажировку на позицию embedded-разработчика в компании Третий пин. Мой приход совпал с началом изучения операционной системы реального времени Zephyr. Чтобы не делать исследование на пустом месте, мне и другим стажерам предложили придумать небольшой проект, где можно использовать эту операционную систему. Мы остановились на идее устройства для отладки оборудования, когда отсутствует возможность подключения к компьютеру. Устройство позволяет считывать, хранить и отображать логи тестируемого устройства на дисплее или передавать их на компьютер по Ethernet. Проект получил внутренне название Logovoz. Прототип решили делать на STM32F7-Discovery. О том, что получилось планирую рассказать в следующих статьях. Сегодня про сам Zephyr.


Что еще за Zephyr?


image

Zephyr это сравнительно новая операционная система реального времени с прицелом на embedded и устройства интернета вещей. Она была разработана в 2015 году компанией Wind River Systems, автора другой популярной в авиационной и космической отраслях ОС VxWorks.


Что такое операционная система реального времени?

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


Чем хорош Zephyr:


  • Мощная архитектура. Системные вызовы, драйвера, потоки, файловая система, shell и так далее. Всё как во взрослых ОС.
  • Поддержка множества контроллеров от разных вендоров. STM, ESP, Atmel, NXP и т. д. Система позволяет запускать одно приложение на разных платах без переписывания кода.
  • Собственный инструмент командной строки West. Сами создатели называют его перочинным ножом для разработчика. Это одновременно система сборки, менеджер модулей, инструмент прошивки, конфигуратор.
  • Open-source. С 2017 года система поддерживается Linux Foundation и в ней используются наработки из кодовой базы Linux. Например, Kconfig и dts.

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


Как устроен Zephyr?


Система во многом схожа с Linux. Как и Linux, Zephyr содержит menuconfig или guiconfig (то же самое, но с отдельным GUI, а не в консоли), которые конфигурируют программные части системы на основе файлов Kconfig. Это могут быть различные драйверы, поддержка сетевых функций и т.д. Для описания же аппаратной части используется структура device tree. С помощью неё конфигурируются диапазоны адресов регистров в памяти, периферия, линии прерываний и др.


image

В качестве системы сборки Zephyr иcпользует CMake. Поэтому каждое приложение должно иметь CMakeList.txt в качестве точки входа системы сборки. Сборка проекта осуществляется с помощью West. Команды west упрощают настройку приложения. Например, написав программу, собрать её под STM32F746G-Discovery надо командой:


west build b stm32f746g_disco

Не меняя исходный код, программа под NUCLEO-F207ZG собирается командой:


west build b nucleo_f207zg

Подробнее про доступные команды можно узнать, вызвав подсказку:


west --h

image

Для использования определенной версии Zephyr и подключения сторонних модулей используется файл манифеста west.yml.


Запуск Zephyr


Если у вас есть отладочная плата и она поддерживается Zephyr открываем статью Getting Started Guide на официальном сайте и проделываем 8 нехитрых пунктов для вашей ОС. Для выполнения 7 пункта придётся найти файл Kconfig.defconfig и в нём посмотреть название отладки в параметре BOARD.


image

Расположение для STM32F746G-Discovery:
zephyr/boards/arm/stm32f746g_disco/Kconfig.defconfig


И, voila, вы гордый обладатель отладки с мигающим светодиодом.


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


Что попробовать сделать


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


image

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


Отладка


Для отладки приложения я пользовался связкой VSCode + marus25.cortexdebug. В документации приведена инструкция для использования Eclipse в
качестве IDE.


Работа с драйверами


image

Для хранения логов в проекте планировалось реализовать файловую систему на SD-карте. Смотрю в щедро предлагаемые мне системой возможности, но не обнаруживаю там поддержки SD.
Zephyr на момент версии 2.1 умеет работать с SD-картами SDHC от 2 до 32 Гб ёмкости через SPI. Для работы с ними есть примеры, инструкция, всё замечательно. Хорошо, тогда почему же моя отладка не поддерживает работу с SD в Zephyr? Смотрю reference manual на stm32f7 и в разделе SDMMC нахожу строку.


image

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


image

На рисунке представлена модель драйверов в Zephyr. Для использования конкретной реализации к ней надо обратиться через обобщенный API. Конечная цель работы с SD картой взаимодействовать с ней, как с файловой системой. Такой интерфейс предоставляется через подсистему disk. Директория с этой подсистемой содержит как обобщенный API, так и API, предоставляющие доступ к файловой системе в ОЗУ, во флеше или на SDкартах через SPI. Соответственно, надо добавить сюда свой интерфейс, который будет обращаться к реализации драйвера работы с SD.


Берём готовые реализации интерфейсов, смотрим, как там всё сделано, и пишем что-то подобное. При написании драйверов и интерфейсов рекомендуется использовать язык Си, а также макросы, которые применяются в подобных файлах Zephyr. В конце создания API не забываем вписать о нём информацию в файлы CMakeList.txt и Kconfig, чтобы драйвер можно было собрать и включить в системе. В итоге, на карточку памяти гордо записан текстовый файл с приветствием миру.


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


Версионирование


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


Бывают и другие досадные моменты. В мажорном обновлении с Zephyr 1.14 до 2.0 сменилась такая незначительная деталь, как спецификация device tree.


image

В итоге поменялся формат статуса в файлах .dts c ok на okay. Вроде бы мелочь, но при создании собственной платы и переходе на другую версию, проект не будет собираться. Поэтому если вы работаете на определенном релизе Zephyr, внимательно следите и за версией документации.




Работа с RTC


В попытках запустить RTC (Real Time Clock) на плате, я находил, что драйвер часов реального времени был, но потом его не стало. Неприятная ситуация. Позже оказалось, что его функциональность осталась, но была переименована и получила интерфейс Counter.


Воспользовавшись примерами, запустить RTC оказалось несложно. И он даже работал. До первого reset-а. А вот потом обнулился, хотя суть часов реального времени и заключается в том, чтобы не сбрасываться во время reset-а. Это могло произойти из-за того, что в отладку нельзя подключить батарейку и проверить работу часов с ней, а в Zephyr всё на самом деле прекрасно работает. Помогла возможность подсмотреть реализацию RTC в HAL. Те драйверы, которые удалось пощупать, были написаны с применением LL. Не найдя чего-то в нужном драйвере, можно узнать реализацию в HAL и дописать это. Выяснилось, что при инициализации часов в системе, сбрасываются регистры RCC. Не делая этого при reset-е можно оставить нетронутыми значения RTC и он будет работать, как и должен.


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


Выводы


Стоит ли пробовать Zephyr? Кратко да. Zephyr действительно поддерживает много фишек из коробки. Достаточно сделать пару кликов в guiconfig и вот в проекте появляется поддержка UART, SPI, Ethernet. Посмотрел пример, повторил, изменил, оно ещё и работать будет. Возможность не переписывать исходный код при переезде на другую плату тоже подкупает.


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


Если у вас был опыт работы с Zephyr, поделитесь им в комментариях.

Подробнее..

Error success и что делать по этому поводу

04.12.2020 12:06:11 | Автор: admin

image


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


Для тех, кому букв много
  • правильно обрабатывать возвращаемые функцией read() значения важно
  • не забываем проверять актуальность используемого софта (а OpenSource особенно)
  • многофункциональные программы-комбайны в большинстве случаев одинаково неудобны для решения любой из допустимых задач. С другой стороны плодить москитный флот не лучшая идея

А вообще у меня грустная новость. Не будет больше никаких картинок. Мы опустимся на уровень системной консоли Linux и будем жить там. При этом будем радоваться. Ибо проект, с которым предстоит работать достаточно известный загрузчик U-Boot. Проект с открытым исходным кодом, поддерживаемый компанией DENX Software Engineering. Потому порадуемся тому, что у нас есть консоль, есть системное окружение и вообще жизнь кипит. Потому как при работе с этим проектом, как правило, нет ничего подобного сплошные области памяти, пересылки байт из одного места в другое да ожидание готовности периферии. Но, к частью, эта часть уже выполнена и есть вполне себе рабочий загрузчик для железки. Пора заняться украшениями, которые позволят прикладным программистам как-то влиять на процесс загрузки системы. Ничто не предвещает проблем. Задача давно решена и активно используется таким популярным проектами как OpenWRT и множеством других, чуть менее известных.


Суть очень проста. U-Boot корректирует свое поведение в зависимости от переменных среды. Переменные среды между перезагрузками могут быть сохранены в энергонезависимой памяти. Утилиты командной строки fw_printenv и fw_setenv позволяют соответственно выводить и менять их значение прямо из Linux. Все. В принципе большего и не требуется. Как всегда инструкцию мы будем читать когда дым рассеется. Да и откуда здесь взяться дыму? Дым весь был выпущен когда загрузчик под эту плату адаптировали. Потому смело набираем команду fw_printenv, потому как она-то точно ничего сломать не может.


localhost ~ # fw_printenvCannot open /dev/mtd1: No such file or directorylocalhost ~ # fw_printenv --helpUsage: fw_printenv [OPTIONS]... [VARIABLE]...Print variables from U-Boot environment -h, --help           print this help. -v, --version        display version -c, --config         configuration file, default:/etc/fw_env.config -n, --noheader       do not repeat variable name in output -l, --lock           lock node, default:/var/lock

Ну ожидаемо. Конечно. Мы не указали где именно хранятся переменные среды. А быстрая помощь однозначно говорит о том, что указать в командной строке и не получится. Надо править конфигурационный файл /etc/fw_env.config. Формат файла довольно простой и интуитивно понятный. Для того, чтоб не создавать самому себе (и окружающим) трудностей я разместил переменные среды U-Boot в самом доступном месте, которое только можно себе придумать. Конкретно в файле uboot.env первого раздела основного накопителя, отформатированного реально переносимой файловой системой vfat (она же FAT-32). И проверил. Из консоли U-Boot переменные сохраняются в файл, при старте из него читаются. Красота. Осталось только дать возможность их править из Linux. Раздел c файлом uboot.env, а еще ядром, файлом дерева устройств, и некоторым дополнительным наполнением критичным для работы системы совершенно логично монтируется к /boot. Потому совершенно не сомневаясь комментирую строчки 11 и 12 (/dev/mtd1 и /dev/mdt2 соответственно) и убираю комментарий со строчки 30 (/boot/uboot.env) в конфигурационном файле.


# VFAT example/boot/uboot.env 0x0000          0x4000

Все. Вроде все подготовительные операции выполнены. Дубль два.


localhost ~ # fw_printenvRead error on /boot/uboot.env: Success

Ну здравствуй, КДПВ. Первая разумная мысль, которая посещает любого Linuxоидника в такой ситуации а что с правами. Впрочем, наш лозунг Слабоумие и отвага мы работаем от rootа. Логично. Чего бояться человеку, который для железки делает загрузчик и имеет самый что ни на есть физический (с паяльником) доступ к плате? А может файла просто нет? Забыл в консоли U-Bootа сказать saveenv? Проверим


localhost ~ # ls -l /boot/uboot.env-rwxr-xr-x 1 root root 8192 Dec  2 13:22 /boot/uboot.env

Нет, есть он. И даже читаться может всем миром (ай, как не хорошо). Интересно, а если его не будет?


localhost ~ # mv /boot/uboot.env /boot/uboot.env.baklocalhost ~ # fw_printenvCannot open /boot/uboot.env: No such file or directorylocalhost ~ # mv /boot/uboot.env.bak /boot/uboot.env

Логично. Тут все правильно. Ладно, тяжело вздохнули и Это наш кактус, нам его и грызть. Хорошо хоть исходники есть. Надо глянуть, что там у нас происходит. Может мысли какие появятся? Очень быстро находим строку 950 в файле tools/env/fw_env.c:


        lseek(fd, blockstart + block_seek, SEEK_SET);        rc = read(fd, buf + processed, readlen);        if (rc == -1) {            fprintf(stderr, "Read error on %s: %s\n",                DEVNAME(dev), strerror(errno));            return -1;        }        if (rc != readlen) {            fprintf(stderr,                "Read error on %s: Attempted to read %zd bytes but got %d\n",                DEVNAME(dev), readlen, rc);            return -1;        }

Нет. Тут вполне себе классическая обертка над функцией read(). Практически прямиком из учебника. И, судя по поведению итоговой программы не остается сомнений в том, что read() возвращает -1, но при этом errno остается нулевым. Подождите. Что это за скрежет раздается? А, это мозги шевелиться начали Хорошо


Ну что, можно почитать мануал на read? Не, ерунда Уж вроде про read-то все читано-перечитано. Все мыслимые и немыслимые варианты ошибок с функцией read() давно известны. Не должно быть такого. Что делаем дальше? Правильно, раз исходники не дают ответ пусть его даст сама система.


localhost ~ # strace fw_printenvexecve("/usr/bin/fw_printenv", ["fw_printenv"], 0x7ebf2400 /* 28 vars */) = 0brk(NULL)                               = 0x2118000uname({sysname="Linux", nodename="localhost", ...}) = 0access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3fstat64(3, {st_mode=S_IFREG|0644, st_size=42265, ...}) = 0mmap2(NULL, 42265, PROT_READ, MAP_PRIVATE, 3, 0) = 0x76f14000close(3)                                = 0openat(AT_FDCWD, "/lib/libc.so.6", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\f~\1\0004\0\0\0"..., 512) = 512fstat64(3, {st_mode=S_IFREG|0755, st_size=1286448, ...}) = 0mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f12000mmap2(NULL, 1356160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76da1000mprotect(0x76ed7000, 65536, PROT_NONE)  = 0mmap2(0x76ee7000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x136000) = 0x76ee7000mmap2(0x76eea000, 8576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x76eea000close(3)                                = 0set_tls(0x76f12ca0)                     = 0mprotect(0x76ee7000, 8192, PROT_READ)   = 0mprotect(0x4a9000, 4096, PROT_READ)     = 0mprotect(0x76f1f000, 4096, PROT_READ)   = 0munmap(0x76f14000, 42265)               = 0openat(AT_FDCWD, "/var/lock/fw_printenv.lock", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3flock(3, LOCK_EX)                       = 0brk(NULL)                               = 0x2118000brk(0x2139000)                          = 0x2139000openat(AT_FDCWD, "/etc/fw_env.config", O_RDONLY) = 4fstat64(4, {st_mode=S_IFREG|0644, st_size=1342, ...}) = 0read(4, "# Configuration file for fw_(pri"..., 4096) = 1342read(4, "", 4096)                       = 0close(4)                                = 0openat(AT_FDCWD, "/boot/uboot.env", O_RDONLY) = 4fstat64(4, {st_mode=S_IFREG|0755, st_size=8192, ...}) = 0close(4)                                = 0openat(AT_FDCWD, "/boot/uboot.env", O_RDONLY) = 4_llseek(4, 0, [0], SEEK_SET)            = 0read(4, "n.'\202__INF0__=Ravion-V2 I.MX6 CPU"..., 16384) = 8192write(2, "Read error on /boot/uboot.env: S"..., 39Read error on /boot/uboot.env: Success) = 39close(4)                                = 0flock(3, LOCK_UN)                       = 0close(3)                                = 0exit_group(1)                           = ?+++ exited with 1 +++localhost ~ #

Люблю Linux. Ай какая красота. Все сразу встало на свои места. Ладно, согласен не все. Но уже что-то. Самое интересное здесь:


openat(AT_FDCWD, "/boot/uboot.env", O_RDONLY) = 4_llseek(4, 0, [0], SEEK_SET)            = 0read(4, "n.'\202__INF0__=Ravion-V2 I.MX6 CPU"..., 16384) = 8192write(2, "Read error on /boot/uboot.env: S"..., 39Read error on /boot/uboot.env: Success) = 39

Пытаемся прочитать 16384 (16K), а можем только 8192 (8K). В принципе всё. Бинго. Поднимаемся выше и смотрим размер файла. Да, он действительно 8192 байта. Поднимаемся еще выше и смотрим строку в конфиге. Смещение 0, длинна 0x4000 или 16384. Исправляем на 0x2000


# VFAT example/boot/uboot.env 0x0000          0x2000

Да, черт возьми я очень стар. По мне U-Bootу для переменных среды и килобайта хватит. Еще тут драгоценную память просто так расходовать. Что вы хотите. Коренной житель Питера. Нам с детства вбили что хлеб (ресурсы) надо беречь. А выбрасывать его это не ценить память погибших из-за его отсутствия в блокаду. И да, мы такие. Спасибо реальным ветеранам, спасибо городским музеям. Которые не смотря ни на что сохраняют память о тех страшных временах. Надеюсь и мои дети этому научатся.


Так вот о переменных среды для U-Boot. Ну два килобайта. Ну, ладно четыре. Куда больше? Что там можно писать в таких количествах (и главное зачем)? Потому действительно был момент когда выделенные по умолчанию 16К урезал до 8. Еще думал куда столько? Ладно, лирику в сторону проверяем.


localhost ~ # fw_printenv__INF0__=Ravion-V2 I.MX6 CPU Module BSP package__INF1__=Created: Alex A. Mihaylov AKA MinimumLaw, MinimumLaw@Rambler.Ru[]boot_os=1localhost ~ #

Работает. И даже fw_setenv работает.


localhost ~ # fw_setenv boot_os 0; fw_printenv boot_osboot_os=0

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


Правильно думаете. Если посмотреть на код, который лежит в репозитарии U-Boot, то можно легко заметить, что такая ситуация должна корректно отрабатываться. Я выше специально не стал вырезать этот кусок. Больше того, strace совершенно честно и открыто говорит что read возвращает значение 8192. Так почему же мы оказываемся в ветке с ошибкой чтения? Ведь 8192 никак не может равняться -1.


Давайте разбираться. Первая мысль, которая приходит в голову подождите, но ведь Das U-Boot это динамично развивающийся проект. Может быть мы смотрим репозитарий с последним релизом загрузчика. Но та часть, которую используем мы совсем не обязана быть последней. Она часть пользовательского окружения операционной системы. Это я адаптирую последнюю версию загрузчика, чтоб пульс проекта ощущать. А авторы сборок прикладного ПО скорее за стабильность ратуют. Потому она наверняка последней и не будет. Проверяем.


localhost ~ # fw_printenv --versionCompiled with U-Boot 2019.10localhost ~ #

Ага! А у меня в работе последняя стабильная (2020.10). Разница в год. Огромная дистанция для динамично развивающегося OpenSource проекта. А давайте посмотрим .


        lseek(fd, blockstart + block_seek, SEEK_SET);        rc = read(fd, buf + processed, readlen);        if (rc != readlen) {            fprintf(stderr, "Read error on %s: %s\n",                DEVNAME(dev), strerror(errno));            return -1;        }

Ну да. Так и есть. Уже все исправили. Обидно. Такой красивый баг был. Ладно, на нашу жизнь багов еще припасено. Только успевай разбирать.


Но ведь и это ещё не все. А давайте заглянем в файл uboot.env


localhost ~ # hexdump -C /boot/uboot.env00000000  0a 43 62 eb 5f 5f 49 4e  46 30 5f 5f 3d 52 61 76  |.Cb.__INF0__=Rav|00000010  69 6f 6e 2d 56 32 20 49  2e 4d 58 36 20 43 50 55  |ion-V2 I.MX6 CPU|00000020  20 4d 6f 64 75 6c 65 20  42 53 50 20 70 61 63 6b  | Module BSP pack|00000030  61 67 65 00 5f 5f 49 4e  46 31 5f 5f 3d 43 72 65  |age.__INF1__=Cre|[...]00000720  3d 71 70 00 76 65 6e 64  6f 72 3d 72 61 76 69 6f  |=qp.vendor=ravio|00000730  6e 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |n...............|00000740  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|*00002000localhost ~ #

Вполне очевиден факт оценка размера блока достаточного для хранения переменных среды, которая была дана мною выше, вполне справедлива. На данный момент использовано 1837 байт (0x7031 4) и формат блока довольно простой. Первый 4 байта CRC32, а дальше разделенные нулем переменные в формате переменная=значение. Другими словами поведение утилиты все равно вызывает вопросы. Ну напишет она что размер файла меньше ожидаемого и завершится с ошибкой. Но ведь это не правда. Все значимые и важные данные в него (даже в двухкилобайтный!) вполне бы влезли. Может все же стоит поправить?


Увы нет. И причина этому вполне банальна. Переменные в U-Boot могут храниться в самых разных местах. Файл на vfat разделе это самое приятное место. За это его и выбрал. Но в том же OpenWRT нет таких удобных накопителей. Там SPI-flash. И под переменные среды выделяется целый сектор. Но и тут все может быть не так плохо. Сектор целиком надо стирать. Писать можно частями. Беда с системами, которые используют dataflash или некоторые варианты raw-NAND накопителей. Т.е. с теми системами, которым помимо данных нужна еще и контрольная информация для контроля целостности и исправности. Вот они обязаны писать весь блок целиком.


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


Вот так неожиданно и получилось легонькое пятничное чтиво. Было бы интересно посмотреть сколько времени суммарно прожила эта ошибка, но Уже не на столько интересно, чтоб тратить на это время. Как говаривал классик: Сказка ложь, да в ней намек. Добрым молодцам урок. Спасибо за то, что дочитали.


P.S.
Пользуясь случаем передаю привет CodeRush Еще раз благодарю за приглашение на Habr. И да, всегда хочется писать о серьезном о компиляторах, о безопасном программировании непосредственно по железу. А сил хватает только на легкое пятничное чтиво. Ладно, будем считать что начало положено. Большое путешествие всегда начинается с маленького шага.

Подробнее..

38 Роботов будущего обзор полуфиналистов 10M ANA Avatar XPRIZE

20.05.2021 18:13:57 | Автор: admin

В полуфинал $10M ANA Avatar XPRIZE прошли 38 команд из 16 стран мира, в том числе одна команда из России. В сентябре 2021 команды продемонстрируют возможности своих систем на отборочных тестах в США. Лучшие команды пройдут в финал, намеченный на лето 2022 года. Призовой фонд конкурса $10M ANA Avatar XPRIZE составляет 10 млн. долларов.

$10M ANA Avatar XPRIZE - четырехлетнее международное соревнование, которое стартовало в 2018 году. Цель соревнования - разработка, совершенствование и ускоренное внедрение перспективных технологий в систему многофункциональных аватаров, которая позволит беспрепятственно переносить человеческие навыки и опыт в любую точку пространства будь то планета Земля или лунная станция. Робот-аватар позволит удаленному оператору видеть, слышать и действовать так, словно он находится на месте событий.

38 Teams from 16 countries expanding our potential

Большинство команд это компании, работающие в сфере робототехники, VR/AR. Команды объединяются, создают коллаборации для расширения экспертизы и привлекают партнеров.

Например, команда i-Botics (Нидерланды) имеет в составе 6 компаний с компетенциями для решения узких задач в создании робота-аватара: сенсоры, человекоподобные роботы, технологии передачи обратной связи оператору, социальные роботы и др.

Другой пример организации команды, японская команда GITAI, специализируется на разработке роботов для индустриального применения. Эта команда пришла на конкурс с глубокой экспертизой и прототипом высокого уровня готовности. Аватар GITAI управляется оператором из специально оборудованного кресла управления, способен передвигаться и выполнять непростые механические манипуляции. В своем демо GITAI говорит о применении создаваемого аватара для работы на Международной космической станции в 2022-23 гг.

В конкурсе также принимают участие академические команды, например: команда SantAnna (Италия) включает в себя профессорский состав, студентов-магистров, бакалавров и аспирантов. Команда Northeastern Северо-Восточный университет (Northeastern University, Бостон, США) сформировала рабочие группы по направлениям: VR, удаленное управление, сенсорика и др, которыми управляют студенты старшего звена. Помимо классической научной работы, они получают реальный опыт, где важна не только глубокая научная экспертиза, но и навыки управления и организации команд.

В списке полуфиналистов 38 квалифицированных команд из 16 стран мира. Наибольшее количество команд из США - 10, на втором месте Япония - 8 команд, Россию в полуфинале представляет 1 команда - Dragon Tree Labs.

Для желающих присоединится к команде Dragon Tree Labs и отправится на отборочные тесты для выхода в финал в Майами с нами открыт прием заявок на стажировку. Подробности на сайте по ссылке Dragon Tree Labs

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

Итак, предлагаем подробнее ознакомится с участниками соревнования $10M ANA Avatar XPRIZE.

#1 Aham - Индия

Команда Aham коллаборация Индийского Научного Института в г. Бангалор, технопарка ARTPARK и компаний TCS Research и Hanson Robotics.

Hanson Robotics уже более двух десятилетий создают человекоподобных роботов, всем известны Sophia, Han, Little Sophia, Zeno, Professor Einstein. Объединенная команда Aham создает робота Ашу (Asha). Этот гуманоидный робот - двойник робота Софии, специально создан для НИОКР в рамках участия в $10M ANA Avatar XPRIZE. Телеуправляемый робот-медсестра Аша разговаривает женским голосом и выражает эмоции, ее лицо выглядит как у реального человека. Оно сделано из запатентованного материала Frubber - нанотехнологическая кожа, имитирующая мускулатуру лица и кожу человека. Во время разговора робот двигает губами, моргает и жестикулирует.

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

Каждый участник в команде Aham вносит существенный вклад в текущее программное и аппаратное обеспечение, создавая удобную для исследования имитационную модель управления роботом и улучшая возможности взаимодействия робота и человека. TCS фокусируется на интеграции туловища Аши со своей AGV платформой для придания мобильности роботу, а также создает интеграцию с различными симуляторами и инструментами визуализации. Исследователи Индийского Научного Института, специализирующиеся на машинном обучении, системах автономного полета дронов и автономных автомобилях, поддерживающих 5-G, фокусируются на телеуправлении и телевзаимодействии робота с людьми.

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

#2 Avatar Quest, США

Команда Avatar Quest состоит из учеников старших классов школы Valley Christian Schools (VCS), г. Сан-Хосе, США. Мы разрабатываем робот телеприсутствия в эпоху, когда все существуют в телеприсутсивии. Это новая зарождающаяся культура поведения людей, где любые вещи можно делать не выходя из дома, потому что это намного безопаснее и эффективнее.

Такой образ жизни мы хотим создать с помощью разрабатываемых технологий - говорит ученик 12 класса Алекс Лопес. Команда Avatar Quest - это уже вторая команда из школы VCS, которая выходит в полуфинал соревнований XPRIZE. Ранее команда Ocean Quest стала самой молодой командой, когда-либо участвовавшей в полуфинале $7M Shell Ocean Discovery XPRIZE. В этом соревновании ребята взяли призовой раунд NOAA Bonus. Но на этом амбиции школы не заканчиваются - третья группа студентов также работает над улучшением экосистемы тропических лесов в рамках $10M XPRIZE Rainforest.

#3 AVATRINA - США

Команда AVATRINA разрабатывает аватар под названием TRINA. TRINA - это робот с продвинутым уровнем телеприсутствия, который умеет видеть, слышать, перемещаться и взаимодействовать с удаленной средой. Оператор управляет роботом, используя стандартную систему виртуальной реальности, а также с помощью встроенных контроллеров и тактильных перчаток.

На данный момент робот TRINA представляет собой подобную торсу человека установку на мобильной платформе и состоит из следующих компонентов:

  • голова робота в виде экрана с изображением человеческого лица с динамиком и камерой (угол вращения 360 градусов);

  • камеры глубины (RGBD-камеры), установленные на руках робота и камера глубины на груди робота

  • сменные концевые устройства для взаимодействия с предметами в качестве кистей руки (swappable end effectors)

  • UR5e рука с возможностью осевого движения (flexible collaborative robot arm)

Планируется добавить: тактильные ощущения руки робота в сочетании с VR, контроллеры в сочетании с VR, возможность управления через мобильное устройство. Компоненты разрабатываемого робота TRINA: 1) газодетектор и стереомикрофон на груди робота, 2) полностью анимированная голографическая голова, 3) сенсоры распределенные по поверхности торса и рук робота, 4) маневренные тактильные манипуляции рук и встроенные в руки камеры

В своем демо команда AVATRINA имитирует прием в клинике, во время которого аватар-доктор выполняет первичный осмотр пациента, сидящего в инвалидном кресле. На видео робот TRINA демонстрирует свои возможности: отслеживание положения головы, стереозрение, манипуляции, выполняемые руками робота, бимануальные манипуляции, безопасное управление механическим импедансом (safe impedance control), возможности навигации и сэйфти фичи.

#4 Cyberselves, Великобритания

"Let the robot be your surrogate self" - Команда Cyberselves разрабатывает софт телепресутвия Teleport, позволяющий управлять роботом из любой точки мира интуитивным и иммерсионным способом. Teleport App - приложение для робототехники, использующее универсальный язык Animus, end-to-end инструмент для программирования роботов, разработанный командой Cyberselves. Софт команды используется другой командой Roboy, также вышедшей в полуфинал $ 10M ANA Avatar XPRIZE.

Используя Teleport App и VR гарнитуру возможно перенестись в теле робота в любую точку мира - телепорт позволяет видеть то, что видит робот; слышать то, что слышит робот; чувствовать то, что чувствует робот, и перемещаться в его теле.

Команда получила грант 100k от UK's Sustainable Innovation Fund для использования своего опыта в сфере роботов телепресутвия для поддержки ключевых секторов экономики пострадавших от COVID-19: рынок недвижимости, выставочная индустрия и удаленное здравоохранение.

#5 Human Fusions - CША

Команда Human Fusions объединяет исследователей из 5 университетов США: Университет Кейс Вестерн Резер (Case Western Reserve University), Калифорнийский университет в Лос-Анджелесе (UCLA), Государственный университет в Кливленде (Cleveland State University), Университет Карнеги Меллон (Carnegie Mellon University), Вайомингский университет (University of Wyoming). Команда Human Fusions создает медицинский аватар Sensa. Первый прототип аватара сфокусирован на предоставлении оператору дополненного опыта удаленного управления роботом с тактильными ощущениями, визуальной и голосовой обратной связью. В аватаре используются следующие компоненты:

  • Робо-платформа Stretch (Hello Robot)

  • Манипулятор MIT GelSight с тактильными датчиками (UCLA)

  • Сенсорная перчатка NeuroReality (CWRU)

  • Oculus headset (Facebook)

Основной технологией, обеспечивающей связь с аватаром Human Fusions, является платформа NeuroRealityTM, а уникальной сильной стороной команды - это нейронный подход к получению сенсорной информации, чтобы обеспечить дополненный опыт через платформу NeuroReality. Интерфейс платформы создан для работы с различными другими роботизированными системами и команда открыта делиться экспертизой и сотрудничать с другими командами.

#6 ENZO AVATAR - Колумбия

Человекоподобный робот ENZO с 23 DOF и телеуправлением

#7 Dragon Tree Labs - Россия

Команда Dragon Tree Labs - единственная российская команда, которая продолжает бороться за победу и главный приз.

Нашего робота зовут Джони, он управляется дистанционно, а оператор наблюдает за происходящим через VR-очки. Робот-аватар оснащён колёсами и может свободно передвигаться по помещению. Для одновременного запуска нескольких нейронных сетей и параллельного выполнения задач с задействованием ML-алгоритмов на роботе установлен бортовой Edge AI компьютер, разработанный стартапом Fast Sense. На данном этапе у робота одна активная рука, которой можно управлять, а в будущем появится вторая. Аватар оснащен системой распознавания объектов для помощи оператору в захвате предметов. На данный момент робот обладает рядом ограничений, которые команда Dragon Tree Labs планирует устранить для повышения шансов на победу, в том числе за счет привлечения новых участников в проект.

До полуфинальных тестов в сентябре 2021, которые пройдут в США аватар обзаведется второй управляемой рукой, разными типами хватов, научится распознавать больше объектов. Также команда стремится обеспечить быструю передачу сигналов для управления роботом из любого точки мира. Важный аспект для дальнейшей проработки то, как аватар обеспечивает иммерсивность, передаёт ощущение присутствия и полноту обратной связи от объектов.

Если вам интересно присоединиться к нашей команде и помочь с апгрейдом аватара Джонни, пишите нам на почту hello@dtlabs.tech или заполните анкету на сайте

#8 I-BOTICS - Нидерланды

Команда I-BOTICS разработала антропоморфного робота, его высота робота 184 см и вес 89 кг. Движения робота соответствуют кинематике человека. Он может балансировать, наклоняясь или работая в сидячем положении, а также сохранит баланс при ударе или столкновении. Однако, передвигаться по неровной поверхности робот не может, так как установлен на колесной базе и есть проблема с динамической балансировкой.

Оператор управляет роботом, находясь в специальной кабине с экранами для управления роботом. Присутствуют элементы экзоскелета. Оператор видит глазами робота через встроенную камеру в голове робота. Используется VR-перчатка для того, чтобы получать обратную связь с кисти робота. Робот безопасен при прикосновении к человеку. При этом рука робота способна поднимать груз весом 8 кг на руку. Таким образом, в будущем робот сможет ухаживать за пациентами, помогая им встать с постели.

В своем демо видео команда демонстрирует как оператор с помощью робота-аватара помогает шеф-повару в Норвегии, находясь на расстоянии более 850 км в Нидерландах.

#9 INBIODROID - Мексика

Команда INBIODROID разрабатывает робот-аватар Prometheus для выполнения спасательных работ при ликвидации последствий в зоне чрезвычайных происшествий.

#10 Pollen Robotics - Франция

Pollen Robotics - команда стартапа, основанного в 2016 году двумя робототехниками из известного во Франции исследовательского центра Inria. Среди прошедших в полуфинал это одна из самых маленьких команд. Штат Pollen Robotics - это всего шесть человек, увлеченных претворением в жизнь своих идей и замыслов. С другой стороны это полностью открытая команда, призывающая к сотрудничеству единомышленников.

На сайте стартапа пока не отображен ход работы над проектом аватара для $10M ANA Avatar XPRIZE. Зато Pollen Robotics щедро делится своими знаниями и наработками по предыдущим реализованным проектам. Все они являются открытыми (open source), как и стартап.

В активе команды имеется открытая роботизированная платформа с интерактивным гуманоидным роботом Reachy, который очевидно и является прообразом аватара для $10M ANA Avatar XPRIZE. На сайте https://github.com/pollen-robotics выложена документация по отдельным компонентам Reachy.

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

#11 proRobot - Чехия

Робота Fimbot создает команда proRobot из чешского университета в городе Градец-Кралове. Свое название робот получил по первым буквам факультета Информатики и управления (FIM - Faculty of Informatics and Management). Команда разработчиков состоит из студентов старших курсов и преподавателей этого университета.

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

За основу 3D печатной конструкции Fimbot взят известный гуманоидный робот InMoov, созданный французом Гаэлем Ланжевеном. Отдельные конструктивные элементы робота создаются методом 3D печати из биоразлагаемого материала PLA (polylactic acid) это полимер молочной кислоты.

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

У проекта Fimbot пока один спонсор, но зато это компания Skoda Auto.

Видео комапнии можно посмотреть на странице в FB

#12 Rezilient - США

Компания Rezilient из США представляет в полуфинальном конкурсном отборе ANA Avatar XPRIZE проект Rezilient Health. Компания занимается оказанием медицинских услуг, отличающихся инновационным характером в организационном и технологическом плане. Роботизированная телемедицина одно из перспективных направлений в деятельности Rezilient.

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

#13 Robot Guru

Проект Robot Guru реализуется на платформе уже готового продукта HoloSuit, куда входят куртка, брюки и перчатки, оснащенные набором датчиков и системой двусторонней беспроводной связи (Wi-Fi и Bluetooth LE) с программным обеспечением на базе Windows, iOS, Android. Встроенные датчики позволяют контролировать все движения человека.

Основанная в 2016 году компания сегодня предлагает свою разработку под названием Holosuit для применения в медицине, робототехнике, промышленности, образовании, спорте и играх.

В стандартный комплект HoloSuit четырех размеров для взрослого человека входят:

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

  • Holosuit может подключаться через Bluetooth, Wi-Fi и USB.

  • 6 датчиков движения на куртке.

  • 5 датчиков движения на брюках.

  • 12 датчиков движения на перчатках (по 6 на каждой перчатке).

  • 9 сенсоров с обратной связью (4 на куртке, 3 на брюках, по 1 на каждой перчатке).

  • 8 кнопок управления (4 на куртке и по 2 на каждой перчатке).

По заказу комплекты HoloSuit комплектуются лицензионным программным обеспечением. Компания HoloSuit обладает большим опытом исследований и разработки аппаратного/программного обеспечения, имеет более 37 патентов, выданных в США, ЕС, Китае, Корее.

#14 Synapse - Япония

Команда Synapse объединяет четыре экспертные группы, сформированные на базе известных компаний NTT Communications, Tokyo Robotics, XELA Robotics и университета Васэда. NTT Communications известная во всем мире телекоммуникационная компания. Основанная в 2015 году Tokyo Robotics занимается роботизацией складских и офисных помещений, ферм и даже домов. В компании XELA Robotics занимаются исследованиями и проектированием надежных и безопасных захватов и манипуляторов для роботизированных систем. Группа из престижного университета Васэда в г. Токио занимается созданием эффективных тактильных датчиков.

Команда Synapse уже имеет в своем активе гуманоидного робота общего назначения под названием TOROBO. На очереди создание робота AVATAR, который будет обеспечивать функции телеприсутствия. Каждая группа команды объединяет квалифицированных специалистов в своих областях: телекоммуникации, робототехника, удаленное управление и тактильные датчики. Демо видео - можно посмотреть по ссылке

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

#15 SNU - Южная Корея

Команда SNU представляет Сеульский национальный университет (SNU). Основа команды студенты из пяти лабораторий университета и преподаватели, занимающиеся проектированием гуманоидных роботов. Среди них есть специалисты в области управления с использованием тактильных ощущений, разработки мягких сенсорных материалов. Интеграцией всей системы руководит профессор Пак Джэ Хын.

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

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

#16 UNIST - Южная Корея

Второй южнокорейской командой, прошедшей в полуфинал ANA Avatar XPRIZE стала команда Национального института науки и технологий (UNIST) из г. Ульсан. В команде представлены студенты и аспиранты из лаборатории BiRC (Биоробототехника и управление) под руководством профессора и научного сотрудника с докторской степенью.

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

В группе Мягких компонентов исследуют материалы для датчиков, которые помогут в создании захватов для аватара и перчаток с тактильными сенсорами для оператора

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

#17 Telexistence - Япония

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

Ключевые особенности технологии Telexistence:

  • Расширенные интерфейсные возможности подключения через стандартный проводной или беспроводной интернет.

  • Незначительная задержка при передаче сигналов управления.

  • Гибридная интеллектуальная система управления.

  • Уникальная механическая конструкция робота.

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

В 2018 году Telexistence анонсировала серийный прототип удаленно управляемого робота MODEL H, в котором используются технологии дистанционного управления, виртуальной реальности и тактильных ощущений. Ориентированная в первую очередь на коммерческое применение MODEL H отличается простотой и долговечностью использования, сокращенным временем загрузки и запуска, имеет собственную облачную инфраструктуру с доступом через проводной и мобильный Интернет.

В июле 2020 года компания объявила о разработке модели T, в которой применяется для розничной торговли технология AWP (Augmented Workforce Platform), предполагающая использование гуманоидных роботов манипуляторов с управлением от удаленного оператора. Рука робота обеспечивает точную и быструю укладку товаров различной формы. Модель T обеспечивает повышенную точность удаленных манипуляций и минимизированную задержку управляющих сигналов при плотном трафике в телекоммуникационной среде.

#18 Touchlab - Великобритания

Команда сформирована на базе компании Touchlab из шотландского г. Эдинбург и занимается исследованиями в области роботехники. Одно из основных направлений деятельности компании разработка и производство электронной кожи eDermis с встраиваемыми в нее сенсорными датчиками.

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

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

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

#19 Virtual Sapiens - Индия

Команда Virtual Sapiens объединила усилия и наработки трех коллективов в области систем виртуальной реальности, робототехники и передачи тактильных ощущений.

PropVR является удобной платформой для интерактивной 3D визуализации чертежей и изображений. Эта платформа, созданная на основе технологий искусственного интеллекта (AI), виртуальной и дополненной реальности (VR/AR), идеально подходит для создания систем удаленного телеприсутствия. Компания уже предлагает варианты использования своей платформы для виртуального присутствия в медицине, торговле и электронной коммерции, при продаже недвижимости, в туристическом бизнесе.

Компания Vicara занимается исследованиями в области технологий виртуальной реальности, которые позволяют управлять с помощью жестов оператора роботом, дронами, игровыми и другими виртуальными объектами. Компания имеет два продаваемых устройства удаленного управления, способных отслеживать малейшие движения кистей рук и пальцев (менее 1 мм). Задержка сигнала управления менее 10 мс. Устройства поддерживают Bluetooth Low Energy 5.0. По замыслу Virtual Sapiens контроллеры Vicara позволят успешно взаимодействовать с удаленным аватаром через каналы связи.

Компания Invent Robotics разрабатывает и продает роботов Mitra, которые способны автономно перемещаться в пространстве, распознавать людей по лицу и поддерживать с ними беседу. Компания позиционирует их в качестве роботов для приема гостей в различных учреждениях и для обслуживания посетителей в кафе и ресторанах. Заложенные в Mitra потенциальные возможности создания новых интерфейсов позволят Virtual Sapiens использовать этого робота в качестве аватара.

#20 GITAI - Япония

Команд GITAI разрабатывает робот-аватар для космической миссии на МКС при поддержке Японского космического агентства. В начале 2021 года в рамках Раунда В стартап GITAI получил инвестиции в размере 1.8 млрд йен (~16,5 млн. долл).

На конкурс $10M ANA Avatar XPRIZE представлен робот-аватар в виде верхней части тела человека (планируется добавить еще 2 руки) на мобильной колесной платформе. Робот способен производить манипуляции с мелкими предметы, открывать застежку-молнию, захватывать очень тонкие предметы.

На руках 2 датчика с опцией осевого вращения в кистях и руках (3 оси вращения на кончиках пальцев). Тактильные ощущения передаются оператору. Оператор управляет роботом при помощи специальной установки, а информацию об окружающей среде робота получает через глаза робота в свои VR очки и в наушники. Перемещением робота оператор управляет педалями.

В настоящий момент проходят процедуры тестов с NASA и эксперименты для работе робота на МКС. Эксперименты в космосе планируются на 2022-23 гг.

В этом обзоре мы разобрали только 20 полуфиналистов соревнования $10M ANA Avatar XPRIZE, и совсем скоро расскажем об оставшихся 28 командах. Подписывайтесь на наш блог, чтобы не пропустить выход статьи.

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

Мы в Dragon Tree Labs также убеждены, что для совершения прорыва нужны коллаборации, совместные тесты и разработки. Это позволит быстрее дать миру принципиально новую платформу и отыскать значимые коммерческие применения систем, которые облегчат жизнь человека.

Наша команда Dragon Tree Labs объединяет индивидуальных разработчиков и команды робототехников для общей цели создать робота-аватара, который стал бы продолжением человека, который им управляет, расширил возможности телеприсутствия и дистанционного физического взаимодействия с объектами реального мира. Мы приглашаем к диалогу университеты и рисерч лабы, которым интересен этот проект. А для студентов открыта оплачиваемая стажировка на лето 2021 года.Подробности о стажировке на сайте Dragon Tree Labs

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

Все, кто хотят присоединиться к проекту, могут писать на hello@dtlabs.tech

Участие в соревновании $10M ANA Avatar XPRIZE для нас это возможность открыть горизонты для всех участников нашей команды, которые по отдельности могут многое, но вместе могут совершить невозможное.

Подробнее..

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

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

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


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

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



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

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

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

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

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



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

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

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

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

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

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

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

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


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

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

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

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


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

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

 EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UP; 

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

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



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

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

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

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

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

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

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

EPwm1Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE;

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

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


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

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

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



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

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

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

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


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



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

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

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



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

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


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

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

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

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

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

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

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

Заключение


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

Категории

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

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