Добавляем свою плату в PlatformIO в разрезе проекта на конкретном примере

platformio ide pio homeВот в старое время, бывало, откроешь текстовой редактор, накидаешь несколько сот строк на ассемблере, откомпилируешь одной программой, затем отлинкуешь другой и все, программа готова. А нынче? А нынче вычислительные мощности подросли настолько, что даже мои часы мощнее компьютера на котором я обучался алгоритмизации. А труд программиста почти не изменился. Разработчики все так же пишут код руками, вводят его в текстовом редакторе, затем компилируют, да линкуют. Только вот со времен Кобола, Алгола, да чистого ассемблера много чего изменилось. Сейчас никому и в голову не придет писать программу на ассемблере даже для микропроцессора. Это не только не эффективно, но и игра не стоит свечей, которые сгорят, пока программист будет выдавливать из себя инструкции для конкретного процессора. «Железо» нынче стоит куда дешевле труда человека.

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

PlatformIO поддерживает наверное все популярные платы, какие только могут быть. Поддерживается все это дело частной компанией и многочисленным комьюнити. И действительно, попробовав написать хоть небольшой проект на PlatformIO обратно на Arduino IDE возвращаться уже не захочется. Эффективность написания сложного кода для встроенных систем на PlatformIO, пожалуй, самая высокая, среди всех доступных пользователям средств. Да и IDE, став Open Source и опираясь на такие же продукты, позволяет без опаски и не пиратствуя писать свой код свободно. Прям утопия.

Но есть у всего этого малинового многообразия и отрицательная сторона, чем больше поддерживается плат и микропроцессоров в PlatformIO, тем сложнее и дольше происходит интеграция новых версий всего программного обеспечения. На такую проблему напоролся и я. Возникла необходимость написать прошивку для устройства на чипе STM32F103RE. Чип вполне актуальный, распространен, его поддерживает версия Arduino для архитектуры STM32 (STM32Duino) начиная с версии 1.7.0. Но вот беда. В PlatformIO самой актуальной версии хоть и интегрирована Arduino для STM32 от STM32Duino, но вот сам чип STM32F103RE поддерживается только устаревшей версией Arduino для STM32 (Maple). И использовать STM32Duino с PlatfromIO я не могу, хотя тот же процессор отлично прошивается через Arduino IDE с установленной поддержкой SMT32Duino. Вопиющая несправедливость, с которой следует разобраться.

Звучит сложно? Именно по этой причине, прежде чем приступать к подключению STM32F103RE к Arduino STM32Duino под PlatformIO мне пришлось на протяжении нескольких дней усиленной изучать документацию, исходные коды и сообщения на форумах. Пришлось даже вспоминать как писать на Python.

Официальная документация

Разработчики оригинального Arduino позаботились о своих пользователях и выложили подробную инструкцию по проблематике добавления новых плат в свой фреймворк. Вкратце, у «итальянцев» все строит на текстовых файлах, которые в последствии разбираются скриптами на Python. Для добавления платы необходимо прописать ее «спецификацию» в файл boards.txt (оный файл может находиться и в разрезе папки скетча под Arduino IDE, тогда новая плата будет видна только и исключительно в рамках этого скетча).

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

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

Углубляем, расширяем и изучаем

Итак, для удобства пользователя PlatformIO содержит несколько уровней в иерархии систем, которые поддерживаются для работы. На самом верхнем уровне располагается так называется платформа (Platform). По сути, платформа — это контейнер со всем необходимым для конкретной аппаратной архитектуры. Все особенности платформы определяются в так называемом манифесте platform.json. Платформа содержит тулчейны (инструментальные наборы: совокупности компиляторов, линкеров и всего прочего, что необходимо для компиляции), описания поддерживаемых плат и фреймворки. Тулчейны из поставки PlatformIO обычно уже предкомпилированы под конкретную среду исполнения (Windows, Linux и т.п.) и хранятся на CDN BinTray. Тут же, в папке boards, хранятся и описания (манифесты) поддерживаемых плат/микропроцессов (поскольку современный микропроцессор можно использовать вообще без платы, подключив к нему только несколько проводков, то в понимании платы и микропроцессора наступила путаница, поэтому в рамках настоящей статьи будем считать их тождественными).

На следующем уровне, ниже платформы, располагается пространство фреймворков. В терминах PlatformIO фреймворк это набор компилируемых исходных кодов, которые необходимы для компиляции исходного кода пользователя. Как бы это не звучало странно, но фреймворки хранятся в папке packages, где так же хранятся и наборы утилит для загрузки скомпилированного кода в конкретную плату. Что же такое фреймворк? Назвать его языком программирования я не могу. Так как по большей части для написания кода для микропроцессоров используется C++ ну или просто С. Набор библиотек? Тоже неверно, ведь кроме самих библиотек в фреймворках содержатся еще и «драйвера» под конкретную периферию микропроцессора. В общем, будем считать, что фреймворк в PlatformIO это некий набор исходных кодов необходимых для компиляции пользовательского исходного кода под конкретную платформу с конкретным набором периферии. Так, возможность компилирования под STM32 в PlatformIO на Arduino дается поддержкой фреймворка STM32 Arduino, а под ту же архитектуру STM32, но уже на Mbed, соответственно фреймворк Mbed.

