Урок 7. Классы в программах Ардуино. Кнопка как объект.

Arduino UNO R3

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

Предыдущий урок     Список уроков     Следующий урок

В прошлом уроке мы написали рабочий, отлаженный программный блок, который можно вполне использовать в боевых программах. Но как-то не совсем красиво получилось.

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

И это притом, что мы используем один и тот же простой объект – кнопку. Для красивого решения этих проблем в языке программирования Ардуино существуют классы.

 

Классы в C++ для Ардуино.

Классы позволяют программисту создавать новые типы объектов. Они состоят из свойств и методов.  Свойства – это данные, которыми можно характеризовать объект класса. Методы – это функции, которые могут выполнять действия над свойствами класса.

  • Свойства класса это его переменные.
  • Методы класса это его  функции.

Определение класса выглядит так:

class имя_класса  { члены класса };

Члены класса это переменные, функции, другие классы и т.п.

Создание класса для обработки сигналов кнопок Button.

Создадим класс для нашего объекта кнопки. Назовем его Button.

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

boolean flagPress= false;    // признак кнопка сейчас нажата
boolean flagClick= false;    // признак кнопка была нажата (клик)
byte  buttonCount= 0;        // счетчик подтверждений стабильного состояния
#define TIME_BUTTON 15       // время стабильного состояния (* 2 мс)

К этим переменным необходимо добавить номер вывода, к которому подключена кнопка, и константу TIME_BUTTON надо объявить переменной, чтобы для каждой кнопки можно было задавать свое время подтверждения.

Оформим перечисленные переменные как свойства класса.

// Описание класса обработки сигналов кнопок
class Button {

boolean flagPress;    // признак кнопка сейчас нажата
boolean flagClick;    // признак кнопка была нажата (клик)
byte  buttonCount;    // счетчик подтверждений стабильного состояния   
byte timeButton;      // время подтверждения состояния кнопки
byte _pin;            // номер вывода
 };

Button – это имя класса, а в скобках указаны его свойства (переменные).

 

Модификаторы доступа private и public.

Все свойства и методы класса имеют права доступа. Например, счетчик buttonCount используется только самим классом для собственных вычислений. Никогда к нему не будут обращаться другие программные модули. Логично запретить доступ к нему всех функций, кроме методов своего класса Button.

Для этого в языке C++ существуют модификаторы private и public.

  • Функции и переменные, которые находятся после модификатора public, доступны из любого места программы.
  • После модификатора private размещаются закрытые функции и переменные. С ними могут работать только методы собственного класса. Если отсутствует модификатор public, то все члены класса считаются закрытыми.

Нам надо выбрать, какие переменные (свойства) сделать открытыми, а какие – закрытыми. Хороший стиль объектно-ориентированного программирования предполагает, что все переменные должны быть закрытыми. А обращение к ним происходит через методы (функции) класса. Но надо сделать поправку на то, что мы пишем программу для микроконтроллера с ограниченной производительностью. А вызов любой функции занимает определенное время. Поэтому, свойства, к которым программа обращается часто, следует сделать открытыми и обращаться к ним явно. Это уменьшит время выполнения программы.

В нашем случае это признаки flagPress и flagClick. К ним постоянно обращается программа для контроля состояния кнопок. А номер вывода _pin и время подтверждения  timeButton обычно устанавливаются только один раз. Сделаем эти переменные закрытыми. Устанавливать их будем из дополнительного метода.

С учетом вышесказанного наш класс будет выглядеть так.

// Описание класса обработки сигналов кнопок
class Button {

  public:
    boolean flagPress;    // признак кнопка сейчас нажата
     boolean flagClick;    // признак кнопка была нажата (клик)

  private:
    byte  _buttonCount;    // счетчик подтверждений стабильного состояния   
     byte _timeButton;      // время подтверждения состояния кнопки
    byte _pin;            // номер вывода
};

Переменные timeButton и pin нам придется заявлять как аргументы метода для установки значений. Поэтому мы добавили _ перед именами, чтобы отличать аргументы метода и переменные класса.

Созданный нами класс пока состоит только из свойств. Надо добавить в него методы – функции.

Нам необходим метод для проверки состояния сигнала кнопки, тот самый, что мы вызывали в цикле каждые 2 мс. Назовем его  scanState (проверка состояния).

Принято названия классов писать в смешанном регистре, начиная с большой буквы, а названия методов - в смешанном регистре, начиная с маленькой буквы, первая часть – глагол.

Т.к. переменные номер вывода и время подтверждения мы сделали закрытыми, то необходима функция установки их значений. Например, setPinTime.

Т.е. для класса Button необходимы два метода.  С методами наш класс будет выглядеть так.

// Описание класса обработки сигналов кнопок
class Button {

  public:
    boolean flagPress;    // признак кнопка сейчас нажата
    boolean flagClick;    // признак кнопка была нажата (клик)

    void scanState();     // метод проверки состояние сигнала
    void setPinTime(byte pin, byte timeButton); // метод установки номера вывода и времени подтверждения

  private:
    byte  _buttonCount;    // счетчик подтверждений стабильного состояния   
    byte _timeButton;      // время подтверждения состояния кнопки
    byte _pin;            // номер вывода
};

Метод void scanState() не имеет аргументов и ничего не возвращает. Метод  void setPinTime(byte pin, byte timeButton) ничего не возвращает и имеет два аргумента: номер вывода и время подтверждения стабильного состояния кнопки.

 

