Как стать автором
Обновить

Миландр + GCC + VSCode. Пробуем мигать светодиодом на отечественном ARM32 микроконтроллере

Уровень сложностиСредний
Время на прочтение15 мин
Количество просмотров12K
Всего голосов 22: ↑20 и ↓2+26
Комментарии51

Комментарии 51

ИМХО, вопрос "почему c++" по отношению к микроконтроллерам устарел.

Даже для куда более простых MK в Arduino IDE есть C++ 20, почему бы его не использовать?

Библиотеки для Arduino с использованием шаблонов пишут, например. Да и в своём коде классы, шаблоны и банальное слово auto вполне могут пригодиться.

Тем не менее, насколько мне известно, Keil и IAR остаются самыми распространенными инструментами для программирования МК, и их компиляторы поддерживают максимум cpp14 (хотя вообще у armclang из кейла есть community поддержка gnu++17)

C++14 - это уже весьма неплохо.

Однако даже и они далеко не всегда используются, и часто пишут просто на C.

Библиотека LoRa MAC даже написана на С, а не на плюсах, хотя казалось бы

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

Ну так я не осуждаю же)

Просто вот что есть

Это далеко не показатель. Тут вероятно два момента. Первый - не привязываться к мейнстримным компиляторам, поскольку это же Лора - гипотетически завтра ее захотят запустить на каком-нибудь новом китайском дешёвом чипе, для которого не будет поддержки новых стандартов со стороны компиляторов. Да и обработка там может и не тривиальная, но врядли требующая реализации множества сложных сущностей. Второй - все таки в сообществе эмбедед МК пока что довольно много чего разрабатывается по традиции на С. И не потому что с С++ беда, а скорее из-за большого количества проектов на С и не таких раздутых в сложности и обладающих высокой изменчивостью в процессе разработки систем, как это на десктопах часто бывает.

FatFs тоже почему-то написали на Си а не на С++.

Заминусовать можно что угодно - еще бы пояснили, что именно хотели сказать этим минусом. По сравнению с C++98 или C++03 даже C++11 - уже значительный шаг вперед. А если это становится доступным для МК - чем плохо?

IAR утверждает следующее:

The IAR C++ implementation fully complies with the ISO/IEC 14882:2014 C++ (“C++14”) or 14882:2017 C++ (“C++17”) standard, except for source code that depends on thread-related system headers, or the filesystem header. In this guide, the ISO/IEC 14882:2017 C++ standard is referred to as Standard C++.

Как видно для сложных систем с RTOS C++ и негомогенной памятью C++ плохо подходит. И создаст больше проблем чем предоставит преимуществ. Его ниша - это Arduino или большие embedded системы. Т.е. там где постарались изолировать приложения в некой песочнице, подальше от нюансов архитектуры памяти.

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

Да, выделение памяти проблема. Я вам скажу, что все развитые RTOS имеют свои механизмы выделения памяти никак не связанные с механизмами C++. Я работал и с FreeRTOS, и с uCOS, и с Nucleus Plus, MQX, mbed, Keil RTX, теперь ThreadX и Azure RTOS - везде были свои хипы и пулы.
Но что ещё печальней, сама память в разных областях имеет разные свойства. Внутри чипа есть HSRAM, DTCM , Standby RAM, SRAM1, SRAM2 , последний тренд - это Secure и Nonsecure RAM и т.д. У каждой разные тайминги доступа со стороны процессора и со стороны DMA, разное кэширование, разное управление доступом. Если у вас больше 5 одновременных каналов DMA, то планирование размещения в пулов памяти целый квест если нужно избежать заторов потоков данных.

никак не связанные с механизмами C++

Как и в целом с механизмами любого языка.

У каждой разные тайминги доступа со стороны процессора и со стороны DMA, разное кэширование, разное управление доступом

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

Если у вас больше 5 одновременных каналов DMA

Ну DMA - это тоже своего рода многопоточность. И её можно вполне использовать вместе с переключением контекста ОСРВ. Да, не получится с ней использовать, например, <threads>, однако у ОСРВ есть свои механизмы для этого, и использовать их вместе с плюсами, равно как и с любым другим языком - это вопросы архитектуры программы.

Как и в целом с механизмами любого языка

Это утверждение спорное. Потому что в embedded язык тесно связан с архитектурой. И скажем, C может поставляться с так называемым tailoring и retargeting. Retargeting для C сравнительно легко делается. Приходилось делать его под использование пулов RTOS. Т.е. язык C работал с нативными механизмами выделения памяти RTOS. Но в C++, как вы правильно сказали, все гораздо сложнее. Там обращение к динамической памяти просто бесконтрольное. И это проблема, затмевающая все остальные.

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

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

А в C ничего такого в библиотеках нет. Там только сторонние сорсы надо перепроверить. Хотя тоже трудно.

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

