image

Всем привет! Как часто вы задумываетесь как код написанный в красивой IDE превращается в набор байт, удобоваримый для процессора или микроконтроллера? Вот я лично не часто. Работает себе и хорошо. Нажал кнопку Add File — IDE сама добавила исходник в проект, сама вызывает компилятор и линковщик. Пиши себе код и не думай о мелочах.

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

Сегодня речь пойдет о замене билд системы для моего проекта. По различным причинам мне было тесно с Ардуино и пришлось искать что нибудь где можно развернуться. Под катом описан мой опыт перехода от билд системы Ардуино к сборке прошивки под микроконтроллер STM32 и фреймворк stm32duino с помощью CMake.

Билд система Ардуино


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

Итак, я бы выделил в Ардуино следующие вещи:

  • Arduino IDE — редактор, компилятор и набор библиотек в одном флаконе. Позволяет быстренько стартовать с небольшим проектом или же собрать проект скачанный из интернета. Нет необходимости в инсталляции дополнительных средств — все есть из коробки.
  • Ардуино платы — опенсорсный дизайн плат на процессорах ATMega (или других от Atmel). Платы более-менее стандартизированы и под них существует множество шилдов и периферии.
  • Ардуино фреймворк — набор C++ классов, интерфейсов и библиотек, которые скрывают низкоуровневую логику и регистры микроконтроллера. Пользователю предоставляют удобный достаточно высокоуровневый интерфейс для работы.

Каждая из этих частей является независимой от остальных. Так в ArduinoIDE можно писать и под другие микроконтроллеры (например под ESP8266), или наоборот отказаться от ArduinoIDE и познавать все прелести Ардуино где нибудь в Atmel Studio, Eclipse или даже vim. Некоторые платы достаточно не похожи на платы Arduino (например полетные контроллеры квадрокоптеров), но при этом в них легко можно вливать Ардуино скетчи. И наоборот, под платы Ардуино можно программировать на голом C или ассемблере. Что касается Ардуино фреймворка, то можно отметить, что он портирован на многие микроконтроллеры (STM32, TI) что опускает порог вхождения в этот мир до приемлемого уровня.

image
Посредине плата на stm32f103c6 (по факту stm32f103cb), справа arduino nano. Фото отсюда (тут чуть более глубоко затронут вопрос stm32duino)

В своем проекте я практически сразу отказался от Arduino IDE в пользу более привычной мне Atmel Studio. От плат Ардуино я также отказался в пользу STM32 как более мощной платформы с чуточку бОльшим количеством периферии и памяти. От Ардуино фреймворка отказываться очень не хотелось бы — он достаточно красив, удобен и предоставляет все что нужно. Я использую stm32duino — порт ардуино под микроконтроллеры STM32. А вот SPL (православная абстракция над регистрами и начинкой контроллера от самой ST) как то не зашла — код слишком громоздкий и некрасивый.

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

  • Невозможность разложить исходники по директориям

    Если исходников всего пару штук то и ориентироваться в них просто. Если количество файлов в проекте переваливает за несколько десятков то скидывать их все в кучу не очень хорошая идея. Удобно разные компоненты или независимые части проекта разложить в разные директории и, при необходимости, прописать include path’ы.

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

  • Библиотеки нужно инсталлировать

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

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

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

  • Настройка параметров библиотек выполняется в самой библиотеке

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

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

    И, опять же, открытым остается вопрос контроля версий настроечного файла

  • Проект всегда пересобирается целиком

    Даже если поменялась какая то мелочь в одном файле нужно все равно ждать пока перекомпилятся все файлы. В моем случае это около минуты.

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

  • Наконец, нельзя гибко менять ключи компиляции

    Даже настройки оптимизации нельзя поменять. А уж про тонкую настройку можно забыть. Да что там говорить — нельзя прописать include path или дефайн!

Изучаем пациента


image

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

Первым делом система сборки пытается сбилдить файлик скетча (.ino) с ключиком -o nul (ничего не пишем, собираем только ошибки компиляции). Если компилятор не находит какой нибудь хедер, билдсистема ищет нет ли там этого хедера в установленных библиотеках. Если есть — добавляет ключик -I (дополнительный include path) и пытается сбилдить еще разок. Если система сборки не находит еще одного хедера — операция повторяется. Строка с путями для ключа -I накапливается и применяется к следующему файлу в проекте.

Кроме хитрости с ключиком -I система сборки будет также собирать и саму библиотеку. Для этого она пробует скомпилировать и все *.c и *.cpp файлы в директории библиотеки. Зависимости по хедерам для библиотек решаются уже описанным способом. Библиотека компилируется целиком, даже если не все файлы из нее используется. И хорошо, если линкер потом выкинет лишнее. А если нет?

Так, если в библиотеке объявлен какой нибудь глобальный объект (например класс Serial или SPI), то код для этого объекта всегда попадает в прошивку, даже если этот код на самом деле не используется. Т.е. нечаянно добавленная директива #include может добавить к прошивке пару лишних кило.

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

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

Я думаю для мелкого ардуиновского проекта с 1-2 библиотеками такой подход к сборке не создает слишком большой оверхед. Но мой проект из 25 cpp файлов и 5 библиотек стал компилироваться почти минуту. Знаете сколько раз запускаются компилятор, линкер и другие программы из toolchain? 240 раз!!!

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

Стоит отметить, что я исследовал работу билд системы при сборке под STM32 и фреймворк stm32duino. Но при сборке под классический ардуино на контроллере ATMega все практически тоже самое (только чуточку попроще)

Пробуем CMake


image

Коллега по цеху порекомендовал посмотреть на CMake — там уже есть готовые тулчены для сборки Ардуино проектов. Так, я установил CMake, взял минимальный проект из примеров, подпилил чуток CMakeList.txt. Но когда я запустил CMake, то получил креш в ld.exe на этапе конфигурации (когда проверяются компиляторы, линкеры и подобные штуки). Попытка понять что же именно происходит не увенчалась успехом — тот же самый комманд лайн запущенный отдельно выполнялся без проблем. Как это объехать я так и не понял.

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

Второй тулчейн выглядел получше. Он парсил ардуиновские файлы типа boards.txt и шарился по директориям variants, собирая все доступные варианты сборки, плат и процессоров. Тем не менее что-то меня в этом смутило. Показалось, что на выходе я опять получу монолит в стиле ардуино. Не очень понятно было заработает ли ARM компилятор вместо AVR. К тому же все равно не ясно как контролировать библиотеки.

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

CooCox


Я люблю писать код в хорошем IDE. В интернете часто упоминают самые популярные IDE для STM32 — Keil, IAR и CooCox. Первые 2 платные и с нелестными отзывами на тему юзабилити. А вот CooCox весьма хвалят. Подкупало также то, что он построен на базе Eclipse, а мне давно с ним хотелось познакомиться. Мне показалось, что это позволяет делать тонкую настройку процесса сборки. Забегая вперед, скажу, что увы, я ошибался.

Установив CooCox я был весьма удивлен, что он идет без компилятора. В целом это объяснимо — IDE отдельно а компилятор отдельно. Просто это как непривычно после многих лет в Visual Studio. Ну да ладно, это просто решается установкой компилятора отдельным инсталятором, хотя я, поначалу, взял компилятор из Atmel Studio.

image

И тут начались проблемы. Я битый час пытался завести моргалку на светодиоде. В интернете нашел с десяток вариантов разной степени компилябельности. Казалось бы, где там можно ошибиться? Ан-нет. Компилируем, заливаем — не работает. Я пересоздавал проект несколько раз — не помогало. В итоге пошел по самой нубской инструкции, четко и дословно выполняя все указания. Прошивку залил через UART и системный бутлоадер и лампочка радостно заморгала.

Хотелось бы обвинить stm32duino/libmaple USB бутлоадер, который и был причиной проблемы, но разумнее признать мое непонимание процесса инициализации на контроллерах STM32. Разобравшись в вопросе я описал свои “открытия” в отдельной статье. В двух словах проблема была в некорректном начальном адресе прошивки. При использовании бутлоадера прошивку нужно собирать с другим начальным адресом. Такого параметра, вообще-то, нет в классическом ардуино, но он весьма важен для микроконтроллеров STM32.

Мысли про STM32 бутлоадеры
Прошиваться через UART, конечно, можно, но это тот еще геморрой. Нужно постоянно переключать Boot перемычки и много кликать мышкой в ST Flash Demonstrator. Китайский ST-Link у меня есть, можно было бы его подключить, шить через него и дебажиться. Наверное, я еще дойду до этого, когда мне действительно понадобится внутрисхемная отладка.

