В предыдущей статье, мы рассмотрели, как уйти от простой команды delay();. Предлагаю убрать дребезг кнопки открытия двери нашим новым кодом, с использованием millis. Просто будем опрашивать кнопку раз в 70 мс, чаще смысла нет, потому что физически нажать кнопку на более короткое время, учитывая дребезг контактов практически невозможно. Но сначала зададим переменные, которые мы будем использовать для организации ожидания.
// Переменные времени
uint8_t buttonTime; // Переменная времени дребезга кнопки открытия
uint16_t openLockTime; // Переменная времени задержки открытия времени замка
uint16_t openTime; // Время открытия замка
Часто в среде arduino переменные объявляют, как byte, char, long, с приставкой unisgned, или без неё, но более универсальная запись, так, как записано выше. Да и мне больше нравится так записывать переменные. Достаточно запомнить, что приставка u – означает unsigned – т.е. без знаковая, а индекс 8,16,32 – количество бит, используемых для хранения данных. Можно пользоваться такой табличкой для удобства.
int8_t | char | from -128 to 127
uint8_t | unsigned char | from 0 to 255
int16_t | int | from -32768 to 32767
uint16_t | unsigned int | from 0 to 65535
int32_t | long | from -2147483648 to 2147483647
uint32_t | unsigned long | from 0 to 4294967295
Понятно, что для хранения маленьких переменных со значением менее 255, лучше использовать int8_t, а то многие бездумно лепят long даже для значений, вроде 10. Вернёмся к нашей задаче, и сделаем опрос кнопки с ожиданием, с использованием переменной buttonTime.
if (millis() - buttonTime > 70) // Проверяем нажатие кнопки раз в 70мс
{
buttonTime = millis(); // Сбрасываем переменную времени сканирования кнопки
if (digitalRead(buttonOpen) == LOW ) // Если кнопка открытия нажата
{
lock_open(); // Запускаем функцию открытия замка
}
}
Из предыдущих статей должно быть полностью понятно, что мы сделали сейчас. При нажатии кнопки, мы запускаем функцию lock_open();. В широком смысле функции нужны, чтобы не писать простыню кода по несколько раз, если мы используем одни и те-же операции, их можно вызывать по имени функции. Также они могут возвращать значения расчётов, исходя из значений, которые мы передали в функцию. Удобный инструмент, но сейчас мы будем использовать функцию void, это тип функций, которые не возвращают значений, просто делают код. Чтобы создать новую функцию, нужно вынести за пределы loop и setup (это кстати, тоже функции) следующую конструкцию.
void lock_open()
{
//код функции
}
Пустые скобки после названия () означают, что мы не передаём никаких параметров в функцию. Теперь, если в любом месте нашего кода вставить
lock_open();
Выполнится код, который мы указали в теле функции lock_open();. Это очень удобно, код будет читаться лучше и понятнее, если логически разбивать программу на такие блоки. Перед тем, как писать код далее, определимся с основными действиями, которые нам понадобятся.
Нужно понять закономерности, когда у нас срабатывает та или иная функция. У нас есть факт нажатия кнопки и факт открытия замка. Всё было бы просто, но нужно вспомнить, что мы управляем двумя типами замка, которые работают ровным счётом наоборот. Для определения типа замка, у нас была следующая переменная
lockType
В зависимости от значения этой переменной, факт открытого замка, будет определяться разным напряжением на контакте замка lock. Для механического замка, открытый замок, означает отсутствие напряжения на контакте, а для магнитного – наоборот наличие.
Мы должны помнить, что в AVR, даже когда pin настроен на выход, мы можем считать значение, и это очень удобно, не придётся использовать никакие лишние флаги, чтобы понять состояние замка. Я считаю, кнопке открытия двери, нечего срабатывать, пока замок открыт. Значит, чтобы нажатая кнопка, выполнила открытие замка, нам нужно соблюсти два условия – знать, в каком состоянии замок сейчас, и прошло ли время задержки 70мс. Но нужно помнить, что для разных замков условия будут разные. Предлагаю изобразить графически.
Итого, мы проверяем 3 условия. Первое – проверка совершается по прошествии 70мс. Но чтобы при открытом замке, он не открывался второй раз ( это даст некорректное время открытия), мы проверяем тип замка – lockType. И зная тип замка, мы проверяем уровень напряжения на выходе lock, чтобы понять его состояние. Мы можем делать конструкцию if, и давать несколько условий в скобках, в вариантах логического И и ИЛИ. В данном случае, нам нужно соблюдение всех условий, и мы воспользуемся И – &&.
if (условие 1 && условие 2 && условие 3)
Также предлагаю сразу вынести два условия, для двух типов замков в отдельную функцию open_button() для удобства
void open_button()
{
if (millis() - buttonTime > 70 && lockType == 1 && digitalRead(lock) == LOW)
{ // Проверяем условия для электромеханического замка
buttonTime = millis(); // Сбрасываем переменную времени сканирования кнопки
if (digitalRead(buttonOpen) == LOW) // Если кнопка открытия нажата
{
lock_open(); // Запускаем функцию открытия замка
}
}
if (millis() - buttonTime > 70 && lockType == 0 && digitalRead(lock) == HIGH)
{ // Проверяем условия для электромагнитного замка
buttonTime = millis(); // Сбрасываем переменную времени сканирования кнопки
if (digitalRead(buttonOpen) == LOW) // Если кнопка открытия нажата,
{
lock_open(); // Запускаем функцию открытия замка
}
}
}
Если у нас совпадают все условия, то мы сбрасываем переменную времени опроса кнопки buttonTime, и собственно проверяем сам факт нажатия кнопки if (digitalRead(buttonOpen) == LOW ). Если кнопка действительно нажата, то самое время открыть замок вызвав функцию lock_open();.
void lock_open()
{
openLockTime = millis(); // Сбрасываем переменную времени openLockTime, чтобы могла сработать функция закрытия
if (lockType == 1) // Если замок электромеханический, то
{
digitalWrite(lock, HIGH); // Подаём напряжение на замок
digitalWrite(ledAccess, HIGH); // Индицируем, что дверь открыта
Serial.println("open_electric_lock");
}
else if (lockType == 0) // Если замок электромагнитный
{
digitalWrite(lock, LOW); // Снимаем напряжение на замке
digitalWrite(ledAccess, HIGH); // Индицируем, что дверь открыта
Serial.println("open_magnetic_lock");
}
}
Здесь мы выполняем простые и быстрые команды, результат которых, зависит также от типа замка. Нам нужно изменить напряжение на контакте замка, и на светодиодах, которые показывают, открыт проход или закрыт. В обоих случаях ledAccess мы переводим в состояние HIGH. Контакт lock, в случае механического замка переводим в HIGH, а в случае магнитного в LOW, для открытия. Также для наглядности, отправляем в серийный порт результат.
Ну хорошо, замок открылся, а как закрыть то его, спустя время openTime? Для дальнейших действий, предлагаю в теле функции setup(), обозначить значения этих переменных
lockType = 1; // Задаём тип замка
openTime = 1000; // Задаём время открытия замка
Чтобы опять не пользоваться простым delay(); можно закрывать замок всегда в цикле при некоторых условиях. Самое простое – взять какую-то переменную, которая приравнивается к времени контроллера millis, при открытии, а потом отсчитать время открытия замка. Т.е. программа закрытия крутится в цикле постоянно, а не только после открытия, и проверяет некоторые условия. Как вы заметили, в теле lock_open(), мы сбросили переменную openLockTime , приравняв её к millis(); в момент открытия замка.
openLockTime = millis();
Теперь понятно, что замок нужно закрыть по прошествии времени открытия замка, коим у нас является переменная openTime
if (millis() - openLockTime > openTime)
Но получается, после процедуры открытия, и выдержки паузы в размере переменной openTime, бесполезно будут выполняться команды закрытия замка, и выключения светодиода. Чтобы этот код не срабатывал зря, допишем ещё пару условий. Зачем нам закрывать замок, когда он и так закрыт? Тогда помимо времени, будем ещё проверять факт закрытого замка – и если в первом проходе цикла, спустя время openTime после открытия, у нас сработает закрытие, то дальше уже условие выполняться не будет, ведь замок и так закрыт. Получается для механического замка, условия будут такие
if (millis() - openLockTime > openTime && lockType == 1 && digitalRead(lock) == HIGH)
Если замок в состоянии HIGH (т.е. открыт), то функция закрытия может сработать. В противном случае, условие if, не будет выполнено. А для магнитного замка, всё, с точностью наоборот.
else if (millis() - openLockTime > openTime && lockType == 0 && digitalRead(lock) == LOW)
Теперь соберём это всё в единую функцию lock_close(), где будем выключать светодиод, и закрывать замок.
void lock_close()
{
if (millis() - openLockTime > openTime && lockType == 1 && digitalRead(lock) == HIGH)
{ // Задержка время открытия при электрическом замке
digitalWrite(lock, LOW); // Снимаем напряжение замка
digitalWrite(ledAccess, LOW); // Снимаем напряжение светодиода
Serial.println("close");
}
else if (millis() - openLockTime > openTime && lockType == 0 && digitalRead(lock) == LOW)
{ // Задержка время открытия при электромагнитном замке
digitalWrite(lock, HIGH); // Подаём напряжение замка
digitalWrite(ledAccess, LOW); // Снимаем напряжение светодиода
Serial.println("close");
}
}
Теперь достаточно поместить две важные функции, которые у нас ведут опрос кнопки и закрывают замок, в тело бесконечной функции loop.
void loop()
{
open_button();
lock_close();
}
Скомпилировать и запустить. Посмотрим, как это работает на примере электромеханического замка.
При нажатии на кнопку – получаем 1000мс задержки на открытие, что равно переменной openTime. Если нажать быстро 2-3 раза, задержка всё равно будет 1000мс. Теперь посмотрим работу магнитного замка.
Логика работы светодиода не поменялась, а вот сигнал на пине lock, инвертировался, так-как логика работы замка другая. Сейчас мы получили систему, которая отслеживает работу кнопки, следит за режимом работы замка, и управляем им. При этом, мы не перегрузили МК бесполезными операциями, и можем дальше наращивать функционал. В следующей статье, попробуем считать х-значное значение с клавиатуры, и если оно совпадёт с заданным числом, также разрешим проход.
Текущий код, доступен на гитхаб.
Добавить комментарий
Для отправки комментария вам необходимо авторизоваться.