Не отступай и не сдавайся !!!

+7(495)233-9079
arduino4home@gmail.com

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

Предыдущий урок Ардуино - главная Следуюший урок

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

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

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

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

Классы в 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);  // определяем вывод как вход
}

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

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


Хостинг нашего сайта осуществляется узлом www.cherepovets-city.ru
© 2000-2018 24/8/18 9:18