Но на данный момент я рассматриваю прошивку через USB как самую удобную по нескольким причинам. Все равно у меня подключен USB шнур для питания и USB serial. Я так же собираюсь в скором времени активно ковырять USB Mass Storage. В конце концов в готовом устройстве также будет выведен USB. Так зачем использовать дополнительные компоненты, вместо того, чтобы прошиваться через USB?

Возвращаясь к теме конфигурационного менеджмента, и конкретнее где и как хранить библиотеки. Я тяготею к варианту “все свое ношу с собой”. Все изменения библиотек (включая их конфигурацию) я бы хотел версионировать в своем репозитории. Конечно же есть классические юниксовые патчи, но это выглядит как прошлый век. Но что использовать взамен? Хотелось бы решение в стиле “вытянул исходники одной командой, сбилдил, залил в МК”.

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

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

Так я затянул stm32duino для контроллеров серии STM32F1xx к себе в репо. Затянул целиком, с примерами и библиотеками. Если честно, то структура мне не нравится, и уж точно все оттуда мне не нужно. Но я не знаю как сделать по другому и как это потом мержить. Компенсировал тем, что разложил все файлики как мне нравится в CooCox проекте.

В итоге я затянул все нужные файлы в проект, выставил те же самые дефайны, что и Ардуино, после чего начался процесс линковки. Шел по ошибкам. Если не находился какой-то символ я искал в каком исходнике он объявлен и добавлял только этот исходник. Это был весьма интересный опыт, т.к. я по ходу разбирался что и где лежит (я имею в виду stm32duino/libmaple) и как оно там устроено.

image

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

Оказалось низкоуровневый код может ссылаться на символы, которые объявляются в скриптах линковщика и наоборот. Если линковщик вызывать со стандартными настройками, то эти символы не находятся. Чтобы решить эту проблему на странице настроек линковщика я снял галку ‘Use Memory Layout from Memory Window” — открылось поле scatter file. Туда я прописал скрипт линковщика, который использовался в билд системе ардуино (STM32F1/variants/generic_gd32f103c/ld/bootloader_20.ld). После этого все сразу слинковалось.

image

Но тут меня ждал неприятный сюрприз — собранная прошивка заняла сходу 115кб (на ардуино было 56к). При чем все символы там были перемешаны, а прошивка содержала очень много C++ рантайма — rtti, манглеры, abi, эксепшены и кучу еще всего ненужного. Пришлось сравнивать командлайны линкера, которые делает ардуино и CooCox и по каждому ключу курить документацию.

Оказалось в CooCox нельзя выбирать между C и C++ компиляторами. Из-за этого невозможна отдельная настройка каждого из компиляторов. Так С++ код компилируется с помощью gcc (а не g++) и по умолчанию генерирует тонну кода, отсылки на rtti, ABI и еще чего-то. И, похоже, это не так-то просто закостылять. Есть поле для дополнительных флагов компилятора, но если туда добавить что нибудь типа -fno-rtti, то gcc сразу начинает ругаться, мол что ты мне подсовываешь флаги от g++?

image

В интернетах народ предлагал CooCox второй версии — он уже имеет больше настроек, умеет различать C и C++ код и вызывает правильный компилятор для каждого из типов файлов. Но при этом все равно нельзя настроить отдельно ключи для каждого из компиляторов. Так что ключик -fno-rtti по прежнему нельзя добавить только для g++ — он так же передается и gcc, который на него злобно ругается.

А вообще второй CooCox как то не зашел — слишком много гламурного UI, при этом в ущерб удобству. Первый, кстати, тоже не супер в плане UI — миллион настроек, а шрифт в консоли билда поменять нельзя (при том, что в полноценном Eclipse можно).

Опять CMake


Раз уж такие известные тулы как CooCox не позволяют сделать банальную настройку компиляторов, то ну его нафиг. Будем спускаться на самый низкий уровень и писать все руками. Ну как руками… С мейкфайлами, конечно.

Последний раз я писал голые мейкфайлы почти 10 лет назад. Уже позабывал все тонкости, но точно помню, что дело это крайне неблагодарное. Дурацкий синтаксис и очень легко ошибиться. А еще крайне не портируемо. Решил еще разок попробовать CMake, но уже не с ардуино тулчейном, а STM32. При чем по всем правилам: отдельно установил эклипс, компилятор, CMake, MinGW32 для make.

На тулчейны особо не смотрел. На глаза попалось это, откуда я почерпнул общую идею, но сам тулчейн взял отсюда. Решил его не инсталлировать в общую кучу, а таскать с собой. К тому же мне там не все нужно, а только 2 файла — gcc_stm32.cmake где объявляются общие переменные и процедуры, и gcc_stm32f1.cmake где описываются параметры моего контроллера.

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

CMake скрипт для сборки библиотек (один скрипт на все библиотеки)
# Not pushing this file down to libraries in order to keep source tree as is (not populating with extra files, such as CMakeList.txt)
#
# Below each section represents a library with its own settings



###################
# NeoGPS
###################

SET(NEOGPS_SRC
	NeoGPS/DMS.cpp
	NeoGPS/GPSTime.cpp
	NeoGPS/Location.cpp
	NeoGPS/NeoTime.cpp
	NeoGPS/NMEAGPS.cpp
)
ADD_LIBRARY(NeoGPS STATIC ${NEOGPS_SRC})



###################
# FreeRTOS
###################

SET(FREERTOS_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/STM32duino/libraries/FreeRTOS821)

SET(FREERTOS_SRC
	${FREERTOS_SRC_DIR}/MapleFreeRTOS821.cpp
	${FREERTOS_SRC_DIR}/utility/heap_1.c
	${FREERTOS_SRC_DIR}/utility/list.c
	${FREERTOS_SRC_DIR}/utility/port.c
	${FREERTOS_SRC_DIR}/utility/queue.c
	${FREERTOS_SRC_DIR}/utility/tasks.c
)
ADD_LIBRARY(FreeRTOS STATIC ${FREERTOS_SRC})



###################
# Adafruit GFX library
###################

ADD_LIBRARY(AdafruitGFX STATIC 
	AdafruitGFX/Adafruit_GFX.cpp 
)



###################
# Adafruit SSD1306 library
###################

ADD_LIBRARY(AdafruitSSD1306 STATIC 
	STM32duino/libraries/Adafruit_SSD1306/Adafruit_SSD1306_STM32.cpp
)
TARGET_INCLUDE_DIRECTORIES(AdafruitSSD1306 PRIVATE
	STM32duino/libraries/Wire
	STM32duino/libraries/SPI/src  	#In fact it should not depend on it
	AdafruitGFX
)


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

Скрипт сборки основной части проекта
# Build rules for GPS logger target.
# App specific compiler/linker settings are also defined here

SET(SOURCE_FILES
	# Screens and screen management stuff
	Screens/AltitudeScreen.cpp
	Screens/AltitudeScreen.h
	Screens/CurrentPositionScreen.cpp
	Screens/CurrentPositionScreen.h
	Screens/CurrentTimeScreen.cpp
	Screens/CurrentTimeScreen.h
	Screens/DebugScreen.cpp
	Screens/DebugScreen.h
	Screens/OdometerActionScreen.cpp
	Screens/OdometerActionScreen.h
	Screens/OdometerScreen.cpp
	Screens/OdometerScreen.h
	Screens/ParentScreen.cpp
	Screens/ParentScreen.h
	Screens/SatellitesScreen.cpp
	Screens/SatellitesScreen.h
	Screens/Screen.cpp
	Screens/Screen.h
	Screens/ScreenManager.cpp
	Screens/ScreenManager.h
	Screens/SelectorScreen.cpp
	Screens/SelectorScreen.h
	Screens/SettingsGroupScreen.cpp
	Screens/SettingsGroupScreen.h
	Screens/SpeedScreen.cpp
	Screens/SpeedScreen.h
	Screens/TimeZoneScreen.cpp
	Screens/TimeZoneScreen.h

	8x12Font.cpp
	Buttons.cpp
	FontTest.cpp
	GPSDataModel.cpp
	GPSLogger.cpp
	GPSOdometer.cpp
	GPSSatellitesData.cpp
	GPSThread.cpp
	IdleThread.cpp
	TimeFont.cpp
	Utils.cpp
)

INCLUDE_DIRECTORIES(
	.
	${GPSLOGGER_LIBS_DIR}/AdafruitGFX
	${GPSLOGGER_LIBS_DIR}/NeoGPS
	${GPSLOGGER_LIBS_DIR}/STM32duino/libraries/Adafruit_SSD1306
	${GPSLOGGER_LIBS_DIR}/STM32duino/libraries/SPI/src
	${GPSLOGGER_LIBS_DIR}/STM32duino/libraries/FreeRTOS821
	)


# Do not link to libc or newlib-nano - we are not using anything from that
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --specs=nosys.specs")