Таким образом, мы объявили класс. Остается написать коды методов. Т.к. методы это функции, то и коды для них оформляются как функции. Можно написать коды прямо внутри класса, но это приведет к плохой читаемости текста программы. Представьте, что в начале программы, где описываются переменные, классы, будут громадные блоки кодов.  Поэтому лучше коды методов написать в конце программы. Отличие кодов методов классов от пользовательских функций заключается только в том, что в первом случае надо указать принадлежность метода к конкретному классу.

void Button:: scanState() {

// код метода

}

Button::  означает, что функция scanState()  это метод класса Button.

Напишем код метода scanState().

// метод проверки состояния кнопки
// flagPress= true  - нажата 
//  flagPress= false - отжата
//  flagClick= true - была нажата (клик)
void Button::scanState() {

 if ( flagPress == (! digitalRead(_pin)) ) {
     // состояние сигнала осталось прежним
     _buttonCount= 0;  // сброс счетчика состояния сигнала
  }
  else {
     // состояние сигнала изменилось
     _buttonCount++;   // +1 к счетчику состояния сигнала

     if ( _buttonCount >= _timeButton ) {
      // состояние сигнала не менялось заданное время
      // состояние сигнала стало устойчивым
      flagPress= ! flagPress; // инверсия признака состояния
_buttonCount= 0;  // сброс счетчика состояния сигнала

 

      if ( flagPress == true ) flagClick= true; // признак клика на нажатие     
     }   
  }
}

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

Теперь код  для метода setPinTime(byte pin, byte timeButton). Он совсем простой. Перегружает аргументы метода в закрытые свойства класса и устанавливает режим вывода.

// метод установки номера вывода и времени подтверждения
void Button::setPinTime(byte pin, byte timeButton)  {

  _pin= pin;
  _timeButton= timeButton;
  pinMode(_pin, INPUT_PULLUP);  // определяем вывод как вход
}

Мы завершили создание класса Button. Осталось научиться пользоваться им.

Класс  это только описание типа объекта, самого объекта еще нет. Его надо создать. Делается это так же, как и создание переменных при использовании встроенных типов данных.

int x;     // мы создали переменную типа int с именем x

Button button1;   // мы создали объект типа Button с именем button1

Button buttonPlus;   // мы создали еще один объект типа Button с именем buttonPlus

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

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

button1.flagClick= false;   // переменная flagClick j, объекта button1 = false

button1.scanState();       // вызов метода scanState(), объекта button1

button1.setPinTime(12, 20); // вызов метода setPinTime (), объекта button1 с параметрами 12, 20

Проверка состояния кнопки button1 из любого места программы будет выглядеть так:

