Научимся создавать собственную библиотеку для программирования на Ардуино.
Предыдущий урок Список уроков Следующий урок
В прошлом уроке мы закончили создание класса 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 можно по этой ссылке:
Зарегистрируйтесь и оплатите. Всего 25 руб. в месяц за доступ ко всем ресурсам сайта!
Теперь нужно правильно разместить файлы библиотеки.
Я сделал так:
- Запустил 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 его можно не подключать?
Здравствуйте!
Насколько я помню, там производится проверка повторного подключения.