ADD_EXECUTABLE(GPSLogger ${SOURCE_FILES})
TARGET_LINK_LIBRARIES(GPSLogger
	NeoGPS
	FreeRTOS
	AdafruitGFX
	AdafruitSSD1306
	ArduinoLibs
	STM32duino
)

STM32_SET_TARGET_PROPERTIES(GPSLogger)
STM32_PRINT_SIZE_OF_TARGETS(GPSLogger)

# Additional handy targets
STM32_ADD_HEX_BIN_TARGETS(GPSLogger)
STM32_ADD_DUMP_TARGET(GPSLogger)


Что касается параметров самого CMake. В примерах stm32-cmake предлагается указывать какой именно тулчейн файл использовать. Делается это отдельным ключиком во время вызова CMake для генерации мейкфайлов. Но я не планирую собирать проект под разные платформы и компиляторы. Поэтому я просто прописал ссылку на нужный тулчейн файл в главном CMakeLists.txt.

# Load the toolchain file that uses vars above
SET(CMAKE_TOOLCHAIN_FILE cmake/gcc_stm32.cmake)

А вот компилятор автоматически не угадывается. Точнее, угадывался бы на юниксе (он ищется по дефлотныму пути в /usr), но на винде его нужно указывать явно. В общем мой комманд лайн (не забываем менять виндовые слеши на юниксовые — CMake их не любит):

cmake -G "MinGW Makefiles" "-DTOOLCHAIN_PREFIX=C:/Program Files (x86)/GNU Tools ARM Embedded/5.4 2016q3"  .

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

Проблема: кросс зависимости между библиотекам


Фреймворк stm32duino довольно большой. Еще на этапе создания CMakeList.txt для библиотек я попробовал разделить его на 2 отдельные библиотеки — одну часть, которая эмулирует API Arduino (собственно stm32duino) и libmaple, которая представляет собой Hardware Abstraction Layer (HAL) и скрывает низкоуровневые штуки микроконтроллера. Во всяком случае мне показалось это логичным. Но оказалось это проблематично слинковать. Я думал, что stm32duino построен поверх libmaple, но во втором так же нашлись вызовы в верхний слой.

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

Потом я, правда, узнал, что у линкера есть специальные ключики -Wl,--start-group / --end-group которые меняют поведение линкера. Они как раз и заставляют его по нескольку раз проходить по библиотекам. Пока не пробовал.

CMake скрипт для сборки stm32duino и libmaple
###################
# STM32duino, libmaple and system layer
# Has to be as a single library, otherwise linker does not resolve all crossreferences
# Unused files are commented on the list
###################

SET(LIBMAPLE_SRC
	STM32duino/variants/generic_stm32f103c/board.cpp
	STM32duino/variants/generic_stm32f103c/wirish/boards.cpp
	STM32duino/variants/generic_stm32f103c/wirish/boards_setup.cpp
	STM32duino/variants/generic_stm32f103c/wirish/start.S
	STM32duino/variants/generic_stm32f103c/wirish/start_c.c
 	STM32duino/variants/generic_stm32f103c/wirish/syscalls.c

	STM32duino/cores/maple/cxxabi-compat.cpp
#	STM32duino/cores/maple/ext_interrupts.cpp
	STM32duino/cores/maple/HardwareSerial.cpp
#	STM32duino/cores/maple/HardwareTimer.cpp
#	STM32duino/cores/maple/IPAddress.cpp
	STM32duino/cores/maple/main.cpp
#	STM32duino/cores/maple/new.cpp
	STM32duino/cores/maple/Print.cpp
#	STM32duino/cores/maple/pwm.cpp
#	STM32duino/cores/maple/Stream.cpp
#	STM32duino/cores/maple/tone.cpp
	STM32duino/cores/maple/usb_serial.cpp
#	STM32duino/cores/maple/wirish_analog.cpp
	STM32duino/cores/maple/wirish_digital.cpp
#	STM32duino/cores/maple/wirish_math.cpp
#	STM32duino/cores/maple/wirish_shift.cpp
	STM32duino/cores/maple/wirish_time.cpp
#	STM32duino/cores/maple/WString.cpp

#	STM32duino/cores/maple/hooks.c
	STM32duino/cores/maple/itoa.c

#	STM32duino/cores/maple/stm32f1/util_hooks.c
#	STM32duino/cores/maple/stm32f1/wiring_pulse_f1.cpp
	STM32duino/cores/maple/stm32f1/wirish_debug.cpp
	STM32duino/cores/maple/stm32f1/wirish_digital_f1.cpp

	STM32duino/cores/maple/libmaple/adc.c
	STM32duino/cores/maple/libmaple/adc_f1.c
#	STM32duino/cores/maple/libmaple/bkp_f1.c
#	STM32duino/cores/maple/libmaple/dac.c
#	STM32duino/cores/maple/libmaple/dma.c
#	STM32duino/cores/maple/libmaple/dma_f1.c
#	STM32duino/cores/maple/libmaple/exc.S
#	STM32duino/cores/maple/libmaple/exti.c
#	STM32duino/cores/maple/libmaple/exti_f1.c
	STM32duino/cores/maple/libmaple/flash.c
#	STM32duino/cores/maple/libmaple/fsmc_f1.c
	STM32duino/cores/maple/libmaple/gpio.c
	STM32duino/cores/maple/libmaple/gpio_f1.c
	STM32duino/cores/maple/libmaple/i2c.c
	STM32duino/cores/maple/libmaple/i2c_f1.c
	STM32duino/cores/maple/libmaple/iwdg.c
	STM32duino/cores/maple/libmaple/nvic.c
#	STM32duino/cores/maple/libmaple/pwr.c
	STM32duino/cores/maple/libmaple/rcc.c
	STM32duino/cores/maple/libmaple/rcc_f1.c
#	STM32duino/cores/maple/libmaple/spi.c
#	STM32duino/cores/maple/libmaple/spi_f1.c
	STM32duino/cores/maple/libmaple/systick.c
	STM32duino/cores/maple/libmaple/timer.c
#	STM32duino/cores/maple/libmaple/timer_f1.c
	STM32duino/cores/maple/libmaple/usart.c
	STM32duino/cores/maple/libmaple/usart_f1.c
	STM32duino/cores/maple/libmaple/usart_private.c
	STM32duino/cores/maple/libmaple/util.c

	STM32duino/cores/maple/libmaple/stm32f1/performance/isrs.S
	STM32duino/cores/maple/libmaple/stm32f1/performance/vector_table.S

	STM32duino/cores/maple/libmaple/usb/stm32f1/usb.c
	STM32duino/cores/maple/libmaple/usb/stm32f1/usb_cdcacm.c
	STM32duino/cores/maple/libmaple/usb/stm32f1/usb_reg_map.c

	STM32duino/cores/maple/libmaple/usb/usb_lib/usb_core.c
	STM32duino/cores/maple/libmaple/usb/usb_lib/usb_init.c
	STM32duino/cores/maple/libmaple/usb/usb_lib/usb_mem.c
	STM32duino/cores/maple/libmaple/usb/usb_lib/usb_regs.c
)

ADD_LIBRARY(STM32duino STATIC ${LIBMAPLE_SRC})

TARGET_INCLUDE_DIRECTORIES(STM32duino PRIVATE
	STM32duino/system/libmaple/usb/stm32f1
	STM32duino/system/libmaple/usb/usb_lib
)

TARGET_COMPILE_DEFINITIONS(STM32duino PRIVATE 
	-DVECT_TAB_ADDR=${VECT_TAB_ADDR} 
	-DGENERIC_BOOTLOADER 
	-DBOARD_maple
)


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


Дальше не получалось нормально слинковаться со стандартной библиотекой — не хватало _sbrk(), _open()/_close(), _read()/_write() и некоторых других, которые почему-то торчат из стандартной библиотеки.

На самом деле у них есть тривиальная реализация в STM32duino\variants\generic_stm32f103c\wirish\syscalls.c, но она не линковалась по той же самой причине: в момент линковки stm32duino эти функции (они объявлены как weak) никому не нужны и выкидываются. Стандартная библиотека подключается неявно в самом конце процесса линковки и начинает требовать эти символы, а линковщик назад не возвращается. Можно, конечно, файл syscalls.c отдельно прилинковать после всех остальных, но чисто из спортивного интереса я стал разбираться откуда оно вообще лезет.

Тривиальная реализация некоторых системных вызовов
__weak int _open(const char *path, int flags, ...) {
    return 1;
}

__weak int _close(int fd) {
    return 0;
}

__weak int _fstat(int fd, struct stat *st) {
    st->st_mode = S_IFCHR;
    return 0;
}