Но ситуация с Arduino под STM32 немного более сложная. Под эту платформу существует аж два фреймворка Arduino. Первый Maple, в настоящее время практически не поддерживается разработчиками, и STM32Duino развиваемый сообществом и вполне себе живом. В рамках PlatformIO эти два фреймворка называются ядрами (Cores) и пользователь вправе выбрать с каким ядром компилировать свой код на Arduino под STM32. И именно тут порылась изюминка. Далеко не все платы поддерживаются сразу в двух ядрах. С моей платой STM32F103RE вообще все оказалось смешно. В STM32Duino она поддерживается, а вот в платформе STM32 от PlatformIO ее поддержку записать забыли. И даже если напомнить команде разработчиков о сим прискорбном факте, исправления ждать придется ой как долго. Поэтому спасение утопающих дело исключительно рук самих утопающих.

Разбираемся с манифестом boards.json

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

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

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

-------------- genericSTM32F103RB.json --------------
{
  "build": {  <-- группа относящаяся к компиляции и линковке
    "core": "maple", <-- ядро, которое используется по умолчанию
    "cpu": "cortex-m3", <-- тип процессора
    "extra_flags": "-DSTM32F103xB -DSTM32F1", <-- дополнительные параметры, передающиеся компилятору
    "f_cpu": "72000000L", <-- частота процессора
    "hwids": [ <-- группа определяющие аппаратный идентификатор, если плата подключается к ПК по USB
      [
        "0x1EAF",
        "0x0003"
      ],
      [
        "0x1EAF",
        "0x0004"
      ]
    ],
    "ldscript": "stm32f103xb.ld", <-- скрипт для линковщика
    "mcu": "stm32f103rbt6", <-- тип процессора на плате или микроконтроллере
    "variant": "NUCLEO_F103RB" <-- название директории в которой находятся файлы конфигурации
  },
  "debug": { <-- группа определяющая параметры для отладчика
    "jlink_device": "STM32F103RB",
    "openocd_target": "stm32f1x",
    "svd_path": "STM32F103xx.svd"
  },
  "frameworks": [ <-- перечисление доступных фреймворков для платы
    "arduino",
    "cmsis",
    "mbed",
    "libopencm3",
    "stm32cube"
  ],
  "name": "STM32F103RB (20k RAM. 128k Flash)", <-- наименование платы, которое видит пользователь
  "upload": { <-- группа определяющая загрузку и такие физические параметры как размер ОЗУ и места под программу (нужно уже линкеру)
    "disable_flushing": false,
    "maximum_ram_size": 20480,
    "maximum_size": 131072,
    "protocol": "stlink",
    "protocols": [
      "jlink",
      "stlink",
      "blackmagic",
      "serial",
      "dfu"
    ],
    "require_upload_port": true,
    "use_1200bps_touch": false,
    "wait_for_upload_port": false
  },
  "url": "http://www.st.com/content/st_com/en/products/microcontrollers/stm32-32-bit-arm-cortex-mcus/stm32f1-series/stm32f103/stm32f103rb.html",
  "vendor": "Generic"
}

Как видно структура манифеста платы не такая уж сложная. И если соблюдать синтаксис, то можно ее хорошенько модифицировать. Кстати, некоторые параметры из манифеста, можно переопределять в platfromio.ini (настроечный файл, хранится в корне проекта). Например, настройка board_build.core = stm32 позволяет включить ядро STM32Duino при выбранном фреймворке Arduino.

Конфигурационный файл platformio.ini важен, так как после того, как вы создадите свой манифест платы, ее же надо как-то подключить в проект. Подключение происходит просто, достаточно только указать что-то типа того board = genericSTM32F103RB, где genericSTM32F103RB есть название файла манифеста (без расширения .json). Если название файла будет не совпадать с настройкой, то PlatformIO не увидит манифест и грязно выругается. Еще один важный момент — параметр variant в манифесте. В приведенном выше примере он имеет значение NUCLEO_F103RB. И это означает, что скрипт добавить в путь из которого будут вытаскиваться все исходные коды еще и каталог располагающийся вот тут .platformio\packages\framework-arduinoststm32\variants\NUCLEO_F103RB\ (вариант для Windows), обращаем внимание на последний каталог.

И именно в последнем каталоге хранятся файлы настройки конкретной платы, ее пинов, ее периферии. Разработчики PlatformIO не изобретают их сами, а берут их от разработчиков фреймворков. Т.е. файлы из variants от STM32Duino, что используются в Arduino IDE такие же, как и файлы из variants из поставки PlatformIO.

Соответственно для того, чтобы настроить мою отсутствующую STM32F103RE мне нужно запилить новый манифест и использовать variant из поставки STM32Duino для Arduino IDE. Кажется, все, но не совсем. Именно в этот момент возникает неловкая пауза, взятая на размышления. Если я буду изменять или дополнять файлы, которые автоматически установились установщиком PlatformIO, то при следующем апдейте платформы или еще чего угодно, мои изменения пропадут. Нужна какая-то автоматизация. Но если немного подумать, то есть вариант красивее.

