Выбрать главу

const int switchPin = 5;

const int period = 1000;

boolean flashing = false;

void setup()

{

  pinMode(ledPin, OUTPUT);

  pinMode(switchPin, INPUT_PULLUP);

}

void loop()

{

  if (digitalRead(switchPin) == LOW)

  {

    flashing = ! flashing;

  }

  if (flashing)

  {

    digitalWrite(ledPin, HIGH);

    delay(period);

    digitalWrite(ledPin, LOW);

    delay(period);

  }

}

Проблема данной реализации в том, что она проверяет нажатие кнопки только после того, как завершится цикл включения/выключения светодиода. Если кнопка будет нажата во время этого цикла, факт нажатия зафиксирован не будет. Это может быть не важно для нормальной работы скетча, но если важно фиксировать каждое нажатие кнопки, следует полностью исключить любые задержки в функции loop. Фактически после перехода в режим мигания Arduino будет тратить основное время на задержки и только малую часть времени — на проверку состояния кнопки.

Пример в следующем разделе решает эту проблему.

Пауза без приостановки

Предыдущий скетч можно переписать без использования функции delay:

// sketch_14_02_flashing_2

const int ledPin = 13;

const int switchPin = 5;

const int period = 1000;

boolean flashing = false;

long lastChangeTime = 0;

int ledState = LOW;

void setup()

{

  pinMode(ledPin, OUTPUT);

  pinMode(switchPin, INPUT_PULLUP);

}

void loop()

{

  if (digitalRead(switchPin) == LOW)

  {

    flashing = ! flashing;

    // и выключить светодиод

    if (! flashing)

    {

      digitalWrite(ledPin, LOW);

    }

  }

  long now = millis();

  if (flashing && now > lastChangeTime + period)

  {

    ledState = ! ledState;

    digitalWrite(ledPin, ledState);

    lastChangeTime = now;

  }

}

В этом скетче я добавил две новые переменные, lastChangeTime и ledState. Переменная lastChangeTime хранит время последнего перехода светодиода между состояниями «включено» и «выключено», а переменная ledState хранит текущее состояние светодиода — «включено» или «выключено», чтобы знать, в каком состоянии он пребывает, когда потребуется переключить его.

Теперь функция loop не выполняет задержек. В первой части loop проверяется нажатие кнопки, и, если кнопка нажата, переключается режим мигания. Дополнительная инструкция if, следующая далее, просто выключает светодиод, если нажатие кнопки вызвало выключение режима мигания. В противном случае светодиод мог бы остаться включенным:

if (! flashing)

{

  digitalWrite(ledPin, LOW);

}

Во второй части функция loop читает текущее значение счетчика миллисекунд вызовом millis() и сравнивает со значением lastChangeTime, увеличенным на значение period. То есть код внутри этой инструкции if выполняется, только если с момента последнего переключения светодиода прошло более period миллисекунд.

Затем значение переменной ledState изменяется на противоположное, и на цифровом выходе устанавливается соответствующий уровень напряжения. Потом значение now копируется в lastChangeTime, чтобы можно было определить, когда наступит момент следующего переключения.

Библиотека Timer

Решение, представленное в разделе «Пауза без приостановки», было обобщено и реализовано в виде библиотеки, позволяющей планировать выполнение повторяющихся операций с использованием функции millis. Несмотря на свое название, библиотека не использует аппаратные таймеры и потому прекрасно работает в большинстве моделей Arduino.

Получить библиотеку можно по адресу http://playground.arduino.cc//Code/Timer.

Применение библиотеки может существенно упростить код, как показано далее:

// sketch_14_03_flashing_3

#include <Timer.h>

const int ledPin = 13;

const int switchPin = 5;

const int period = 1000;

boolean flashing = false;

int ledState = LOW;

Timer t;

void setup()

{

  pinMode(ledPin, OUTPUT);

  pinMode(switchPin, INPUT_PULLUP);

  t.every(period, flashIfRequired);

}

void loop()

{

  if (digitalRead(switchPin) == LOW)

  {

    flashing = ! flashing;

    if (! flashing)

    {

      digitalWrite(ledPin, LOW);

    }

  }

  t.update();

}

void flashIfRequired()

{

  if (flashing)

  {

    ledState = ! ledState;

    digitalWrite(ledPin, ledState);

  }

}

Чтобы задействовать возможности библиотеки, необходимо определить объект таймера (в данном скетче он получил имя t) и в функции setup указать функцию для вызова через установленные периоды:

t.every(period, flashIfRequired);

Затем нужно добавить в функцию loop следующую строку:

t.update();

В каждом вызове функция update проверит значение millis, определит необходимость выполнения повторяющихся действий и, если пришло время для этого, вызовет указанную функцию (в данном случае flashIfRequired).

Библиотека Timer имеет также множество других вспомогательных функций. Более подробную информацию о ней можно найти, если пройти по ссылке, приведенной в начале раздела.

В заключение

В этой главе вы узнали, как организовать решение сразу нескольких задач без использования механизма потоков выполнения. Для этого достаточно рассмотреть ограничения, накладываемые микроконтроллером, под другим углом.

В заключительной главе вы узнаете, как поделиться своими разработками с сообществом Arduino, создавая и публикуя библиотеки для Arduino.

15. Создание библиотек

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

Когда создавать библиотеки

Библиотеки создаются не только разработчиками Arduino — любой пользователь Arduino может создать свою библиотеку. Если библиотека окажется по-настоящему полезной, в адрес ее создателя потечет поток благодарностей. Никто не занимается продажей библиотек, так как это противоречит ценностям сообщества Arduino. Библиотеки должны публиковаться с открытым исходным кодом и рассматриваться как помощь своим собратьям по увлечению Arduino.

Часто наиболее полезными оказываются библиотеки, дающие доступ к некоторому аппаратному обеспечению. Они нередко упрощают процесс использования аппаратных средств и некоторых сложных протоколов. Нет причин, почему более одного человека должны преодолевать тернистый путь, выясняя, как работает некоторая аппаратура, и, если вы опубликуете свою библиотеку, благодаря Интернету люди смогут отыскать ее.

СОВЕТ

Прикладной программный интерфейс (Application Programmer Interface, API) — это комплект функций, которые пользователь библиотеки будет включать в свой скетч. Проектируя API, всегда задавайте себе вопрос: «Что действительно волнует пользователя?» Низкоуровневые детали реализации должны быть максимально скрыты. В примере библиотеки, который будет представлен в разделе «Пример библиотеки (радиоприемник TEA5767)», мы продолжим обсуждение этой проблемы.

Использование классов и методов

Создатели скетчей обычно полагают, что пишут код на языке C, и используют довольно ограниченный круг возможностей, в действительности скетчи пишутся на языке C++, объектно-ориентированном расширении языка C. В этом языке используется понятие классов объектов, объединяющих информацию об объектах (их данные) и функции, которые применяются к этим данным. Эти функции похожи на обычные функции, но, когда они ассоциируются с конкретным классом, их называют методами. Кроме того, методы могут объявляться общедоступными, и тогда они доступны всем, кто пользуется ими, или приватными, и тогда они доступны только из методов внутри класса.