Тулчейн разработки под Arduino для ценителей командной строки: PlatformIO или как перестать использовать Arduino IDE

в 21:29, , рубрики: arduino, diy или сделай сам, platformio, метки:

Тулчейн разработки под Arduino для ценителей командной строки: PlatformIO или как перестать использовать Arduino IDE - 1
За последний год я написал довольно много кода для Arduino и попутно сменил несколько инструментов разработки. В статье упоминаются варианты которые пробовал и более подробно о том, на чем остановился. Речь пойдет про набор инструментов для случая когда >10 проектов под разные платы и немного про разработку и установку библиотек.

Среда разработки

В чем проблема?

Вероятно потому, что Arduino не ориентирована на профессиональных разработчиков, экосистема вокруг стандартной IDE отличается отсутствием привычных для меня инструментов:

  • Только в последних версиях появилось какое-то управление библиотеками, пока без подобия Gemfile/requirements.txt/package.json, то есть нельзя для проекта указать какие либы каких версий используются
  • нет интеграции с Git или другими VCS
  • текстовый редактор не сравнить с моим любимым текстовым редактором
  • нет возможности сохранить выбор платы в проекте
  • неудобный вывод ошибок компиляции

На сайте Arduino есть перечень алтернативных инструментов для разработки. В этом списке есть варианты, которые по разным причинам не стал пробовать. Например Atmel Studio и Visual Studio CE не рассматривал. Хотелось найти инструмент поддерживающий работу из коммандной строки.

Что пробовал

Ino

Ino — проект от российской компании Амперка, утилита командной строки для прошивки Arduino.
Довольно популярный был проект, >200 форков. Последний коммит в апреле 2014, поэтому не работает со свежими версиями IDE (кажется начиная с 1.5).
Есть живой форк Arturo, немного его использовал, но были проблемы в каких-то экзотических случаях.

Arduino-Makefile

Arduino-Makefile — компиляция и загрузка с помощью make. Не поддерживаются Arduino Due, Zero и другие 32-битные платы. Отличием от стандартной IDE является то, что методы должны быть объявлены до использования, так что при переносе готовых проектов может понадобится редактирование исходников. Если правильно помню, мне не удалось подружить Arduino-Makefile и SparkFun Pro Micro.

Что использую

PlatformIO

PlatformIO — это отличный проект, созданный разработчиками из Украины. Он включает в себя утилиту коммандной строки, через которую можно запускать компиляцию и загрузку программ на несколько семейств микроконтроллеров (Atmel AVR, Atmel SAM, ST STM32, TI MSP430 и другие). При этом поддерживаются разные наборы библиотек(на сайте PlatformIO называются фреймворками): Arduino, Energia, mbed, а также нативный код для Atmel AVR, espressif, MSP430.
PlatformIO изначально ориентирована на работу из командной строки, также есть плагины для интеграции с текстовыми редакторами и IDE: Atom, CLion, Eclipse, Emacs, NetBeans, Qt Creator, Sublime Text, Vim и Visual Studio
PlatformIO особенно подходит если у вас:

  • один проект под несколько плат, т.е. тот же самый код должен компилироваться под разные платы
  • много проектов под разные платы, т.е. каждый проект под одну плату, но проектов много и платы разные
  • есть необходимость работать через ssh, например если PlatformIO установлен на Raspberry Pi
  • острое неприятие графического интерфейса

image

Использование с Arduino

Пересказывать документацию не буду, тут инструкция по установке, об использовании можно посмотреть в разделе Quick Start
Структура папок проекта для PlatformIO отличается от проекта Arduino IDE, каждый проект содержит файл platformio.ini в котором указано какие платы используются. Таким образом не приходится каждый раз выбирать нужную плату.
Расскажу на примере как использую PlatformIO при разработке библиотеки для Arduino. У библиотеки есть два примера, каждый из них является проектом в формате PlatformIO. В файле настроек проекта platformio.ini перечислены все платы на которых должна работать библиотека:

[env:nanoatmega328]
platform = atmelavr
framework = arduino
board = nanoatmega328

[env:sparkfun_promicro16]
platform = atmelavr
framework = arduino
board = sparkfun_promicro16

[env:due]
platform = atmelsam
framework = arduino
board = due