__weak int _isatty(int fd) {
    return 1;
}

__weak int isatty(int fd) {
    return 1;
}

__weak int _lseek(int fd, off_t pos, int whence) {
    return -1;
}

__weak unsigned char getch(void) {
    return 0;
}

__weak int _read(int fd, char *buf, size_t cnt) {
    *buf = getch();

    return 1;
}

__weak void putch(unsigned char c) {
}

Как я понял, компилятор arm-gcc довольно могуч и может компилировать под добрую половину существующих микроконтроллеров и процессоров. Но поскольку это могут быть и мелкие микроконтроллеры с маленьким количеством памяти, так и толстые “камни” на которых линукс можно крутить, то и собирать нужно с учетом возможностей платформы. Поэтому в комплекте с линкером есть набор *.specs файлов, которые описывают что именно нужно линковать для той или иной платформы (это вдобавок к скриптам линкера).

Так по умолчанию к проекту прилинковывается стандартная библиотека, в данном случае это newlib-nano. Только если libc на больших компах опирается на системные вызовы и ядро, то в случае newlib-nano эти системные вызовы должен предоставить пользователь. Поэтому линковщик и требует объявления этих самых _sbrk(), _open()/_close(), _read()/_write().

Проблема решилась добавлением ключа --specs=nosys.specs в настройки линковщика. Этот ключик указывает линковщику на specs файлик где отключена линковка части стандартной библиотеки.

# Do not link to libc or newlib-nano - we are not using anything from that
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --specs=nosys.specs")

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


В какой то момент вылезла проблема с функцией _init(). Из newlib-nano есть вызов этой функции, но в коде этот символ нигде не объявлен. Мне кажется идея дизайна тут в следующем.

  • После ресета или подачи питания выполняется самый низкоуровневый старт. В случае libmaple это STM32duino\variants\generic_stm32f103c\wirish\start.S. Задача этого кода передать управление в сишную функцию инициализации

    start.S
            .type __start__, %function
    __start__:
            .fnstart
            ldr r1,=__msp_init
            mov sp,r1
            ldr r1,=start_c
            bx r1
            .pool
            .cantunwind
            .fnend


  • Следующий шаг это подготовить память. Тут мы чистим секцию .bss (переменные заполненые нулями) и заполняем начальными значениями переменные из секции .data, Код находится в файле STM32duino\variants\generic_stm32f103c\wirish\start_c.c

    start_c.c
    void __attribute__((noreturn)) start_c(void) {
        struct rom_img_cfg *img_cfg = (struct rom_img_cfg*)&_lm_rom_img_cfgp;
        int *src = img_cfg->img_start;
        int *dst = (int*)&__data_start__;
        int exit_code;
    
        /* Initialize .data, if necessary. */
        if (src != dst) {
            int *end = (int*)&__data_end__;
            while (dst < end) {
                *dst++ = *src++;
            }
        }
    
        /* Zero .bss. */
        dst = (int*)&__bss_start__;
        while (dst < (int*)&__bss_end__) {
            *dst++ = 0;
        }
    
        /* Run initializers. */
        __libc_init_array();
    
        /* Jump to main. */
        exit_code = main(0, 0, 0);
        if (exit) {
            exit(exit_code);
        }
    
        /* If exit is NULL, make sure we don't return. */
        for (;;)
            continue;
    }


  • После этого передается управление в функцию __libc_init_array() из newlib-nano. В этой функции происходит вызов инициализаторов, включая функции premain() и init() для инициализации платы, а также конструкторы глобальных объектов.

    __libc_init_array()
    /* Iterate over all the init routines.  */
    void
    __libc_init_array (void)
    {
      size_t count;
      size_t i;
    
      count = __preinit_array_end - __preinit_array_start;
      for (i = 0; i < count; i++)
        __preinit_array_start[i] ();
    
      _init ();
    
      count = __init_array_end - __init_array_start;
      for (i = 0; i < count; i++)
        __init_array_start[i] ();
    }

    premain() и init()
    // Force init to be called *first*, i.e. before static object allocation.
    // Otherwise, statically allocated objects that need libmaple may fail.
     __attribute__(( constructor (101))) void premain() {
        init();
    }
    
    void init(void) {
        setup_flash();
        setup_clocks();
        setup_nvic();
        systick_init(SYSTICK_RELOAD_VAL);
        wirish::priv::board_setup_gpio();
        setup_adcs();
        setup_timers();
        wirish::priv::board_setup_usb();
        wirish::priv::series_init();
        boardInit();
    }


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

  • Дальше идет вызов main(), но нам это сейчас не интересно.

Как я понял функция _init() должна подставляться согласно выбранной платформе, например ее реализация есть тут: <ARM Toolchain>\lib\gcc\arm-none-eabi\5.4.1\armv7-m\crti.o. Ардуино каким-то неявным образом подключает этот объектник, но у меня этот файл не подтягивался. Скорее всего это потому, что я отключил часть стандартной библиотеки ключиком --specs=nosys.specs. По рекомендации отсюда просто добавил пустую реализацию в код libmaple.

void __attribute__ ((weak)) _init(void)  {}

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

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

Проблема: неверно определены кодовые секции


Первое, что бросилось в глаза это стартовый адрес. Стартовый адрес был то ли 0x00000000, то ли 0x00008000, но точно не такой как нужно — 0x08002000. После внимательного изучения STM32 CMake тулчейна я понял, что нужные параметры выставляются в скрипте линковщика, который также идет с тулчейном. Только этот скрипт по умолчанию не используется, а включается отдельной командой STM32_SET_TARGET_PROPERTIES(). Стартовый адрес пофиксился, и прошивка даже похудела до 100к.

# Flash offset due to bootloader
SET(VECT_TAB_ADDR "0x08002000")
SET(STM32_FLASH_ORIGIN "0x08002000")

ADD_EXECUTABLE(GPSLogger ${SOURCE_FILES})
STM32_SET_TARGET_PROPERTIES(GPSLogger)

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

Я битый час пытался разобраться в линкер скриптах. Но там происходит какое-то очень сильное низкоуровневое колдунство: определяются какие-то секции, идет какая-то отсылка к коду. В частности, как я понял, секция с таблицей векторов прерываний должна описываться определенным образом где-то в коде (скорее всего в CMSIS, которого у меня-то и нет). Т.е. моя ошибка была в том, что я пытаться использовать generic скрипт линковщика с кодом инициализации от libmaple, а у них секции кода и данных называются и располагаются по разному.

Решение состояло в явном указании скрипта линковки от libmaple (STM32duino/variants/generic_stm32f103c/ld/bootloader_20.ld)

# Using Maple's linker script that corresponds maple/stm32duino code
SET(STM32_LINKER_SCRIPT ${GPSLOGGER_LIBS_DIR}/STM32duino/variants/generic_stm32f103c/ld/bootloader_20.ld)
LINK_DIRECTORIES(${GPSLOGGER_LIBS_DIR}/STM32duino/variants/generic_stm32f103c/ld)

Проблема: неверные настройки оптимизации


Но вот облом. Линковщик сказал, что он не может вместить сгенерированный код в мою флешку. Помимо того, что в прошивку попадает куча всякой лишней гадости, оказалось, что CMake по умолчанию собирает в дебажной конфигурации. А в дебажной конфигурации оптимизация -O2 (оптимизация по скорости), тогда как в релизной -Os (оптимизация по размеру). Я переключился на релизную конфигурацию, но с ней все равно не сложилось: тулчейн выставляет флаг -flto (Link Time Optimization), с которым не билдится половина stm32duino.

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

# TODO: It would be nice to use -flto for all of these 3 settings (except for asm one)
SET(CMAKE_C_FLAGS_RELEASE "-Os -g" CACHE INTERNAL "c compiler flags release")
SET(CMAKE_CXX_FLAGS_RELEASE "-Os -g" CACHE INTERNAL "cxx compiler flags release")
SET(CMAKE_ASM_FLAGS_RELEASE "-g" CACHE INTERNAL "asm compiler flags release")
SET(CMAKE_EXE_LINKER_FLAGS_RELEASE "" CACHE INTERNAL "linker flags release")

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

  • -DDEBUG_LEVEL=DEBUG_NONE отключает логгирование внутри libmaple. Выставление дефайна убирает примерно килобайт из результирующей прошивки
  • -fno-rtti -fno-exceptions — убирает огромную кучу кода (те самые RTTI, исключения, ABI и многое другое). Естественно, эти флаги я скормил только g++
  • Комбинация -fno-unroll-loops -ffast-math -ftree-vectorize просто генерит чуток более компактный код (100-200 байт на всю прошивку)

Вот мои ключики, которые я, в итоге, передаю компилятору:

