Главная chevron_right Наши уроки chevron_right Создаём свою библиотеку для Arduino IDE

Создаём свою библиотеку для Arduino IDE

Разбираем, как создать собственную библиотеку для Arduino IDE: от минимального набора файлов .h и .cpp до полной структуры с примерами, файлом свойств и подсветкой синтаксиса.

Общие сведения:

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

Если Вы часто используете один и тот же фрагмент кода, одни и те же константы и/или структуры данных, возможно пора задуматься об оптимизации времени и оформления этого кода как библиотеки.

Данная статья не является полным руководством по написанию библиотек для Arduino IDE. Более подробную информацию о библиотеках можно узнать по ссылкам:

Минимальная библиотека:

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

В папке, где находятся скетчи по умолчанию, есть папка libraries. (Для ОС Windows это C:\Users\%UserName%\Documents\Arduino\libraries или Быстрый доступ -> Документы -> Arduino -> libraries). Перейдём в неё и создадим папку, например, myNewLib:

Далее создадим в ней два файла myNewLib.h и myNewLib.cpp.

Файл с расширением *.h (myNewLib.h) - это файл заголовка языка Си. В нём указываются прототипы функций и константы, которые можно будет использовать в любом скетче после подключения этого файла директивой препроцессора #include.

Отредактируем этот файл. Откроем его при помощи, например, блокнота:

Напишем в нём следующий код:

#ifndef __myNewLib__
#define __myNewLib__            // "if-def" стражи

const float myPI = 3.141592;    // Константа, которую мы хотим использовать во всех наших скетчах
void printPI();                 // Функция, которую мы часто используем, и не хотим каждый раз описывать

#endif

Про использование "if-def" стражей описано ниже в разделе "Подробнее о библиотеках".

Файл с расширением *.cpp (myNewLib.cpp) - это файл, в котором храниться сам код функций. Все переменные и константы указанные в этом файле будут недоступны из скетча, если их нет в *.h файле.

Напишем в нём следующий код (для корректного отображения кириллических символов в мониторе последовательного порта файл должен быть сохранён в кодировке UTF-8):

#include <Arduino.h>
#include "myNewLib.h"

void printPI()
{
        Serial.print("Число ПИ = ");
        Serial.println(myPI);
}

Готово! Теперь константу myPI и функцию printPI можно использовать из любого скетча, подключив в него myNewLib.h

Создадим новый скетч и вызовем нашу функцию printPI:

#include <myNewLib.h>

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

void loop()
{
}

Подробнее о библиотеках:

Файловое дерево

При оформлении библиотеки по всем канонам Arduino необходимо соблюдать следующую структуру файлов библиотеки:

  • Папка: Название_библиотеки Папка: examples - здесь находятся примеры использования текущей библиотеки Папка: extras - здесь разработчик библиотеки может разместить файлы документации Папка: src - здесь находится исходный код библиотеки Файл: Название_библиотеки.h Файл: Название_библиотеки.cpp Другие исходные файлы, используемые библиотекой Файл: library.properties - файл свойств библиотеки, подробнее про этот файл рассмотрено ниже. Файл: keywords.txt - файл подсветки синтаксиса, подробнее ниже.

На примере библиотеки iarduino_I2C_Relay:

Файлы заголовков

Преобразование кода языка Си в машинный происходит в несколько этапов: обработка препроцессором (preprocessor), компиляция (compile) и компоновка (linking). Здесь мы рассмотрим обработку кода препроцессором.

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

Когда мы добавляем файл .h в нём обычно описаны прототипы функций, а сами функции находятся в .cpp или .c файле, который обычно называется также и в самом его начале подключён этот самый файл .h. Но файла .cpp или .c может не быть и вместо него может использоваться уже скомпилированный код (обычно у таких файлов суффикс .o или .so). Именно необходимость использовать уже скомпилированные части программы и привела к использованию файлов заголовков на заре развития цифровой электронных вычислительных машин, когда и память и вычислительные возможности этих машин были невелики по сравнению с современными персональными компьютерами и даже смартфонами.

