Написание библиотеки Arduino
Этот документ объясняет, как создать библиотеку для Arduino. Он начинается со скетча для мигания кодом Морзе и объясняет, как преобразовать его функции в библиотеку. Это позволяет другим людям легко использовать написанный вами код, а также легко обновлять его по мере улучшения библиотеки.
Для получения дополнительной информации ознакомьтесь с Руководством по стилю API — оно содержит рекомендации по созданию хорошего API в стиле Arduino для вашей библиотеки.
Начнём со скетча, который реализует простой код Морзе:
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);
}
Если запустить этот скетч, он будет мигать кодом SOS (сигнал бедствия) на пине 13.
В скетче есть несколько различных частей, которые нужно будет перенести в библиотеку. Во-первых, это функции dot() и dash(), которые выполняют само мигание. Во-вторых, переменная pin, которую функции используют для определения того, какой пин задействовать. Наконец, есть вызов функции pinMode(), которая инициализирует пин как выход.
Давайте начнём превращать скетч в библиотеку!
Заголовочный файл (Morse.h)
Для библиотеки необходимо как минимум два файла: заголовочный файл (с расширением .h) и исходный файл (с расширением .cpp). Заголовочный файл содержит определения для библиотеки: по сути, перечень всего, что в ней есть, тогда как исходный файл содержит собственно код. Нашу библиотеку мы назовём «Morse», поэтому заголовочный файл будет называться Morse.h. Посмотрим, что в нём должно быть. Поначалу это может показаться немного странным, но всё станет яснее, когда вы увидите исходный файл, который к нему прилагается.
Основу заголовочного файла составляет строка для каждой функции в библиотеке, обёрнутая в класс вместе с необходимыми переменными:
class Morse
{
public:
Morse(int pin);
void begin();
void dot();
void dash();
private:
int _pin;
};
Класс — это просто набор функций и переменных, хранящихся вместе в одном месте. Эти функции и переменные могут быть публичными (public), то есть доступными для тех, кто использует вашу библиотеку, или приватными (private), то есть доступными только внутри самого класса. У каждого класса есть специальная функция, называемая конструктором, которая используется для создания экземпляра класса. Конструктор имеет то же имя, что и класс, и не имеет возвращаемого типа.
В заголовочный файл нужно добавить ещё кое-что. Во-первых, директиву #include, которая предоставляет доступ к стандартным типам и константам языка Arduino (она автоматически добавляется в обычные скетчи, но не в библиотеки). Она выглядит так (и размещается выше определения класса, приведённого ранее):
#include "Arduino.h"
Наконец, принято оборачивать весь заголовочный файл в конструкцию, которая выглядит необычно:
#ifndef Morse_h
#define Morse_h
// директива #include и код размещаются здесь...
#endif
По сути, это предотвращает проблемы, если кто-то случайно подключит (#include) вашу библиотеку дважды.
Наконец, обычно в начало библиотеки добавляют комментарий с её названием, кратким описанием того, что она делает, именем автора, датой и лицензией.
Посмотрим на полный заголовочный файл:
/*
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 begin();
void dot();
void dash();
private:
int _pin;
};
#endif
Исходный файл (Morse.cpp)
Теперь рассмотрим различные части исходного файла — Morse.cpp.
Первым делом идут несколько директив #include. Они предоставляют остальному коду доступ к стандартным функциям Arduino и к определениям из вашего заголовочного файла:
#include "Arduino.h"
#include "Morse.h"
Затем идёт конструктор. Он объясняет, что должно происходить при создании экземпляра класса. В данном случае пользователь указывает, какой пин он хотел бы использовать. Конструктор записывает это значение в приватную переменную для использования в других функциях:
Morse::Morse(int pin)
{
_pin = pin;
}
В этом коде есть несколько необычных вещей. Первая — это Morse:: перед именем функции. Это указывает, что функция является частью класса Morse. То же самое вы увидите и в других функциях класса. Вторая необычная вещь — подчёркивание в имени нашей приватной переменной _pin. На самом деле эта переменная может называться как угодно, главное, чтобы имя совпадало с определением в заголовочном файле. Добавление подчёркивания в начало имени является распространённым соглашением, которое наглядно показывает, какие переменные являются приватными, а также позволяет отличить их имя от имени аргумента функции (pin в данном случае).
Далее нужно создать функцию begin() для настройки аппаратного обеспечения. Она будет вызываться из функции setup() скетча. Настройка аппаратного обеспечения выполняется в отдельной функции, а не в конструкторе, потому что аппаратное обеспечение ещё не инициализировано в момент выполнения кода конструктора. В нашей библиотеке нам нужно настроить пин как выход:
void Morse::begin()
{
pinMode(_pin, OUTPUT);
}
Затем идёт собственно код из скетча, который вы превращаете в библиотеку (наконец-то!). Он выглядит практически так же, за исключением Morse:: перед именами функций и _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);
}
Наконец, принято включать заголовочный комментарий и в начало исходного файла. Посмотрим на него целиком:
/*
Morse.cpp - Library for flashing Morse code.
Created by David A. Mellis, November 2, 2007.
Updated by Jason A. Cox, February 18, 2023.
Released into the public domain.
*/
#include "Arduino.h"
#include "Morse.h"
Morse::Morse(int pin)
{
_pin = pin;
}
void Morse::begin()
{
pinMode(_pin, OUTPUT);
}
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);
}
Это всё, что вам нужно (есть ещё кое-какие дополнительные полезные вещи, но мы поговорим о них позже). Посмотрим, как использовать библиотеку.
Использование библиотеки в скетче
Сначала создайте директорию Morse внутри поддиректории libraries вашей директории скетчей. Скопируйте или переместите файлы Morse.h и Morse.cpp в эту директорию. Теперь запустите среду Arduino. Если вы откроете меню Sketch > Import Library, вы должны увидеть там Morse. Библиотека будет компилироваться вместе со скетчами, которые её используют. Если библиотека не собирается, убедитесь, что файлы действительно заканчиваются на .cpp и .h (без дополнительного расширения .ino, .pde или .txt, например).
Посмотрим, как можно воспроизвести наш старый скетч SOS с помощью новой библиотеки:
#include <Morse.h>
Morse morse(13);
void setup()
{
morse.begin();
}
void loop()
{
morse.dot(); morse.dot(); morse.dot();
morse.dash(); morse.dash(); morse.dash();
morse.dot(); morse.dot(); morse.dot();
delay(3000);
}
По сравнению со старым скетчем есть несколько отличий (помимо того, что часть кода переехала в библиотеку).
Во-первых, в начало скетча мы добавили директиву #include. Это делает библиотеку Morse доступной в скетче и включает её в код, отправляемый на плату. Это означает, что если библиотека больше не нужна в скетче, следует удалить директиву #include, чтобы сэкономить место.
Во-вторых, теперь мы создаём экземпляр класса Morse с именем morse:
Morse morse(13);
Когда эта строка выполняется (что на самом деле происходит ещё до функции setup()), вызывается конструктор класса Morse, которому передаётся указанный здесь аргумент (в данном случае просто 13).
Обратите внимание, что в нашем setup() теперь есть вызов morse.begin(), который настраивает пин, заданный в конструкторе.
Наконец, для вызова функций dot() и dash() нам нужно предварять их именем morse. — именем экземпляра, который мы хотим использовать. Мы могли бы создать несколько экземпляров класса Morse, каждый на своём пине, хранящемся в приватной переменной _pin этого экземпляра. Вызывая функцию для конкретного экземпляра, мы указываем, переменные какого экземпляра должны использоваться при вызове этой функции. То есть, если бы у нас было:
Morse morse(13);
Morse morse2(12);
то внутри вызова morse2.dot() переменная _pin была бы равна 12.
Файл ключевых слов (keywords.txt)
Если вы попробовали новый скетч, вы наверняка заметили, что ничто из нашей библиотеки не распознаётся средой и не выделяется цветом. К сожалению, программное обеспечение Arduino не может автоматически определить, что вы определили в своей библиотеке (хотя это было бы полезной функцией), поэтому нужно ему немного помочь. Для этого создайте файл с именем keywords.txt в директории Morse. Он должен выглядеть следующим образом:
Morse KEYWORD1
begin KEYWORD2
dash KEYWORD2
dot KEYWORD2
В каждой строке указывается имя ключевого слова, затем табуляция (не пробелы), затем тип ключевого слова. Классы должны быть KEYWORD1 и выделяются оранжевым цветом; функции должны быть KEYWORD2 и будут коричневыми. Для того чтобы среда распознала новые ключевые слова, необходимо перезапустить Arduino.
Примеры использования
Также будет полезно предоставить людям пример скетча, использующего вашу библиотеку. Для этого создайте директорию examples внутри директории Morse. Затем переместите или скопируйте директорию со скетчем (назовём его SOS), написанным выше, в директорию examples. (Скетч можно найти с помощью команды Sketch > Show Sketch Folder.) Если вы перезапустите среду Arduino (это в последний раз, обещаю) — вы увидите пункт Library-Morse в меню File > Sketchbook > Examples, содержащий ваш пример. Возможно, вы захотите добавить несколько комментариев, которые лучше объяснят, как пользоваться вашей библиотекой.
Если хотите ознакомиться с полной библиотекой (с файлом ключевых слов и примером), вы можете скачать её: Morse.zip.
Публикация в Library Manager
Если вы хотите сделать свою библиотеку доступной для других пользователей через Library Manager Arduino, вам также потребуется включить файл library.properties. Подробнее об этом читайте в спецификации библиотек.
По общим вопросам, связанным с Arduino Library Manager, см. FAQ.
Если у вас возникли проблемы или есть предложения, пожалуйста, опубликуйте их на форуме разработки программного обеспечения.
Для получения дополнительной информации ознакомьтесь с Руководством по стилю API — оно содержит рекомендации по созданию хорошего API в стиле Arduino для вашей библиотеки.