Как создать библиотеку в ардуино
Создание собственной библиотеки для Ардуино
Этот документ поможет разобраться, как создавать библиотеки для Ардуино. Сначала будет рассмотрена программа, генерирующая сигналы азбуки Морзе, а затем даны пояснения, как вынести ее функции в отдельную библиотеку. Использование библиотек позволяет другим людям использовать написанный вами код, а также легко обновлять его по мере выхода новых версий вашей библиотеки.
Начнем с программы, генерирующий простой сигнал азбуки Морзе:
int pin = 13; void setup() < pinMode(pin, OUTPUT); >void loop() < dot(); dot(); dot(); dash(); dash(); dash(); dot(); dot(); dot(); delay(3000); >void dot() < digitalWrite(pin, HIGH); delay(250); digitalWrite(pin, LOW); delay(250); >void dash()
Если запустить эту программу, то можно убедиться, что она подает сигнал SOS (сигнал бедствия) светодиодом, подключенным к 13 выводу.
В программе есть несколько участков, которые нам необходимо объединить в библиотеку. Во-первых, конечно же, это функции dot() и dash(), которые и формируют сигнал. Во-вторых, это переменная pin, которая используется функциями для того, чтобы знать, с каким именно выводом необходимо работать. И, наконец, в программе есть вызов функции pinMode(), которая заставляет работать указанный вывод в качестве выхода.
Пора бы сделать из нашей программы библиотеку!
Для этого вам понадобится, по меньшей мере, два файла: заголовочный файл (с расширением .h) и файл с исходным кодом (с расширением .cpp). Заголовочный файл представляет собой описание библиотеки: чаще всего, это просто список всего, что в ней есть. Файл-исходник содержит непосредственно программный код библиотеки. Назовем нашу библиотеку «Morse», соответственно, наш заголовочный файл будет «Morse.h». Давайте посмотрим, что внутри этого файла. Поначалу содержимое файла может показаться вам немного странным, однако все станет на свои места, как только вы увидите исходник, идущий «в комплекте».
Структура заголовочного файла представляет собой набор строк, каждая из которых соответствует одной функции библиотеки. Эти строки находятся внутри класса, который также может включать в себя все необходимые переменные:
class Morse < public: Morse(int pin); void dot(); void dash(); private: int _pin; >;
Класс — это просто набор функций и переменных, собранных в одном месте. Эти функции и переменные могут быть общедоступными (public) — т.е. могут вызываться людьми, работающими с вашей библиотекой, или внутренними (private) — которые видны только в пределах самого класса. У каждого класса есть специальная функция, называемая конструктором (constructor), которая предназначена для создания экземпляра (instance) класса. Конструктор имеет такое же имя, как и класс, и не возвращает никаких значений.
Помимо этого, есть еще несколько деталей, которые необходимо включить в заголовочный файл. Одна из них — это оператор #include, который позволяет подключить к нашей библиотеке стандартные типы и константы языка Ардуино (такой оператор добавляется автоматически в коде обычных программ, но в библиотеке его нужно дописать самому). Он выглядит примерно так (и располагается перед объявлением показанного выше класса):
#include "Arduino.h"
И последнее: общепринято заключать все содержимое заголовочного файла в странную конструкцию:
#ifndef Morse_h #define Morse_h // здесь располагается оператор #include и весь остальной код. #endif
По сути, это предотвращает возможные проблемы на случай, если кто-то подключит вашу библиотеку дважды.
В завершение, наверху библиотеки обычно располагают комментарий, содержащий название библиотеки, краткое описание того, что она делает, автора, дату создания и тип лицензии.
Давайте взглянем на заголовочный файл и посмотрим, что у нас получилось:
/* Morse.h - Library for flashing Morse code. Created by David A. Mellis, November 2, 2007. Released into the public domain. */ #ifndef Morse_h #define Morse_h #include "Arduino.h" class Morse < public: Morse(int pin); void dot(); void dash(); private: int _pin; >; #endif
А теперь давайте разберем содержимое исходного файла — Morse.cpp.
Вначале файла идет несколько операторов #include, которые предоставляют остальной программе доступ к стандартным функциям Ардуино и к объявлениям функций внутри заголовочного файла:
#include "Arduino.h" #include "Morse.h"
Затем идет конструктор, который описывает, что должно произойти, когда кто-то создаст экземпляр вашего класса. В данном случае пользователь указывает, какой именно вывод он хотел бы использовать. Мы конфигурируем этот вывод в качестве выхода и сохраняем его во внутреннюю переменную для последующего использования в других функциях:
Morse::Morse(int pin)
В этом коде есть непонятные моменты. Во-первых, конструкция Morse:: перед именем функции. Это значит, что функция является частью класса Morse. То же самое вы увидите при объявлении других функций этого класса. Второй момент — это знак подчеркивания в имени нашей внутренней private-переменной, _pin. Вообще-то эта переменная может иметь любой имя, главное, чтобы она соответствовала имени, объявленному в заголовочном файле. Добавление подчеркивания перед именем переменной — это общепринятая методика, применяющаяся для того, чтобы явно отличать private-переменные. Кроме того, подчеркивание позволяет программе отличить private-переменную от аргумента функции (pin в данном случае).
Далее идет сам код из первоначальной программы (ну наконец-то!). Он выглядит абсолютно точно так же, за исключением приставки Morse:: перед именами функций и переменной _pin вместо pin:
void Morse::dot() < digitalWrite(_pin, HIGH); delay(250); digitalWrite(_pin, LOW); delay(250); >void Morse::dash()
В завершение, хорошим тоном считается добавление комментария в начале исходного файла. Посмотрим что получилось:
/* Morse.cpp — Library for flashing Morse code. Created by David A. Mellis, November 2, 2007. Released into the public domain. */ #include «Arduino.h» #include «Morse.h» Morse::Morse(int pin) < pinMode(pin, OUTPUT); _pin = pin; >void Morse::dot() < digitalWrite(_pin, HIGH); delay(250); digitalWrite(_pin, LOW); delay(250); >void Morse::dash()
И это все, что необходимо сделать (есть еще некоторые возможности, но мы поговорим о них чуть позже). Теперь попробуем нашу библиотеку в действии.
Прежде всего, создайте папку Morse в директории libraries внутри вашей рабочей папки с проектами. Скопируйте или переместите файлы Morse.h и Morse.cpp в созданную папку. Теперь запустите среду разработки Ардуино — в меню Sketch > Import Library вы должны увидеть библиотеку Morse. Она будет автоматически компилироваться вместе с использующими ее программами. Если этого не произойдет — проверьте ее расширение и убедитесь, что файл действительно имеет формат .cpp или .h (без дополнительных расширений вроде .pde или .txt, например).
Попробуем переписать нашу старую программу «SOS» с использованием новой библиотеки:
#include Morse morse(13); void setup() < >void loop()
По сравнению с предыдущей версией в программе появилось несколько отличий (кроме того факта, что часть кода перенесена в библиотеку).
Во-первых, мы добавили оператор #include в начало программы, который включает библиотеку Morse в отправляемый плате код. Поэтому, если в программе библиотека больше не используется, желательно удалить #include для экономии памяти микроконтроллера.
Во-вторых, теперь мы создаем экземпляр класса Morse с именем morse:
Morse morse(13);
При выполнении этой строки (а фактически, это произойдет даже до функции setup()) будет вызван конструктор класса Morse, которому будет передан указанный здесь аргумент (в данном случае 13).
Обратите внимание, что теперь наша функция setup() пуста, поскольку вызов pinMode() в данном случае происходит внутри библиотеки (при создании экземпляра класса).
Ну и наконец, вызов функций dot() и dash() теперь необходимо предварять префиксом morse. — именем того экземпляра, который мы хотим использовать. Мы можем создать несколько экземпляров класса Morse, каждый со своим выводом, хранимым во внутренней переменной _pin только в пределах этого экземпляра. Указывая определенный экземпляр класса при вызове функции, мы тем самым задаем, переменными какого экземпляра должна оперировать та или иная функция. То есть, если у нас два экземпляра:
Morse morse(13); Morse morse2(12);
то внутри функции morse2.dot() переменная _pin будет равна 12.
При написании новой программы, вы, наверняка заметите, что среда разработки не распознает и не подсвечивает элементы созданной нами библиотеки. К сожалению, IDE Ардуино не умеет автоматически распознавать и интерпретировать то, что мы объявили внутри библиотеки (кстати, было бы хорошо добавить эту функцию), поэтому ей нужно немного помочь. Для этого создайте файл keywords.txt в директории Morse и запишите в него следующее:
Morse KEYWORD1 dash KEYWORD2 dot KEYWORD2
Каждая строка должна содержать ключевое слово, символ табуляции (не пробелы) и тип ключевого слова. Классы подсвечиваются оранжевым и должны иметь тип KEYWORD1; функции — коричневым и должны быть типа KEYWORD2. Для того, чтобы внесенные изменения вступили в силу, необходимо перезапустить среду Ардуино.
Также неплохо было бы снабдить библиотеку примером работы с ней. Для этого, создайте папку examples в директории Morse и переместите (либо скопируйте) в нее папку с нашей программой (назовем ее SOS). (Отыскать программу можно с помощью команды Sketch > Show Sketch Folder). Если вы перезапустите среду Ардуино (честно слово, это в последний раз) — то увидите пункт Library-Morse в меню File > Sketchbook > Examples с вашим примером. Можете добавить немного комментариев, объясняющих, как пользоваться вашей библиотекой.
Если вы захотите посмотреть готовую библиотеку (с примером и ключевыми словами) — можно сказать ее отсюда: Morse.zip.
На сегодня, пожалуй, это все, но в ближайшее время возможно появится расширенное руководство по созданию библиотек. А тем временем, если у вас возникнут проблемы или появятся предложения, пожалуйста, пишите их на форум.
Пишем свою библиотеку


