В этом уроке будем рассматривать кнопки как объекты, создадим класс для них.
Предыдущий урок Список уроков Следующий урок
В прошлом уроке мы написали рабочий, отлаженный программный блок, который можно вполне использовать в боевых программах. Но как-то не совсем красиво получилось.
- Программный блок для обработки сигнала размещается вместе с другими блоками и ухудшает читаемость программы.
- Надо определить для него переменные, ничего не забыть.
- А если нам необходимо подключить несколько кнопок. Для каждой из них надо завести свои переменные, свои программные блоки, свои функции. И ничего не перепутать.
- При каждом использовании программного блока обработки сигнала кнопки придется вспоминать, как он работает, какие переменные требует и, самое главное, менять имена переменных в тексте блока.
И это притом, что мы используем один и тот же простой объект – кнопку. Для красивого решения этих проблем в языке программирования Ардуино существуют классы.
Классы в 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); // определяем вывод как вход
}
Загружаем. Все работает.
В следующем уроке я расскажу о другом методе обработки сигнала кнопки, и мы создадим для него новый метод.
Видимо я упустил какой-то нюанс, поэтому не могу понять зачем мы ввели локальные переменные ( _timeButton, _pin) если есть публичные (timeButton, pin).
Ведь в методе setPinTime мы можем обойтись публичными без операции =?
timeButton, pin это аргументы функции, доступные внутри функции setPinTime(). А _timeButton и _pin это переменные класса, доступные всем методам класса.
button1.flagClick= false; // переменная flagClick j, объекта button1 = false
В комментарии все верно написано?
flagClick это свойство (переменная) объекта button1 класса Button
Я имел ввиду знак j после flagClick
Это ошибка. Я не знаю откуда он там взялся.
Немного не понимаю схему работы переменной flagPress.
В false она возращаетя только через 12 милисекунд после смены состояния?
Да. Это время на устранение дребезга, фильтрацию сигнала. Разве это критично, если программа среагирует на нажатие кнопки на 12 мс позже.
Спасибо за ответ. Я просто новичок в программировании. Вот код и пытаюсь понять 🙂 По моему очень хорошее решение. Не надо лишнюю строчку кода на возврат писать.
А я так и не понял, как FlagPress возвращается в состояние false. Что её меняет?
Эта строка flagPress= ! flagPress; // инверсия признака состояния
Мы вызываем метод scanState() 2 раза для каждого экземпляра класса. Здесь всё логично. Кнопок много — метод один. С++ рулит. Но, где вызов метода setPinTime, коли он объявлен? И, если можно, объясните, пожалуйста, преимущество использование конструктора.
Я цитирую из урока:
» В нашем классе Button мы можем создать конструктор для того, чтобы не делать лишний вызов метода setPinTime(12, 15). А установку параметров производить при создании объекта button1.»
Конструктор позволяет при объявлении экземпляра класса инициализировать его параметры (свойства).
А setPinTime мы не вызываем, потому что задали параметры в конструкторе. Он может потребоваться для изменения параметров Button1, например изменения времени подтверждения.
получается, если в текущей программе дописать вызов button2.setPinTime(12, 1000) , то программа изменит время и покажет что кнопка была нажата больше одной секунды?
Здравствуйте!
Да, все так. Только более 2 секунд, 1000 * 2 мс.
В строке программы 7_1 » if ( flagPress == (! digitalRead(_pin)) ) » мы сравниваем flagPress с инверсией сигнала, считанного с какого, извиняюсь, контакта? Ведь связи между _pin и живым входом до того в тексте не встречалось, только указание, что _pin — типа byte, а значение?
Вот связь в методе setPinTime:
button1.setPinTime(BUTTON_PIN, 15); // вызов метода установки объекта button1 с параметрами: номер вывода 12, число подтверждений 15
Через него мы задали номер вывода, т.е. свойство _pin.
Точно. Простите за слепоту. Ведь метод setPinTime вызывается до того, в void setup…
В программе 7_2 конструктор, как метод, вызывается по количеству создаваемых объектов (2 раза). Однако, если его заменить тем же методом setPinTime, то он тоже будет вызываться 2 раза (по количеству объектов). Какое в таком случае преимущество конструктора (то же количество вызова подпрограмм) помимо инициализации параметров?
В случае с использованием конструктора параметры объекта задаются при его объявлении. Без конструктора надо объявить объект, а потом задать его параметры.
Доброго времени суток. Поймал себя на мысли, что никак не возьму в толк какое состояние имеет переменная flagPress в начале работы, когда мы её сравниваем с digitalRead(_pin) в методе scanState : » if ( flagPress == (! digitalRead(_pin)) ) «
Здравствуйте! При объявлении компилятор инициализирует переменные 0. Реальное состояние кнопки определится только через время фильтрации.
Хочу поблагодарить вас за то, что вы делаете. Хороший курс. Я как новичок пока, что все понимаю и это благодаря вам.
Спасибо. Приятно слышать.
Добрый день! На одном дыхании дошел до этого урока и хочу поблагодарить за проделанную работу…. Хоть у меня и есть небольшой опыт программирования на Бейсике, FoxPro, Ассемблере (20 лет назад), и до сих пор немного для себя на Дельфи, но сталкнувшись с необходимостью разобраться с ардуиновскими контроллерами, понял, что столько нюансов в написании кода… да же в той же орфографии.. Ваши уроки раскрывают по-новому взгляд на многие алгоритмы, команды и тд… Большое спасибо… перехожу к следующему уроку 🙂
Спасибо. Приятно слышать.
Добрый вечер!
Эдуард, я новичок в программировании, но Ваши уроки
мне понравились и во многом помогли. Есть проблема, если есть возможность ее описать, то буду очень благодарен!
Есть библиотека MCP23xxx.h к шилду LCD Keypad Shield I2C. В функции setup, кнопки шилда прописываем как MCP.pinMode(12, INPUT);. и впрограмме обращемся к ним как MCP.digitalRead(12);
Вопрос: можно ли при создании своей библиотеки как то обращаться к этим кнопкам или к библиотеке MCP23xxx.h, заранее спасибо
Здравствуйте! Я никогда не использовал расширители портов. Попробуйте, проверьте на практике.
Каеф!
Саму прогу я бы по другому написал.
Но как все обстоятельно и доходчиво про классы и объекты. Ох***но!
Наконец я понял как делать классы.
Спасибо огромное.
Спасибо за эмоциональную оценку моей работы. А программы в следующих уроках по другому пишутся. Это один из начальных уроках, где показан принцип обработки сигнала кнопки.
Привет, а как поместить в класс такое?
void errorHTML(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete);
c такими параметрами: WebServer &server, WebServer::ConnectionType type,
здесь Webduino server исползует.
Здравствуйте!
Большое спасибо за интересные и бесплатные(!) уроки, которые помогли расширить знание ООП.
В свою очередь, хочу «поделиться» кнопками с другими алгоритмами обработки, которые пришлось «сделать» для небольшого проекта.
Это кнопка «с фиксацией», которая переходит из состояния «вкл» при одном длительном нажатии, и возвращается в состояние «выкл» при другом длительном нажатии.
И «триггерная» кнопка, которая при длительном нажатии вырабатывает сигнал «вкл» на протяжении некоторого короткого интервала, и возвращается в состояние «выкл» не зависимо от дальнейшего нажатия (аналог одновибратора).
Эти кнопки можно, конечно, объединить, но для Арудины с ее крохотными мозгами лучше использовать только тот «сорт» кнопок, который нужен в конкретной задаче.
Удачи!
/*=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#*/
/*+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+*/
/*
Методы обработки нажатия кнопки и очистки от дребезга
путем многократного непрерывного в течение заданного интервала времени
опроса состояния кнопки
*/
/*+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+*/
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 ;
}
}
}
}
Здравствуйте! Спасибо. Такие вещи лучше выкладывать на форуме сайта.
Спасибо.
Попробую повторить в форуме, если кого-нибудь это заинтересует.
Прошу извинить за, может быть, глупый вопрос. Еще раз по поводу строки if ( flagPress == (! digitalRead(_pin)) ).
При инициализации компилятор присвоит flagPress значение 0.
При этом digitalRead(_pin) (при не нажатой кнопке) также равно 0, а инверсия соответственно 1. Тогда условие if ( flagPress == (! digitalRead(_pin)) ) истинно, если состояние кнопки изменилось? Или не так?
Еще раз по поводу строки if ( flagPress == (! digitalRead(_pin)) ).
При инициализации компилятор присвоит flagPress значение 0.
При этом digitalRead(_pin) (при не нажатой кнопке) также равно 0, а инверсия соответственно 1. Тогда условие if ( flagPress == (! digitalRead(_pin)) ) истинно, если состояние кнопки изменилось? Или не так?
Здравствуйте.
digitalRead(_pin) при не нажатой кнопке = 1.
Спасибо. Сразу сказал, вопрос глупый. Уже разобрался )).
Эдуард, великая благодарность за толковые уроки!
Работаю с Ethernet Shield W5100, пытаюсь создать класс вида socket и тиражировать с разными IP-портами. Такая задача реальна?
Удачи!
Спасибо огромное за уроки!
Но у меня есть парочка вопросов.
1) создавать экземпляры класса обязательно только в самом начале программы до функции setup? Или можно в любом месте программы? Тогда возникает вопрос о видимости экземпляров класса из других функций.
2) иногда (довольно редко и без всякой видимой причины и закономерности) при подключении питания к ардуино или ресета в конструкторе
Lampochka::Lampochka(byte pin) {
…
_pin1 = pin1;
pinMode(_pin1, OUTPUT);
digitalWrite(_pin1, HIGH);
…
}
что то идет не так, и физически на пине не появляется 5 вольт. после этого пин остается неработоспособным до следующей перезагрузки.
И вот тут я не понимаю, проблема на программном уровне или в железе? Можно ли присваивать высокое или низкое состояние пину в конструкторе (до начала выполнения функции setup). Отсюда и вопрос номер 1.
Спасибо за ответы!
Здравствуйте!
Экземпляры класса, как и любые переменные, надо объявлять до того места в программе, где они будут использоваться. Лучше в начале.
Состояние pin в конструкторе устанавливать можно.
Возникли некоторые трудности с пониманием кода.
Когда мы изначально запустили программу и не нажимаем на кнопку последовательность следующая:
1)Первое условие не выполняется, так как кнопка отжата (с инверсией равно true), а значение flagPress = false. Следовательно идём по else
2)buttonCount становится в 1. Кстати, что за тип byte? След. if не выполняется, так как buttonCount < 12. Следовательно сразу переходим к блоку управления светодиодом.
3) По скольку flagClick установлен в false, то и здесь ничего не выполняется.
Нажимаем на кнопку:
1)Срабатывает первый if и buttonCount устанавливается в 0. Сразу переходим в блок управления
2)И здесь опять ничего не происходит, так как buttonClick по прежнему false.
Где в моих рассуждениях ошибка?
Это по прошлому уроку
Всё. Понял
Хотя всё-таки когда будет buttonCount >= TIME_BUTTON? buttonCount++ чему равен?
Здравствуйте Эдуард.
Я только начинаю знакомиться с Ардуино и совершенно ничего не знаю. Вот повторил вашу программу с двумя светодиодами. Собрал схему и запустил скетч. Но при загрузке выскочила ошибка. Проблема с 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
После изменений программы схема стала работать, как в описании. Одна кнопка включает и выключает светодиод на плате Ардуино а вторая на монтажной плате.
Приветствую Эдуард.
Про добавление второй кнопки понятно. А если мы делаем два светодиода с управлением от каждой кнопки по отдельности, нужно создавать отдельный объект для второго светодиода или достаточно его добавить в имеющийся Button?
Здравствуйте!
Нужно создавать второй объект Button. Светодиоды — не объекты, а выводы.
Доброго времени суток!
Вопрос такой:
Если закомментировать метод setPinTime, компилятор ругается. Но метод не вызывается в разделе Setup, вместо него там используется конструктор. Почему компилятор ругается?
Здравствуйте!
Надо закомментировать и метод в описании класса, и код метода.
«А вызов любой функции занимает определенное время. Поэтому, свойства, к которым программа обращается часто, следует сделать открытыми и обращаться к ним явно. Это уменьшит время выполнения программы.»
Попробовал посмотреть ассемблерный листинг. Убедился, что можно смело придерживаться хорошего стиля с закрытыми свойствами и доступом через методы. По крайней мере, если в методе только один return, то в листинге никакого вызова функции нет, только обращение к ячейке памяти.
Видимо, оптимизатор мелкие функции автоматичеки делает inline.
Вот это суппер доступные пояснения, аж мозг приятно закипел:)
А то по всему инету размазано все про все и конкретно ничего д кучи сложить невозможно.
Очень благодарен автору!!
Спасибо. Приятно слышать.
Здравствуйте Эдуард, прочитал немного о вашей жизни…
Сочувствую насчет ребёнка.
Однако хочу обратиться к вам со словами благодарности — вы первый человек, который понятным и простым языком объяснил что такое класс и как им пользоваться!
Спасибо за сочувствие. Но жизнь у меня сейчас совсем не скучная. Вроде белая полоса пошла.
Добрый день! Скажите, почему в схемах мы можем не подключать ничего к плюсу, все провода из цифровых портов идут только на минус? Потому что вместо плюс у нас земля?
Здравствуйте, Мария! На мой сайт редко девушки заходят. Вдвойне приятно.
Выход микроконтроллера выглядит так. Два ключа: один между выводом и плюсом питания, второй — между выводом и землей. Когда на выводе логическая единица, то верхний ключ замыкается, а нижний размыкается. При логическом нуле наоборот. Т.е. выход микроконтроллера это источник напряжения 5 В, либо короткое замыкание на землю.
Отсюда вытекают два способа подключения нагрузки, например, светодиода.
Первый — светодиод включается между выводом и землей. В этом случае светится он будет при логической 1 на выходе.
Второй — светодиод подключается между шиной питания +5 В и выводом. В этом случае он будет светится при логическом 0 на выходе.
Т.е. от подключения зависит полярность управления. Как пример, посмотрите в этой статье http://mypractic.ru/drajver-shagovogo-dvigatelya-tb6560-v2-opisanie-xarakteristiki-rekomendacii-po-ekspluatacii.html
два способа подключения к микроконтроллеру STEP/DIR драйвера. В середине статьи, не осмысливая, что такое драйвер. Представьте, что это три светодиода.
Доброго времени суток. Эдуард, в этом 7-ом уроке у Вас хорошо описано понятие классов и что и как с ними работать. Но, я бы хотел сказать, что до работы с классами, хорошо бы рассказать подробно о функциях. А о них упоминается только вскользь в конце 4-го урока, а в этом они уже привязаны к классу.
Здравствуйте!
Я же не пишу уроки по языку C. Функции элементарное понятие, которое доходчиво объясняется в любом учебнике по C. Если есть вопросы, я охотно отвечу на форуме сайта.
Хорошо. Если мы хотим использовать функцию, то её, во-первых, необходимо объявить в описательной части, затем в теле 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, то компилятор не ругается, если — после, то ошибка о незадекларированной функции. Ошибка, известная в сети, оказывается…
Здравствуйте. Подскажите почему flagPress в проверке состояния кнопки не нужно сбрасывать в 0? Допустим после первого нажатия flagPress стал равен 1, мы сравнили его с flagClick, диод загорелся. Но теперь при повторном нажатии строка flagPress=!flagPress меняет flagPress обратно на 0, тогда каким образом засчитывается второе нажатие, если не выполняется условие if ( flagPress == true ) flagClick= true;?
Здравствуйте!
Признак falgPress показывает текущее состояние кнопки. В зависимости от состояния кнопки библиотека Button устанавливает его или сбрасывает. У этого признака нет памяти.
flagClick только устанавливается в библиотеке. Сбрасываться он должен после обработки этого события. Момент нажатия кнопки это кратковременное событие, которое запоминается и хранится до обработки.
Прочувствуйте разницу между состоянием и событием.
Добрый день, Эдуард!
У Вас в тексте, в разделе про Конструкторы класса, есть такое предложение: «Использовать метод setPinTime необязательно. Хотя лучше его оставить, чтобы программа могла менять параметры после создания объекта.»
Как я понял метод setPinTime можно вовсе изъять из программы? Или его все же необходимо оставить? И что за параметры может менять программа после создания объекта?
Спасибо!
Здравствуйте!
Метод позволяет задавать номер вывода и время подтверждения состояния кнопки. Эти параметры задаются в конструкторе. Если менять их не надо, то не используйте этот метод.
Здравствуйте, Эдуард!
Можно коротко, а в чем разница между функцией и классом? Можно ли, с таки-же успехом, пользоваться функцией вместо класса?
Здравствуйте!
Функции — это подпрограммы. А классы — это объекты. Попробуйте реализовать обработку разных кнопок одной функцией. Увидите во что это выливается.
Я создаю устройство в котором используется Инкодер. Мною написана удачная (на мой взгляд) функция работы с ним. Видимо нет смысла создавать для этого класс. Я правильно понимаю?
Вполне возможно. Например, при оптимизации работы программы по времени быстрее всего будет работать программный блок без функции или класса.
Эдуард, во-первых большое Вам спасибо за такие хорошие и главное «систематизированные» уроки. А то на просторах инета много всякой шелухи, а у вас чувствуется именно системный подход.
Но разрешите пару замечаний. Идеология ООП предполагает, что класс объединяет в себе все, что его касается и к нему относится. И он сам за собой должен все делать и все подчищать. А у вас установка флага происходит внутри класса, а его сброс — вне класса, у его так сказать «потребителя» (button1.flagClick= false;). Если например я использую ваш класс, то как я должен догадаться, что флаг нужно сбрасывать?… Из описания, да, но вообще-то все должно работать и без описания.
Я бы лично сделал scanState функцией, возвращающей boolean — этот самый признак фронта нажатия, без всякого флага.
Может я конечно применительно к программированию МК не прав, но по идеологии положено вообще-то так….
Здравствуйте! Спасибо за приятные слова.
По поводу идеологии ООП вы совершенно правы. Но мы работаем на микроконтроллере с ограниченными ресурсами. Я пишу в уроке, что я со «скрипом» принял такое решение. Но для практического программирования, думаю, оно правильное. Попробуйте мысленно оттранслируйте вызов простейшей функции, анализирующей флаг. Сколько это будет команд микроконтроллера? Сколько будет вызовов подпрограмм, использование стека аргументов функции?
Т.е. я согласен с вашей последней фразой.
А разве правильно текущее состояние светодиода определять по переменной ledState? А вдруг светодиод выключили/включили каким-то другим способом, а ledState об этом ничего не знает?… Не правильней ли считывать реальное состояние светодиода с пина и его уже инвертировать?
Здравствуйте!
Могут быть разные решения. Хороший стиль все таки использовать переменную. Это более надежный способ.
Чтение выводов микроконтроллеров иногда приводит к непредсказуемым результатам. Например, если вы отверткой кратковременно замкнете выход, к которому подключен светодиод, то светодиод погаснет. Уберете отвертку — светодиод загорится. Но если в момент замыкания считаете состояние светодиода, то результат будет не соответствовать реальному состоянию светодиода. Подобно отвертке на выходы микроконтроллера могут влиять помехи, особенно если выходы подключены к длинным линиям.
Понял, спасибо
Эдуард, спасибо огромное за Ваш сайт! Очень полезный ресурс для учебы, я взял его за основу. Последний скетч этого урока я пытаюсь использовать для управления через реле, 12 вольтовым шаровым краном для полива. Открытие крана и закрытие управляется по двум контактам изменением полярности напряжения. Т.е. надо исключить событие, при котором обе кнопки будут срабатывать одновременно. Для выполнения этого условия я пытаюсь использовать переменные ledState. Но ничего не получается. Голову сломал. Подскажите как сделать, чтобы при работе оного светодиода исключалось включение второго и наоборот. Заранее спасибо!
Здравствуйте!
Блокировка должна осуществляться на аппаратном уровне. Если у вас используются реле, то сделать это очень просто. Пришлите вашу схему или лучше откройте тему на форуме.
Или подождите пару недель. Сейчас в разделе «Умный дом» я веду разработку системы. На этой неделе появиться общая аппаратная и программная часть локальных контроллеров. А следующая — разработка контроллера водоснабжения. Там есть управление краном.
Ответ принял) Чтобы его осмыслить мне надо немного время. Хороших выходных!
Добрый день.
Подскажите, пожалуйста, как объявить массив элементов класса 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)};
Добрый день! Есть ли возможность, как в c++, сохранить этот класс? Что-то типа Button.h. Спасибо!
Прошу прощения за беспокойство, прочитал дальше, нашёл, спасибо за материал
Здравствуйте, Эдуард. Очень нравятся ваши уроки и то как вы их грамотно доносите) Но всё равно остаётся много вопросов, например откуда берутся те или иные строки… Просто повторить и помигать светодиодом или переключить состояние это понятно, но я задумал несложное устройство, а реализовать его тяму не хватает. Может сможете помочь на примере урока и подробно опИшите код и все действия? Логика такова: имеем кнопку 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 секунды… Как то так)
Здравствуйте!
У меня нет времени, а вы предлагаете реализовать узко-специализированный проект.
Задавайте локальные вопросы, я отвечу. Можете завести отдельную тему на форуме и спрашивать там.
Здравствуйте Эдуард. Огромное спасибо Вам за Ваши уроки. Вам обязательно стоит задуматься о написании книги на их основе.
Для лучшего закрепления материала данного урока я создал класс управления светодиодом по признаку нажатия кнопки (flagClick==true). Отладил, проверил — работает. Ещё раз огромное Вам спасибо.
Спасибо.
Извините возможно за глупый вопрос но в » Программа sketch_7_2 урока 7 »
Вы сделали описание класса Вutton обьявили переменные и методы
Но там нет тела этих методов как тогда будет работать программа
Вопрос снят тело функций описано после loop()
А разве нет способа вынести класс Button отдельный фаил?
Здравствуйте!
Через один урок узнаете.
Спасибо за уроки ничего подобного по ардуино ни где больше не нашел
Скажите нельзя ли как то с Украины оплатить подписку через Приват24
Так неохота всякие кошельки электронные заводить
Здравствуйте!
Я не знаю. У меня куча электронных кошельков и карточек. Какие из них можно использовать для Украины — не знаю. Обычно украинцы платят через WebMoney.
Здравствуйте Эдуард!
Возник вопрос: в 6 уроке в программе sketch_6_2 мы устанавливали признакам значение изначально:
boolean flagPress= false;
boolean flagClick= false;
В программе sketch_7_1 этого не делаем, почему?
Здравствуйте!
В принципе вы правы. Формально надо было задать значение. Но в Ардуино все переменные при определении устанавливаются в нули.
И есть еще один момент. Любые алгоритмы с использованием фильтрации работают с задержкой. Поэтому при включении питания признаки станут достоверными только через время фильтрации. Если это важно, то в рабочих программах необходимо перед работой основной программы сделать определенное количество вызовов функции обработки сигнала. Только после этого признаки будут отслеживать реальное состояние кнопки.
Спасибо за обратную связь. Не совсем понятно, что Вы называете рабочими программами? А какие ещё бывают?
Ещё вопрос: Вы говорите, что если важно знать достоверные значения признаков при включении питания, то необходимо вызвать функции обработки сигнала определённое количество раз. Это количество зависит от времени подтверждения (timeButton), которое мы задаём или я что-то не так понял?
Заранее благодарю Вас за ответ
Здравствуйте!
Программы бывают демонстрационные, отладочные, для проверки отдельных блоков, функций и т.п. По крайней мере я это имел в виду.
По второму вопросу — все так. Можно в конце setup() после разрешения прерывания по таймеру сделать задержку функцией delay.
Здравсвуйте у меня есть вопрос по поводу кнопки когда у меня кнопка не нажата digitalRead(PIN_BUTTON) == HIGH возвращает 1 а когда я нажимаю на кнопку то 0 почему так работает и что можно сделать чтоб оно работала правильно?
Здравствуйте!
Все правильно. Так и должно быть. Когда кнопка не нажата, напряжение на входе равно 5 В за счет подтягивающего резистора. Когда нажата, то она замыкает вывод на землю и напряжение равно 0.
Состояние логического 0 более подвержено помехам. Поэтому принято, чтобы сигналы в неактивном состоянии были высокого уровня.
Если вы все-таки желаете, чтобы активным уровнем кнопки была 1, то надо подключить к выходу резистор на землю, а кнопку к 5 В. Тогда в не нажатом состоянии на входе будет 0, а в нажатом 5 В.
Добрый день. А разве не проще кнопку подтягивать к земле? Ведь в таком случае получиться — кнопку нажали и 1 получили, если кнопка не нажата то 0 имеем в наличии (как дома выключатель включили светло и тд.) Тем более я много раз видел советы (на других ресурсах) что лучше аппаратно от дребезга избавляться и не нужно значения переменных инвертировать.
Здравствуйте!
В схемотехнике есть правило — активный уровень сигналов должен быть низкий. Сигнал в состоянии высокого уровня значительно меньше подвержен помехам, чем низкого уровня. Если вы хотите, чтобы вероятность наведения сигнала на кнопку в отжатом состоянии была ниже, то выбирайте неактивный высокий уровень.
Что касается аппаратного устранения дребезга. Чем вам не нравится программный? Как вы собираетесь устранять дребезг аппаратно? Устранение дребезга — это вычисление среднего значения сигнала за определенный промежуток времени. Как аппаратно это сделать?
Поставить резистор на 10к и подключить его между кнопкой и землей. Я здесь не могу выложить фото схемы (мы с сыном сделали схему с 3-мя светодиодами и кнопкой, которая меняет режим работы светодиодов). Питание кнопки взяли с контакта 5+ (кнопка тактильная 2-2 контактов).
Я на форуме создал тему по этому поводу (добавил картинку схемы и скетч). Просто мне интересно, что лучше подтяжка к питанию или земле в отношении кнопок. А за урок Вам спасибо.
Самое понятное объяснение класса срр . Спасибо. В предыдущем уроке не плохо добавить схему подключения кнопки,
для понимания инверсии после прочтения состояния порта .
Добрый день Эдуард, спасибо за предоставленный материал, но есть вопрос, можно ли обойтись без строки ledState1= ! ledState1 минуя её указать в следующей строке digitalWrite(LED_1_PIN, ledState1); вместо ledState1 значение Hight? Заранее спасибо.
Здравствуйте!
Надо инверсию сделать. Поэтому две строки можно заменить одной:
digitalWrite(LED_1_PIN, ! digitalRead(LED_1_PIN) );
Почему то кнопка срабатывает один раз
int pmenu = 0;
void loop() {
bmenu.scanState();
if ( bmenu.flagClick == true )
{
// было нажатие кнопки
pmenu++;
lcd.setCursor(5,1);
lcd.print(pmenu);
bmenu.flagClick= false; // сброс признака клика
}
}
выводится на дисплей всегда 1
Здравствуйте!
Добавьте в конце loop задержку delay(1).
Если у вас практическая задача, то дочитайте до урока 10. Функцию scanState надо вызывать в обработчике прерывания по таймеру.
Большое спасибо за ваши уроки! Нравится что все разложено по полочкам и систематизировано. Занимался программированием еще в школьные годы, и то только паскаль. Но благодаря вам и прошлым знаниям материал очень легко усваивается и изучается. Еще раз спасибо!
Спасибо.
Очень неудобно и неправильно подобраны слова-определения «методы» и «свойства».
По сути это ПОДПРОГРАММА и АРГУМЕНТЫ.
ПОДПРОГРАММА — это цельная и законченная часть общей программы, выполняющая отдельную, конкретно поставленную задачу.
АРГУМЕНТЫ — это переменные, которыми оперирует подпрограмма.
Назвать эти вещи методами и свойствами мог только какой-то очень косноязычный человек. Такими названиями внесена путаница и неразбериха в процесс понимания предмета программирования.
К слову, КЛАСС и ОБЪЕКТ — тоже не самые удачные названия. Ну, объект — ещё куда ни шло, а всё остальное…
Как будто русского языка нет совсем(
Серж, согласен.
Если не прав — поправьте:
Это не КЛАСС+ОБЪЕКТ, а отдельная ПОДПРОГРАММА, необходимая нам, чтобы при наличии тысяч кнопок с разными обозначениями (пин, состояние, метод подтяжек входа, время ожидания) использовать — в данном случае — одну функцию опроса scanState().
тогда возникает вопрос: всё-равно же параметры для каждой кнопки мы прописываем руками в конструкторе объекта, затем пишем ещё код конструктора….?
Почему нельзя было сделать одну функцию опроса со всеми нашими параметрами передаваемыми scanState(пин, состояние, метод подтяжек входа, время ожидания);
И при вызове функции закидывать ей все эти данные, она смотрит «вход» и даёт нам результат(возвращает значение): 0/1 — мы радуемся.
тут же получается:
1.создание КЛАССА и конструктора в нём
2.программа конструктора — закидывает данные в КЛАСС
3. КЛАСС вызывает функцию обработки этих данных scanState() чтобы получить одно значение 1/0 и вернуть его там где мы опрашиваем КЛАСС.
И это же последовательно в loop() происходит: опрос каждой кнопки. Если подключить 1000 кнопок, то я буду долго жать на 1000-ю пока поймаю очередь до неё…
Итого: в данном случае создание КЛАССА актуально?
Чем КЛАСС лучше функции? Что КЛАСС делает того что не может сделать функция?
Не знаю как вы все воспринимаете текстовую информацию — я пока на листочке не нарисовал со стрелками кто что куда закидывает и хранит — понять не мог.
Хотя не до конца понял в чем преимущество КЛАССОВ этих..
—————-
«Для красивого решения этих проблем в языке программирования Ардуино существуют классы.»
красота тут не подходит.
нужно либо чтобы не писать 1000 раз одно и то же, либо чтобы программа работала быстрее.
А здесь получается мы гоняем эти бедные входные данные которые сами же руками прописываем три раза: сначала в создании объекта, потом в конструктор, затем в класс, а он уже в функцию, а снимаем информацию — обращаясь к КЛАССУ.
————
Или получается, что вначале программа забивает оперативную память всеми тысячами классов для каждой кнопки, а затем обращается к одной функции?
Это выгоднее чем постоянное закидывание параметров и получение возврата результата из функции без посредников которым является КЛАСС? При том что из 1000 кнопок, например, сегодня мы будем нажимать только на пять?
Здравствуйте!
Попробуйте реализовать обработку дребезга нескольких кнопок функциями и увидите, зачем нужны классы.
Здравствуйте. Столкнулся с такой проблемой при создании класса.
Компилятор не хочет принимать пустой конструктор класса.
Обязательно ему нужен хотя бы один параметр в конструкторе. Вот два элементарных кода,
первый компилируется:
class A {
public:
A(byte x){_x=x;}
boolean y;
private:
byte _x;
};
A a1(0);
byte z = a1.y;
void setup() {
}
void loop() {
}
_______________________
второй ни в какую:
_________________________
class A {
public:
A();
boolean y;
private:
byte _x;
};
A a1();
byte z = a1.y;
void setup() {
}
void loop() {
}
Эдуард, Вы с таким не сталкивались?
убил на это два дня. Форматы при копиравании кода нарушились, но на результат не влияет.
Здравствуйте!
Если у вас конструктор пустой, то зачем его объявлять в классе.
class A {
public:
boolean y;
private:
byte _x;
};
A a1;
byte z = a1.y;
void setup() {
}
void loop() {
}
Если конструктор не имеет аргументов, то надо экземпляр класса объявлять без скобок.
class A {
public:
A(){_x= _x * 2;};
boolean y;
private:
byte _x;
};
A a1;
byte z = a1.y;
void setup() {
}
void loop() {
}
Вот такой код заработал:
class A {
public:
int y;
private:
int _x;
};
A a1;
int z = a1.y;
остальные варианты ведь в книжках и на форумах смотрел, брал за образец. Даже были прямые советы описывать конструктор, даже если он пустой. Теперь даже и не знаю кому верить. Верить невозможно, бери и проверяй(((((
имеет смысл исправить ошибку
byte _timeButton; // время подтверждения состояния кнопки
на правильный комментарий
byte _timeButton; // задаваемое количество правильных подтверждений состояния кнопки
вместо маловнятного слова конструктор более точным будет выражение — организатор внутренних связей . не помешает комментарий
_pin= pin; // увязываем вместе недоступный _pin c доступным pin
_timeButton= timeButton; // то же самое и для этих закрытых постоянных .(именно постоянных а не переменных)
отличная подача материала . просто прелесть .
АХАХАХАХ!!! ОНО РАБОТАЕТ!
Спасибо Вам, Эдуард! Вы сделали революцию в моем мозгу!
У Вас очень хорошая культура программирования.
Не устаю Вас благодарить.
Храни Вас Бог, Эдуард!)
На таких как Вы мир держится)
Здравствуйте. Чёт я не догоню, как сделать различие в длительности нажатия кнопки. К примеру, как отличить короткое нажатие на кнопку (клик) от длинного (в течение 2 или 3 секунд) на ту же кнопку. С уважением, Юрий.
Здравствуйте!
Разные варианты можно придумать.
Например. Длительность нажатия можно определить только после того, как кнопку отпустят. Т.е. срабатывать она должна на отпускание.
Заводите счетчик. В прерывании по таймеру через каждую, например, миллисекунду увеличиваете его значение.
В цикле loop ставите условие: если кнопка отжата (флаг flagPress == false), то счетчик сбрасываете. Таким образом, при отжатой кнопке счетчик равен 0. При нажатой кнопке ( flagPress == true) счетчик считает.
Второе условие в цикле loop: если кнопка отжата и значение счетчика больше порога, то кнопка была нажата не менее порогового времени. Такое условие будет только в момент отпускания кнопки, а значение счетчика покажет время удержания кнопки нажатой.
Временных порогов может быть несколько.
Только проверку последнего условия надо делать до сброса счетчика по отжатой кнопке.
О, Вы мне буквально той же ночью ответили. Признаюсь, не ожидал так скоро. Спасибо. Сейчас буду пробовать по вашему совету.
Да, так и вышло. Запустил от таймера подсчёт времени нахождения кнопки в нажатом состоянии. В цикле Loop разместил такой код:
// блок включения светодиодов
if ( button1.flagPress == false ) {
// было нажатие кнопки
if ((PushRun>15)&&(PushRun <= 500)) Serial.println("Green, HIGH"); // если кнопка нажата 1000) && (PushRun = 1500) Serial.println(«Red, HIGH»); // если кнопка нажата >=3 секунд, зажжём красный светодиод
PushRun = 0;
}
При этом вторая кнопка работает по признаку flagClick:
// блок гашения светодиодов
if ( button2.flagClick == true ) {
// было нажатие кнопки
button2.flagClick = false; // сброс признака клика
Serial.println(«Green, LOW»); // погасить зеленый светодиод
Serial.println(«Blue, LOW»); // погасить синий светодиод
Serial.println(«Red, LOW»); // погасить красный светодиод
}
// блок включения светодиодов
if ( button1.flagPress == false ) {
// признак отжатой кнопки
if ((PushRun>15)&&(PushRun <= 500)) Serial.println("LED_Green_PIN, HIGH"); // если кнопка нажата 1000) && (PushRun = 1500) Serial.println(«LED_Red_PIN, HIGH»); // если кнопка нажата >=3 секунд, зажжём красный светодиод
PushRun = 0;
}