Разбираемся с директориями

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

Если с местом размещения манифеста новой платы все просто (в папке проекта создается папка boards и в ней уже располагается манифест), то со специфическими настройками сложнее. За настройку местоположения специфических настроек отвечает параметр variants_dir. Если он отсутствует в манифесте платы, то берется путь по умолчанию (в папке фреймворка), а если присутствует, то скрипт компиляции прописывает в пути для поиска кода при компиляции и линкования эту директорию в папке проекта. Однако, следует не забывать, что в директории, обозначенной как variants_dir должна быть папка с именем обозначенным в variant, а уже в ней будут лежать все файлы специфической настройки.

Специфическая настройка

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

  • ldscript.ld — скрипт для линковщика, нужен для верного указания адресов для расположения различных частей прошивки.
  • PeripheralPins.c — основной файл, в котором происходит назначение периферии конкретного процессора конкретным пинам. Например, привязка конкретных выводов к конкртеным таймерам, преобразователям и тому подобному. Для платформы STM32 данный файл удачно генерируется при помощи утилиты GenPinMap Tool. Которая в свою очередь берет данные из поставки CubeMX от ST под конкретное семейство плат.
  • PinNamesVar.h — файл с псевдонимами пинов и является дополнительным к PeripheralPins.c
  • variant.cpp — содержит противопоставление наименований пинов в среде Arduino с тем, что используется в среде HAL (Arduino для STM32 это по большей части удобная оболочка над ST HAL).
  • variants.h — определяет соответствие пинов Arduino числовым значениям, а так же создает псевдонимы пинам.

Небольшое замечание по пинам. Наличие сразу нескольких способов адресации к конкретному выводу вносит небольшую путаницу. Итак, в Arduino пины обозначаются как Dx или Ax (для цифрового или аналогово пина), либо просто цифрой, либо буквенночисловым значением как PA10. В среде HAL используются буквенночисловым обозначения с подчеркиванием, например, PA_10. Путать их не стоит, так как они могут ссылаться на разные физические пины, все зависит от этих самый специфических настроек.

Что в итоге

Я настроил поддерживаемую плату в STM32Duino, но не поддерживаемую в PlatformIO (по непонятной причине) с включением поддержки на уровне проекта. Для пущего понимания приведу конкретные примеры:

--------- platformio.ini ---------
[env:KDS-Test]
platform = ststm32
board = genericSTM32F103RE-2
framework = arduino
upload_protocol = stlink
debug_tool = stlink
board_build.core = stm32
-----------------------------------

--------- genericSTM32F103RE-2.json ---------
{
  "build": {
    "core": "stm32",
    "cpu": "cortex-m3",
    "extra_flags": "-DSTM32F103xE -DSTM32F1",
    "f_cpu": "72000000L",
    "hwids": [
      [
        "0x1EAF",
        "0x0003"
      ],
      [
        "0x1EAF",
        "0x0004"
      ]
    ],
    "ldscript": "stm32f103xe.ld",
    "mcu": "stm32f103ret6",
    "variants_dir": "variants",
    "variant": "Generic_F103Rx"    
  },
  "debug": {
    "jlink_device": "STM32F103RE",
    "openocd_target": "stm32f1x",
    "svd_path": "STM32F103xx.svd"
  },
  "frameworks": [
    "arduino"
  ],
  "name": "STM32F103RE (64k RAM. 512k Flash) KDS2",
  "upload": {
    "disable_flushing": false,
    "maximum_ram_size": 65536,
    "maximum_size": 524288,
    "protocol": "stlink",
    "protocols": [
      "jlink",
      "stlink",
      "blackmagic",
      "serial",
      "dfu"
    ],
    "require_upload_port": true,
    "use_1200bps_touch": false,
    "wait_for_upload_port": false
  },
  "url": "http://www.st.com/content/st_com/en/products/microcontrollers/stm32-32-bit-arm-cortex-mcus/stm32f1-series/stm32f103/stm32f103re.html",
  "vendor": "Generic"
}
-----------------------------------------------

А также директории проекта:

|   .gitignore
|   .travis.yml
|   aaa
|   platformio.ini
+---boards
|       genericSTM32F103RE-2.json
+---src
|       main.cpp
+---variants
    +---Generic_F103Rx
            ldscript.ld
            PeripheralPins.c
            PinNamesVar.h
            variant.cpp
            variant.h

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

Update: Как было отмечено в тексте, в конфигурационном файле platformio.ini можно вводить переопределения, в том числе и настроек платы. Так, в приведенном примере можно обойтись и вовсе без дополнительных директорий. Достаточно только сделать поправку на ядро и используемый вариант (если там всё определено верно, разумеется):

[env:genericSTM32F103RE] 
platform = ststm32 
board = genericSTM32F103RE 
framework = arduino 
board_build.core = stm32 
board_build.variant = Generic_F103Rx
debug_tool = stlink

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



Добавить комментарий