Общие сведения:
Иногда один и тот же код необходимо использовать в разных скетчах и для удобства этот код можно записать в одно место и использовать его, подключая к текущему скетчу. Это и есть библиотека и в этой статье мы рассмотрим пример её создания и использования.
Если Вы часто используете один и тот же фрагмент кода, одни и те же константы и/или структуры данных, возможно пора задуматься об оптимизации времени и оформления этого кода как библиотеки.
Данная статья не является полным руководством по написанию библиотек для Arduino IDE. Более подробную информацию о библиотеках можно узнать по ссылкам:
- https://www.arduino.cc/en/Hacking/LibraryTutorial
- https://www.arduino.cc/en/Reference/APIStyleGuide
- https://arduino.github.io/arduino-cli/0.19/library-specification/
Минимальная библиотека:
Далее в статье рассказывается о том, как правильно сделать библиотеку и что в ней должно быть. Но сначала расскажем как сделать минимальное количество действий, чтобы код работал из любого скетча.
В папке, где находятся скетчи по умолчанию, есть папка 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;
}