Иногда в файле скетча используются несколько библиотек, и одна из библиотек может подключать в себе другую библиотеку, которая уже подключена в скетч. Чтобы код одной и той же библиотеки не был подключён несколько раз в один и тот же скетч, используют так называемые иф-деф стражи (#ifdef guards). Для этого определяют пустой макрос с уникальным названием при помощи директивы #define. Перед компиляцией препроцессор не включит код после определённого макроса дважды. Например:

#ifndef __MY_LIBRARY__ // если макрос не определён
#define __MY_LIBRARY__ // определяем макрос

// далее идёт код заголовка библиотеки

#endif // закрываем верхний ifdef (если марос уже был определён, то компилятор пропустит весь код до этой строчки и не включит его в скомпилированную программу.

Расположения строки директивы #include библиотеки в файле скетча имеет значение. Использовать функции и другие атрибуты библиотеки возможно только ниже директивы #include этой библиотеки. Так же, если до директивы #include определить макросы, то это может повлиять на работу библиотеки. Например, если определить iarduino_I2C_SW, pin_SW_SCL и pin_SW_SDA до подключения библиотеки iarduino_I2C_Relay, то библиотека будет использовать программную шину I2C. Например:

// определяем программный i2c
#define iarduino_I2C_SW
#define pin_SW_SCL 7
#define pin_SW_SDA 8

// Подключаем библиотеку и создаём её объект
#include <iarduino_I2C_Relay.h>
iarduino_I2C_Relay relay(0x09);

Файл свойств библиотеки

Файл свойств библиотеки library.properties является списком формата ключ=значение, в нём описываются свойства библиотеки следующими полями:

  • name - название библиотеки. В названии можно использовать только следующие символы: латинские буквы (a-z и A-Z), цифры (0-9), пробел, символ подчёркивания(_), точки (.) и тире (-). Название должно начинаться с буквы.
  • version - версия библиотеки. Должна быть совместима с semver.
  • author - имя авторов и адреса электронной почты, разделённые запятой.
  • maintainer - имя и адрес электронной почты человека, занимающегося поддержкой библиотеки
  • sentence - цель библиотеки в одном предложении
  • paragraph - более развёрнутое объяснение библиотеки
  • category - (по умолчанию Uncategorized) категория библиотеки. Допустимые значения: Display Communication Signal Input/Output Sensors Device Control Timing Data Storage Data Processing Other
  • url - ссылка на страницу библиотеки. Отображается в Менеджере библиотек.
  • architectures - (по умолчанию * - все архитектуры) архитектуры процессоров, поддерживаемых библиотекой. Список, разделённый запятыми.
  • depends - (доступно с версии Arduino IDE 1.8.10, необязательное поле) список, разделённый запятыми. Библиотеки необходимые для компиляции текущей библиотеки.
  • dot_a_linkage - (доступно с версии Arduino IDE 1.6.0, необязательное поле) если поле установлено как true библиотека будет скомпилирована в архивный .a файл. Обычно файлы библиотеки компилируются в объектные файлы .o. В данном случае они будут совмещены вместе в один файл перед компоновкой.
  • includes - (доступно с версии Arduino IDE 1.6.10, необязательное поле) список, разделённый запятыми. Все необходимые директивы #include для данной библиотеки. Применяется при использовании пункта меню "Скетч -> Подключить библиотеку". Если данного поля нет, то подключаются все файлы с расширением .h данной библиотеки.
  • precompiled - (доступно с версии Arduino IDE 1.8.6, необязательное поле) возможно два значения: true - исходные файлы всегда компилируются full - если в библиотеке есть уже скомпилированные файлы, то будут использованы они.
  • ldflags - (доступно с версии Arduino IDE 1.8.6, необязательное поле) дополнительные флаги компоновщика.

Пример:

name =          iarduino I2C Relay (силовые ключи и реле)
version =       1.1.3
author =        iarduino <shop@iarduino.ru>
maintainer =    Панькин Павел <shop@iarduino.ru>
sentence =      Библиотека для работы с силовыми ключами и реле.
paragraph =     Позволяет управлять силовыми ключами и реле, устанавливать ШИМ на силовых ключах, считывать ток с каналов силовых ключей и устанавливать защиту по току.
category =      Signal Input/Output
url =           /file/513.html
architectures = avr,esp8266,esp32
includes =      iarduino_I2C_Relay.h

Файл подсветки синтаксиса

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

Всего возможно использовать пять цветов подсветки:

  • KEYWORD1 - используется для типов данных
  • KEYWORD2 - используется для функций
  • KEYWORD3 - используется для структур
  • LITERAL1 - используется для констант
  • LITERAL2 - ?

Пример файла из библиотеки iarduino_I2C_Relay

#   ####################################################
#   СИНТАКСИЧЕСКАЯ РАСКРАСКА ДЛЯ БИБИЛИОТЕКИ:
#   iarduino_I2C_Relay
#   ####################################################
#   ТИПЫ ДАННЫХ:                               (KEYWORD1)
    iarduino_I2C_Relay  KEYWORD1
#   ####################################################
#   МЕТОДЫ И ФУНКЦИИ:                          (KEYWORD2)
    begin   KEYWORD2
    changeAddress   KEYWORD2
    reset   KEYWORD2
    getAddress  KEYWORD2
    getVersion  KEYWORD2
    getModel
    digitalWrite    KEYWORD2
    digitalRead KEYWORD2
    analogWrite KEYWORD2
    analogRead  KEYWORD2
    analogAveraging KEYWORD2
    freqPWM KEYWORD2
    currentWrite    KEYWORD2
    currentWrite    KEYWORD2
    currentRead KEYWORD2
    setCurrentProtection    KEYWORD2
    getCurrentProtection    KEYWORD2
    delCurrentProtection    KEYWORD2
    resCurrentProtection    KEYWORD2
    enableWDT   KEYWORD2
    disableWDT  KEYWORD2
    resetWDT    KEYWORD2
    getStateWDT KEYWORD2

#   ####################################################
#   КОНСТАНТЫ:                                 (LITERAL1)
    LOW LITERAL1
    HIGH    LITERAL1
    CURRENT_DISABLE LITERAL1
    CURRENT_LIMIT   LITERAL1
    ALL_CHANNEL LITERAL1
    DEF_MODEL_2RM   LITERAL1
    DEF_MODEL_4RT   LITERAL1
    DEF_MODEL_4NC   LITERAL1
    DEF_MODEL_4PC   LITERAL1
    DEF_MODEL_4NP   LITERAL1
    DEF_MODEL_4PP   LITERAL1

Компиляция

Arduino - это не совсем Си/Си++. Arduino использует фреймворк Wiring, который в свою очередь использует набор копмиляторов GNU (GCC - GNU Compiler Collection). В случае с микроконтроллерами на процессорах семейства AVR (Arduino UNO/NANO/MEGA/LEONARDO...) используется AVR-GCC.

После нажатия на кнопку компиляции код скетча обрабатывается следующим образом:

Код скетча и все используемые библиотеки копируются во временную папку. На ОС Window обычно это C:\Users\%UserName%\AppData\Local\Temp\, если в файлах настроек Arduino не указан специфический build.path. Далее в этой папке создаётся папка вида: arduino_build_XXXXXX где XXXXXX - это цифры.

Создаются временные файлы из кода скетча, который прошёл процедуру обработки препроцессором. Код конвертируется в пригодный для компилятора C++:

  • Добавляется файл заголовка Arduino.h
  • Добавляются прототипы всех функций, используемых в программе
  • В main.cpp добавляются директивы #include для всех библиотек, используемых в скетче.

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

Далее происходит компиляция. Код компилируется из файла main.cpp, исходный код которого в ОС Windows находится по следующему пути: C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino\main.cpp

В этом исходном файле и вызываются функции setup() и loop().

Вот код этого файла:

/*
  main.cpp - Main loop for Arduino sketches
  Copyright (c) 2005-2013 Arduino Team.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include <Arduino.h>

// Declared weak in Arduino.h to allow user redefinitions.
int atexit(void (* /*func*/ )()) { return 0; }

// Weak empty variant initialization function.
// May be redefined by variant files.
void initVariant() __attribute__((weak));
void initVariant() { }

void setupUSB() __attribute__((weak));
void setupUSB() { }

int main(void)
{
        init();

        initVariant();

#if defined(USBCON)
        USBDevice.attach();
#endif

        setup();

        for (;;) {
                loop();
                if (serialEventRun) serialEventRun();
        }

        return 0;
}

Ссылки

Все уроки