[env:teensy31]
platform = teensy
framework = arduino
board = teensy31

[env:nodemcu]
platform = espressif
framework = arduino
board = nodemcu

[env:uno]
platform = atmelavr
framework = arduino
board = uno

Скомпилировать пример для всех плат можно командой:

platformio run

Скомпилировать только для uno можно так:

platformio run -e uno

Загрузить прошивку на uno:

platformio run --target upload -e uno

Запустить монитор последовательного порта:

platformio serialports monitor

Добавил алиасы в .zshrc чтобы сделать команды короче:

alias compile="platformio run"
alias upload="platformio run --target upload"
alias serial="platformio serialports monitor"

С ними таже последовательность действий:

compile         # компиляция для всех плат
compile -e uno  # компиляция только uno
upload  -e uno  # прошивка uno
serial          # монитор последовательного порта

Так же есть интеграция с Travis CI и другими CI инструментами, подробнее тут.
Вобще-то у Arduino IDE есть интерфейс коммандной строки, но он далек от совершенства.

Нюансы PlatformIO

PlatformIO ускоряет работу, это более гибкий инструмент по сравнению с Arduino IDE и с ним легче автоматизировать рутинные задачи. Есть при этом несколько моментов которые стоит учитывать:

  • компиляция в PlatformIO не всегда равноценна компиляции в Arduino IDE, то что скопмилировалось в PlatformIO может не компилироваться в Arduino IDE и наоборот
  • структура папок проекта не совпадает со структурой для Arduino IDE
  • не все библиотеки доступны для установки через platformio lib

Serial.print("Может быть лучше");

В чем проблема?

Стандартный Serial.print() слегка неудобен в случае если нужно напечатать
название и значение переменной, например чтобы вывести "pin_2 = <состояние пин 2>, pin_3 = <состояние пин 3>" приходится делать так:

Serial.print("pin_2 = ");
Serial.print(digitalRead(2));

Serial.print(", pin_3 = ");
Serial.println(digitalRead(3));

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

Что пробовал

arduinoLogging

Эта либа использует printf-подобный синтаксис для печати, а также позволяет установить LOGLEVEL и таким образом отключить вывод части или всех сообщений. Сообщения выводятся с помощью методов Error, Info, Debug и Verbose.
Пример:

  #include "Logging.h"

  // LOGLEVEL может быть LOG_LEVEL_X, где X ∈ { NOOUTPUT, ERRORS, INFOS, DEBUG, VERBOSE }
  #define LOGLEVEL LOG_LEVEL_INFOS

  void setup() {
    Serial.begin(9600);
    Log.Init(LOGLEVEL, &Serial);

    // это будет напечатано
    Log.Info("pin_2 = %d, pin_3 = %d"CR, digitalRead(2), digitalRead(3));

    // это не будет напечатано
    Log.Debug("это не будет напечатано, так как LOGLEVEL = LOG_LEVEL_INFOS");
  }

Доступные модификаторы

