Computer motherboard circuit. Jumper BIOS config in the center

Контроль доступа, часть 5. Алгоритм сброса и первого старта программы через джампер в Arduino.

от автора

в

В прошлый раз, мы сравнивали введённый пароль с паролем, который был записан во flash память контроллера. Но ведь известно, что эту область памяти нельзя изменить в процессе работы устройства, и для хранения таких данных нужно бы использовать EEPROM. Сегодня разберём, как можно делать аппаратный сброс, где и как хранить эти данные, как реализовать первичный запуск устройства, с заданием характеристик. Самое простое, что мы можем использовать для сброса – джампер.

#define jumper 14    // Сброс и первичный запуск

Будем использовать для него 14 контакт arduino uno.

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

pinMode(jumper, INPUT);       // Конфигурируем контакт джампера на вход с подтяжкой
digitalWrite(jumper, HIGH);   // Внутренний Pull-Up резистор

Проверять условия мы будем всего один раз – при каждом включении устройства. Значит вызов функции мы сделаем в теле setup, оболочки arduino ide.

void setup()
{
first_start();  // вызываем функцию проверки первого запуска
...

Давайте нарисуем упрощённый алгоритм, по которому будем действовать сегодня. Допустим новая прошивка загружена – и у нас происходит первый старт без jumper’а. Помимо него, мы можем ориентироваться на ячейку EEPROM. При задании пароля, мы помечаем её неким значением, и при последующих стартах, устройство понимает, что оно в рабочем режиме. Если же ячейка пустая или установлен jumper, то входим в процедуру сброса и добавления нового пароля.

С завода в микроконтроллерах avr, значения EEPROM составляют FF, попробуем это проверить

{
    a = EEPROM.read(25);    // Считаем значение ячейки 25 на новом МК
    Serial.println(a, HEX); // Прописывая HEX, через запятую, выведем значение в шестнадцатеричном формате
}

Не знаю, почему исторически не 00, но нам нужно понимать, какое значение записано в новом контроллере, чтобы он мог сделать первичную инициализацию на новой загруженной прошивке

Выберем адрес ячейки 0, для хранения конфигурации устройства. В setup(), при включении устройства не забудем считывать её значение

firstStart = EEPROM.read(0);  // Считываем переменную первого старта из EEPROM

Далее переходим к функции first_start.Читаем состояние джампера и переменной firstStart. Если какое-то условие удовлетворяет нас, то сбрасываем пароль и переходим к заданию нового

void first_start()                                      // Первичная инициализация
{
  if (digitalRead(jumper) == LOW || firstStart == 0xFF) // Если джампер установлен (сброс) или переменная первого старта == 1
  {
    Serial.println("Reset and Initialization");
    password = 0;                                       // Сбрасываем пароль
    set_password();                                     // Вызов функции установки пароля
  }

Теперь перейдём к функции установке нового пароля. Нужно понимать, что пароль нужно ввести будет два раза, для исключения ошибки. Так как у нас, это программа стартовой инициализации, допустимо сделать цикл while с условиями равенства паролей. Первый пароль, мы сохраним в новую переменную repeatPassword, а вторую, в уже знакомую переменную correctPassword. Одной из них дадим любое значение, чтобы это условие выполнилось.

repeatPassword = 1;       // Назначаем переменной любое значение, отличное от нуля, чтобы заработал следующий цикл
while (correctPassword != repeatPassword) 
{
}

Заполним эту функцию кодом ввода первого и второго пароля, чтобы исключить ошибку ввода. Для этой процедуры, введём переменную – флаг newPassword == 1, и пока она равна 1, будем вызывать уже известную нам функцию считывания значений клавиш и записи пароля в переменную key_scan(); Затем опять сбросим эту переменную, и повторим опрос пароля, который нужно ввести повторно. Но перед этим, запишем одну копию переменной пароля в repeatPassword = correctPassword;.

newPassword = 1;                                               // Устанавливаем флаг установки нового пароля
    while (newPassword == 1)                                    // Пока новый пароль не установлен (флаг) - то обрабатываем клавиши в цикле
    {
      key_scan();                                               // Запускаем обработку кнопок
    }
    Serial.print("Password = ");
    Serial.println(correctPassword);
    Serial.println("Please repeat password");
    newPassword = 1;                                            // Сбрасываем флаг цикла опроса пароля
    repeatPassword = correctPassword;                           // Записываем первый введённый пароль в другую переменную, для последующего сравнения
    password = 0;                                               // Обнуляем пароль
    while (newPassword == 1)                                    // Цикл повтора пароля. Пока новый пароль не установлен (флаг) - то обрабатываем клавиши в цикле
    {
      key_scan();                                               // Запускаем обработку кнопок
    }
    if (correctPassword == repeatPassword)                      // Если пароли совпали
    {
      Serial.println("Everything is ok. New password setting successful");
    }
    else                                                        // Если пароли не совпали
    {
      Serial.println("Passwords error. Please try again");
    }

Если вы уже догадались, нам также надо отредактировать опрос клавиш, и что-то изменить в кнопке подтверждения пароля #. Если у нас есть флаг ввода нового пароля newPassword == 1, то надо воспользоваться ею, и добавить в условие. Также предлагаю ввести условия, по величине пароля, чтобы сохранение могло произойти только при значении > 999.

    else if (keyboardResult == 243)                 // Если нажата #
  {
    if (newPassword == 0)                           // Если флаг нового пароля не установлен
    {
      check_password();                             // Переходим на процедуру проверки пароля
    }
    else if (newPassword == 1 && password < 1000)   // Если флаг нового пароля установлен и введён пароль > 3 символов
    {
      Serial.println("Error. password must be more than 4 characters");
      password = 0;
    }
    else if (newPassword == 1 && password > 999)
    {
      save_new_password();                          // Функция сохранения нового пароля
    }
  }

Отсюда, если пользователь подтвердил пароль 4 символа, будем вызывать функцию save_new_password. Здесь всё очень просто, значение пароля мы переносим в переменную correctPassword, а в случае сходства со вторым паролем, записываем значение в ячейки 1 и 2 EEPROM.

void save_new_password()                                      // Функция сохранения нового пароля
{
  correctPassword = password;                                 // Устанавливаем новый пароль
  if (correctPassword == repeatPassword)
  {
  byte *p = (byte*)&correctPassword;                          // Указываем на байты в int переменной correctPassword
  EEPROM.write(1, p[0]);                                      // записываем в ячейку 1 старший байт
  EEPROM.write(2, p[1]);                                      // записываем в ячейку 2 младший байт
  }
  password = 0;                                               // Сбрасываем пароль
  newPassword = 0;                                            // Устанавливаем флаг нового пароля в 1, чтобы выйти из цикла
}

Код для записи в EEPROM, мы подробно разобрали в предыдущей статье, и реализовали в данном случае мы его с помощью указателей

  byte *p = (byte*)&correctPassword;                          // Указываем на байты в int переменной correctPassword
  EEPROM.write(1, p[0]);                                      // записываем в ячейку 1 старший байт
  EEPROM.write(2, p[1]);                                      // записываем в ячейку 2 младший байт

После успешной инициализации нового пароля в функции set_password(). Мы запишем в 0 ячейку EEPROM любое значение, отличное от FF, чтобы при старте мы снова не попали на функцию первичной инициализации.

EEPROM.write(0, 0x01);  // Записываем значение в 0 ячейку, отличное от FF

Теперь нужно не забыть самое главное – описать сценарий, если устройство запускается в обычном режиме, без джампера и с заполненной ячейкой EEPROM. Вернёмся к функции first_start, и пропишем условие else. Здесь наша задача считать с EEPROM значение пароля, и сохранить его в переменную correctPassword

else                                                        // Если не установлен джампер сброса (нормальный старт)
  {
    byte *r = (byte*)&correctPassword;                        // Указываем на байты в переменной типа int
    r[0] = EEPROM.read(1);                                    // Первый байт r[0] читаем из первой ячейки EEPROM
    r[1] = EEPROM.read(2);                                    // Второй байт r[1] читаем из второй ячейки EEPROM
    Serial.print("Password in memory = ");
    Serial.println(correctPassword);                          // Выводим в терминал пароль
  }

Ну что-же, вроде мы описали все пункты нашей блок схемы, попробуем запустить на новом контроллере, с пустой ячейкой и посмотреть на результат. Чертой я отметил две ситуации. Первая – с неправильным подтверждающим паролем. Вторая – когда пароли введены верно.

У нас остался один минус – мы не можем поменять пароль прямо из программы. Но этот пароль у нас для прохода, получается, любой человек, сможет поменять пароль, а другие останутся без доступа. Для этого делается альтернативный пароль администратора, я предлагаю это сделать позже по карте + пароль. Также, мы до сих пор игнорируем код красного светодиода. В принципе, с ним всё понятно, он горит, когда замок закрыт. Сделаем функцию red_led

void red_led()                                         // Функция проверки запрещающего светодиода
{
  if (lockType == 1 && digitalRead(lock) == LOW)       // Если замок электромеханический, то
  {
    digitalWrite(ledDenied, HIGH);                     // Включаем запрещающий диод
  }
  else if (lockType == 0 && digitalRead(lock) == HIGH) // Если замок электромагнитный
  {
    digitalWrite(ledDenied, HIGH);                     // Включаем запрещающий диод
  }
  else
  {
    digitalWrite(ledDenied, LOW);                      // Выключаем запрещающий диод
  }
}

Пропишем два условия, для двух типов замка. Нам нужно проверять тип замка и условие закрытого замка, иначе выключаем диод. Мы уже получили достаточно рабочий контроль доступа с заданием пользовательского пароля, открытием двери с кнопки и клавиатуры, и понятной индикацией. В следующей статье добавим звуковой визуализации – сделаем код для зуммера. Код данной статьи, доступен на github.