SET(CMAKE_C_FLAGS "-mthumb -fno-builtin -mcpu=cortex-m3 -Wall -std=gnu99 -ffunction-sections -fdata-sections -fomit-frame-pointer -mabi=aapcs -fno-unroll-loops -ffast-math -ftree-vectorize -nostdlib -march=armv7-m --param max-inline-insns-single=500" CACHE INTERNAL "c compiler flags")
SET(CMAKE_CXX_FLAGS "-mthumb -fno-builtin -mcpu=cortex-m3 -Wall -std=c++11 -ffunction-sections -fdata-sections -fomit-frame-pointer -mabi=aapcs -fno-unroll-loops -ffast-math -ftree-vectorize -fno-rtti -fno-exceptions -nostdlib -fno-use-cxa-atexit -march=armv7-m --param max-inline-insns-single=500" CACHE INTERNAL "cxx compiler flags")
SET(CMAKE_ASM_FLAGS "-mthumb -mcpu=cortex-m3 -x assembler-with-cpp" CACHE INTERNAL "asm compiler flags")
SET(CMAKE_EXE_LINKER_FLAGS "-Wl,--gc-sections -mthumb -mcpu=cortex-m3 -march=armv7-m -mabi=aapcs -Wl,--warn-common -Wl,--warn-section-align" CACHE INTERNAL "executable linker flags")

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


Итог — 48 килобайт на прошивку. В варианте Ардуино было 56кб. Разница объясняется отсутствием malloc/free и сопутствующих функций, которые я все равно не использовал. Полная пересборка занимает 17с против минуты на ардуино. Инкрементальная сборка всего 1-2 секунды.

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

Для красоты добавил вызов STM32_PRINT_SIZE_OF_TARGETS() — теперь после сборки в консоли пишется статистика по памяти. Сразу видно не скакнуло ли потребление памяти.

image

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

FUNCTION(STM32_ADD_DUMP_TARGET TARGET)
    IF(EXECUTABLE_OUTPUT_PATH)
      SET(FILENAME "${EXECUTABLE_OUTPUT_PATH}/${TARGET}")
    ELSE()
      SET(FILENAME "${TARGET}")
    ENDIF()
    ADD_CUSTOM_TARGET(${TARGET}.dump DEPENDS ${TARGET} COMMAND ${CMAKE_OBJDUMP} -x -D -S -s ${FILENAME} | ${CMAKE_CPPFILT} > ${FILENAME}.dump)
ENDFUNCTION()

QtCreator


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

Я вспомнил, что я когда-то программировал в Qt Creator и это, вроде, было неплохо. Установив Qt Creator 4.2.2 я стал гуглить как подключить CMake проект. Согласно инструкциям в интернете предлагалось просто открыть CMakeLists.txt файл и следовать инструкциям в визарде. Первым делом он предложил насетапить инструментарий (kits). Вполне разумно, учитывая жуткую кастомность сборки.

image

Qt Creator ругается на такой kit, мол не выставлен компилятор. Но если выставить, то он все равно ругается — компилятор который выставляет сам CMake (arm-gcc) не совпадает с выбранным в соответствующем поле (даже если он такой же). Впрочем, все билдит он нормально.

При настройке проекта возник один очень нетривиальный момент. При импорте CMake проекта импортировался только CMakeLists.txt. Никакие исходники в проект не добавлялись. Я штудировал интернет несколько вечеров — безрезультатно. У всех все работает, а у меня нет. Думал проблема в кастомном тулчейне, но простейший hello world под MinGW32 точно так же не импортировался.

Решение подсказал сам QtCreator. Ворнинг в тултипе на созданный мною kit гласил, что я выбрал неверный тип CMake генератора. Нужно выбирать Code Blocks — без этого Qt Creator не может парсить проекты. После установки правильного генератора все файлы и поддиректории появились в окне проекта.

image

Теперь можно удобно навигироваться по файлам, но автокомплит пока еще не работал. На самом деле как бы странно это ни звучало, но Qt Creator попросту игнорил инклуд пасы указанные в CMakeLists.txt. Т.е. сборка проходит отлично, а в редакторе бОльшая часть #include’ов подсвечивает как ошибка (No such file). Работают только инклуды из той же самой директории. Я излазил все настройки, несколько часов гуглил, но ответа так и не нашел.

UPDATE: в комментариях подсказали, что автокомплит работает через компилятор. Поэтому важно выставить правильный компилятор в настройках kit'а. Qt Creator, правда, все равно ругается, но автокомплит работает.

Одной из самых удобных штук, которые делают текстовый редактор полноценной IDE — автоматическая заливка и запуск прошивки. Я это сделал так. Основной билд у меня вызывает таргет GPSLogger.bin, а не классический all. Этот таргет делает из .elf файла .bin, который уже можно вливать в контроллер. Deployment процедура запускает “заливатор” прошивки.

image

Обратите внимание на трюк с вызовом ping. Его задача обеспечить паузу в 1 сек между заливкой и запуском. Иначе микроконтроллер не успевает перезагрузиться и проинициализировать USB.

Странности QtCreator
К слову, это единственный способ организовать задержку из предложеных тут который сработал в QtCreator. Ни powershell, ни timeout работать не захотели.

В качестве “запускатора” (Run) оказалось удобно использовать эмулятор терминала. Эта штука позволяет ловить дебажный вывод в serial. Т.е. прошивка после загрузки, на самом деле, запускается сама. Мы только выбираем хотим ли мы смотреть, что пишется в сериал или нет.

А вот про отладку сегодня не будет. Во-первых про это уже много раз написано, не думаю, что я выдумаю что нибудь новое. Во-вторых, я не использую отладчики — в этом проекте я не использую навороченную периферию, которая требует внутрисхемной отладки. У меня используется только пара UART’ов да I2C. Так что вполне можно дебажиться принтами в лог. Ну а в-третих, я уже месяц не писал код — все вожусь с системами сборки и IDE. Не хотелось бы тратить еще неделю-другую на погружение в отладчики. Кому нужно — погуглите. Там есть много статей про прикручивание отладчиков к разным IDE.

Заключение


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

Я использую фреймворк stm32duino, который основан на libmaple. Это не очень стандартный фреймворк и потому у меня возникло некоторое количество трудностей и нетривиальных моментов. По ходу работы я разобрался в некоторых тонкостях работы линковщика: как происходит инициализация платы, структура libc и от чего она зависит, кто и как распределяет память в кодовых секциях. Обычно эти штуки скрываются в недрах CRT, компилятора и IDE, но в данном случае мне пришлось в явном виде поковырять эти нюансы.

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

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