В этом уроке мы научимся писать собственные библиотеки для Arduino и разберём некоторые типовые вопросы по взаимодействию кода в библиотеке и кода в скетче (в главном файле программы). Это третий урок, который относится к библиотекам: обязательно прочитайте и усвойте урок про объекты и классы из блока программирования, и урок по использованию библиотек из блока базовых уроков, а также урок про создание функций. В этом уроке мы будем использовать все наши предыдущие знания, так что рекомендую разобраться со всем, что было непонятно. Писать библиотеки очень удобно в текстовом редакторе Notepad++ (официальный сайт) – так называемом блокноте программиста. Данный блокнот распознаёт и подсвечивает синтаксис, умеет в автодополнение текста и расширенный поиск, и многое многое другое. Безумно рекомендую работать именно в нём, если вы не умеете пользоваться Microsoft Visual Studio и прочими серьёзными средами разработки. Также рекомендую к прочтению вот этот урок с сайта Arduino.ru, в нём кратко пошагово рассказывают о создании библиотеки без излишеств. Если будете компилировать пример из этой статьи – замените WProgram.h на Arduino.h.
Разбираемся с файлами
Библиотека – это в первую очередь текстовый файл с кодом, который мы можем подключить в свой скетч и использовать имеющиеся там команды. Библиотека может иметь несколько файлов или даже папок с файлами, но подключается всегда один – главный заголовочный файл с расширением .h, а он в свою очередь подтягивает остальные необходимые файлы. В общем случае библиотека имеет такую структуру (название библиотеки testLib):
- testLib – папка библиотеки
- examples – папка с примерами
- testLib.h – заголовочный файл
- testLib.cpp – файл реализации
- keywords.txt – карта подсветки синтаксиса
Иногда файлы .h и .cpp могут находиться в папке src. Все файлы и папки, кроме заголовочного .h, являются необязательными и могут отсутствовать, т.е. библиотека может состоять только из заголовочного файла. В таком виде библиотека лежит в папке со всеми остальными библиотеками и может быть подключена в скетч при помощи команды #include. Вообще есть два места, где программа будет искать библиотеку (именно файл библиотеки):
- Папка со скетчем
- Папка с библиотеками
Соответственно команда include имеет два варианта поиска файла, название заключается в <> или “”:
- #include – будет искать файл в папке с библиотеками
- #include “файл.h” – попробует найти файл в папке со скетчем, если не найдёт – пойдёт искать в папку с библиотеками