if ( button1.flagPress == true )  {

// кнопка нажата  }

Думаю, теперь в программе объяснять ничего не надо. Тем более, что комментариев в ней больше, чем кода.

/*  Программа sketch_7_1 урока 7
 *  Каждое нажатие кнопки меняет состояние светодиода */

#define LED_PIN 13     // светодиод подключен к выводу 13
#define BUTTON_PIN 12  // кнопка подключена к выводу 12

// Описание класса обработки сигналов кнопок
class Button {
  public:
    boolean flagPress;    // признак кнопка сейчас нажата
    boolean flagClick;    // признак кнопка была нажата (клик)
    void scanState();     // метод проверки состояние сигнала
    void setPinTime(byte pin, byte timeButton); // метод установки номера вывода и времени (числа) подтверждения

  private:
    byte  _buttonCount;    // счетчик подтверждений стабильного состояния  
    byte  _timeButton;      // время подтверждения состояния кнопки
    byte  _pin;             // номер вывода
};

boolean ledState;         // переменная состояния светодиода

Button button1;   // создание объекта типа Button с именем button1
  
void setup() {
  pinMode(LED_PIN, OUTPUT);           // определяем вывод 13 (светодиод) как выход
button1.setPinTime(BUTTON_PIN, 15); // вызов метода установки объекта button1 с параметрами: номер вывода 12, число подтверждений 15
}

// бесконечный цикл с периодом 2 мс
void loop() {

  button1.scanState();  // вызов метода сканирования сигнала кнопки
 
  // блок управления светодиодом
  if ( button1.flagClick == true ) {
    // было нажатие кнопки
    button1.flagClick= false;         // сброс признака клика
    ledState= ! ledState;             // инверсия состояния светодиода
    digitalWrite(LED_PIN, ledState);  // вывод состояния светодиода   
  }

  delay(2);  // задержка на 2 мс
}

// метод проверки состояния кнопки
// flagPress= true  - нажата
//  flagPress= false - отжата
//  flagClick= true - была нажата (клик)
void Button::scanState() {

 if ( flagPress == (! digitalRead(_pin)) ) {
     // состояние сигнала осталось прежним
     _buttonCount= 0;  // сброс счетчика состояния сигнала
  }
  else {
     // состояние сигнала изменилось
     _buttonCount++;   // +1 к счетчику состояния сигнала

     if ( _buttonCount >= _timeButton ) {
      // состояние сигнала не менялось заданное время
      // состояние сигнала стало устойчивым
      flagPress= ! flagPress; // инверсия признака состояния
      _buttonCount= 0;  // сброс счетчика состояния сигнала

      if ( flagPress == true ) flagClick= true; // признак клика на нажатие     
     }   
  }
}

// метод установки номера вывода и времени подтверждения
void Button::setPinTime(byte pin, byte timeButton)  {

  _pin= pin;
  _timeButton= timeButton;
  pinMode(_pin, INPUT_PULLUP);  // определяем вывод как вход
}

Загрузите, проверьте. Работает.

 

Конструкторы класса в программах Ардуино.

Конструкторы класса это функция, которая автоматически вызывается при создании объекта этого класса.

  • Конструктор является членом класса;
  • не имеет типа возвращаемого значения, даже void;
  • имеет то же имя, что и класс.

В нашем классе Button мы можем создать конструктор для того, чтобы не делать лишний вызов метода setPinTime(12, 15). А установку параметров производить при создании объекта button1.

Для этого добавим в описание класса описание конструктора.

Button(byte pin, byte timeButton);  // описание конструктора

И в конце программы напишем код конструктора очень похожий на метод  setPinTime.

// описание конструктора класса Button
Button::Button(byte pin, byte timeButton) {

  _pin= pin;
  _timeButton= timeButton;
  pinMode(_pin, INPUT_PULLUP);  // определяем вывод как вход
}

Теперь мы можем установить параметры pin и timeButton при создании объекта.

Button button1(BUTTON_1_PIN, 15);  // создание объекта для кнопки 1 с параметрами BUTTON_1_PIN и 15

Использовать метод setPinTime необязательно. Хотя лучше его оставить, чтобы программа могла менять параметры после создания объекта.

Полный вариант кода программы с конструктором в следующем разделе.

 

Проверим работу программы с двумя объектами (кнопками).

Добавим в схему еще одну кнопку и один светодиод. Подключим их к плате Ардуино по схеме.

Схема подключения

У меня все это выглядит так.

Подключение кнопок и светодиода

Первая кнопка меняет состояние светодиода на плате Ардуино, а вторая – светодиода на макетной плате.

  • Создаем в программе два объекта button1 и button2 с использованием параметров конструктора.
  • В бесконечном цикле вызываем методы scanState для обоих объектов.
  • В бесконечном цикле проверяем флаги обоих объектов и управляем светодиодом.

Вот текст программы.

/*  Программа sketch_7_2 урока 7
 *  Подключены две кнопки и светодиод
 *  Каждое нажатие кнопки 1 инвертирует состояние светодиода на плате Ардуино
 *  Каждое нажатие кнопки 2 инвертирует состояние светодиода на макетной плате */

#define LED_1_PIN 13     // номер вывода светодиода 1 равен 13
#define BUTTON_1_PIN 12  // номер вывода кнопки 1 равен 12
#define BUTTON_2_PIN 11  // номер вывода кнопки 2 равен 11
#define LED_2_PIN 10     // номер вывода светодиода 2 равен 10

// Описание класса обработки сигналов кнопок
class Button {
  public:
    Button(byte pin, byte timeButton);  // описание конструктора
    boolean flagPress;    // признак кнопка сейчас нажата
    boolean flagClick;    // признак кнопка была нажата (клик)
    void scanState();     // метод проверки состояние сигнала
void setPinTime(byte pin, byte timeButton); // метод установки номера вывода и времени (числа) подтверждения
  private:
    byte  _buttonCount;    // счетчик подтверждений стабильного состояния   
    byte _timeButton;      // время подтверждения состояния кнопки
    byte _pin;             // номер вывода
};

boolean ledState1;         // переменная состояния светодиода 1
boolean ledState2;         // переменная состояния светодиода 2

Button button1(BUTTON_1_PIN, 15);  // создание объекта для кнопки 1
Button button2(BUTTON_2_PIN, 15);  // создание объекта для кнопки 2
  
void setup() {
  pinMode(LED_1_PIN, OUTPUT);           // определяем вывод светодиода 1 как выход
  pinMode(LED_2_PIN, OUTPUT);           // определяем вывод светодиода 2 как выход
}

// бесконечный цикл с периодом 2 мс
void loop() {

  button1.scanState();  // вызов метода сканирования сигнала кнопки 1
  button2.scanState();  // вызов метода сканирования сигнала кнопки 2
 
  // блок управления светодиодом 1
  if ( button1.flagClick == true ) {
    // было нажатие кнопки
    button1.flagClick= false;         // сброс признака клика
    ledState1= ! ledState1;             // инверсия состояния светодиода 1
    digitalWrite(LED_1_PIN, ledState1);  // вывод состояния светодиода 1   
  }

  // блок управления светодиодом 2
  if ( button2.flagClick == true ) {
    // было нажатие кнопки
    button2.flagClick= false;         // сброс признака клика
    ledState2= ! ledState2;             // инверсия состояние светодиода 2
    digitalWrite(LED_2_PIN, ledState2);  // вывод состояния светодиода 2   
  }

  delay(2);  // задержка на 2 мс
}

// метод проверки состояния кнопки
// flagPress= true  - нажата 
//  flagPress= false - отжата
//  flagClick= true - была нажата (клик)
void Button::scanState() {

 if ( flagPress == (! digitalRead(_pin)) ) {
     // состояние сигнала осталось прежним
     _buttonCount= 0;  // сброс счетчика состояния сигнала
}
  else {
     // состояние сигнала изменилось
     _buttonCount++;   // +1 к счетчику состояния сигнала

     if ( _buttonCount >= _timeButton ) {
      // состояние сигнала не менялось заданное время
      // состояние сигнала стало устойчивым
      flagPress= ! flagPress; // инверсия признака состояния
_buttonCount= 0;  // сброс счетчика состояния сигнала

 

      if ( flagPress == true ) flagClick= true; // признак клика на нажатие     
     }   
  }
}

// метод установки номера вывода и времени подтверждения
void Button::setPinTime(byte pin, byte timeButton)  {

  _pin= pin;
  _timeButton= timeButton;
  pinMode(_pin, INPUT_PULLUP);  // определяем вывод как вход
}

// описание конструктора класса Button
Button::Button(byte pin, byte timeButton) {

  _pin= pin;
  _timeButton= timeButton;
  pinMode(_pin, INPUT_PULLUP);  // определяем вывод как вход
}

Загружаем. Все работает.

 

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

Предыдущий урок     Список уроков     Следующий урок

88 комментариев на «Урок 7. Классы в программах Ардуино. Кнопка как объект.»

  1. Видимо я упустил какой-то нюанс, поэтому не могу понять зачем мы ввели локальные переменные ( _timeButton, _pin) если есть публичные (timeButton, pin).
    Ведь в методе setPinTime мы можем обойтись публичными без операции =?

    • timeButton, pin это аргументы функции, доступные внутри функции setPinTime(). А _timeButton и _pin это переменные класса, доступные всем методам класса.

  2. button1.flagClick= false; // переменная flagClick j, объекта button1 = false

    В комментарии все верно написано?

  3. Немного не понимаю схему работы переменной flagPress.
    В false она возращаетя только через 12 милисекунд после смены состояния?

    • Да. Это время на устранение дребезга, фильтрацию сигнала. Разве это критично, если программа среагирует на нажатие кнопки на 12 мс позже.

      • Спасибо за ответ. Я просто новичок в программировании. Вот код и пытаюсь понять 🙂 По моему очень хорошее решение. Не надо лишнюю строчку кода на возврат писать.

          • Эта строка flagPress= ! flagPress; // инверсия признака состояния

  4. Мы вызываем метод scanState() 2 раза для каждого экземпляра класса. Здесь всё логично. Кнопок много — метод один. С++ рулит. Но, где вызов метода setPinTime, коли он объявлен? И, если можно, объясните, пожалуйста, преимущество использование конструктора.

    • Я цитирую из урока:
      » В нашем классе Button мы можем создать конструктор для того, чтобы не делать лишний вызов метода setPinTime(12, 15). А установку параметров производить при создании объекта button1.»
      Конструктор позволяет при объявлении экземпляра класса инициализировать его параметры (свойства).
      А setPinTime мы не вызываем, потому что задали параметры в конструкторе. Он может потребоваться для изменения параметров Button1, например изменения времени подтверждения.

      • получается, если в текущей программе дописать вызов button2.setPinTime(12, 1000) , то программа изменит время и покажет что кнопка была нажата больше одной секунды?

  5. В строке программы 7_1 » if ( flagPress == (! digitalRead(_pin)) ) » мы сравниваем flagPress с инверсией сигнала, считанного с какого, извиняюсь, контакта? Ведь связи между _pin и живым входом до того в тексте не встречалось, только указание, что _pin — типа byte, а значение?

    • Вот связь в методе setPinTime:
      button1.setPinTime(BUTTON_PIN, 15); // вызов метода установки объекта button1 с параметрами: номер вывода 12, число подтверждений 15
      Через него мы задали номер вывода, т.е. свойство _pin.

      • Точно. Простите за слепоту. Ведь метод setPinTime вызывается до того, в void setup…

  6. В программе 7_2 конструктор, как метод, вызывается по количеству создаваемых объектов (2 раза). Однако, если его заменить тем же методом setPinTime, то он тоже будет вызываться 2 раза (по количеству объектов). Какое в таком случае преимущество конструктора (то же количество вызова подпрограмм) помимо инициализации параметров?

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

  7. Доброго времени суток. Поймал себя на мысли, что никак не возьму в толк какое состояние имеет переменная flagPress в начале работы, когда мы её сравниваем с digitalRead(_pin) в методе scanState : » if ( flagPress == (! digitalRead(_pin)) ) «

    • Здравствуйте! При объявлении компилятор инициализирует переменные 0. Реальное состояние кнопки определится только через время фильтрации.

  8. Хочу поблагодарить вас за то, что вы делаете. Хороший курс. Я как новичок пока, что все понимаю и это благодаря вам.

  9. Добрый день! На одном дыхании дошел до этого урока и хочу поблагодарить за проделанную работу…. Хоть у меня и есть небольшой опыт программирования на Бейсике, FoxPro, Ассемблере (20 лет назад), и до сих пор немного для себя на Дельфи, но сталкнувшись с необходимостью разобраться с ардуиновскими контроллерами, понял, что столько нюансов в написании кода… да же в той же орфографии.. Ваши уроки раскрывают по-новому взгляд на многие алгоритмы, команды и тд… Большое спасибо… перехожу к следующему уроку 🙂

  10. Добрый вечер!
    Эдуард, я новичок в программировании, но Ваши уроки
    мне понравились и во многом помогли. Есть проблема, если есть возможность ее описать, то буду очень благодарен!
    Есть библиотека MCP23xxx.h к шилду LCD Keypad Shield I2C. В функции setup, кнопки шилда прописываем как MCP.pinMode(12, INPUT);. и впрограмме обращемся к ним как MCP.digitalRead(12);
    Вопрос: можно ли при создании своей библиотеки как то обращаться к этим кнопкам или к библиотеке MCP23xxx.h, заранее спасибо

    • Здравствуйте! Я никогда не использовал расширители портов. Попробуйте, проверьте на практике.

  11. Каеф!
    Саму прогу я бы по другому написал.
    Но как все обстоятельно и доходчиво про классы и объекты. Ох***но!
    Наконец я понял как делать классы.
    Спасибо огромное.

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

  12. Привет, а как поместить в класс такое?
    void errorHTML(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete);
    c такими параметрами: WebServer &server, WebServer::ConnectionType type,

    здесь Webduino server исползует.

  13. Здравствуйте!
    Большое спасибо за интересные и бесплатные(!) уроки, которые помогли расширить знание ООП.
    В свою очередь, хочу «поделиться» кнопками с другими алгоритмами обработки, которые пришлось «сделать» для небольшого проекта.
    Это кнопка «с фиксацией», которая переходит из состояния «вкл» при одном длительном нажатии, и возвращается в состояние «выкл» при другом длительном нажатии.
    И «триггерная» кнопка, которая при длительном нажатии вырабатывает сигнал «вкл» на протяжении некоторого короткого интервала, и возвращается в состояние «выкл» не зависимо от дальнейшего нажатия (аналог одновибратора).
    Эти кнопки можно, конечно, объединить, но для Арудины с ее крохотными мозгами лучше использовать только тот «сорт» кнопок, который нужен в конкретной задаче.
    Удачи!

    /*=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#*/
    /*+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+*/
    /*
    Методы обработки нажатия кнопки и очистки от дребезга
    путем многократного непрерывного в течение заданного интервала времени
    опроса состояния кнопки
    */
    /*+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+*/

    void Button:: set( byte pin, boolean defaultButt, // установка параметров кнопки
    boolean pullup, int retention ) {

    _digitPin = pin ; // присвоение номера PIN
    _defaultButt = defaultButt ; // исходное состояние разомкнуто
    _pullup = pullup ; // подтяжка к ПЛЮС
    _retention = retention ; // время удержания [ мсек ]

    if (_pullup) {
    pinMode(_digitPin, INPUT_PULLUP) ;
    } else {
    pinMode(_digitPin, INPUT) ;
    }
    }

    /*+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+*/
    void Button:: sScan() { // ПРОСТЕЙШАЯ кнопка SIMPLE

    if (digitalRead( _digitPin) == _defaultButt ) { // кнопка в исходном состоянии
    timeClick = millis() ; // запоминаем время вызова
    pressed = false ; // кнопка не нажата
    } else {
    if ( millis() — timeClick > _retention ) { // время прошло —
    pressed = true ; // и кнопка хорошо нажата
    }
    }
    }

    /*+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+*/
    void Button:: fScan() { // кнопка нажал-отжал FIXED

    if (digitalRead( _digitPin) == _defaultButt ) { // кнопка в исходном состоянии
    timeClick = millis() ; // запоминаем время вызова
    pressed = false ; // кнопка не нажата
    _lock = false ;
    } else {

    if ( millis() — timeClick > _retention ) { // время прошло —
    if ( _lock == false ) {
    fixing = ! fixing ; // и кнопка хорошо нажата
    pressed = true ; // и кнопка хорошо нажата
    _lock = true ;
    }
    }
    }
    }

    /*+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+*/
    void Button:: tScan ( ) { // одновибратор 100 мсек TRIGGER

    static boolean _burst ;

    if ( digitalRead( _digitPin) == _defaultButt ) { // кнопка в исходном состоянии ОТЖАТО
    timeClick = millis() ; // запоминаем время вызова
    pressed = false ; // кнопка не нажата
    } else { // НАЖАТО
    if ( millis() — timeClick > _retention ) { // время прошло —
    pressed = true ; // значит кнопка хорошо нажата
    _burst = true;
    }
    }

    if ( _burst ) { // время нажатия прошло,ждем отпускания
    if ( !digitalRead( _digitPin) == _defaultButt ) { // кнопка в !исходном состоянии НАЖАТО
    timeBurst = millis() ;
    } else { // ОТЖАТО, пошло время
    if ( millis() — timeBurst < 100 ) { // 100 мсек прошло —
    burst = true ;
    } else {
    _burst = false ;
    burst = false ;
    }
    }
    }
    }

  14. Прошу извинить за, может быть, глупый вопрос. Еще раз по поводу строки if ( flagPress == (! digitalRead(_pin)) ).
    При инициализации компилятор присвоит flagPress значение 0.
    При этом digitalRead(_pin) (при не нажатой кнопке) также равно 0, а инверсия соответственно 1. Тогда условие if ( flagPress == (! digitalRead(_pin)) ) истинно, если состояние кнопки изменилось? Или не так?

  15. Еще раз по поводу строки if ( flagPress == (! digitalRead(_pin)) ).
    При инициализации компилятор присвоит flagPress значение 0.
    При этом digitalRead(_pin) (при не нажатой кнопке) также равно 0, а инверсия соответственно 1. Тогда условие if ( flagPress == (! digitalRead(_pin)) ) истинно, если состояние кнопки изменилось? Или не так?

  16. Эдуард, великая благодарность за толковые уроки!
    Работаю с Ethernet Shield W5100, пытаюсь создать класс вида socket и тиражировать с разными IP-портами. Такая задача реальна?
    Удачи!

  17. Спасибо огромное за уроки!
    Но у меня есть парочка вопросов.
    1) создавать экземпляры класса обязательно только в самом начале программы до функции setup? Или можно в любом месте программы? Тогда возникает вопрос о видимости экземпляров класса из других функций.
    2) иногда (довольно редко и без всякой видимой причины и закономерности) при подключении питания к ардуино или ресета в конструкторе

    Lampochka::Lampochka(byte pin) {

    _pin1 = pin1;
    pinMode(_pin1, OUTPUT);
    digitalWrite(_pin1, HIGH);

    }
    что то идет не так, и физически на пине не появляется 5 вольт. после этого пин остается неработоспособным до следующей перезагрузки.
    И вот тут я не понимаю, проблема на программном уровне или в железе? Можно ли присваивать высокое или низкое состояние пину в конструкторе (до начала выполнения функции setup). Отсюда и вопрос номер 1.
    Спасибо за ответы!

    • Здравствуйте!
      Экземпляры класса, как и любые переменные, надо объявлять до того места в программе, где они будут использоваться. Лучше в начале.
      Состояние pin в конструкторе устанавливать можно.

  18. Возникли некоторые трудности с пониманием кода.
    Когда мы изначально запустили программу и не нажимаем на кнопку последовательность следующая:
    1)Первое условие не выполняется, так как кнопка отжата (с инверсией равно true), а значение flagPress = false. Следовательно идём по else
    2)buttonCount становится в 1. Кстати, что за тип byte? След. if не выполняется, так как buttonCount < 12. Следовательно сразу переходим к блоку управления светодиодом.
    3) По скольку flagClick установлен в false, то и здесь ничего не выполняется.

    Нажимаем на кнопку:
    1)Срабатывает первый if и buttonCount устанавливается в 0. Сразу переходим в блок управления
    2)И здесь опять ничего не происходит, так как buttonClick по прежнему false.

    Где в моих рассуждениях ошибка?

  19. Здравствуйте Эдуард.
    Я только начинаю знакомиться с Ардуино и совершенно ничего не знаю. Вот повторил вашу программу с двумя светодиодами. Собрал схему и запустил скетч. Но при загрузке выскочила ошибка. Проблема с LED_2_PIN. Начал выяснять почему. И обнаружил, что в схеме только один светодиод. Второй на плате Ардуино, а в программе этого светодиода нет. Посмотрел скетч примера 01,Basics и обнаружил, что всетодиод платы обозначается как LED_BUILTIN, Изменил вашу программу следующим образом:

    #define LED_1_PIN 10 // номер вывода светодиода 1 равен 10
    #define BUTTON_1_PIN 11 // номер вывода кнопки 1 равен 11
    #define BUTTON_2_PIN 12 // номер вывода кнопки 2 равен 12
    #define LED_BUILTIN 13 // номер вывода кнопки 2 равен 13

    и ещё:

    pinMode(LED_BUILTIN, OUTPUT); // определяем вывод светодиода 2 как выход

    и ещё:

    digitalWrite(LED_BUILTIN, ledState2); // вывод состояния светодиода 2

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

  20. Приветствую Эдуард.
    Про добавление второй кнопки понятно. А если мы делаем два светодиода с управлением от каждой кнопки по отдельности, нужно создавать отдельный объект для второго светодиода или достаточно его добавить в имеющийся Button?

    • Здравствуйте!
      Нужно создавать второй объект Button. Светодиоды — не объекты, а выводы.

  21. Доброго времени суток!

    Вопрос такой:
    Если закомментировать метод setPinTime, компилятор ругается. Но метод не вызывается в разделе Setup, вместо него там используется конструктор. Почему компилятор ругается?

    • Здравствуйте!
      Надо закомментировать и метод в описании класса, и код метода.

  22. «А вызов любой функции занимает определенное время. Поэтому, свойства, к которым программа обращается часто, следует сделать открытыми и обращаться к ним явно. Это уменьшит время выполнения программы.»

    Попробовал посмотреть ассемблерный листинг. Убедился, что можно смело придерживаться хорошего стиля с закрытыми свойствами и доступом через методы. По крайней мере, если в методе только один return, то в листинге никакого вызова функции нет, только обращение к ячейке памяти.
    Видимо, оптимизатор мелкие функции автоматичеки делает inline.

  23. Вот это суппер доступные пояснения, аж мозг приятно закипел:)
    А то по всему инету размазано все про все и конкретно ничего д кучи сложить невозможно.
    Очень благодарен автору!!

  24. Здравствуйте Эдуард, прочитал немного о вашей жизни…
    Сочувствую насчет ребёнка.
    Однако хочу обратиться к вам со словами благодарности — вы первый человек, который понятным и простым языком объяснил что такое класс и как им пользоваться!

    • Спасибо за сочувствие. Но жизнь у меня сейчас совсем не скучная. Вроде белая полоса пошла.

  25. Добрый день! Скажите, почему в схемах мы можем не подключать ничего к плюсу, все провода из цифровых портов идут только на минус? Потому что вместо плюс у нас земля?

    • Здравствуйте, Мария! На мой сайт редко девушки заходят. Вдвойне приятно.
      Выход микроконтроллера выглядит так. Два ключа: один между выводом и плюсом питания, второй — между выводом и землей. Когда на выводе логическая единица, то верхний ключ замыкается, а нижний размыкается. При логическом нуле наоборот. Т.е. выход микроконтроллера это источник напряжения 5 В, либо короткое замыкание на землю.
      Отсюда вытекают два способа подключения нагрузки, например, светодиода.
      Первый — светодиод включается между выводом и землей. В этом случае светится он будет при логической 1 на выходе.
      Второй — светодиод подключается между шиной питания +5 В и выводом. В этом случае он будет светится при логическом 0 на выходе.
      Т.е. от подключения зависит полярность управления. Как пример, посмотрите в этой статье http://mypractic.ru/drajver-shagovogo-dvigatelya-tb6560-v2-opisanie-xarakteristiki-rekomendacii-po-ekspluatacii.html
      два способа подключения к микроконтроллеру STEP/DIR драйвера. В середине статьи, не осмысливая, что такое драйвер. Представьте, что это три светодиода.

  26. Доброго времени суток. Эдуард, в этом 7-ом уроке у Вас хорошо описано понятие классов и что и как с ними работать. Но, я бы хотел сказать, что до работы с классами, хорошо бы рассказать подробно о функциях. А о них упоминается только вскользь в конце 4-го урока, а в этом они уже привязаны к классу.

    • Здравствуйте!
      Я же не пишу уроки по языку C. Функции элементарное понятие, которое доходчиво объясняется в любом учебнике по C. Если есть вопросы, я охотно отвечу на форуме сайта.

  27. Хорошо. Если мы хотим использовать функцию, то её, во-первых, необходимо объявить в описательной части, затем в теле loop вызвать и, наконец, описать саму нашу функцию уже вне loop. Правильно?

    • В принципе правильно. Объявлять функцию в Ардуино не надо. Хотя классический C требует объявление прототипа функции.
      И еще, вызывать функцию можно и в setup().

      • Эдуард, тогда вопрос. Вызываю функцию из loop, которая не объявлена в описательной, а компилятор ругается на это…

        • Не ругается же компилятор на функцию sum из такого примера:

          void setup() {
          }

          void loop() {
          int z = sum(3, 5);
          }

          int sum(int x, int y) {
          return(x+y);
          }

          Хотя, стандартный C++ потребовал бы прототипа функции:
          int sum(int x, int y);

          • Да, всё верно у Вас. Эдуард, какая штука получается… Если я свою функцию размещаю до loop, то компилятор не ругается, если — после, то ошибка о незадекларированной функции. Ошибка, известная в сети, оказывается…

  28. Здравствуйте. Подскажите почему flagPress в проверке состояния кнопки не нужно сбрасывать в 0? Допустим после первого нажатия flagPress стал равен 1, мы сравнили его с flagClick, диод загорелся. Но теперь при повторном нажатии строка flagPress=!flagPress меняет flagPress обратно на 0, тогда каким образом засчитывается второе нажатие, если не выполняется условие if ( flagPress == true ) flagClick= true;?

    • Здравствуйте!
      Признак falgPress показывает текущее состояние кнопки. В зависимости от состояния кнопки библиотека Button устанавливает его или сбрасывает. У этого признака нет памяти.
      flagClick только устанавливается в библиотеке. Сбрасываться он должен после обработки этого события. Момент нажатия кнопки это кратковременное событие, которое запоминается и хранится до обработки.
      Прочувствуйте разницу между состоянием и событием.

  29. Добрый день, Эдуард!
    У Вас в тексте, в разделе про Конструкторы класса, есть такое предложение: «Использовать метод setPinTime необязательно. Хотя лучше его оставить, чтобы программа могла менять параметры после создания объекта.»
    Как я понял метод setPinTime можно вовсе изъять из программы? Или его все же необходимо оставить? И что за параметры может менять программа после создания объекта?
    Спасибо!

    • Здравствуйте!
      Метод позволяет задавать номер вывода и время подтверждения состояния кнопки. Эти параметры задаются в конструкторе. Если менять их не надо, то не используйте этот метод.

  30. Здравствуйте, Эдуард!
    Можно коротко, а в чем разница между функцией и классом? Можно ли, с таки-же успехом, пользоваться функцией вместо класса?

    • Здравствуйте!
      Функции — это подпрограммы. А классы — это объекты. Попробуйте реализовать обработку разных кнопок одной функцией. Увидите во что это выливается.

      • Я создаю устройство в котором используется Инкодер. Мною написана удачная (на мой взгляд) функция работы с ним. Видимо нет смысла создавать для этого класс. Я правильно понимаю?

        • Вполне возможно. Например, при оптимизации работы программы по времени быстрее всего будет работать программный блок без функции или класса.

  31. Эдуард, во-первых большое Вам спасибо за такие хорошие и главное «систематизированные» уроки. А то на просторах инета много всякой шелухи, а у вас чувствуется именно системный подход.
    Но разрешите пару замечаний. Идеология ООП предполагает, что класс объединяет в себе все, что его касается и к нему относится. И он сам за собой должен все делать и все подчищать. А у вас установка флага происходит внутри класса, а его сброс — вне класса, у его так сказать «потребителя» (button1.flagClick= false;). Если например я использую ваш класс, то как я должен догадаться, что флаг нужно сбрасывать?… Из описания, да, но вообще-то все должно работать и без описания.
    Я бы лично сделал scanState функцией, возвращающей boolean — этот самый признак фронта нажатия, без всякого флага.
    Может я конечно применительно к программированию МК не прав, но по идеологии положено вообще-то так….

    • Здравствуйте! Спасибо за приятные слова.
      По поводу идеологии ООП вы совершенно правы. Но мы работаем на микроконтроллере с ограниченными ресурсами. Я пишу в уроке, что я со «скрипом» принял такое решение. Но для практического программирования, думаю, оно правильное. Попробуйте мысленно оттранслируйте вызов простейшей функции, анализирующей флаг. Сколько это будет команд микроконтроллера? Сколько будет вызовов подпрограмм, использование стека аргументов функции?
      Т.е. я согласен с вашей последней фразой.

  32. А разве правильно текущее состояние светодиода определять по переменной ledState? А вдруг светодиод выключили/включили каким-то другим способом, а ledState об этом ничего не знает?… Не правильней ли считывать реальное состояние светодиода с пина и его уже инвертировать?

    • Здравствуйте!
      Могут быть разные решения. Хороший стиль все таки использовать переменную. Это более надежный способ.
      Чтение выводов микроконтроллеров иногда приводит к непредсказуемым результатам. Например, если вы отверткой кратковременно замкнете выход, к которому подключен светодиод, то светодиод погаснет. Уберете отвертку — светодиод загорится. Но если в момент замыкания считаете состояние светодиода, то результат будет не соответствовать реальному состоянию светодиода. Подобно отвертке на выходы микроконтроллера могут влиять помехи, особенно если выходы подключены к длинным линиям.

  33. Эдуард, спасибо огромное за Ваш сайт! Очень полезный ресурс для учебы, я взял его за основу. Последний скетч этого урока я пытаюсь использовать для управления через реле, 12 вольтовым шаровым краном для полива. Открытие крана и закрытие управляется по двум контактам изменением полярности напряжения. Т.е. надо исключить событие, при котором обе кнопки будут срабатывать одновременно. Для выполнения этого условия я пытаюсь использовать переменные ledState. Но ничего не получается. Голову сломал. Подскажите как сделать, чтобы при работе оного светодиода исключалось включение второго и наоборот. Заранее спасибо!

    • Здравствуйте!
      Блокировка должна осуществляться на аппаратном уровне. Если у вас используются реле, то сделать это очень просто. Пришлите вашу схему или лучше откройте тему на форуме.
      Или подождите пару недель. Сейчас в разделе «Умный дом» я веду разработку системы. На этой неделе появиться общая аппаратная и программная часть локальных контроллеров. А следующая — разработка контроллера водоснабжения. Там есть управление краном.

  34. Добрый день.
    Подскажите, пожалуйста, как объявить массив элементов класса Button?
    Вот такая конструкция не работает:
    int numBtn = 4; //Количество кнопок
    Button buttons[4];
    При компилировании возвращает ошибку:
    108: error: no matching function for call to ‘Button::Button()’
    Button buttons[4];

    • Здравствуйте!
      Создавать массив экземпляров класса (массив объектов) с аргументами в конструкторе сложнее. Попробуйте так:
      Button buttons[4] = { Button(3,10), Button(4,10), Button(5,10), Button(6,10)};

  35. Добрый день! Есть ли возможность, как в c++, сохранить этот класс? Что-то типа Button.h. Спасибо!

    • Прошу прощения за беспокойство, прочитал дальше, нашёл, спасибо за материал

  36. Здравствуйте, Эдуард. Очень нравятся ваши уроки и то как вы их грамотно доносите) Но всё равно остаётся много вопросов, например откуда берутся те или иные строки… Просто повторить и помигать светодиодом или переключить состояние это понятно, но я задумал несложное устройство, а реализовать его тяму не хватает. Может сможете помочь на примере урока и подробно опИшите код и все действия? Логика такова: имеем кнопку 1 и кнопку 2, светодиод 1 , пищалка или зиммер и выход Х . при подаче питания зиммер издаёт короткий «Пик», светодиод 1 вспыхивает с периодичностью в две секунды, на выходе Х логический 0;
    при нажатии и удержании кнопки 1 через 2 секунды зимер проигрывает короткую мелодию включения, на выходе Х появляется логическая 1, светодиод 1 начинает постоянно светиться;
    далее при коротком нажатии на кнопку 1 зиммер издает короткий «Пик» и программа запускает отчёт 2-х минутного таймера отключения выхода Х, при этом светодиод 1 мигает периодичностью в 1 секунду индицируя работу таймера;
    если нажать и удерживать более 2-х секунд кнопку 1 зиммер издаёт короткий «Пик» и таймер отменяется, светодиод 1 светится постоянно;
    при нажатии кнопки 2 зиммер издает короткий «Пик»,программа запускает отчёт 10 минутного таймера отключения выхода Х, светодиод 1 мигает периодичностью в 1 секунду индицируя работу таймера;
    если кратковременно нажать кнопку 2 зиммер издаёт «Пик» и таймер добавляет ещё 10 минут, всего можно добавить до 6 раз после чего кнопка 2 не реагирует на команды, идёт отсчёт таймера с последующим переключением на логический 0 выхода Х;
    в любое время после включения таймера можно нажать и удерживать кнопу 1 при этом зиммер издаст «Пик» и таймер отменится, светодиод 1 будет светиться постоянно;
    при нажатии и удержании кнопки 1 более 2-х секунд зиммер проиграет короткую мелодию выключения, на выходе Х появится логический 0, светодиод 1 будет вспыхивать периодичностью в 2 секунды… Как то так)

    • Здравствуйте!
      У меня нет времени, а вы предлагаете реализовать узко-специализированный проект.
      Задавайте локальные вопросы, я отвечу. Можете завести отдельную тему на форуме и спрашивать там.

  37. Здравствуйте Эдуард. Огромное спасибо Вам за Ваши уроки. Вам обязательно стоит задуматься о написании книги на их основе.
    Для лучшего закрепления материала данного урока я создал класс управления светодиодом по признаку нажатия кнопки (flagClick==true). Отладил, проверил — работает. Ещё раз огромное Вам спасибо.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *