Posted by lex232 on

Контроль доступа. Часть 3 — Управляем замком и читаем кнопку.

Контроль доступа. Часть 3 —  Управляем замком и читаем кнопку.

В предыдущей статье, мы рассмотрели, как уйти от простой команды 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();. Это очень удобно, код будет читаться лучше и понятнее, если логически разбивать программу на такие блоки. Перед тем, как писать код далее, определимся с основными действиями, которые нам понадобятся.

Смотрите также:  Таймер на avr
Контроль доступа. Часть 3 -  Управляем замком и читаем кнопку.

Нужно понять закономерности, когда у нас срабатывает та или иная функция. У нас есть факт нажатия кнопки и факт открытия замка. Всё было бы просто, но нужно вспомнить, что мы управляем двумя типами замка, которые работают ровным счётом наоборот. Для определения типа замка, у нас была следующая переменная

lockType

В зависимости от значения этой переменной, факт открытого замка, будет определяться разным напряжением на контакте замка lock. Для механического замка, открытый замок, означает отсутствие напряжения на контакте, а для магнитного — наоборот наличие.

Контроль доступа. Часть 3 -  Управляем замком и читаем кнопку.

Мы должны помнить, что в AVR, даже когда pin настроен на выход, мы можем считать значение, и это очень удобно, не придётся использовать никакие лишние флаги, чтобы понять состояние замка. Я считаю, кнопке открытия двери, нечего срабатывать, пока замок открыт. Значит, чтобы нажатая кнопка, выполнила открытие замка, нам нужно соблюсти два условия — знать, в каком состоянии замок сейчас, и прошло ли время задержки 70мс. Но нужно помнить, что для разных замков условия будут разные. Предлагаю изобразить графически.

Контроль доступа. Часть 3 -  Управляем замком и читаем кнопку.

Итого, мы проверяем 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, для открытия. Также для наглядности, отправляем в серийный порт результат.

Смотрите также:  Демоплата на AVR

Ну хорошо, замок открылся, а как закрыть то его, спустя время 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();
}

Скомпилировать и запустить. Посмотрим, как это работает на примере электромеханического замка.

Смотрите также:  Установка Arduino IDE на Ubuntu 18
Контроль доступа. Часть 3 -  Управляем замком и читаем кнопку.

При нажатии на кнопку — получаем 1000мс задержки на открытие, что равно переменной openTime. Если нажать быстро 2-3 раза, задержка всё равно будет 1000мс. Теперь посмотрим работу магнитного замка.

Контроль доступа. Часть 3 -  Управляем замком и читаем кнопку.

Логика работы светодиода не поменялась, а вот сигнал на пине lock, инвертировался, так-как логика работы замка другая. Сейчас мы получили систему, которая отслеживает работу кнопки, следит за режимом работы замка, и управляем им. При этом, мы не перегрузили МК бесполезными операциями, и можем дальше наращивать функционал. В следующей статье, попробуем считать х-значное значение с клавиатуры, и если оно совпадёт с заданным числом, также разрешим проход.

Текущий код, доступен на гитхаб.