Выглядит это вот так:
Основа библиотеки
Давайте заполним наш файл testLib.h, нашу тестовую библиотеку, минимальным кодом для работы:
#ifndef testLib_h #define testLib_h #include // код библиотеки #endif
Конструкция из директив препроцессора запрещает повторное подключение библиотеки и в целом является необязательной, но лучше не лениться и писать так. Файл библиотеки testLib.h находится в папке testLib в папке со всеми остальными библиотеками. Также мы подключаем основной файл Arduino.h для использования ардуино-функций в своём коде. Если таковых нет – его можно не подключать. Также подключаем testLib.h в наш тестовый скетч, как на скриншоте в прошлой главе. Конструкцию с #ifndef-define вы найдёте практически во всех библиотеках. На текущих версиях IDE (и, соответственно версии компилятора) можно делать так:
#pragma once // подключаем Ардуино.н // код библиотеки
Конструкция pragma once говорит компилятору, что данный файл нужно подключить только один раз, это просто короткая альтернатива #ifndef-define. Дальше будем использовать её
Пишем класс
Давайте воспользуемся наработками из урока объекты и классы и вставим финальную версию класса в testLib.h
#pragma once #include // описание класса class Color < // класс Color public: Color(byte color = 5, byte bright = 30); void setColor(byte color); void setBright(byte bright); byte getColor(); byte getBright(); private: byte _color; // переменная цвета byte _bright; // переменная яркости >; // реализация методов Color::Color(byte color = 5, byte bright = 30) < // конструктор _color = color; // запоминаем _bright = bright; >void Color::setColor(byte color) void Color::setBright(byte bright) byte Color::getColor() byte Color::getBright()
testSketch.ino
#include Color myColor(10); // создаём объект myColor, указав _color (получим 10, 30) Color myColor2(10, 20); // указываем цвет и яркость! (получим 10, 20) Color myColor3; // без инициализации (получим 5, 30) void setup() < >void loop()Собственно вот так мы разместили наш класс в отдельном файле, подключили его в основную программу и воспользовались кодом: просто создали несколько объектов. Давайте проверим, работает ли оно: выведем возвращающие методы в порт:
testSketch.ino
#include Color myColor(10); // создаём объект myColor, указав _color (получим 10, 30) Color myColor2(10, 20); // указываем цвет и яркость! (получим 10, 20) Color myColor3; // без инициализации (получим 5, 30) void setup() < Serial.begin(9600); Serial.println(myColor.getColor()); // 10 Serial.println(myColor2.getBright()); // 20 Serial.println(myColor3.getColor()); // 5 >void loop()Код выводит значения из класса, выводит правильно. Собственно вот мы и написали свою библиотеку! Далее можно разделить описание и реализацию, создав файл testLib.cpp
#pragma once #include // описание класса class Color < // класс Color public: Color(byte color = 5, byte bright = 30); void setColor(byte color); void setBright(byte bright); byte getColor(); byte getBright(); private: byte _color; // переменная цвета byte _bright; // переменная яркости >;
testLib.cpp
#include // подключаем заголовок обязательно // реализация методов Color::Color(byte color = 5, byte bright = 30) < // конструктор _color = color; // запоминаем _bright = bright; >void Color::setColor(byte color) void Color::setBright(byte bright) byte Color::getColor() byte Color::getBright()
testSketch.ino
#include Color myColor(10); // создаём объект myColor, указав _color (получим 10, 30) Color myColor2(10, 20); // указываем цвет и яркость! (получим 10, 20) Color myColor3; // без инициализации (получим 5, 30) void setup() < Serial.begin(9600); Serial.println(myColor.getColor()); // 10 Serial.println(myColor2.getBright()); // 20 Serial.println(myColor3.getColor()); // 5 >void loop()Важный момент: если в библиотеке есть файл имябиблиотеки.cpp, то реализация методов и функций должна находиться именно там! В файле имябиблиотеки.h реализацию указывать нельзя, будет ошибка.
Если библиотека состоит только из заголовочного файла имябиблиотеки.h , то реализацию можно расписать в нём.
И вот уже у нас полноценная взрослая библиотека, разбитая на файлы. Можно дополнить её файлом keywords.txt, чтобы наши методы подсвечивались в коде.
Keywords.txt
keywords.txt это файл, в котором содержится “карта” подсветки синтаксиса, то есть каким цветом какие слова подсвечивать. Синтаксис построения этого файла очень прост: с новой строки перечисляются названия функций/методов, и через табуляцию (нажатие клавиши TAB) – тип ключевого слова.
- KEYWORD1 – жирный оранжевый, подсветка для типов данных и названий классов
- KEYWORD2 – оранжевый цвет, для методов и функций
- LITERAL1 – голубой цвет, для констант
Вот так будет выглядеть keywords.txt для нашей библиотеки:
# комментарий testLib KEYWORD1 Color KEYWORD1 setColor KEYWORD2 setBright KEYWORD2 getColor KEYWORD2 getBright KEYWORD2