Все эти очереди, списки, стеки и сортировки из std можно вообще не использовать, и все равно иметь RAII, возможность писать действительно обобщенный код с использованием шаблонов, constexpr/consteval, type traits, и так далее.

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

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

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

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

Фокус в том что это не мне надо с reserve() знакомиться, а скорее вам. Нужно как минимум залезть в его внутренности и убедится что он работает с той RAM с которой нужно. Это нужно как раз для того чтобы бездумно не махать reserve. Если конечно у вас достаточно комплексное приложение. Если Arduino или просто хобби, то не парьтесь.
На С можно сделать всё. И на плюсах можно сделать всё. Давайте договоримся об этом не спорить.

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

Он работает с той RAM, с которой нужно. Я же говорил, что для этого есть несколько путей:

  1. определить глобально operator new, который будет делать аллокацию из некого дефолтного пула. Generic подход, если это устраивает в поведение проекта.

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

У нас реализуется оба подхода: есть некий дефолтный подход и есть аллокаторы, типа (есть под разные задачи):

template<typename T, std::size_t Alignment, Pool& Pool = g_cachedPool>
class AlignedAllocator;

using CacheLineAlignedAllocatorCached = AlignedAllocator<uint8_t, CACHE_LINE_SIZE, g_cachedPool>>
using CacheLineAlignedAllocatorUncached = AlignedAllocator<uint8_t, CACHE_LINE_SIZE, g_uncachedPool>>;

Использование примерно такое:

std::vector<uint8_t, CacheLineAlignedAllocatorUncached> vec;

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

Да даже с обычным operator new, можно гибкости добавить

void* operator new  ( size_t size, const MemAllocInfo& allocInfo);
void* operator new[]( size_t size, const MemAllocInfo& allocInfo);

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

Так что моё мнение, что утверждение:

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

вызвано только незнанием инструмента.

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

Моя претензия была к фразе указанной выше, так что этот спор не про это. Но и "а отлаживать в микроконтроллерах сложнее", то и тут плюсы помогают. Повторяюсь: при грамотном использовании, а не уровня С-с-классами. Как минимум RAII, шаблоны очень помогают не отстреливать себе ноги на ровном месте. Повторюсь: это как минимум. И в мешанине "void ptr" куда сложнее запутаться.

И да, я не топлю за то, что бы ВСЕ возможности тащили в embedded. В ограниченных условиях вполне можно исключить использование RTTI, исключений и т.п. и связанного с этим оверхеда в плюсовом рантайме. По понятным причинам, инкапсулировать в std::thread функциональность тредов того же ThreadX (ныне Azure) или FreeRTOS, хоть и возможно, но ценой потери гибкости, а значит не сильно и нужно (это не мешает нам использовать свою обёртку над примитивами RTOS с необходимым функционалом). Это касается и других частей. Хотя вот stack-less корутины хотелось бы пощупать на baremetal...

Вы просто написали свою реализацию. Это все тот же спор что можно, а что нельзя сделать на некоем языке. Реализаций своих и у меня достаточно. И конечно их не две. Еще нужны пулы с блоками фиксированной длины, нужны алокаторы с защитой от одновременного доступа, нужны алокаторы с ожиданием, нужна статистика памяти и проч. и проч.
Я же говорил о стандартных библиотеках. Вы не показали внутренности reserve() и не показали, как конкретно reserve() работает с памятью. Также не показали как выглядит отладка шаблона по шагам в окне отладчика.

Ок, вы может и найдете исходники reserve() в директории своего компилятора. Но откуда вы знает как это выглядит в моем компиляторе?

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

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

Как видно для сложных систем с RTOS C++ и негомогенной памятью C++ плохо подходит. И создаст больше проблем чем предоставит преимуществ.

Изначальное утверждение из-за которого идет спор.
Как C, так и C++ вынуждены использовать разные способы выделения памяти для разных целей. Логично что и освобождение памяти должно происходить по-разному. С++ позволяет обернуть это выделение в отдельный тип, облегчить слежение за освобождением памяти, и продолжить использовать тот же самый код.
Внутренности reserve() не обязательно знать, описание поведения оставляет очень мало простора для интерпретации.

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

Мне кажется, что тут больше речь про функционал из заголовочников типа thread и filesystems. Я заворачивал threadx в std::thread, не хватает гибкости. А так, просто не реализовали нужное.

C++ для микроконтроллеров? Ну хз.

По идее надо отключать RTTI, не использовать динамическую аллокацию памяти, исключения.

Нафиг оно надо.

Кому интересны подробности см. например тут

Они отключены - в CMakeLists это прописано.

Динамическая аллокация - не особенность плюсов, она есть и в си; и плюсы дают больше возможностей к её неиспользованию.

Просто в плюсах динамическая аллокация много где есть.

Какой-нибудь vector например.

Слишком много чего использует динамическую аллокацию под капотом.

