Научимся создавать собственную библиотеку для программирования на Ардуино.
Предыдущий урок Список уроков Следующий урок
В прошлом уроке мы закончили создание класса Button – кнопка. Все проверили, отладили код, и собираемся использовать его в дальнейших проектах для Ардуино. Только как практически пользоваться созданным классом. Не очень удачно получилось.
- В новую программу надо скопировать описание класса и код методов. При этом необходимо выделить эти блоки из старой программы и ничего не перепутать.
- Эти части программы давно проверены, отлажены и забыты. Тем не менее, они будут постоянно попадаться на глаза, увеличивать код программы, ухудшать ее структурность и читаемость.
- Еще в эти модули можно случайно занести ошибку, и потом, скопировать ошибочный вариант в следующие программы.
Красивое и практичное решение – создать библиотеку для объекта типа Button.
Последовательность действий для создания библиотеки в программах для Ардуино.
Библиотека в Ардуино это не что иное, как дополнительный класс. Поэтому, прежде всего, необходимо определить функции для библиотеки как класс. Как это сделать, подробно описано в уроке 7.
Оформите свои функции как класс, прежде чем производить дальнейшие действия, а мы используем готовый класс Button из урока 8.
Библиотека должна иметь как минимум два файла:
- заголовочный файл (расширение .h);
- файл с исходным кодом (расширение .cpp).
В первом файле содержится описание самого класса, переменные, константы. Кода программы здесь нет. А второй файл содержит программный код методов.
Назовем новую библиотеку Button и создадим заголовочный файл Button.h.
Arduino IDE не поддерживает редактирование текстовых файлов. Редактировать файлы библиотеки можно в любой среде разработки для C++ или в текстовом редакторе, желательно с подсветкой синтаксиса. Я использую Notepad.
Заголовочный файл Button.h
Сначала напишем в файл текстовую информацию о библиотеке. Сообщим там все, что считаем нужным: как называется, для чего предназначена, как пользоваться, кто ее создал и т.п. Конечно, текст надо оформить как комментарий.
/*
информация о библиотеке
*/
Все остальное содержимое h-файла мы должны заключить в конструкцию:
// проверка, что библиотека еще не подключена
#ifndef Button_h // если библиотека Button не подключена
#define Button_h // тогда подключаем ее
// ............
#endif
Эти директивы исключают повторное подключение библиотеки.
Внутри конструкции следует написать:
#include "Arduino.h"
Директива #include предписывает компилятору включить в код программы текст из файла, имя которого следует после директивы. В данном случае будет включен файл Arduino.h, содержащий стандартные константы и переменные языка Ардуино. В обычных программах он добавляется автоматически, а для библиотеки должен быть указан явно.
Осталось добавить описание нашего класса Button. Полностью файл Button.h выглядит так.
/*
Button.h - библиотека для цифровой обработки сигналов контактов кнопок
и сигналов других компонентов параллельным процессом
В параллельном процессе должен регулярно вызываться один из методов:
void scanState(); // метод проверки ожидание стабильного состояния сигнала
void filterAvarage(); // метод фильтрации сигнала по среднему значению
В результате формируются признаки:
для метода scanState():
- при нажатой кнопке flagPress= true
- при отжатой кнопке flagPress= false
- при нажатии на кнопку flagClick= true
для метода filterAvarage() :
- при сигнале низкого уровня flagPress= true
- при сигнале высокого уровня flagPress= false
- при изменении состояния с высокого на низкий flagClick= true
Объект типа Button при создании имеет параметры:
- номер вывода, к которому подключена кнопка или сигнал
- время обработки сигнала (умножается на период вызова метода scanState() или filterAvarage()
Button button1(12, 15); // создание объекта для кнопки, подключенной к 12 выводу
с временем фильтрации 30 мс (при цикле 2 мс)
Библиотека разработана Калининым Эдуардом
http://mypractic.ru/urok-8-cifrovaya-filtraciya-signalov-v-programmax-dlya-arduino.html
*/
// проверка, что библиотека еще не подключена
#ifndef Button_h // если библиотека Button не подключена
#define Button_h // тогда подключаем ее
#include "Arduino.h"
// класс обработки сигналов
class Button {
public:
Button(byte pin, byte timeButton); // конструктор
boolean flagPress; // признак кнопка нажата (сигнал в низком уровне)
boolean flagClick; // признак клика кнопки (фронт)
void scanState(); // метод проверки ожидание стабильного состояния сигнала
void filterAvarage(); // метод фильтрации по среднему значению
void setPinTime(byte pin, byte timeButton); // установка номера вывода и времени фильтрации
private:
byte _buttonCount; // счетчик времени фильтрации
byte _timeButton; // время фильтрации
byte _pin; // номер вывода
};
#endif
Исходный файл библиотеки Button.cpp.
В начале файла разместим ту же самую текстовую информацию, как и в Button.h. Неизвестно какой из файлов будет изучать пользователь.
Далее пишем директивы #include для включения стандартных функций Ардуино и заголовочного файла.
#include "Arduino.h"
#include "Button.h"
А затем коды методов нашего класса.
Полностью файл Button.cpp выглядит так:
/*
Button.h - библиотека для цифровой обработки сигналов контактов кнопок
и сигналов других компонентов параллельным процессом
В параллельном процессе должен регулярно вызываться один из методов:
void scanState(); // метод проверки ожидание стабильного состояния сигнала
void filterAvarage(); // метод фильтрации сигнала по среднему значению
В результате формируются признаки:
для метода scanState():
- при нажатой кнопке flagPress= true
- при отжатой кнопке flagPress= false
- при нажатии на кнопку flagClick= true
для метода filterAvarage() :
- при сигнале низкого уровня flagPress= true
- при сигнале высокого уровня flagPress= false
- при изменении состояния с высокого на низкий flagClick= true
Объект типа Button при создании имеет параметры:
- номер вывода, к которому подключена кнопка или сигнал
- время обработки сигнала (умножается на период вызова метода scanState() или filterAvarage()
Button button1(12, 15); // создание объекта для кнопки, подключенной к 12 выводу
с временем фильтрации 30 мс (при цикле 2 мс)
Библиотека разработана Калининым Эдуардом
http://mypractic.ru/urok-8-cifrovaya-filtraciya-signalov-v-programmax-dlya-arduino.html
*/
#include "Arduino.h"
#include "Button.h"
// метод фильтрации сигнала по среднему значению
// при сигнале низкого уровня flagPress= true
// при сигнале высокого уровня flagPress= false
// при изменении состояния с высокого на низкий flagClick= true
void Button::filterAvarage() {
if ( flagPress != digitalRead(_pin) ) {
// состояние кнопки осталось прежним
if ( _buttonCount != 0 ) _buttonCount--; // счетчик подтверждений - 1 с ограничением на 0
}
else {
// состояние кнопки изменилось
_buttonCount++; // +1 к счетчику подтверждений
if ( _buttonCount >= _timeButton ) {
// состояние сигнала достигло порога _timeButton
flagPress= ! flagPress; // инверсия признака состояния
_buttonCount= 0; // сброс счетчика подтверждений
if ( flagPress == true ) flagClick= true; // признак клика кнопки
}
}
}
// метод проверки ожидание стабильного состояния сигнала
// при нажатой кнопке flagPress= true
// при отжатой кнопке flagPress= false
// при нажатии на кнопку flagClick= true
void Button::scanState() {
if ( flagPress != digitalRead(_pin) ) {
// признак flagPress = текущему состоянию кнопки
// (инверсия т.к. активное состояние кнопки LOW)
// т.е. состояние кнопки осталось прежним
_buttonCount= 0; // сброс счетчика подтверждений состояния кнопки
}
else {
// признак flagPress не = текущему состоянию кнопки
// состояние кнопки изменилось
_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); // определяем вывод как вход
}
Для того чтобы Arduino IDE выделяла цветом новые типы и методы из нашей библиотеки можно создать файл keywords.txt.
Button KEYWORD1
scanState KEYWORD2
filterAvarage KEYWORD2
setPinTime KEYWORD2
Каждая строка содержит ключевое слово, табуляцию (не пробелы) и тип ключевого слова. KEYWORD1 определяет классы, KEYWORD2 – методы.
Загрузить zip-архив с тремя файлами библиотеки Button можно по этой ссылке:
Теперь нужно правильно разместить файлы библиотеки.
Я сделал так:
- Запустил Arduino IDE.
- Файл -> Настройки -> Размещение папки скетчей задал D:\Arduino Projects. Это я указал папку моих проектов Ардуино (D:\Arduino Projects).
- В этой папке я создал папку libraries (D:\Arduino Projects\libraries).
- В папке libraries создал папку новой библиотеки Button (D:\Arduino Projects\libraries\Button).
- И уже в эту папку скопировал файлы Button.h, Button.cpp и keywords.txt.
Для проверки надо закрыть и заново запустить Arduino IDE. Открыть Скетч -> Подключть библиотеку и посмотреть, что в списке библиотек присутствует новая библиотека Button.
Как пользоваться библиотекой.
Очень просто. В начале программы включить заголовочный файл директивой
#include <Button.h>
Теперь можно пользоваться всеми открытыми методами и переменными класса Button абсолютно так же, как в предыдущем уроке.
Перепишем программу управления светодиодами из предыдущего урока. Естественно с использованием библиотеки Button.
/* Программа sketch_9_1 урока 9
* К плате Ардуино подключены 2 кнопки и светодиод
* Каждое нажатие кнопки 1 инвертирует состояние светодиода на плате Ардуино
* Каждое нажатие кнопки 2 инвертирует состояние светодиода на макетной плате */
#include <Button.h>
#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
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); // определяем выводы светодиодов как выходы
pinMode(LED_2_PIN, OUTPUT);
}
// цикл с периодом 2 мс
void loop() {
button1.filterAvarage(); // вызов метода фильтрации по среднему для кнопки 1
button2.scanState(); // вызов метода ожидания стабильного состояния для кнопки 2
// блок управления светодиодом 1
if ( button1.flagClick == true ) {
// был клик кнопки
button1.flagClick= false; // сброс признака
ledState1= ! ledState1; // инверсия состояние светодиода
digitalWrite(LED_1_PIN, ledState1); // вывод состояния светодиода
}
// блок управления светодиодом 2
if ( button2.flagClick == true ) {
// был клик кнопки
button2.flagClick= false; // сброс признака
ledState2= ! ledState2; // инверсия состояние светодиода
digitalWrite(LED_2_PIN, ledState2); // вывод состояния светодиода
}
delay(2); // задержка 2 мс
}
Ничего лишнего. Только объекты, с которыми мы работаем.
Хороший стиль добавить в файлы библиотеки примеры. Но у нас в последующих уроках примеров будет достаточно.
В следующем уроке мы научимся работать с прерываниями по аппаратному таймеру.
Зачем keywords.txt?
Как правильно использовать библиотеку Arduino в Atmel Studio 7?
keywords.txt не обязателен. Он нужен для того, чтобы Arduino IDE (там, где вы пишете скетч для платы) по разному разукрашивал названия классов и методов. Как, например, он делает с analogRead или digitalWrite.
Подскажите пожалуйста! В Вашей библиотеке создан только класс Button для встроенного «подтягивающего» pullup резистора. Есть ли практический смысл в том, что бы добавить классы для внешнего стягивающего и подтягивающего резисторов?
Так как логика if в этом случае будет иной, инверсия будет не нужна и следовательно кнопка работать не будет?
Сигнал находящийся в высоком уровне меньше подвержен действию помех. Хороший стиль — выбирать активным низкое состояние сигнала. Поэтому лучше использовать схему с резисторами подтягивающими к + 5 В.
спасибо
Рад, если помог.
Добрый день!
Не могу осознать ваше сообщение. Помогите пожалуйста, чтобы понять.
Почему сигнал, находящийся в высоком (+5В), меньше подвержен помехам? Из чего это следует? Ведь если будут помехи, они могут быть как внизу (около 0В), так и наверху (около 5В).
При этом, следующей фразой вы сообщаете: «хороший стиль — выбирать активным низкое состояние сигнала». Как так? Раз меньше помех вверху, то может лучше активным сделать высокое состояние?
И как фаталили финальная фраза: «поэтому лучше использовать схему с резисторами подтягивающими к + 5 В». Т.е. мы вроде как бы договорились за активное считать низкое состояние. Может, тогда к 0 лучше подтягивать?
Я наверняка что-то путаю, направьте, пожалуйста, что и где :).
Здравствуйте!
Это известный принцип схемотехники. Не я его придумал. Считается, что у сигналов активный уровень должен быть низким.
Активный уровень для кнопки — нажатая кнопка. Порог срабатывания цифровых входов обычно ниже половины напряжения питания. Поэтому низкий уровень больше подвержен влиянию помех. И желательно, чтобы кнопка в не нажатом состоянии выдавала высокий уровень, что уменьшает вероятность ложных срабатываний. Мы подтягиваем резистором сигнал к 5 В, и получается высокий уровень, когда кнопка не нажата.
Эдуард, спасибо!
Просветление почти наступило :).
Буквально один нюанс: коли активное состояние — низкое, то получается, что при отсутствии сигнала (кнопка не нажата) у нас постоянное напряжение на пине? А если речь идет о каком-нибудь датчике, то следуя этому принципу, мы также должны постоянно держать под напряжением сигнальный контакт? Насколько это эффективно, если допустим, датчик будет раз в сутки срабатывать? Получается, что все время мы просто греем провод?
Ничего мы не греем. Ток в цепи не течет.
Блин, точно :)!
Большое спасибо!
А почему тогда вообще возникла схема со стягивающим резистором? Его установку «диктует» железо или есть иные причины когда его необходимо ставить?
Бывает необходимость обрабатывать сигнал с активным высоким уровнем.
Иногда так удобнее из соображений схемотехники. Например, в уроке 21 я подключаю матрицу кнопок и светодиодные индикаторы к плате используя общие выводы. Для индикаторов сигналы выбора имеют высокий активный уровень. Эти же сигналы я использую для сканирования матрицы кнопок.
Добрый вечер. Еще один вопрос? При создании библиотек файлы набирать в блокноте, а потом переименовывать с расширением h и cpp?
Или их можно набрать в редакторе arduino?
Если нет, как тогда можно проверить правильность написания и работы библиотеки если пишешь ее сам?
Я создаю полностью рабочий класс в Arduino IDE и проверяю его. Затем выделяю файлы h и cpp. Опять проверяю работу программы уже с библиотекой. Все как в уроке 9.
Добрый день! Как можно сделать так, чтобы при долгом нажатии кнопки не было реакции? Я подключил вашу библиотеку для работы с 7-сегментными индикаторами и надо сделать, чтобы 1 раз нажали на кнопку и счетчик увеличивается, а на долгое нажатие не реагировало
Здравствуйте!
Наверное, много вариантов. Например, считать время нажатой кнопки и, если оно больше заданного, то возвращать счетчик в предыдущее состояние.
Добрый день. Вылетает такая ошибка при компиляции:
C:\Documents and Settings\Мои документы\Arduino\libraries\Button/Button.cpp:41: multiple definition of `Button::Button(unsigned char, unsigned char)’
sketch\Button.cpp.o:sketch/Button.cpp:8: first defined here
collect2.exe: error: ld returned 1 exit status
Здравствуйте!
У вас дважды определяется библиотека Button. Проверьте в папках:
C:\Documents and Settings\Мои документы\Arduino\libraries\Button/Button.cpp
и sketch\Button.cpp.
Одну библиотеку надо удалить. У меня вылетала ошибка этой библиотеки при обновлении Arduino IDE. Дело в том, что появилась еще одна библиотека с тем же именем Button.h. Проверьте внимательно и лишнюю удалите.
Спасибо Огромное!
Рад, если помог.
Должен сказать спасибо ещё раз.
Сергей, г. Владимир.
Вопрос про массив — можно ли его объявить как публичную переменную?
Информация из массива выводится в дисплей с помощью библиотеки,
А записывается в массив в основной программе.
Объявил массив в public: строчкой
byte ozu32disp[33];
но компиляция основной программы выдаёт, что массив в этой области не объявлен.
Что я сделал неправильно, и как его объявить?
Заранее спасибо ещё раз.
Сделал программу для дисплея 32х8 мах7219.
одновременный вывод нескольких данных русскими символами в разных режимах, если Вам интересно — могу поделиться. Позже будет видео с демонстрацией возможностей.
Дисплей не сравнить с 16х2 — большой, яркий, просвечивает сквозь пластмассу — не требует окна в корпусе.
Дисплей i2c
Здравствуйте!
Никаких ограничений на объявление массивов в классах нет. Вот пример, который компилируется без ошибок.
class Test{
public:
byte ozu32disp[33];
};
Test test1;
void setup() {
}
void loop() {
test1.ozu32disp[0]= 1;
}
Ну конечно, как всегда сижу и ломаю голову над элементарной ошибкой)))
Не написал название объекта с точкой перед обращением к массиву.
Первая библиотека в моей жизни), и сразу из пары сотен строчек.
Спасибо за подсказку, ломал бы голову до завтрашнего вечера
Правильно ли я понял что можно создать два объекта
Button1(12,15); //краткое нажатие
Button2(12,500);// нажатие более 1сек.
и т.д
Это не совсем так. Кнопка будет медленно реагировать и на отжатие.
Button button1(BUTTON_1_PIN, 15); // создание объекта для кнопки 1
Button button2(BUTTON_2_PIN, 15); // создание объекта для кнопки 2
Здравствуйте!
что значит: » PIN, 15″ ?
Button button1(BUTTON_1_PIN, 15); — создается объект типа Button с именем button1 и с двумя параметрами:
BUTTON_1_PIN — номер вывода, к которому подключена кнопка;
15 — число подтверждений состояния кнопки.
Спасибо. Нашёл ответ в уроке 7.
Здравствуйте Эдуард! Очередной раз передаю Вам слова благодарности за Ваши уроки и Вашу помощь! У меня такой вопросик — можно ли в библиотеки включать другие библиотеки? Никак не могу разобраться с такой проблемой — хочу подключить кнопку к I2C расширителю портов на PCF8575 и преминить Вашу библиотеку. Но никак не могу сообразить — как правильно в библиотеке Button объявить expander и через него подключать кнопки. То есть как сделать так, что бы созданный в библиотеке expander не конфликтовал с созданными (возможно) экспандерами в основной программе. Понятно, что адреса I2C устройств нужно будет сконфигурировать — но ещё же и объявить корректно? Типа expander01, expander02… и т.д.
Или, может правильней будет не включать в библиотеку Button библиотеку расширителя портов, а включить её в основную программу? Но тогда я не могу сообразить, как правильно назначать пины для объекта Button, используя Вашу библиотеку
Добрый день Эдуард! Есть ли у вас готовое решение или библиотека фильтрации входного сигнала с активным высоким уровнем? Предполагается применять для фильтрации сигнала от различных модулей ардуино в которых активный высокий уровень. Спасибо
Здравствуйте!
Для этого достаточно в файле Button.cpp в 2 местах сделать изменения изменения.
if ( flagPress != digitalRead(_pin) ) {
заменить на
if ( flagPress == digitalRead(_pin) ) {
По сути инвертируется чтение вывода.
Здравствуйте!
Очень нужна библиотека для RTC FM30C256. Можете ли вы ее создать?
Готов обсудить коммерческую сторону вопроса.
Здравствуйте!
У меня нет этой микросхемы, не на чем проверять. Обращайтесь к ней через библиотеку для I2C интерфейса. Если не ошибаюсь, библиотека Wire. Это несложно.
Ошибка ‘Button’ does not name a type. Помогите пж
Здравствуйте!
Может библиотека Button.h не подключена или подключена другая библиотека с таким же именем. Загрузите библиотеку с сайта, как написано в уроке 9.
Бывают в Ардуино проблемы, если путь к файлам содержит имена с кириллицей.
Здравствуйте.
Спасибо за ваши уроки.
У вас файл Arduino.h подключен сначала в Button.h, а потом в Button.cpp. Я правильно понимаю, что если Arduino.h подключен в Button.h, то уже в Button.cpp его можно не подключать?
Здравствуйте!
Насколько я помню, там производится проверка повторного подключения.
Недавно почитал книжицу по с++. там про какие то конструкторы и деструкторы написано. мол для избежания утечки памяти. А у вас таким и не пахнет. Это как то связано с особенностями процессоров АРМ архитектуры?
Здравствуйте!
Про конструкторы было в уроке 8. О динамических переменных немного есть в уроке 15.
У меня нет цели преподать все тонкости языка C++. Я все это можно найти в книжках, в интернете. Я стараюсь научить практическому программированию микроконтроллеров.
Здравствуйте Эдуард.
Библиотеку переписал. Загрузил. При загрузке скетча выходит ошибка следущего содержания
…../Button.h:56:2: error: #endif without #if
Спасибо.
Эдуард,доб.вечер,зашел на ArduinoPlus.ru,воспроизвел простейший вариант библиотеки с программой BlinkLED,в списке
библиотека появилась как INSTALLED и в Contributed LIbraries тоже, но при нажатии в программе появляются вместо include…
пустые строки,в настройках указываю то же,что и у вас,что не так?спасибо за совет
Здравствуйте!
Откуда же я знаю, что вы не так сделали. Попробуйте повторить мои действия из урока 9.
Доб.день,Эдуард,обращался уже к вам по поводу подключения своей библиотеки с пустыми строками вместо include…Не могли бы вы проиллюстрировать на простом примере BlinkLED из ArduinoPlus.ru,где затык?Спасибо
Здравствуйте!
Все там правильно написано. Выполнил описанную там последовательность действий. Компилируется без ошибок.
Эдуард, здравствуйте. Можно ли сделать так чтобы признак нажатия срабатывал при отпускании кнопки, а не при нажатии? Я задаю двумя кнопками нижний порог температуры, а при нажатии одновременно двух кнопок перехожу в другой режим для задания верхнего порога температуры, Так вот при нажатии двух кнопок нижний порог успевает измениться прежде чем произойдет переход в другой режим.
else if ( mode == 1 ) {
disp.digit[2] = 0x38; // отображается L в 3м раряде
disp.print(Ltemperature, 2, 1);
disp.digit[1] &= 0x7f; // погасить точку второго разряда
if ( (button1.flagPress == true) || (button2.flagPress == true) ) {
commonTimer = 0; // сброс счетчика времени
//buttonHoldCount = 0;
//buttonHold = false;
// кнопку нажали
// если нажали кнопку 1 уменьшаем значение нижнего порога температуры
if ( button1.flagPress == true ) {
if ( buttonHold == true) {
buttonHoldCount = 0;
buttonHold = false;
Ltemperature—;
if (Ltemperature MAX_TEMP) Ltemperature = MAX_TEMP;
if (Ltemperature >= Htemperature) Ltemperature = Htemperature — 1;
}
}
}
// переход на режим установки верхнего порога температуры (короткое удержание двух кнопок)
// при нажатии двух кнопок одновремено вырабатывается признак flagTwoButtons
// переход на режим 2 происходит, если были нажаты обе кнопки, а затем они обе отжаты
if (timer > DELAY) {
if ( (button1.flagPress == true) && (button2.flagPress == true) ) flagTwoButtons = true;
if ( (flagTwoButtons == true) && (button1.flagPress == false) && (button2.flagPress == false) ) {
// переход на установку верхней температуры
commonTimer = 0;
EEPROM.update(L_TEMP_ADR, Ltemperature);
flagTwoButtons = false;
button1.flagClick = false;
button2.flagClick = false;
timer = 0;
mode = 2;
}
}
// проверка времени бездействия и переход на режим 0
if (commonTimer >= TEMP_SET_DELAY) {
EEPROM.update(L_TEMP_ADR, Ltemperature);
// переход на режим 0
commonTimer = 0;
showDisplayCount = 0;
showDisplay = true;
button1.flagClick = false;
button2.flagClick = false;
flagTwoButtons = false;
timer = 0;
mode = 0;
}
}
Здравствуйте!
Да, конечно. Достаточно изменить строку
if ( flagPress == true ) flagClick= true; // признак фронта кнопки на нажатие
Надо устанавливать признак при flagPress == false.
Можно добавить такую строку, оставив прежнюю. Тогда будут 2 признака на нажатие и отжатие.
А изменять строку if ( flagPress == true ) flagClick= true; нужно в файле библиотеки или в скетче?
Если оставить оба варианта признака нажатия, то как их различить? Ведь flagClick= true будет устанавливаться как при нажатии так и при отпускании кнопки.
Я так понял в Вашей библиотеке flagPress == true и flagClick= true устанавливаются одновременно при нажатии кнопки, только flagClick автоматически не сбрасывается в false?
В библиотеке, конечно.
Если надо, то введите два признака, например, flagPress и flagFree.
Большое спасибо.
……boolean flagClick; // признак клика кнопки (фронт)…..
……boolean flagPress; ……………
Позвольте вопрос —
следует ли перед этими строчками поставить volatile ?
Допустим, в основной программе мы используем
цикл while(flagClick == 0) или while (flagPress == 0),
а в теле цикла никаких операций с этими переменными не производим. Компилятор может упростить и выкинуть это условие из программы? Если не указана volatile?
Заранее спасибо за ответ.
Здравствуйте!
Если программа каждый цикл loop() проходит до конца, не зависает в операторе вроде while(flagClick == false) {}, то можно обойтись без volatile.
Эдуард, создаю свою библиотеку для кнопок,
вопрос по созданной в Вашем уроке библиотеке:
зачем нужна функция setPinTime(byte pin, byte timeButton),
если мы ей совсем не пользуемся, а параметры мы задаём в конструкторе объекта?
Может, логичней будет эту функцию выкинуть из библиотеки?
Ещё раз спасибо)
Здравствуйте!
Вдруг захочется динамически, в ходе программы изменять число подтверждений состояния кнопки. Но, в принципе, я этой функцией никогда не пользовался.
Вопрос про библиотеку.
Библиотека — это всегда класс?
История вопроса — нужно упростить код.
В моём устройстве кнопок четыре.
ESC,DEC,INC,ENTER — это общий случай, минимум, который нужен человеку для общения с компьютером. Блуждать по меню, задавать параметры, вызывать программы.
Неудобно каждый раз писать четыре одинаковых строки:
button_Esc.readButton1(); // опрос кнопок
button_Dec.readButton1();
button_Inc.readButton1();
button_Enter.readButton1();
Хочется заменить их одной строчкой:
readButton4(); // опрос кнопок
И оформить код функции в новой библиотеке.
Вопрос — обязательно создавать новый класс, к которому будет принадлежать функция void readButton4()?
Нельзя просто код функции без создания класса сохранить?
И второй вопрос — если я этот класс создаю…
Допустим..
class Button4 {
public: ……………
Я должен создать новый объект, состоящий из четырёх объектов класса Button (у меня ButtonSerge):
ButtonSerge button_Esc(ESC_PIN0,20); // объект из 4 кнопок
ButtonSerge button_Dec(DEC_PIN,20);
ButtonSerge button_Inc(INC_PIN,20);
ButtonSerge button_Enter(ENTER_PIN,20);
В каком месте программы я должен это делать?
В конструкторе Button4::Button4(){,
в функции ли void Button4::button4init(
компиллятор везде ругается.
Без класса скетч работает. В класс «заворачиваться не хочет.
Вот код скетча, возможно кому-нибудь пригодится:
/*
* скетч создан как основа для библиотеки
* Button4.h — библиотека для цифровой обработки сигналов контактов четырёх кнопок параллельным процессом
* Сделана на основе (использует) библиотеку ButtonSerge.h
* Необходимо её указать в шапке скетча ( #include )
*
* Инициализация кнопок производится в void setup() функцией
*void button4init(byte esc_pin,byte dec_pin,byte inc_pin,byte enter_pin,byte period);
*
*Пример: button4init(12,11,10,9,20); // прописываем объектам — четырём кнопкам ESC,DEC,INC,ENTER
*(соответственно!!!) номера портов 12,11,10,9, время отклика (20х5мс) = 0.1сек
*
* В параллельном процессе должна регулярно (5мс) вызываться функция
* void readButton4(); — считывание сигнала с четырёх портов и ожидание стабильного состояния сигнала
* по среднему значению. Функция так же запрещает одновременное нажатие двух кнопок,
* в результате которого блокируются и все одиночные нажатия на 0.75сек
*
*В результате обработки в функции void readButton4(); формируются данные:
*
*public:
*
* boolean pressEsc = false; // true — если кнопка нажата
* boolean pressDec = false;
* boolean pressInc = false;
* boolean pressEnter = false;
*
* boolean buttsClick=0; // true — если одну из четырёх кнопок нажали. Сбрасывается верхней программой
* byte butStat=0; // 1,2,3,4 если нажата соотв. Esc,Dec,Inc,Enter.
* // «0» если ничего не нажато, либо нажаты две одновременно
* byte butStatKeep=0; // Сохраняет номер нажатой кнопки,когда она уже отпущена.
* //Сбрасывается верхней программой, или устанавливается следующим нажатием
* byte butPrev[4] = {0,0,0,0}; // память ненулевых butStat (нажатых кнопок)
* // butPrev[0]-текущая/последняя, butPrev[1]-предыдущая, и т.д.
* unsigned long button_time=0; // время неизменного ненулевого состояния butStat, мс (удержания кнопки)
*
*Библиотека разработана Карабановым Сергеем
*
*/
#include
#include // библиотека прерываний по таймеру2
byte chek1 = 0;
ButtonSerge button_Esc(0,0); // описание объекта для кнопок
ButtonSerge button_Dec(0,0);
ButtonSerge button_Inc(0,0);
ButtonSerge button_Enter(0,0);
boolean buttsClick=0; // true — если одну из четырёх кнопок нажали. Сбрасывается верхней программой
byte butStat=0;// 1,2,3,4 если нажата соотв. Esc,Dec,Inc,Enter.»0″ если ничего не нажато, либо нажаты две одновременно
byte butStatKeep=0;// Сохраняет номер нажатой кнопки,когда она уже отпущена. Сбрасывается верхней программой.
byte butPrev[4] = {0,0,0,0};
unsigned long button_time=0; // время неизменного состояния butStat, если butStat > 0, мс
unsigned long button_fix=0; // точка отсчёта времени, мс
boolean pressEsc = false; // true — если кнопка нажата
boolean pressDec = false;
boolean pressInc = false;
boolean pressEnter = false;
unsigned long time_current;
unsigned long time_fix = 0;
unsigned long time_Delta;
void setup() {
button4init(12,11,10,9,20); // создаём объект из четырёх кнопок ESC,DEC,INC,ENTER, время отклика (20х5мс)
MsTimer2::set(5, timerInterupt); // задаем период прерывания по таймеру 5 мс
MsTimer2::start(); // разрешаем прерывание по таймеру, вызываем функцию timerInterupt каждые 5мс
Serial.begin(9600);
}
void loop() {
time_current = millis();
time_Delta = time_current — time_fix;
if ( time_Delta > 100)
{
time_fix = time_current;
chek1 = chek1 + 1;
Serial.print(chek1); Serial.print(«__»);
Serial.print(buttsClick); Serial.print(«__»);
Serial.print(butStatKeep); Serial.print(«__»);
Serial.print(butStat); Serial.print(«__»);
Serial.print(butPrev[0]);Serial.print(butPrev[1]);
Serial.print(butPrev[2]);Serial.print(butPrev[3]); Serial.print(«__»);
Serial.println(button_time);
if(chek1 == 50) {
chek1 = 0;
buttsClick=0;
butStatKeep=0;
}
}
}
void timerInterupt() { //программа прерывания по таймеру, вызывается каждые 5 мс
readButton4();
}
void button4init(byte esc_pin,byte dec_pin,byte inc_pin,byte enter_pin,byte period) {
button_Esc.setMode(esc_pin, period); // настройка объекта и режима ввода (инициализация кнопки)
button_Dec.setMode(dec_pin, period);
button_Inc.setMode(inc_pin, period);
button_Enter.setMode(enter_pin, period);
}
void readButton4() { // опрос кнопок и формирование данных(каждые 5 мс)
static byte buttonStop; // время запрета активного состояния кнопок, х5мс, «0»-разрешено, >0 — запрещено
if (buttonStop > 0) buttonStop = buttonStop — 1; // уменьшаем каждые 5мс, считаем время
button_Esc.readButton1(); // опрос кнопок
button_Dec.readButton1();
button_Inc.readButton1();
button_Enter.readButton1();
pressEsc = button_Esc.press1;
pressDec = button_Dec.press1;
pressInc = button_Inc.press1;
pressEnter = button_Enter.press1;
// если нажато более одной кнопки:
byte i=0;
if(pressEsc) i=i+1;
if(pressDec) i=i+1;
if(pressInc) i=i+1;
if(pressEnter) i=i+1;
if (i>1){butStat=0;
buttonStop = 150; // 150х5мс = 0.75 сек блокировка кнопок при одновременном нажатии более 1 кнопки
return;
}
// если ничего не нажато,или время запрета активного состояния кнопок ещё не окончилось
if ((i==0)||(buttonStop > 0)) {
button_time = 0;
butStat=0;
return;
}
// если нажата одна кнопка:
if(butStat==0) {
buttsClick=1;
button_fix = millis();
butPrev[3] = butPrev[2];
butPrev[2] = butPrev[1];
butPrev[1] = butPrev[0];
}
else{
button_time = millis()- button_fix; // время удержания нажатой кнопки
}
if(pressEsc) butStat=1;
if(pressDec) butStat=2;
if(pressInc) butStat=3;
if(pressEnter) butStat=4;
butStatKeep = butStat;
butPrev[0]= butStat;
}
поясню: в результате всё это «общение» контроллера с четырьмя кнопками панели оператора в верхней программе должно свестись к одной строчке
readButton4();
И всё.
Здравствуйте!
Библиотека не обязательно класс. Если вам не требуется создание однотипных объектов, то проще создать функцию. И не стоит ее оформлять библиотекой.
Если все же хотите использовать библиотеку, то посмотрите в уроке 24 STM32, как создана библиотека DelayDWT. Она очень простая.
Еще в качестве примера библиотеки без класса могу привести MsTimer2. В ней используется разрешение области видимости.
Спасибо, Всё работает! Огромное спасибо!
Здравствуйте, Эдуард. Разъясните пожалуйста такую ситуацию. Я изменил Вашу библиотеку так, чтобы считывать не нажатия кнопки, а состояния битов в байте. Для работы со сдвиговым регистром 165 или с регистрами портов напрямую без digitalRead. Так вот, при создании объекта типа ButtonReg but1(PIND, BUT1_PIN, 15), считывания методом but1.filterAvarage() не происходит. Считывание происходит только если перед методом but1.filterAvarage() добавить метод but1.setPinTime(PIND, BUT1_PIN, 25). Можно ли уйти от использования метода but1.setPinTime(PIND, BUT1_PIN, 25) или в данном случае это не возможно? Я хочу уменьшить итоговый объем скетча.
ButtonReg.h:
// проверка, что библиотека еще не подключена
#ifndef ButtonReg_h // если библиотека Button не подключена
#define ButtonReg_h // тогда подключаем ее
#include «Arduino.h»
// класс обработки сигналов
class ButtonReg {
public:
ButtonReg(byte reg, byte pin, byte timeButton); // конструктор
boolean flagPress; // признак кнопка в нажатом состоянии (сигнал в низком)
boolean flagClick; // признак клика кнопки (фронт)
void scanState(); // метод проверки ожидание стабильного состояния сигнала
void filterAvarage(); // метод фильтрации сигнала по среднему значению
void setPinTime(byte reg, byte pin, byte timeButton); // метод установки номера вывода и времени (числа) подтверждения
private:
byte _buttonCount; // счетчик времени фильтрации
byte _timeButton; // время фильтрации
byte _reg; // регистр
byte _pin; // номер бита в регистре
};
#endif
ButtonReg.cpp:
#include «Arduino.h»
#include «ButtonReg.h»
// метод фильтрации сигнала по среднему значению
// при сигнале низкого уровня flagPress= true
// при сигнале высокого уровня flagPress= false
// при изменении состояния с высокого на низкий flagClick= true
void ButtonReg::filterAvarage() {
if ( flagPress != bitRead(_reg, _pin) ) {
// состояние кнопки осталось прежним
if ( _buttonCount != 0 ) _buttonCount—; // счетчик подтверждений — 1 с ограничением на 0
}
else {
// состояние кнопки изменилось
_buttonCount++; // +1 к счетчику подтверждений
if ( _buttonCount >= _timeButton ) {
// состояние сигнала достигло порога _timeButton
flagPress = ! flagPress; // инверсия признака состояния
_buttonCount = 0; // сброс счетчика подтверждений
if ( flagPress == false ) flagClick = true; // признак клика кнопки
}
}
}
// метод проверки ожидание стабильного состояния сигнала
// при нажатой кнопке flagPress= true
// при отжатой кнопке flagPress= false
// при нажатии на кнопку flagClick= true
void ButtonReg::scanState() {
if ( flagPress != bitRead(_reg, _pin) ) {
// признак flagPress = текущему состоянию кнопки
// (инверсия т.к. активное состояние кнопки LOW)
// т.е. состояние кнопки осталось прежним
_buttonCount = 0; // сброс счетчика подтверждений состояния кнопки
}
else {
// признак flagPress не = текущему состоянию кнопки
// состояние кнопки изменилось
_buttonCount++; // +1 к счетчику состояния кнопки
if ( _buttonCount >= _timeButton ) {
// состояние кнопки не мянялось в течение заданного времени
// состояние кнопки стало устойчивым
flagPress = ! flagPress; // инверсия признака состояния
_buttonCount = 0; // сброс счетчика подтверждений состояния кнопки
if ( flagPress == false ) flagClick = true; // признак фронта кнопки на нажатие
}
}
}
// метод установки номера вывода и времени подтверждения
void ButtonReg::setPinTime(byte reg, byte pin, byte timeButton) {
_reg = reg;
_pin = pin;
_timeButton = timeButton;
//pinMode(_pin, INPUT_PULLUP); // определяем вывод как вход
}
// описание конструктора класса Button
ButtonReg::ButtonReg(byte reg, byte pin, byte timeButton) {
_reg = reg;
_pin = pin;
_timeButton = timeButton;
//pinMode(_pin, INPUT_PULLUP); // определяем вывод как вход
}
Здравствуйте!
Я не вижу разницы между операциями конструктора и метода setPinTime. А где у вас происходит разворот вывода на вход и подключение подтягивающего резистора? Или вы используете внешний?
Я тоже не вижу разницы между конструктором и методом setPinTime. Но конструктор создается, как я понимаю при компиляции, и в процессе работы программы состояние регистра в конструкторе не обновляется. Поэтому приходиться использовать setPinTime в прерывании вместе с filterAvarage, чтобы прочитать значения битов из регистра. Или может быть перед чтением бита в методе filterAvarage добавить несколько строк из метода setPinTime. Хотя в итоге получится тоже самое.
Параметры портов я задаю в setup через регистры DDR и PORT.