Можно оставлять комментарии, здесь они начинаются с решётки #. Констант у нас нет, поэтому LITERAL1 не использовал. Давайте посмотрим, как выглядит код с подсветкой наших команд из библиотеки. Важный момент: чтобы изменения вступили в силу, нужно закрыть все окна Arduino IDE и открыть скетч заново. Почему Color не выделен жирным, да и вообще уже выделен в скетче без подсветки? Дело в том, что Arduino IDE собирает keywords из всех библиотек, и где-то имя Color видимо уже используется. Собственно вот и всё!
Примеры реализации
Структуру создания библиотеки мы разобрали, давайте рассмотрим некоторые частные варианты с примерами. Я буду делать примеры именно с классами, а не с функциями, потому что механика работы с классом, с библиотекой, гораздо сложнее, а мы тут с вами учимся библиотеки писать. Во всех примерах у меня создана тестовая библиотека testLib.h, и тестирую я её в скетче testSketch.
Библиотека без класса
В библиотеке необязательно должен быть класс, может быть просто набор функций:
#pragma once #include void printLol()
testSketch.ino
#include void setup() < Serial.begin(9600); printLol(); // выведет lol >void loop()Очевидные вариации: более грамотно будет написать объявление отдельно от реализации функции. Или даже поместить реализацию в файл .cpp.
#pragma once #include // объявление void printLol();
testLib.cpp
#include // подключаем заголовок обязательно // реализация void printLol()
testSketch.ino
#include void setup() < Serial.begin(9600); printLol(); // выведет lol >void loop()Обернём в namespace
Данный пример относится к примеру выше: в “библиотеке” мы создали функции, имена этих функций могут совпасть с другими функциями в скетче, что приведёт к проблемам. Вместо написания класса, функции можно обернуть в “пространство имён” – namespace. Смотрите пример, я думаю всё станет понятно.
#pragma once #include // пространство имён myFunc namespace myFunc < void printLol(); >; // реализация void myFunc::printLol()
testSketch.ino
#include void setup() < Serial.begin(9600); // выведет kek из функции скетча printLol(); // выведет lol из функции библиотеки myFunc::printLol(); >void printLol() < Serial.println("kek"); >void loop()Использование namespace позволяет разделить функции с одинаковыми названиями из разных документов, обращение к функции из пространства имён выглядит точно так же, как к классу: имяПространстваИмён::имяФункции .
Передача и вывод значения в класс
Рассмотрим такой пример: нужно передать в класс некое значение, обработать его и вернуть результат обратно в скетч. В качестве примера просто вернём умноженное на 10 число:
#pragma once #include class testClass < public: long get_x10(int value); private: >;
testLib.cpp
#include // подключаем заголовок обязательно long testClass::get_x10(int value)
testSketch.ino
#include testClass testObject; void setup() < Serial.begin(9600); Serial.println(testObject.get_x10(450)); // выведет 4500 >void loop()Рассмотрим более сложную ситуацию: нужно принять значение в класс, записать в приватную переменную, и отдельным методом получить её:
#pragma once #include class testClass < public: void setValue(int val); int getValue(); private: int _value = 0; >;
testLib.cpp
#include // подключаем заголовок обязательно void testClass::setValue(int val) < // берём внешнюю val и пишем в свою _value _value = val; >int testClass::getValue() < return _value; // вернуть переменную из класса >
testSketch.ino
#include testClass testObject; void setup() < Serial.begin(9600); testObject.setValue(666); Serial.println(testObject.getValue()); // выведет 666 >void loop()Изменение переменной из класса
Рассмотрим такую ситуацию: нам нужно при помощи метода/функции библиотеки изменить значение переменной в скетче. Тут есть два варианта: присваивать напрямую, или использовать указатель. Рассмотрим оба варианта в одном примере:
#pragma once #include class testClass < public: int multTo5(int value); void multTo7(int* value); private: >;
testLib.cpp
#include // подключаем заголовок обязательно int testClass::multTo5(int value) < // вернуть значение, умноженное на 5 return value * 5; >void testClass::multTo7(int* value) < // умножить переменную на 7 *value = *value * 7; >
testSketch.ino
#include testClass testObject; void setup() < int a = 10; a = testObject.multTo5(a); // a == 50 testObject.multTo7(&a); // a == 350 >void loop()В первом варианте мы передаём значение переменной, внутри метода умножаем его на 5 и возвращаем обратно, и можем приравнять эту же переменную в скетче к новому значению. В случае с указателем всё работает более интересно: мы передаём методу адрес переменной, умножаем эту переменную на 7 внутри класса, и всё. Грубо говоря, в этом примере *value является куклой вуду для переменной a: что мы будем делать с *value внутри метода – это сразу же будет отражаться на a. Данную тему можно развить до такого варианта: мы можем хранить в классе адрес переменной, и класс всегда будет иметь прямой доступ к значению переменной, его не нужно будет передавать каждый раз!
#pragma once #include class testClass < public: void takeControl(int* value); void multTo6(); private: int *_value; // храним адрес >;
testLib.cpp
#include // подключаем заголовок обязательно void testClass::takeControl(int* value) < _value = value; >void testClass::multTo6()
testSketch.ino
#include testClass testObject; int a = 10; void setup() < // передали адрес a testObject.takeControl(&a); // сейчас а == 10 testObject.multTo6(); // тут а станет 60 a = 5; testObject.multTo6(); // тут а станет 30 testObject.multTo6(); // тут а станет 180 >void loop()
Таким образом класс и его методы могут иметь полный контроль над переменной в основной программе!Передача массива в класс
Попробуем передать массив в класс, чтобы методами класса можно было, например, сложить сумму элементов массива и вернуть её!
#pragma once #include class testClass < public: long getSum(int *array, byte length); private: >;
testLib.cpp
#include // подключаем заголовок обязательно long testClass::getSum(int *array, byte length) < long sum = 0; // вычисляем длину массива length = length / sizeof(int); for (byte i = 0; i < length; i++) < sum += array[i]; >return sum; >
testSketch.ino
#include testClass testObject; void setup() < // делаем массив int myArray[] = ; // передаём массив и его размер (в байтах) long arraySum = testObject.getSum((int*)myArray, sizeof(myArray)); // arraySum == 438 > void loop()
Основной механизм я думаю понятен, оставлю тут ещё пример, как передать структуру
Передача структуры по указателю// передаем структуру по указателю struct foo_param_t < float *u; int n; float b; float c; >void foo(foo_param_t *p) < for (int i=0; in; i++) < float x = i*M_PI; p->u[i] = 1.0+p->b*x+p->c*x*x; > > void bar() < const int N = 10; float a[N]; foo_param_t p = ; foo(&p); >
Передача структуры по ссылке
// передаем структуру по ссылке struct foo_param_t < float *u; int n; float c; float b; >void foo(foo_param_t& p) < for (int i=0; i> void bar() < const int N = 10; float a[N]; foo_param_t p = ; foo(p); >
Передача функции в класс
Я думаю вы помните, как работают штуки типа attachInterrupt: мы указываем имя функции, которую можно будет вызвать из другой функции. Делается это через указатель на функцию. Посмотрим простой пример без класса:
#pragma once #include // тут хранится приаттааченная функция void (*atatchedF)(); // подключаем функцию void attachFunction(void (*function)()) < atatchedF = *function; >// вызов приаттаченной функции void callFunction()
testSketch.ino
#include void setup() < Serial.begin(9600); // подключили функцию printKek attachFunction(printKek); // вызвали подключенную функцию callFunction(); // вызовет printKek >void printKek() < Serial.println("kek"); >void loop()Теперь засунем это всё в класс и будем хранить адрес прикреплённой функции внутри класса. Тут есть два варианта, покажу оба, просто чтобы было тут в одном месте это всё. Первый вариант:
#pragma once #include class testClass < public: void attachFunction(void (*function)()); void callFunction(); private: void (*atatchedF)(); >;
testLib.cpp
#include // подключаем заголовок обязательно void testClass::attachFunction(void (*function)()) < atatchedF = *function; >void testClass::callFunction()
testSketch.ino
#include testClass testObj; void setup() < Serial.begin(9600); // подключили функцию printKek testObj.attachFunction(printKek); // вызвали подключенную функцию testObj.callFunction(); // вызовет printKek >void printKek() < Serial.println("kek"); >void loop()
Второй вариант:#pragma once #include extern "C" < typedef void (*func)(void); >class testClass < public: void attachFunction(func newFunc); void callFunction(); private: func _attachedFunc; >;
testLib.cpp
#include // подключаем заголовок обязательно void testClass::attachFunction(func newFunc) < _attachedFunc = newFunc; >void testClass::callFunction()
testSketch.ino
#include testClass testObj; void setup() < Serial.begin(9600); // подключили функцию printKek testObj.attachFunction(printKek); // вызвали подключенную функцию testObj.callFunction(); // вызовет printKek >void printKek() < Serial.println("kek"); >void loop()Автоматическое создание объекта
Создание класса подразумевает также создание объекта, но иногда библиотека пишется только для одного объекта (например – библиотека для работы с одним интерфейсом), и создание объекта в скетче выглядит как лишний код. Но, если вы откроете любой пример с использованием библиотеки Wire.h, вы не найдёте там создания объекта Wire, а он используется! Например:
#include void setup() < Wire.begin(); >// .
Мы используем объект Wire, но мы его не создавали! Иногда это может быть удобно, давайте покажу, как это сделать: нужно всего лишь добавить в заголовочный файл строчку:
extern имя_класса имя_объекта;
А в .cpp, если он есть, добавить:
имя_класса имя_объекта = имя_класса();
Таким образом объект будет создан внутри библиотеки, и мы сможем им пользоваться из скетча. Давайте возьмём самый первый пример из урока, из главы “Передача и вывод значения в класс”, и избавимся от лишнего создания объекта:
#pragma once #include class testClass < public: long get_x10(int value); private: >; extern testClass testObject;
testLib.cpp
#include // подключаем заголовок обязательно long testClass::get_x10(int value) < return value*10; >testClass testObject = testClass();
testSketch.ino
#include // объект не создаём! void setup() < Serial.begin(9600); Serial.println(testObject.get_x10(450)); // выведет 4500 >void loop()Задание размера массива при создании объекта
Вы должны помнить из урока про массивы, что размер массива должен быть известен до начала выполнения программы. А что делать, если в классе нам нужен массив с возможностью задать его размер? Если объект в программе будет один, или для всех объектов размер массива будет одинаковый, то очевидно можно сделать вот так:
#define ARRAY_LEN 20 class myClass < public: byte vals[ARRAY_LEN]; private: >; myClass obj1; // тут у obj1.vals будет 20 ячеек myClass obj2; // тут у obj2.vals будет 20 ячеек myClass obj3; // тут у obj3.vals будет 20 ячеек
Если мы хотим иметь возможность задать размер массива каждому объекту, то тут есть варианты:
При создании объектов появится соответственно
template < int ARRAY_LEN >class myClass < public: byte vals[ARRAY_LEN]; private: >; myClass obj1; // тут у obj1.vals будет 10 ячеек myClass obj2; // тут у obj2.vals будет 20 ячеек myClass obj3; // тут у obj3.vals будет 30 ячеек
Также можно записать размер массива в переменную, чтобы использовать в дальнейшем коде:
template < int ARRAY_LEN >class myClass < public: byte vals[ARRAY_LEN]; byte arrSize = ARRAY_LEN; private: >; myClass obj1; // obj1.vals имеет 30 ячеек // obj1.arrSize имеет значение 30
При глобальном создании объекта такой массив будет храниться в области глобальных переменных, компилятор сможет посчитать его размер.
Можно выделить “массив” динамически и хранить его как указатель. В качестве “константного” значения используется фишка С++ под названием список инициализации (двоеточие после myClass(int x) ):
class myClass < public: int* arr; myClass(int x) : arr(new int[x]) < // конструктор >private: >; myClass obj(5); void setup() < Serial.begin(9600); obj.arr[0] = 1; obj.arr[1] = 2; obj.arr[2] = 3; obj.arr[3] = 4; obj.arr[4] = 5; for (byte i = 0; i < 5; i++) < Serial.println(obj.arr[i]); // выведет 1 2 3 4 5 с переносом >> void loop()
Даже при глобальном создании объекта такой массив будет храниться в динамической памяти и компилятор не сможет посчитать его размер!
Делаем константы
Вы наверное часто видели в библиотеках передачу константы в функцию, далеко ходить не надо: digitalWrite(13, HIGH); , где HIGH – что это? Если вы откроете Arduino.h, то найдёте там HIGH, это – константа, дефайн:
#define HIGH 0x1
А в keywords.txt она указана как LITERAL1, что и даёт ей синий цвет. Давайте сделаем библиотеку, которая выводит текст в зависимости от указанной константы:
#pragma once #include // константы #define KEK 0 #define LOL 1 #define AZAZA 2 #define HELLO 3 class testClass < public: void printer(byte value); private: >;
testLib.cpp
#include // подключаем заголовок обязательно void testClass::printer(byte value) < switch (value) < case 0: Serial.println("kek"); break; case 1: Serial.println("lol"); break; case 2: Serial.println("azaza"); break; case 3: Serial.println("hello"); break; >>testSketch.ino
#include testClass testObject; void setup() < Serial.begin(9600); testObject.printer(KEK); // выведет kek testObject.printer(LOL); // выведет lol testObject.printer(AZAZA); // выведет azaza testObject.printer(HELLO); // выведет hello >void loop()Вот так можно передать вместо значения – слово, и работать с такой библиотекой будет удобнее. Заметьте, мы использовали константы (дефайн), это не очень правильно: если в другом подключенном ниже документе или в самом скетче наш дефайн совпадёт с названием другой переменной, функции или другого дефайна, то программа будет работать некорректно! Дефайн ведь распространяется на другие документы, включая главную программу (скетч). Что же делать? Можно называть свои константы настолько уникально, чтобы никто никогда с ними не пересекался, например добавлять префикс с названием библиотеки: MYLIB_CONSTANT. Ещё можно заменить дефайн перечислением, тогда ваша библиотека не будет влиять на другие и на главный документ, но вот другие библиотеки и внешние дефайны могут залезть и в вашу библиотеку… Смотрим пример, такой же как выше, но на enum:
#pragma once #include // константы enum printModes < KEK, LOL, AZAZA, HELLO, >; class testClass < public: void printer(printModes value); private: >;
testLib.cpp
#include // подключаем заголовок обязательно void testClass::printer(printModes value) < switch (value) < case KEK: Serial.println("kek"); break; case LOL: Serial.println("lol"); break; case AZAZA: Serial.println("azaza"); break; case HELLO: Serial.println("hello"); break; >>testSketch.ino
#include testClass testObject; void setup() < Serial.begin(9600); testObject.printer(KEK); // выведет kek testObject.printer(LOL); // выведет lol testObject.printer(AZAZA); // выведет azaza testObject.printer(HELLO); // выведет hello >void loop()Использование enum в голом виде тоже может приводить к проблемам: имена enum (как имя самого enum, так и его констант) не должны пересекаться в подключенных документах, иначе будет ошибка двойного объявления. О чём конкретно я говорю:
Пересекающиеся enum
// документ doc1.h enum engineControl < start, stop, restart, >; // документ doc2.h enum soundControl < play, pause, stop, replay, >; // основной документ #include "doc1.h" #include "doc2.h" // уже приведёт к ошибке "значение stop объявлено в другом месте"
Чтобы разделить enum в разных файлах, т.е. изолировать их значения друг от друга, есть три способа:
- Пространство имён namespace
- enum class
- Внесение enum в основной класс библиотеки
Рассмотрим пример через namespace
// документ doc1.h namespace engine < enum engineControl < start, stop, restart, >; >; // документ doc2.h namespace sound < enum soundControl < play, pause, stop, replay, >; >; // основной документ #include "doc1.h" #include "doc2.h" // используем соответствующие namespace'ы engine::engineControl control1 = engine::stop; sound::soundControl sound1 = sound::stop;
enum class
// документ doc1.h enum class engineControl < start, stop, restart, >; // документ doc2.h enum class soundControl < play, pause, stop, replay, >; // основной документ #include "doc1.h" #include "doc2.h" // используем пространство имён для ЗНАЧЕНИЙ enum engineControl control1 = engineControl::stop; soundControl sound1 = soundControl::stop;
Переделаем наш самый первый пример под enum class, спрятав таким образом константы от других файлов:
#pragma once #include // константы enum class printModes < KEK, LOL, AZAZA, HELLO, >; class testClass < public: void printer(printModes value); private: >;
testLib.cpp
#include "testLib.h" // подключаем заголовок обязательно void testClass::printer(printModes value) < switch (value) < case printModes::LOL: Serial.println("kek"); break; case printModes::KEK: Serial.println("lol"); break; case printModes::AZAZA: Serial.println("azaza"); break; case printModes::HELLO: Serial.println("hello"); break; >>testSketch.ino
#include «testLib.h» testClass testObject; void setup() < Serial.begin(9600); testObject.printer(printModes::KEK); // выведет kek testObject.printer(printModes::LOL); // выведет lol testObject.printer(printModes::AZAZA); // выведет azaza testObject.printer(printModes::HELLO); // выведет hello >void loop()Ещё одним вариантом является объявление enum внутри класса, тогда обращаться к нему нужно будет через ИМЯ_КЛАССА::
#include // нужно для ардуино-функций class testClass < public: enum printModes < KEK, LOL, AZAZA, HELLO, >; void printer(printModes value); private: >;
testLib.cpp
#include "testLib.h" // подключаем заголовок обязательно void testClass::printer(printModes value) < switch (value) < case LOL: Serial.println("kek"); break; case KEK: Serial.println("lol"); break; case AZAZA: Serial.println("azaza"); break; case HELLO: Serial.println("hello"); break; >>testSketch.ino
#include «testLib.h» testClass testObject; void setup() < Serial.begin(9600); testObject.printer(testClass::KEK); // выведет kek testObject.printer(testClass::LOL); // выведет lol testObject.printer(testClass::AZAZA); // выведет azaza testObject.printer(testClass::HELLO); // выведет hello >void loop()Вмешательство в компиляцию
Далее рассмотрим такую ситуацию: мы умеем пользоваться директивами препроцессора и хотим влиять на процесс компиляции библиотеки, не трогая ничего в файле библиотеки. Возможно ли это? Да, возможно. Важный момент: данный трюк работает только в заголовочном файле библиотеки, то есть от файла реализации .cpp скорее всего придётся отказаться. Если сделать define до подключения файла библиотеки, то этот дефайн будет “виден” из заголовочного файла библиотеки и его можно использовать для операторов условной компиляции. Важный момент: при создании библиотеки не рекомендуется писать исполнительный код в заголовочном файле вне класса, потому что это приведёт к ошибкам при подключении библиотеки в разных файлах. Для использования “магии дефайнов” нужно правильно оформить реализацию в заголовочном файле, смотрим пример:
Вот так можно.h
// lib.h class testClass < public: int func() private: >;
А вот так нельзя.h
// lib.h class testClass < public: int func(); private: >; int testClass::func()
Ну и пример как работает дефайн, “влезающий” в библиотеку:#pragma once #include void printResult() < // если определена SEND_NUDES #ifdef SEND_NUDES Serial.begin(9600); Serial.println("nudes"); #endif >testSketch.ino
// дефайним SEND_NUDES // ДО подключения библиотеки #define SEND_NUDES #include void setup() < // выведет "nudes" если задефайнен SEND_NUDES printResult(); >void loop()Зачем это нужно? Условная компиляция позволяет управлять компиляцией кода, то есть жёстко задавать, какие части кода будут компилироваться, а какие – нет. Более подробно об опасностях и тонкостях работы с define, в том числе и для создания библиотек, читайте в предыдущем уроке про директивы препроцессора.
Полезные страницы
- Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
- Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
- Полная документация по языку Ардуино, все встроенные функции и макросы, все доступные типы данных
- Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
- Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
- Поддержать автора за работу над уроками
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])
Как создать библиотеку в Arduino IDE

Как создать библиотеку Ардуино и встроить ее в Arduino IDE? Если у вас появился такой вопрос, значит вы уже достаточно хорошо знакомы с программированием на языке Arduno и знаете, что библиотеки значительно упрощают работу. Рассмотрим на двух простых примерах, как самому написать библиотеку в Ардуино для выполнения различных операций и вызывать их в скетче с помощью собственных команд.
Создание своей библиотеки для Ардуино
Для этого занятия потребуется:
- Arduino Uno / Arduino Nano / Arduino Mega
- светодиод и резистор;
- макетная плата;
- провода «папа-папа».
В среде Arduino IDE есть масса стандартных библиотек с примерами, еще множество библиотек написано для различных датчиков и модулей, которые можно скачать в интернете. Если вы занимаетесь разработкой проекта на Arduino, то хочется сделать скетч максимально удобным. Для этого множество операций можно вынести в библиотеку, написанную самостоятельно. Рассмотрим, как это сделать.
// таким способом подключаются библиотеки в скетче #include SoftwareSerial.h> #include DHT.h> // команды, которые можно использовать при подключении библиотек SoftwareSerial mySerial(2, 3); DHT dht(7, DHT11);
Для работы нам потребуется лишь текстовый редактор — «Блокнот». Первым делом необходимо в директории «C:\Program Files\Arduino\libraries» создать папку для файлов библиотеки, именно сюда устанавливаются библиотеки для Arduino. Название папки, как и имена создаваемых файлов можно придумать свое. Для примера мы будем использовать название «BlinkLed», которое отражает назначение библиотеки.

