Создание собственной библиотеки для Ардуино

Этот документ поможет разобраться, как создавать библиотеки для Ардуино. Сначала будет рассмотрена программа, генерирующая сигналы азбуки Морзе, а затем даны пояснения, как вынести ее функции в отдельную библиотеку. Использование библиотек позволяет другим людям использовать написанный вами код, а также легко обновлять его по мере выхода новых версий вашей библиотеки.

Начнем с программы, генерирующий простой сигнал азбуки Морзе:

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(), которая заставляет работать указанный вывод в качестве выхода. 

Пора бы сделать из нашей программы библиотеку!

Для этого вам понадобится, по меньшей мере, два файла: заголовочный файл (с расширением .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)
{
  pinMode(pin, OUTPUT);
  _pin = 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()
{
  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.
  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()
{
  digitalWrite(_pin, HIGH);
  delay(1000);
  digitalWrite(_pin, LOW);
  delay(250);
}

И это все, что необходимо сделать (есть еще некоторые возможности, но мы поговорим о них чуть позже). Теперь попробуем нашу библиотеку в действии.

Прежде всего, создайте папку Morse в директории libraries внутри вашей рабочей папки с проектами. Скопируйте или переместите файлы Morse.h и Morse.cpp в созданную папку. Теперь запустите среду разработки Ардуино - в меню Sketch > Import Library вы должны увидеть библиотеку Morse. Она будет автоматически компилироваться вместе с использующими ее программами. Если этого не произойдет - проверьте ее расширение и убедитесь, что файл действительно имеет формат .cpp или .h (без дополнительных расширений вроде .pde или .txt, например).

Попробуем переписать нашу старую программу "SOS" с использованием новой библиотеки:

#include <Morse.h>

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 в начало программы, который включает библиотеку 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.

На сегодня, пожалуй, это все, но в ближайшее время возможно появится расширенное руководство по созданию библиотек. А тем временем, если у вас возникнут проблемы или появятся предложения, пожалуйста, пишите их на форум.