А зачем использовать вектор, боясь при этом динамической аллокации? Зато можно сделать свой класс для вектора на статической аллокации. Это можно сделать и на чистом С, но придется прятать кишки в отдельный файл и повозиться с тем, чтобы не засорять пространство имен.

Или взять std::array :-)

Можно сделать "свой vector", и заиметь при этом преимущество в виде деструктора

Можно просто аллокатор сделать и вектору передать.

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

  2. или используй std::array

  3. или определи буффер, и оберни его аллокатором, который передай в std::vector. Если процесс детерминирован, таких нарезок сделать можно сколько нужно

Только недавно чинил утечку на Си-шном проекте, связанную со strdup()... Это так, к слову :)

А как обстоят дела с наличием микроконтроллеров Миландр?

Ну моё руководство говорило, что у нас есть возможность заказать необходимое количество (около 1000), если мы на них перейдем. Да, с АлиЭкспресс не заказать по 150р как stm32f103 (Миландр идут по ~1200р за штуку, как я знаю), но заказать можно

с али вы получите не stm32f103, а китайский клон... настоящий stm - это ещё поискать...

1200р - это даже как-то вполне приемлемо для отечки имхо...

а китайский клон... 

Перешли на GigaDevice. Нареканий нет. Коллеги по цеху переходят на Arthery.

Про китайский клон - это да. Сейчас с октября у них ещё какая-то партия хреновая, которая отваливается при отключении лишних jtag пинов (SWD_NOJTAG макрос из HAL), чего на старых клонах не происходило. Хотя возможно раньше оригинальные клали, или левые с того же производства

А физикам реально приобрести? Но 1200... явно только поиграться.

Есть дистрибьюторы. Мб есть на ЧиД, смдру или аймаксай

Что-то бегло пошукал - не нашёл. Есть отладочные платки на 4900, но там похоже более урезанный чип, судя по частотам.

Как бы купить дешёвую учебную отладочную плату для изучения микроконтроллеров Миландр?

Я у миландра на сайте нашёл ссылку:

https://ldm-systems.ru/catalog/milandr

Вот эти вполне доступные и вроде как сейчас в наличии (если верить каталогу):

https://ldm-systems.ru/product/19036

https://ldm-systems.ru/product/19001

Но, конечно, подороже, чем, например, STMовские Nucleo. С другой стороны, у ST объём производства и рынок больше, так что могут позволить себе меньшую стоимость.

Я могу ошибаться, но цены LDM Systems далеки определения "дешёвый". Особенно если учесть, что функционал (поправьте, тут я не уверен) описываемого контроллера в статье не сильно отличается от stm32f103, который можно (ну или его клона, но если это GigaDevice, то и не сильно важно) купить за условные 150 руб в виде готовой платы. Да, переферии там нет, но для быстрого знакомства на бредборде - вполне себе.

Ну я же написал "доступный", а не "дешёвый" ;)

Сейчас bluepill с оригинальными stm32 найти тяжело, и даже клон там может оказаться "с особенностями". Так, мы недавно закупали с али 10шт таких, и приехавшие МК отваливаются в reset handler при отключении jtag пинов (у оригинальных такой проблемы нет, а отключение этих выводов "кубик" генерирует автоматически, если использовать SWD)

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

P.S. да, функционал данного контроллера не сильно отличается от f103 (хотя имеет пару необычных особенностей, мне понравилось)

Эх, богат и могут русский язык :)

На Nucleo, если я не ошибаюсь, распаян st-link? Хотя глянул, на Nucleo-32 нет, на бОльших - есть, но цена как-то рядома :-\

отваливаются в reset handler при отключении jtag пинов (у оригинальных такой проблемы нет, а отключение этих выводов "кубик" генерирует автоматически, если использовать SWD)

Если не секрет, то какие именно клоны? маркированы как STM32 (т.е. подделка и закос) или другие (APM32/GD32/etc)?

ИМХО, если выбросить "на С++" из предложения, то смысл не поменяется. У нас тут заказчик со своими мега-идеями, который на новой версии железа совсем другой PD Phy поставил, и о чём узнаю совершенно случайно и благо, на этапе дизайна. Убедили, что оставить текущий выйдет тупо дешевле и быстрее можно с продуктов выйти на рынок. Но это мелочи, он посреди разработки решил менять SoC (типа более дешёвый). Не, ну тут дело житейское, хотя, ну что стоило довести текущий дизайн до ума, выйти на рынок и делать BOM Cost Reduction. Стартапы... они такие. При этом проводки и коннекторы для GMSLv2 как были проблемными, так и не заменены. Причём настолько проблемными, что связка Serializer/Deserializer вообще не работает, а отрезаешь коннекторы, паяшь напрямую - работает :)

Видимо те "умники" просто запутались в языке С++ и не смогли довести разработку относительного простого прибора до финала.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории