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


    За последний год я написал довольно много кода для 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 смотрите в документации.

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 11

      +3
      Большое СПАСИБО за первую статью o PlatformIO :) У нас просто рук не хватает на это дело :(
      Если можно, добавьте, пожалуйста, линк на https://github.com/platformio/platformio, а то нету упоминания что это 100% open source, free and Apache 2.0 license.
      Также у PlatformIO есть отдельная команда для установки библиотек platformio lib, к сожалению она по умолчанию устанавливает библиотеки глобально

      В PlatformIO 3.0 по-умолчанию библиотеки будут ставиться в project's lib. Я открыл на эту тему issue #475. Если хватит сил, то сделаем ну очень продвинутый Library Manager по типу npm, когда библиотека будет ложить свои зависимости в свой dependency list. Но тогда очень сильно придется повозиться с PlatformIO Build System.
      В целом, library логика будет переписана с нуля в PlatformIO 3.0. Здесь можно посмотреть что нас ждет...
        0
        Ссылка на гитхаб есть, незаметная. В первом комментарии гораздо заметнее.
        У меня пока не было нужды в сложных зависимостях между библиотеками, так что вроде в 3.0 ожидается все что мне нужно.
        Пользуясь случаем плюсую semver https://github.com/platformio/platformio/issues/410
        Классный проект!
          0
          А подскажите, есть для Atom плагин, позволяющий переходить к телу функции при клике через ctrl, или каким-либо иным способом? Начал изучение, было бы удобно по исходникам шастать.
            +1
            У Вас уже стоит плагин (autocomplete-clang) который будет это делать. Надо переключиться на PR (ждем когда добавлят в главный бренч) PlatformIO IDE for Atom: Go To Declaration.
            +1
            >> устанавливать библиотеки в подпапку вашего проекта и поместить симлинки(ярлыки?) для каждой библиотеки в папку libraries

            А зачем ярлыки? Пишите в скетче в #include путь с указанием подпапки
              0
              Да, вариант. Можно указать относительные пути, типа
              #include "libs/Logging/Logging.h"
              0
              Serial.print("pin_2 = ");
              Serial.print(digitalRead(2));
              
              Serial.print(", pin_3 = ");
              Serial.println(digitalRead(3));

              Попробуйте вот так:
              Serial.println("pin_2 = " + String(digitalRead(2)) + ", pin_3 = " + String(digitalRead(3)) );
                0
                Не всегда удобно. Как раз при отладке можно легко комментить не нужную часть вывода.
                  +1
                  #define vLog Serial.println
                  #ifndef vLog
                    #define vLog(x)
                  #endif
                  ...
                    vLog("pin_2 = " + String(digitalRead(2)) + ", pin_3 = " + String(digitalRead(3)) );

                  Тут достаточно закомментить #define vLog и это не просто подавит ненужный вывод, но и вообще вычеркнет его из кода.
                +1
                Спасибо за статью. Очень интересный проект.
                  +1
                  Пробовал и под Windows 10 и под Ubuntu. Осваивал на Platformio MSP430 launchpad. Потом откопал AVR Butterfly. Поддерживает без проблем.

                  Only users with full accounts can post comments. Log in, please.