Написание библиотеки для Arduino
Пошаговое руководство по созданию собственной библиотеки Arduino на примере кода азбуки Морзе — от скетча до полноценной библиотеки с примерами.
В этом уроке разберём, как создать собственную библиотеку для Arduino. Начнём со скетча, который мигает светодиодом по азбуке Морзе, и шаг за шагом превратим его в полноценную библиотеку. После этого любой сможет легко использовать ваш код и получать обновления по мере его улучшения.
Дополнительную информацию об оформлении API в стиле Arduino смотрите здесь: API Style Guide.
С чего начать: исходный скетч
Вот простой скетч, который передаёт сигнал Морзе:
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);
}
Если загрузить этот скетч на плату, на пине 13 начнёт мигать сигнал SOS — международный сигнал бедствия.
В скетче есть несколько ключевых частей, которые понадобятся в библиотеке:
- функции
dot()иdash(), которые выполняют само мигание; - переменная
pin, в которой хранится номер пина; - вызов
pinMode(), настраивающий пин как выход.
Теперь начнём превращать всё это в библиотеку.
Структура библиотеки: два обязательных файла
Для библиотеки нужно минимум два файла:
- заголовочный файл с расширением
.h— содержит объявления всего, что есть в библиотеке; - файл реализации с расширением
.cpp— содержит сам рабочий код.
Нашу библиотеку назовём Morse, поэтому заголовочный файл будет называться Morse.h.
Заголовочный файл 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 — библиотека для передачи азбуки Морзе световыми сигналами.
Создана David A. Mellis, 2 ноября 2007 г.
Передана в общественное достояние.
*/
#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
Теперь разберём файл реализации по частям.
Подключение заголовков
Вначале идут директивы #include, дающие доступ к стандартным функциям Arduino и к определениям из вашего заголовочного файла:
#include "Arduino.h"
#include "Morse.h"
Конструктор
Конструктор описывает, что происходит при создании объекта класса. Пользователь передаёт номер пина, конструктор сохраняет его в приватной переменной:
Morse::Morse(int pin)
{
_pin = pin;
}
Обратите внимание на два момента:
Morse::перед именем функции — указывает, что функция принадлежит классуMorse. Так же оформляются все остальные функции класса.- Подчёркивание в имени приватной переменной
_pin— распространённое соглашение, которое помогает отличить приватные переменные от аргументов функции (pinв данном случае). Имя можно выбрать любое, главное — чтобы оно совпадало с объявлением в заголовочном файле.
Функция begin() для настройки железа
Настройку оборудования лучше выносить в отдельную функцию begin(), которую вызывают из setup() скетча. Это важно: в момент выполнения конструктора железо ещё не инициализировано, поэтому настраивать пины там не следует. В нашей библиотеке нужно задать пин как выход:
void Morse::begin()
{
pinMode(_pin, OUTPUT);
}
Функции dot() и dash()
Теперь сам код из исходного скетча — с префиксом 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
Комментарий-шапку принято добавлять и в файл реализации. Вот полный файл:
/*
Morse.cpp — библиотека для передачи азбуки Морзе световыми сигналами.
Создана David A. Mellis, 2 ноября 2007 г.
Обновлена Jason A. Cox, 18 февраля 2023 г.
Передана в общественное достояние.
*/
#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 IDE.
После этого в меню Sketch > Import Library появится пункт Morse. Библиотека будет автоматически компилироваться вместе со скетчами, которые её используют.
Типичная ошибка: файлы не распознаются, если к расширению случайно добавился
.ino,.pdeили.txt. Проверьте, что файлы называются именноMorse.cppиMorse.h.
Скетч с использованием библиотеки
Вот как выглядит переписанный 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 в начале скетча подключает библиотеку и включает её код в прошивку. Если библиотека больше не нужна — удалите #include, чтобы не тратить память зря.
Создание объекта класса Morse:
Morse morse(13);
Эта строка выполняется ещё до вызова setup(). В этот момент вызывается конструктор класса Morse и получает аргумент 13.
Вызов begin(): функция morse.begin() теперь вызывается из setup() скетча — именно там нужно настраивать пин.
Вызов функций через имя объекта: чтобы вызвать dot() или dash(), перед ними ставится имя объекта morse. Это позволяет иметь несколько независимых объектов класса Morse, каждый со своим пином в приватной переменной _pin:
Morse morse(13);
Morse morse2(12);
Внутри вызова morse2.dot() переменная _pin будет равна 12.
Подсветка синтаксиса: файл keywords.txt
Вы, наверное, заметили, что IDE не подсвечивает имена из вашей библиотеки. Нужно помочь редактору — создайте файл keywords.txt в папке Morse:
Morse KEYWORD1
begin KEYWORD2
dash KEYWORD2
dot KEYWORD2
Каждая строка содержит имя ключевого слова, затем табуляцию (не пробелы!), затем тип:
- KEYWORD1 — для классов, подсвечивается оранжевым;
- KEYWORD2 — для функций, подсвечивается коричневым.
После сохранения файла перезапустите Arduino IDE, и подсветка заработает.
Примеры для библиотеки
Хорошая практика — добавить к библиотеке готовые примеры:
- Создайте папку examples внутри папки Morse.
- Переместите туда папку со скетчем SOS (найти её можно через Sketch > Show Sketch Folder).
- Перезапустите Arduino IDE.
Теперь в меню File > Sketchbook > Examples появится пункт Library-Morse с вашим примером. Добавьте в скетч поясняющие комментарии — пользователи скажут спасибо.
Публикация библиотеки
Готовую библиотеку (с keywords.txt и примером) можно скачать здесь: Morse.zip.
Чтобы опубликовать библиотеку в Library Manager Arduino, потребуется добавить файл library.properties. Подробнее — в library specification.
По общим вопросам работы с Library Manager смотрите FAQ.
Если возникнут вопросы или предложения — пишите на Software Development forum.
Дополнительные рекомендации по созданию API в стиле Arduino: API Style Guide.
Практические замечания
- Не настраивайте пины в конструкторе — делайте это в отдельной функции
begin(), которую вызывают изsetup(). В момент создания глобального объекта микроконтроллер ещё не готов к работе с периферией. - Приватные переменные с подчёркиванием — это соглашение, а не требование языка. Главное, чтобы имена совпадали между
.hи.cpp. - Защитные макросы в
.h(#ifndef/#define/#endif) обязательны: без них повторное подключение заголовка вызовет ошибку компиляции. - Проверяйте расширения файлов: IDE на некоторых операционных системах может скрывать расширения, и файл
Morse.cppокажется на самом делеMorse.cpp.txt.