wildcard comment Example
%s replace with an string (char*) Log.Info("String %s", myString);
%c replace with an character Log.Info("use %c as input", myChar)
%d replace with an integer value Log.Info("current value %d",myValue);
%l replace with an long value Log.Info("current long %l", myLong);
%x replace and convert integer value into hex Log.Info ("as hex %x), myValue);
%X like %x but combine with 0x123AB Log.Info ("as hex %X), myValue);
%b replace and convert integer value into binary Log.Info ("as bin %b), myValue);
%B like %x but combine with 0b10100011 Log.Info ("as bin %B), myValue);
%t replace and convert boolean value into "t" or "f" Log.Info ("is it true? %t), myBool);
%T like %t but convert into "true" or "false" Log.Info ("is it true? %T), myBool);

Что использую

advancedSerial

Названия уровней сообщений Error, Info, Debug и Verbose в arduinoLogging не являются нейтральными. Error не обязательно выводит ошибку, это просто сообщение которое выводится при любом LOGLEVEL (кроме NOOUTPUT).
Учитывая также некоторые неудобства printf я написал свой вариант, advancedSerial.
Собственно advancedSerial это две вещи: возможность вызывать print() и println() в цепочке и уровни сообщений.

  int a = 1;
  int b = 2;

  aSerial.print("a = ").print(a).print("b = ").println(b);

  // также доступны короткие названия методов
  aSerial.p("a = ").p(a).p("b = ").pln(b);

Полный пример Basic.ino
Так как названия методов совпадают с названиями стандартных методов Serial.print() и Serial.println(), то, при желании, в исходниках можно просто заменить Serial на aSerial.
Для названия уровней сообщений я выбрал v, vv, vvv, vvvv, довольно распространенный способ обозначать уровни подробности выводимых сообщений, обычно встречается в качестве флагов -v, -vv и тд.
При таких названиях проще отредактировать один уровень на другой, например vv -> vvv проще чем Info -> Debug.

  #include "advancedSerial.h"

  void setup() {
    Serial.begin(9600);

    aSerial.setPrinter(Serial);    // выбираем сериал через который будем печатать

    // устанавливаем фильтр, будут выводиться только сообщения уровня v и vv, а vvv и vvvv выводится не будут
    aSerial.setFilter(Level::vv);
  }

   void loop() {
     aSerial.l(Level::vv).pln("Будет напечатано");
     aSerial.l(Level::vvv).pln("Не будет напечатано");

     delay(3000);
   }

Полный пример Advanced.ino

Экономия памяти

Если обернуть строку в макрос F(), то она не будет загружена в память (SRAM), так что для экономии памяти используйте F():

  aSerial.print(F("экономия 16 байт"));

Безусловно использование advancedSerial добавляет некоторый оверхед по сравнению со стандартным Serial, я попробовал приблизительно оценить какой. Ниже привожу результаты компиляции для Arduino Uno, так как у нее 2KB памяти и это минимум среди плат которые обычно использую.

Обычный сериал, без каких-либо библиотек:

  void setup() {
   Serial.begin(9600);
  }

  void loop() {
    Serial.print("test");
    Serial.println("test");
  }

storage space: 5%
dynamic memory: 9%

advancedSerial:

  #include <advancedSerial.h>

  void setup() {
   Serial.begin(9600);

   aSerial.setPrinter(Serial);
   aSerial.setFilter(Level::vv);
  }

  void loop() {
    aSerial.print("test").println("test");
  }

storage space: 5%
dynamic memory: 10%

examples/Advanced.ino
storage space: 9%
dynamic memory: 26%

examples/Advanced.ino с использованием F() макроса
storage space: 9%
dynamic memory: 10%
Получается что использование памяти увеличивается незначительно. Но advancedSerial не оптимальное решение в плане ресурсов, есть альтернативные реализации, например Debug.

Установка библиотек

В чем проблема?

По умолчанию Arduino IDE устанавливает библиотеки глобально и в скетче никак не фиксируется какие именно библиотеки используются(кроме директив #include конечно же) и каких версий. Из-за этого чтобы скомпилировать скетч на другом компьютере нужно знать где скачать нужные бибилиотеки, опять же версии библиотек также нужно указывать. Чтобы избежать таких проблем я устанавливаю библиотеки только локально, внутри папки со скетчем. Ниже приведу два способа локальной установки библиотек для Arduino IDE и для PlatformIO.

Arduino IDE

Редко пользуюсь Arduino IDE, возможно есть способ лучше. Способ такой: устанавливать библиотеки в подпапку вашего проекта и поместить симлинки(ярлыки?) для каждой библиотеки в папку libraries (в папку куда устанавливает библиотеки Arduino IDE).
Кстати, если правильно помню, Arduino IDE компилирует все библиотеки из папки libraries при компиляции любого скетча, так что время компиляции увеличивается если в libraries много библиотек. Еще один повод не использовать Arduino IDE.

PlatformIO

В каждом проекте PlatformIO есть подпапка lib, в которую можно помещать библиотеки. Это при ручной установке библиотек. Также у PlatformIO есть отдельная команда для установки библиотек platformio lib, к сожалению она по умолчанию устанавливает библиотеки глобально, чтобы библиотеки устанавливались локально в подпапку lib надо в platformio.ini проекта добавить:

[platformio]
lib_dir = ./lib

Подробнее про platformio lib смотрите в документации.

Автор: klenov

Источник

Поделиться новостью

* - обязательные к заполнению поля