Комментарии (44)


  1. longtolik
    04.05.2017 21:12

    Здравствуйте, если у Вас исследовательский склад ума, гляньте на это:
    https://www.embitz.org Установил, проверил, работает, на большее пока не хватило времени.
    Мне нравится ADS1.2, применял ее для ARM9, Cortex-A8, а теперь еще и для ARM7 в составе MRE SDK для микросмартфона Vphone S8 на базе MT2502. Кстати, Mediatek на нем сделал платы LinkIt, а программируется все это на Eclipse с добавлением GCC.


    1. grafalex
      04.05.2017 23:10

      Спасибо за наводку
      уже качаю


    1. grossws
      09.05.2017 23:01

      еще и для ARM7 в составе MRE SDK для микросмартфона Vphone S8 на базе MT2502

      Он таки ARMv7 или ARMv5TEJ? По https://www.mediatek.com/products/wearables/mt2502 не поймёшь, т. к. они указывают ARM7EJ-S (это ARMv5TEJ), а рядом написано ARMv7 (хотя на деле может быть и ARMv7E).


      А http://labs.mediatek.com/en/download/A6lpjTJP утверждает, что ARM7EJ-S. Если так, то это древность с ненужным никому в реальном мире jazelle.


      1. longtolik
        10.05.2017 09:17

        ARM7 — семейство, ARMv5TE — набор инструкций, который был представлен с более мощным ядром ARM9E.
        То есть, у MT2502 набор инструкций (ARMv5TE) соответствует ядру ARM9E.
        (Понаделали ядер! :) )
        https://en.wikipedia.org/wiki/ARM7 говорит:
        ARM7EJ-S
        The ARM7EJ-S (ARM7 + Enhanced + Jazelle - Synthesizable) is a version of the ARM7 implementing the ARMv5TE instruction set originally introduced with the more powerful ARM9E core.
        О древности — личное мнение каждого. Это как сравнивать пистолет, автомат и пушку. В связи с появлением IoT, многие фирмы стали делать экономичные процессоры, но по современным технологиям.


        1. grossws
          10.05.2017 13:38

          Я не просто так привёл ссылку https://www.mediatek.com/products/wearables/mt2502:


          Part of the MediaTek MT2502 product family, MT2502 is a highly integrated, ultra-small chip that contains dual-mode Bluetooth and integrated 2G modem. The microcontroller unit is an ARMv7 processor with integrated memory.

          Что меня и смутило. При том, что ниже указан ARM7EJ-S (который ARMv5TEJ).


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

          Ну, ARMv5TEJ сильно современным назвать сложно. Его, скорее, взяли, чтобы не покупать лицензию на ARMv7E-M/ARMv8-M. ARM7 не поддерживает более-менее человеческого Thumb2, кусок площади отжирается декодером jazelle. С другой стороны, ещё более древний ARM7TDMI пока в строю и ещё лет 10-15 будет.


          1. longtolik
            10.05.2017 14:16

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

            P.S. Вовсе я его не хвалю и не сравниваю, каждому применению — свой процессор. (Например, работает Vphone S8 на Nucleus ОС, эта же система используется и фирмой Siemens, и фирмой Apple в base-band процессоре. И никто не говорит, чтобы Apple туда поставила A9 под управлением iOS, так, для «современности». На эту тему можно долго говорить. (Linux — вообще отстой, она и iOS сделаны на базе Unix, а та — аж в 60-х годах прошлого столетия и прошлого тысячелетия). И т.п.


            1. grossws
              10.05.2017 14:33

              Ну, application процессоры стараются делать на относительно мелких техпроцессах, но в данном случае SoC из другой категории. Те же stm32f7 делают на 90nm IIRC. И энергосбережение сильно зависит от наличия у конкретного чипа хитрых режимов и умения софта их правильно использовать. Во многих режимах cpu вообще стоит без тактирования. А wfi/wfe или аналог на arm7 уже должен был быть.


  1. Amomum
    04.05.2017 23:03

    С ключом -fno-threadsafe-statics можно еще несколько байт срезать (если, конечно, вы используете статические переменные).


    1. grafalex
      04.05.2017 23:09

      не, ничего не срезалось :(
      Статические переменные не использую. Когда-то попробовал синглтон Мейерса — прошивка увеличилась в 2 раза. С тех пор использую только (полу)ручное размещение объектов.


      1. Amomum
        05.05.2017 11:13

        Честно говоря, мне не совсем понятно, как использование синглтона могло так сильно повлиять на размер прошивки. Но в целом в С++ от статических переменных толку действительно немного. А вы не могли бы пояснить, что такое «полуручное размещение объектов»?


        1. grafalex
          05.05.2017 12:22

          Ключевое слово тут «синглтон Мейерса»

          Singleton& Singleton::instance()
          {
               static Singleton s;
               return s;
          }
          


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

          Я даже деструкторы поудалял из своих классов, чем весомо уменьшил прошивку (см. раздел UPDATE: в статье про мой проект). Правда, было это еще во времена классического Ардуино, перед переездом на STM32. Но уверен, что и на arm-gcc в это также актуально.

          Возвращаясь к синглтону. Возможно, если бы это был классический синглтон (статическая переменная объявлена членом класса), то ничего плохого бы и не было. Это висит у меня в TODO. Попробую — отпишусь.

          А вы не могли бы пояснить, что такое «полуручное размещение объектов»?

          Конечно. Все мои объекты размещаются как обычные глобальные переменные. Сложные объекты включают в себя другие объекты. Если объектам нужно знать друг о друге — я просто инициализировал указатели нужными адресами. Вызовы new() не используются. Все объекты инициализируются до main().

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


          1. Amomum
            05.05.2017 14:08

            Т.о. в этом месте кода появляется вызов atexit(), проверка а был ли объект уже инициализирован, сама функция atexit(), а вместе с ней еще список объектов для удаления на выходе, который за собой тянет код обслуживания этого списка, удаления объектов, вызовы деструкторов, сами деструкторы и т.д. Там порядка 40кб добавляется. Учитывая, что в моем устройстве в принципе не предусмотрена процедура выхода/деинициализации, то я не вижу смысла тащить все эти штуки.

            Вы же уже удаляете вызов atexit и всю эту муру ключом -fno-use-cxa-atexit.

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

            Эээ, а они при этом в разных файлах?


            1. grafalex
              06.05.2017 15:13

              Вы же уже удаляете вызов atexit и всю эту муру ключом -fno-use-cxa-atexit.

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

              Сейчас, вот, попробовал. -fno-use-cxa-atexit действительно не генерирует код atexit(), но при этом требует __cxa_guard_acquire()/__cxa_guard_release(). Впрочем, -fno-threadsafe-statics это исправляет.

              Эээ, а они при этом в разных файлах?

              Да. А это проблема? Ключевое слово extern все решает


              1. Amomum
                06.05.2017 16:38

                Да. А это проблема? Ключевое слово extern все решает

                Ну, как минимум это не решает проблемы порядка создания глобальных объектов в разных единицах компиляции (т.е. в разных файлах .cpp). Этот порядок является неопределенным.

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

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

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

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

                Не буду говорить клишированное «глобальные переменные — эт плохо, понятненько?», но это реально не очень хорошо ведь. Особенно если без пространств имен и с коротенькими именами. Видал я как-то код, где было глобальное int i. Ну что б во всех циклах его использовать, удобно, не правда ли?


                1. grafalex
                  06.05.2017 16:44

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

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

                  глобальные переменные — эт плохо, понятненько?

                  Согласен. Но в данном (контроллируемом) случае это работает неплохо. А самое главное, экономит ресурсы.

                  Видал я как-то код, где было глобальное int i. Ну что б во всех циклах его использовать, удобно, не правда ли?

                  Это я уже проходил году эдак в 2003 (фигасе, уже 14 лет прошло o_O)


                  1. Amomum
                    06.05.2017 16:48

                    Но в данном (контроллируемом) случае это работает неплохо. А самое главное, экономит ресурсы.

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

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

                    Ну, разве что, исходник main'a становится больше.

                    Это я уже проходил году эдак в 2003 (фигасе, уже 14 лет прошло o_O)

                    Успокоили :)


          1. VioletGiraffe
            05.05.2017 15:00
            +1

            У меня была ровно та же проблема, увеличение прошивки на десятки КБ после добавления синглтона. И именно ключ -fno-threadsafe-statics её исправил.


  1. AlexPublic
    05.05.2017 04:08

    Ужасает, когда инженеры пользуются методом тыка (и даже не скрывают этого, гордо называя это «шаманством»), вместо чтения документации и осознанного выбора правильного пути. Это приводит к дикостям в стиле культа карго. Как пример из данного текста: ключи -fno-unroll-loops -ffast-math -ftree-vectorize имеют свой собственный очень важный смысл (каждый свой) и использование их в роли уменьшителей размера кода — это приблизительно как выбирать между джипом и гоночным автомобилем по их цвету. И подобное встречается в данном тексте чуть ли не в каждом абзаце, и про stm32 с st-link и про компилятор (опции) и про компоновщик (weak связывание и скрипты) и про qtcreator и ещё много про что, даже с ходу трудно перечислить все непонимания. Собственно это моя единственная претензия к данной статье, но при этом глобальная — она относится практически ко всем её разделам, потому как почти везде автор демонстрировал непонимание происходящего и делал выбор по принципу «а вот в такой комбинации оно неожиданно заработало».

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

    По самой теме. Меня крайне удивила итоговая система, полученная автором. Потому как на мой взгляд (и это мнение основано совсем не на методе тыка, а на знание) связка stm32+st-link+gcc+CMake (или другая сравнимая современная система сборки, например я предпочитаю waf)+QtCreator+STM32CubeMX (автоматически генерирующий код на базе HAL, а не убогих ардуино-библиотечек) — это практически идеальный выбор для профессионального разработчика, работающего с микроконтроллерами. И автор при таком своём подходе умудрился прийти практически к такому же решению — в его схеме не хватает только STM32CubeMX (который в принципе легко добавляется поверх) и имеются ненужные артефакты из мира ардуинки. Действительно чудо. )

    Да, и напоследок, чтобы текст моего комментария не казался просто пустой болтовнёй, приведу для примера параметры одного из проектов для МК под рукой (естественно построенного с помощью набора инструментов, перечисленных выше): нормальный C++14 код (с лямбдами, шаблонами и т.п.), активно работающий с UART, I2C, ADC, PWM, DMA без проблем умещается в 16 КиБ, причём с опцией -O2 (которая кстати не имеет никакого отношения к «дебажной конфигурации»). Причём собирается проект где-то за 0,5 секунды и естественно имеется полноценное автодополнение на базе Clang'a.


    1. eugenebabichenko
      05.05.2017 12:22

      По поводу нормального С++14 кода есть вопрос: в проекте используется STM32 HAL? Если да, то интересно, получается дикая мешанина из чисто сишного и плюсового стиля, или пишутся каждый раз оопшные обёртки над HAL чтоб было красиво?


      1. AlexPublic
        05.05.2017 18:18

        Да, всё на основе HAL. А вот насчёт стиля не соглашусь. Современный C++ — это совсем не упор на ООП, а скорее движение в сторону функциональных стилей (хотя он там собственно уже давно был — вспомним хотя бы STL). Т.е. у C++ есть огромное количество преимуществ над C и без использования ООП. При этом использование C библиотек в таком коде выглядит вполне естественно и красиво. Разве что изредка требуется добавить явное преобразование типа (которое в C не требуется), но это только улучшает качество кода. Так что краткий ответ на вопрос: ООП обёртки не используем и не планируем — и без них всё отлично (ну в контексте данной беседы — так то HAL сам по себе далёк от идеала и там много где можно было бы подправить, но это уже дело ST).


    1. grafalex
      05.05.2017 13:00
      +1

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

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

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


      Выбор между HAL и ардуино/stm32duino/libmaple вполне осознан (в пользу последнего):
      — стиль интерфейсов/классов arduino/stm32duino мне ближе, чем громоздкость HAL
      — HAL еще нужно заворачивать в классики — в stm32duino это уже сделано
      — у меня используются библиотеки для ардуино. У меня к ним претензий по функционалу нет. При переходе на HAL нужно было бы искать замену и переписывать клиентский код

      причём с опцией -O2 (которая кстати не имеет никакого отношения к «дебажной конфигурации»).

      Читайте внимательнее: эта опция уже была выставлена в тулчейне stm32-cmake.

      без проблем умещается в 16 КиБ

      Маленький проект без проблем вместится в 16 КиБ независимо от HAL или не HAL, С или C++14. В моем проекте под сотню исходных файлов.

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


      1. AlexPublic
        05.05.2017 20:21

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

        Всегда найдется человек, который разбирается в определенной области лучше. Я бы предпочел, что бы этот человек был рядом и мог ответить на вопросы. Если его нет, то приходится внимательно читать документацию и гуглить. Кстати, с чего Вы решили, что я не читал документацию? У меня, между прочим, это было явно написано: «Пришлось сравнивать командлайны линкера, которые делает ардуино и CooCox и по каждому ключу курить документацию.»

        Потому что как раз это и не верный подход (смотреть что там делает какая-то другая система сборки и пытаться скопировать), а надо просто понимать требуемую задачу и искать нужное в документации используемого инструмента. Вот с тем же линкером «проблема» weak функций могла бы быть тогда решена совсем по другому (и речь не про группы). Но на самом деле против такого (против не использования по не знанию) я ничего особого против не имею — наверняка и я сам где-то что-то упускаю. А вот против использование без понимания я имею очень много чего против. Вот возьмём для примера указанные в статье опции компилятора -fno-unroll-loops -ffast-math -ftree-vectorize и разберём по одной:
        1. fno-unroll-loops — ну это допустим действительно вызывает маленькое снижение размера кода, ценой его замедления
        2. -ftree-vectorize — для начале оно тупо бесполезно на Cortex-M3, так что в вашем случае это просто бессмысленная опция. Но если бы у вас был предположим более мощный процессор (например с наличием Neon'a), то данная опция привела бы к существенному увеличению быстродействия, а вот размер кода скорее всего только увеличился бы (загрузка/выгрузка SIMD регистров выглядит посложнее классического кода). Т.е. по сути эта опция прямо противоположна предыдущей по своему действию.
        3. -ffast-math — практически аналогично предыдущему пункту. Только помимо увеличения быстродействия на старших процессорах, от неё можно ещё и получить некорректные (в специфических случаях) вычисления с плавающей точкой.
        А у вас эти опции добавлены для «уменьшения размера кода». Теперь понятно как эта часть статьи выглядит в глазах людей, которые в курсе смысла этих опций? Прямо типичный культ карго.

        Выбор между HAL и ардуино/stm32duino/libmaple вполне осознан (в пользу последнего):
        — стиль интерфейсов/классов arduino/stm32duino мне ближе, чем громоздкость HAL
        — HAL еще нужно заворачивать в классики — в stm32duino это уже сделано
        — у меня используются библиотеки для ардуино. У меня к ним претензий по функционалу нет. При переходе на HAL нужно было бы искать замену и переписывать клиентский код

        Ну это в какой-то степени вопрос личного вкуса. Лично меня устройство и качество arduino фреймворка не устраивает.

        Читайте внимательнее: эта опция уже была выставлена в тулчейне stm32-cmake.

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

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

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

        Стиль написания статьи такой какой он есть.

        Со стилем всё нормально. Ненормально со знанием мат.части. )))

        P.S. Кстати, а у вас в QtCreator то включён модуль BareMetal (как раз дающий основные преимущества в разработке под МК)? А то по скринам не видно, но Device type — desktop в настройках комплекта сборки как бы намекает на его отсутствие… Да, и я так и не понял из статьи, почему у вас не настроен в комплекте компилятор — это вообще то используется не только для сборки, но и для реализации правильной подсветки/автодополнения…


        1. grafalex
          05.05.2017 22:19

          -fno-unroll-loops -ffast-math -ftree-vectorize

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

          Оригинальная билд система выставляла штук 20 опций. Я был почти уверен, что половина точно нужна (-fno-rtti), вторая выставлена из непонятных соображений. Я честно вычитывал документацию по каждой опции, решая нужна она мне или нет. Если опция выглядела полезной — я ее добавлял к себе в билд скрипт. Помимо документации после каждой добавленой опции я смотрел на потребление флеша. Много опций я себе не скопировал (это к вопросу о слепом копировании).

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

          Ну это в какой-то степени вопрос личного вкуса. Лично меня устройство и качество arduino фреймворка не устраивает.

          Хотя бы этот холиварный вопрос мы закрыли :)

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

          С CMake за последние 10 лет плотно не работал. По сути, постигал технологию заново. Так что взять за основу чей-то проект и допилить под себя считаю более разумным выбором, чем пилить свое. C gcc примерно на том же уровне — код компилировал, но с ключами не баловался.

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

          Кстати, а у вас в QtCreator то включён модуль BareMetal

          Нет, эта штука мне пока не знакома. Завтра вернусь в город — поковыряю

          .Да, и я так и не понял из статьи, почему у вас не настроен в комплекте компилятор — это вообще то используется не только для сборки, но и для реализации правильной подсветки/автодополнения…

          Не захотел он у меня заводится. Если указываю компилятор, то qt creator все равно ругается, что он не совпадает с тем, который CMake выбирает. Прошерстил все доступные настройки, целый вечер гуглил интернеты. Пока решения я не вижу. Завтра еще разок попробую.


          1. grafalex
            06.05.2017 14:45

            Посмотрел bare metal. Судя по документации это способ прикрутить gdb и st-link. Буду знать, когда буду прикручивать дебаггер. Как я уже говорял, пока это мне не актуально.

            А вот за идею настроить компилятор спасибо. Я бы в полной уверенности, что автодополнение использует какую нибудь либу от clang или что нибудь в таком духе. Не думал, что для этого компилятор использовался. Прописал правильный компилятор — код комплит завелся (QtCreator, правда, все равно ругается). Подсветка синтаксиса, кстати, и до этого работала.


            1. AlexPublic
              11.05.2017 13:28

              Если в QtCreator включён модуль ClangCodeModel (настоятельно рекомендую использовать его), то для разбора кода используется libclang, если нет, то используется свой собственный парсер (который существенно быстрее, но делает ошибки). GCC в любом случае не используется для этих целей. Однако для правильного разбора исходников и первому и второму инструменту требуется ряд параметров (каталоги с заголовочными файлами, стандартные макросы компилятора и т.п.), часть из которых настраивается в опциях проекта, а часть получается от компилятора (gcc просто выдаёт их при вызове с соответствующим ключом). Соответственно без настроенного компилятора парсеру исходников неоткуда узнать где например лежат исходники стандартной библиотеки языка, какая версия стандарта используется и т.п.


    1. VioletGiraffe
      05.05.2017 15:08

      Под библиотеки STM32CubeMX ни примеров, ни, тем более, готовых библиотек более высокого уровня днём с огнём не сыскать. В отличие от «ненужных артефактов из мира ардуинки», которые позволяют мне скачать с Github готовую, проверенную и отлаженную библиотеку для работы с периферией, и я сразу могу писать функциональность, а не заморачиваться с поддержкой экранчиков, или MIDI-протокола, или другой периферии.

      Именно поэтому, провозившись с STM32 (и даже доделав один проект), я зарёкся ещё когда-либо использовать STM32 и купил взамен Atmel SAM3x8E (Arduino Due) — просто потому, что экосистема Ардуино под него работает из коробки, без шаманства и убогих костылей.


      1. GarryC
        05.05.2017 17:19

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


      1. golf2109
        05.05.2017 17:42

        попробуйте Вашу мысь внушить «убогим» заказчикам которые хотят подключить TFT 800X480 8-ми канальный Дельта сигма, USB-Host, SDIO, SDRAM и что бы это все крутилось под FreeRTOS и предложите им Ардуинку с кокосом…


        1. VioletGiraffe
          05.05.2017 18:32

          Я ничего хорошего не могу сказать об убогих AVR, естественно, речь была об ARM Cortex-М3. В будущем, надеюсь, и посерьёзнее процы добавят в семью.


        1. grafalex
          05.05.2017 22:27

          Не понял из дискуссии кто против кого :)
          Но хочу поинтересоваться: а заказчикам не пофиг ли на какой технологии сделан проект? Если он работает, разумеется.

          и предложите им Ардуинку с кокосом…

          Вот у меня как раз ардуинка с кокосом и фриртосом на STM32. Что это доказывает?


      1. AlexPublic
        05.05.2017 20:38

        Что-то у вас тут какая-то мешанина из понятий. STM32CubeMX — это не библиотека, а автоматический генератор кода инициализации нужной периферии и обработчиков. Весьма удобный инструмент, в первую очередь благодаря своему удобному интерфейсу настройки конкретных выводов МК (многие даже не используют его генератор кода, а только планируют работу МК в Cube'е, вбивая код инициализации потом руками). Работает он на основе библиотеки HAL от ST, которая представляет собой всего лишь тонкую обёртку вокруг манипуляций с регистрами, позволяющей скрывать особенности конкретного МК (т.е. писать код, работающий под всеми МК в серии). И не смотря на относительную молодость HAL (раньше у ST была другая библиотека для подобных целей), высокоуровневых библиотек на его основе уже очень много. Причём они очевидно не требуют использования STM32CubeMX. )))


        1. grafalex
          08.05.2017 22:49
          -1

          всего лишь тонкую обёртку вокруг манипуляций с регистрами

          Смотрю, вот. Не такая уж и тонкая.
          HAL_GPIO_Init() — 150 строк, модуль I2C цельных 4000, SPI — 2500


  1. lingvo
    07.05.2017 19:30

    Странно, что обошли промышленные системы разработки под STM32 — Keil и IAR. Там вообще-то народ денюжку не просто так берет. Юзабилити может быть с точки зрения ардуинщика и не очень, но вот для профессионала она в самый раз. Я даже, например, и не знал, что в каких-то средах надо бросать все файлы в одну директорию и что при изменении одного файла надо перекомпилировать весь проект.


    1. grafalex
      07.05.2017 19:51

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

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

      Это только ардуино так отличился :) У всех остальных это по человечески сделано.


      1. lingvo
        08.05.2017 17:39

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

        ИМХО не очень правильная цель в данном случае. Когда очередной баг новых тулов превысит вашу чашу терпения опять будете перелазить? К выбору средств разработки надо подходить более осознано, так как это может сэкономить множество часов работы в дальнейшем. Например я стараюсь всеми способами избегать замены средств в процессе разработки — это чревато очень многими неожиданностями, особенно, когда надо сделать быстро. Тулы надо менять на новом проекте, чтобы не было спешки. При этом, естественно, стоит опробовать все возможные варианты сценариев, чтобы потом не было разочарований.
        Не стоит также недооценивать покупной продукт против open-source. Когда танцы с бубном не помогают, советы на форумах закончились и все воркэраунды не работают, а оно все равно не пашет, бывает очень полезно иметь еще один козырь в загашнике в виде поддержки производителя ПО. Оно, конечно, далеко не панацея, но все равно лучше, чем ничего.
        Также стоит отметить, что Keil и IAR имеют очень хорошую репутацию у производителей управляющего ПО — для автомобилей, лифтов, станков и т.д.


        1. grafalex
          08.05.2017 20:29

          Спасибо за Ваше мнение, заинтриговали.

          Признаться, я бы и рад попробовать, просто жаль времени. Если уж переходить, то точно зная куда. Раз уж у Вас есть опыт с этими системами, подскажите пару моментов:
          — Какой там компилятор? gcc или что-то свое?
          — Насколько там гибкий процесс сборки? Потянет ли вот такую кастомную сборку как у меня? Что бы из нескольких библиотек, с кастомными ключаеми компиляции для всех трех компиляторов + компоновщика, со своим скриптом линкера и стандартной библиотекой
          — Могу ли я там работать со своим системным слоем (libmaple) или там все заточено под CMSIS/HAL?
          — Могу ли я там прикрутить свой заливатор прошивки? или только через отладчик?
          — Могу ли я там использовать монитор COM порта во время дебага?


          1. lingvo
            08.05.2017 22:38

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


            — Какой там компилятор? gcc или что-то свое?

            Свой. Кейл и IAR именно начали с компиляторов, а потом уже подтянули IDE. Кейл вообще спецы по эмбеддерным компиляторам — их C51 был и наверное остается лучшим компиляторам под 8051. В ARM у них тоже наработки серьезные. Как правило они хвастаются, что ихний компилятор лучше GCC по оптимизации размера кода и данных.


            — Насколько там гибкий процесс сборки? Потянет ли вот такую кастомную сборку как у меня? Что бы из нескольких библиотек, с кастомными ключаеми компиляции для всех трех компиляторов + компоновщика, со своим скриптом линкера и стандартной библиотекой

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


            — Могу ли я там работать со своим системным слоем (libmaple) или там все заточено под CMSIS/HAL?

            Тоже думаю да. Тот же STM32Cube прекрасно импортируется и работает в Keil IDE. Но я, естественно, рекомендую обратить внимание на Keilовские Middleware — файловую систему, USB, TCP IP, RToS и CMSIS. Там все очень грамотно сделано, описано и нет того бардака, который творится в open-source. Ну и естественно не страшно, как оно поведет себя в реальном проекте.


            — Могу ли я там прикрутить свой заливатор прошивки? или только через отладчик?

            Без проблем.


            — Могу ли я там использовать монитор COM порта во время дебага?

            Без проблем. Кстати, насколько я помню, как раз Кейл первыми придумали эту фичу для 51-ых.


            1. grafalex
              08.05.2017 22:41

              ок, спасибо.
              В кейле когда то писал под С51. Вроде удобно было. Только давно, лет 15 назад. Уже ничего не помню


        1. dion
          08.05.2017 20:55
          +1

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

          Как раз хорошо проверенная и универсальная система сборки (CMake) позволяет очень легко менять IDE. Если вдруг внезапно QtCreator перестанет работать (или просто надоест), переехать на какой-нибудь CDT или CLion займет минимум времени.


        1. grossws
          09.05.2017 23:25
          +1

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

          Особенно хорошо, когда эта поддержка хоть в течении полугода реагирует. Как-то писал им на форум в конце прошлого года про багу в SVD в их паке под stm32f4xx, а реакции — ноль.


          Примерно такое же впечатление было и по поддержке при наличии платной среды и компилятора под 8051/8052 в районе 2007-2008 года, динамили на ура.


          1. lingvo
            10.05.2017 12:36

            Не путайте фирменный Discussion Forum с контактом через email, case и пр. Многие думают, что это эквивалентно, а это совсем разные вещи и у платных сред зачастую форум мертвый или закрытый. Все решается через кейсы.


            1. grossws
              10.05.2017 13:43

              Я сейчас не имею их поддержки, поэтому через форум.


              Но и их поддержка при наличии платной среды не вызвала большой радости. Возможно, что-то изменилось с 2007 года, я хз. Сейчас пользуюсь gcc и clang/llvm, а с 8051/8052 дела давно не имею.


    1. AlexPublic
      11.05.2017 13:44
      +2

      Как раз для профессионала Keil и IAR максимально не подходят. Потому что обладают совершенно убогими средствами работы с кодом. Они не то что несравнимы по этому параметру с нормальными IDE (типа QtCreator или VisualStudio), но и уступают даже просто редакторам (типа Notepad++ или Sublime). Уровень возможностей этих IDE по работе с кодом соответствует где-то началу 90-ых, так что даже смешно называть это убожество выбором профессионалов. Единственное, что в них было удобным, это автоматическая настройка периферии чипов и отладчиков различных типов. Для каких-то экзотических чипов это возможно актуально и сейчас. Но в случае STM32 с их STM32CubeMX и st-link даже этого слабого преимущества перед действительно профессиональными IDE у Keil и IAR уже нет.


  1. grafalex
    08.05.2017 20:28

    #Я_опять_промахнулся