Поставим себе цель научиться с помощью одной команды включать и выключать светодиод на Ардуино заданное количество раз. Если бы мы решили написать скетч для мигания светодиода, то нам необходимо было бы использовать следующий код:
digitalWrite(13, HIGH); delay(1000); digitalWrite(13, LOW); delay(1000);
Если требуется мигать светодиодом определенное количество раз, то следует уже использовать цикл for, где можно установить счетчик для повторения операций. Именно этот код мы будем использовать для библиотеки, но вместо заданного количества повторений цикла, номера порта светодиода и времени задержки между операциями зададим переменные, которые можно будет указывать в скетче.
for (int x=0; xdigitalWrite(13, HIGH); delay(1000); digitalWrite(13, LOW); delay(1000); >
Как написать библиотеку для Arduino IDE
Библиотека Arduino IDE должна иметь минимум два файла: заголовочный файл (с расширением .h) и файл с исходным кодом (с расширение .cpp). В первом файле содержится описание класса и переменные, второй файл содержит программный код методов. Файл keywords.txt не обязателен, но он позволяет выделять цветом в среде Arduino IDE новые типы и методы из созданной вами библиотеки.
- Зайдите в папку C:\Program Files\Arduino\libraries;
- Создайте новую папку c названием BlinkLed;
- Создайте текстовый документ keywords.txt;
- Создайте текстовый документ BlinkLed с расширением .h;
- Создайте текстовый документ BlinkLed с расширением .cpp.
Код для файла BlinkLed.h:
Заголовочный файл содержит класс, где объявляются функции, которые будут использоваться в скетче (в нашем случае это blink) и используемые переменные.
/* здесь размещают информацию о разработчике и инструкцию для пользователя */ // включение стандартных функций Ардуино #include "Arduino.h" void blink (int pin, int pause, int repeat);
Код для файла BlinkLed.cpp:
Исходный файл содержит основной код библиотеки. В нашем примере в программе используются три переменные, которые следует указывать в скетче: pin — номер порта, pause задержка в миллисекундах, repeat — количество раз выполнения цикла for.
/* здесь размещают информацию о разработчике и инструкцию для пользователя */ // включение стандартных функций Ардуино и заголовочного файла #include "Arduino.h" #include "BlinkLed.h" void blink (int pin, int pause, int repeat) < pinMode(pin, OUTPUT); for (int x=0; xdigitalWrite(pin, HIGH); delay(pause); digitalWrite(pin, LOW); delay(pause); > >
Код для файла keywords.txt:
В файле указывается подсветка синтаксиса в Arduino IDE. Функция KEYWORD1 окрашивает слово в оранжевый цвет, LITERAL1 окрашивает слово в синий цвет.
BlinkLed KEYWORD1 blink KEYWORD1
Встраивание библиотеки в Arduino IDE

На этом написание библиотеки закончено. Также вы можете в папке BlinkLed создать папку examples, где можно разместить примеры программ для демонстрации работы вашей библиотеки. При открытии среды программирования Arduino IDE, созданную библиотеку можно найти через панель инструментов Файл > Примеры / BlinkLed. В итоге мы можем вызвать функцию мигания светодиода с помощью одной команды.
// подключаем библиотеку BlinkLed.h #include BlinkLed.h> void setup() < >void loop() < /* используем новую команду blink и указываем в скобках через запятую: номер порта, время задержки, количество раз включения светодиода */ blink (13, 500, 10); >
Все файлы библиотеки из примера вы можете скачать по прямой ссылке здесь. Если у вас есть вопросы по этой теме, то задавайте их в комментариях на этой
Как создать библиотеку в ардуино
На основе примера генератора кода Морзе здесь показано, как сконвертировать его функции в библиотеку (перевод официальной документации [1]). Это позволит другим людям проще использовать код, и проще обновить свой проект, когда Вы улучшите библиотеку. Начнем с простого примера скетча, который генерирует морзянку, мигая светодиодом (после его запуска он постоянно выдает сигнал SOS):
int pin = 13;
void setup() < pinMode(pin, OUTPUT); >
void loop() < dot(); dot(); dot(); dash(); dash(); dash(); dot(); dot(); dot(); delay(3000); >
void dot() < digitalWrite(pin, HIGH); delay(250); digitalWrite(pin, LOW); delay(250); >
void dash() < digitalWrite(pin, HIGH); delay(1000); digitalWrite(pin, LOW); delay(250); >В этом скетче есть несколько разных частей, которые нам нужно перевести в библиотеку. Первое, конечно же, это функции dot() и dash(), которые выполняют основную работу по миганию светодиодом . Второе это переменная ledPin, которая используется функциями — эта переменная определяет, какой вывод порта использовать. И наконец, здесь есть вызов pinMode(), который настраивает вывод порта как выход. Итак, давайте превратим код скетча в библиотеку.
Для библиотеки нам нужно как минимум 2 файла: файл заголовка (header file, файл с расширением .h) и файл исходного кода (с расширением .cpp). В заголовочном файле есть определения для библиотеки: в основном здесь кратко описано все, что есть внутри библиотеки; файл исходного кода в то же время содержит действительный код. Назовем нашу библиотеку «Morse», так что заголовочный файл получит имя Morse.h.
[Заголовочный файл библиотеки]
Ядро файла заголовка состоит из нескольких строк, в каждой из которых описана одна функция, и все это объединено в класс вместе со всеми переменными, которые Вам нужны:
class Morse < public: Morse(int pin); void dot(); void dash(); private: int _pin; >;
Класс это просто набор функций и переменных, содержащихся в одном месте. Функции и переменные имеют атрибут public, который означает, что эти функции доступны для всех, кто использует библиотеку, или private, который означает, что доступ возможен только из самого класса. В каждом классе есть специальная функция, называемая конструктором, которая используется для создания экземпляра (instance) класса. Конструктор всегда имеет то же самое имя, что и имя класса, и у не указан возвращаемый тип (потому что заранее известно, что он возвратит тип класса).
Примечание: определение класса в заголовке можно рассматривать как пользовательский ТИП (по аналогии с typedef struct), а экземпляр класса можно рассматривать как ПЕРЕМЕННУЮ, которая имеет этот тип. Конструктор предназначен для инициализации этой переменной. Может быть также еще и деструктор, который предназначен для уничтожения переменной класса. Наличие и конструктора, и деструктора необязательно: это зависит от реализации класса.
Кроме определения класса, в заголовочный файл нужно добавить подключение заголовка Arduino.h директивой #include. Это дает доступ к стандартным типам и константам языка Arduino. Заголовочный файл Arduino.h автоматически добавляется при компиляции скетча (см. [3]), но для библиотек это нужно сделать самому. Подключение заголовка Arduino.h будет выглядеть следующим образом:
#include "Arduino.h"В завершение нужно обернуть заголовочный файл в стандартную конструкцию #ifndef / #define / #endif (это защита от повторного включения заголовка []). Также в начало заголовка добавляют комментарии, описывающие содержимое библиотеки. В результате получится вот такой заголовок:
/* Morse.h - библиотека для генерации кода Morse. Автор David A. Mellis, 2 ноября 2007. Выпущено для публичного использования. */ #ifndef Morse_h #define Morse_h #include "Arduino.h" class Morse < public: Morse(int pin); void dot(); void dash(); private: int _pin; >; #endif
[Файл исходного кода библиотеки]
Займемся конструктором. В нем расположен код, который вызывается при создании экземпляра класса. В нашем примере пользователь в параметре конструктора указывает номер цифрового порта, который будет использоваться. Таким образом, конструктор у нас будет конфигурировать ножку порта, и сохранит номер используемого порта в частную переменную _pin (эта переменная будет использоваться функциями класса):
Morse::Morse(int pin) < //Конфигурируем порт как выход: pinMode(pin, OUTPUT); //Сохраним номер порта во внутреннюю переменную класса: _pin = pin; >
В коде конструктора видно кое-что, с чем мы раньше не встречались. Первое это необычный префикс Morse:: перед именем функции. Это говорит о том, что функция входит в состав класса Morse. Тот же самый префикс мы увидим позже в определении других функций класса. Второе это private-переменная _pin. В принципе имя этой переменной может быть любым, лишь бы оно соответствовало определению класса в файле заголовка. Добавление символа подчеркивания к имени файла является стандартным соглашением — чтобы сразу было видно, что это частная (private) переменная, и чтобы получить отдельное имя, отличающееся от имени входной переменной функции конструктора.
Теперь напишем код для функций библиотеки. Код будет выглядеть точно таким же, как он был в скетче, за исключением того, что к имени функции будет добавлен префикс Morse. и будет использоваться частная переменная _pin вместо глобальной переменной pin. Нужно также добавить подключение заголовочных файлов и текст комментария в начало файла. В результате получится файл исходного кода (модуль) библиотеки:
/* Morse.h - библиотека для генерации кода Morse. Автор David A. Mellis, 2 ноября 2007. Выпущено для публичного использования. */ #include "Arduino.h" #include "Morse.h" Morse::Morse(int pin) < pinMode(pin, OUTPUT); _pin = pin; > void Morse::dot() < digitalWrite(_pin, HIGH); delay(250); digitalWrite(_pin, LOW); delay(250); > void Morse::dash() < digitalWrite(_pin, HIGH); delay(1000); digitalWrite(_pin, LOW); delay(250); >
[Как использовать библиотеку]
Файлы библиотеки готовы. Теперь надо разобраться, как использовать библиотеку в среде разработки Arduino.
Сначала создайте папку Morse как поддиректорию в папке libraries (папка libraries находится в каталоге установки системы Arduino, полный путь до папки Morse будет наподобие C:\Program Files\Arduino\libraries\Morse). Скопируйте файлы Morse.h и Morse.cpp в директорию Morse. После этого запустите среду разработки Arduino IDE. Если Вы зайдете в меню Sketch -> Import Library (Скетч -> Импортировать библиотеку), то среди списка библиотек Вы должны увидеть Morse. Библиотека будет компилироваться вместе с теми скетчами, которые её используют. Если у Вас имеются проблемы при создании или компиляции библиотеки, то проверьте, что файлы действительно имеют расширения .cpp и .h — например, без дополнительного расширения .pde или .txt (имейте в виду, что подобная ситуация может произойти, когда в Проводнике Windows отключено отображение расширений зарегистрированных типов файлов).
Теперь давайте посмотрим, как можно создать скетч, который будет делать то же самое, что и раньше, но с использованием библиотеки:
#include Morse morse(13); void setup() < >void loop() < morse.dot(); morse.dot(); morse.dot(); morse.dash(); morse.dash(); morse.dash(); morse.dot(); morse.dot(); morse.dot(); delay(3000); >Очевидно, что есть несколько различий от старого скетча (основанные на том, что некоторая часть кода перемесилась в библиотеку). Кроме того, код значительно сократился, и стал читабельнее. Давайте рассмотрим эти отличия.
Директива #include. Первое отличие — добавлен оператор #include в самом начале скетча. Это делает библиотеку Morse доступной для использования внутри скетча, и её код (при компиляции) будет добавлен в общий код, который будет впоследствии прошит в память микроконтроллера платы Arduino. Это означает, что если больше в скетче не нужна библиотека, то Вы должны удалить подключение её заголовочный файла (закомментировать или удалить строку с соответствующей директивой #include).
Обратите внимание, что имя подключаемого файла теперь указано в угловых скобках, а не в двойных кавычках. Это обычная практика программирования на языках C/C++: имена стандартных библиотечных файлов в директиве #include указываются с использованием угловых скобок, а подключение заголовочных файлов пользователя указывается с помощью двойных кавычек.
Вызов конструктора класса. Второе отличие — как создается переменная класса Morse (в нашем примере мы создаем переменную класса morse):
Morse morse(13);
Когда выполняется эта строка кода (это происходит до вызова функции setup()), то будет вызвана функция конструктора класса Morse, и ей будет передан аргумент (в нашем случае номер цифрового порта 13).
Обратите внимание, что функция setup() теперь пуста; это потому, что вызов pinMode() происходит внутри библиотеки (когда конструктор создает экземпляр переменной класса).
Вызов функций класса. Последнее отличие — функции dot() и dash() вызываются с префиксом из имени экземпляра класса и точки (morse.). У нас может быть несколько экземпляров класса Morse, каждый из них будет использовать свой номер порта. Путем вызова функции с отдельным экземпляром класса, мы указываем, какие экземпляры переменных используются для вызова функции. К примеру, если у нас есть два экземпляра класса:
Morse morse1(13); Morse morse2(12);
то внутри вызова morse2.dot(), значение переменной _pin будет 12.
[Подсветка синтаксиса библиотеки]
Если Вы попробуете использовать библиотеку в новом скетче, то не увидите, что наша библиотека не будет подсвечиваться особым образом в редакторе кода Arduino IDE, как это принято для стандартных библиотек (например, для общеизвестной библиотеки Serial). К сожалению, программное обеспечение среды разработки Arduino не может автоматически распознать определения в Вашей библиотеке (что было бы приятной возможностью), так что Вам нужно ей немного в этом помочь. Чтобы настроить подсветку синтаксиса для Вашей библиотеки, создайте файл keywords.txt в каталоге Morse. Его содержимое должно выглядеть так:
Morse KEYWORD1 dash KEYWORD2 dot KEYWORD2
В каждой строке должно быть имя ключевого слова, за которым через табуляцию (не через пробелы) должен идти тип ключевого слова. Имя класса должно быть обозначено как KEYWORD1, и это дает оранжевую подкраску; функции (методы) класса должны обозначены как KEYWORD2, и имена функций будут коричневыми. После создания файла keywords.txt в каталоге Morse нужно перезапустить среду разработки Arduino, чтобы она распознала новые ключевые слова.
[Пример кода использования библиотеки]
Вместе с библиотекой полезно предоставить пример скетча с демонстрацией использования библиотеки. Для этого в директории Morse создайте подкаталог examples. Затем скопируйте или переместите папку, содержащую код скетча (пусть эта папка называется SOS), в папку examples. Где находится копируемый скетч можно узнать из меню Sketch -> Show Sketch Folder (Скетч -> Показать папку скетчей). Если после этого Вы еще раз перезапустите среду разработки Arduino, то в меню File -> Sketchbook -> Examples (Файл -> Примеры) увидите Morse, и внутри неё пример SOS. Также для кода примера можно создать дополнительные комментарии, чтобы лучше разъяснить, как использовать Вашу библиотеку.
Если Вы хотите проверить содержимое готовой библиотеки, то можете загрузить её: Morse.zip [3]. Вы также можете просмотреть реализации других готовых библиотек, которые можете найти в папке libraries каталога установки Arduino. Дополнительную информацию по Arduino-стилю оформления библиотек можете получить из [2]. Если у Вас есть вопросы, то можете задать их на форуме разработчиков Arduino [4].
[Ссылки]
1. Writing a Library for Arduino site:arduino.cc .
2. Arduino Style Guide for Writing Libraries site:arduino.cc .
3. Morse.zip — архив копии каталога библиотеки, содержимое архива следует поместить в папку libraries каталога установки Arduino IDE.
4. Arduino Forum > Software > Development site:forum.arduino.cc .