В прошлой статье мы научились работать с кнопкой и управлять замком и светодиодом. Чтобы не мешать этому коду, сканирование клавиатуры мы временно закомментировали. Сегодня мы немного доработаем код, чтобы всё это не конфликтовало друг с другом, а самое главное, реализуем программу считывания цифрового кода в 4 значную переменную. Если она совпадёт, с числом-кодом, что записан в постоянной памяти микроконтроллера, дверь также откроется.
Попробуем вспомнить, какой командой, мы отправляли значение из массива
Serial.print(keyboardValue[r-1][c-1]);
Сам массив у нас был двумерный, и выглядел так
const char keyboardValue[4][3] //Создаём двумерный массив
{
{'1', '2', '3'}, //структура клавиатуры
{'4', '5', '6'},
{'7', '8', '9'},
{'*', '0', '#'}
};
Теперь попробуем назначить значение массива, какой-нибудь переменной, и посмотрим, что будет. Пусть это будет переменная keyboardResult, типа unsigned char, или uint8_t, так как, у нас всегда с клавиатуры приходит положительное число, не более 9
keyboardResult = keyboardValue[r - 1][c - 1];
Serial.print(keyboardResult);
Результат, если проверить, нас не порадует

Вместо 1 — 49, вместо 2 — 50, и так далее. Кажется, есть какая-то закономерность, но почему числа уехали на 48 значений вверх? Все современные системы оперируют таблицей символов ASCii, давайте, взглянем на неё.

Вот и ответ на первый вопрос. В таблице числовые значения, начинаются с кода 48, что равно «0». Именно поэтому, 1 у нас превратилась в 49. Но почему же тогда, в статье вывода данных с клавиатуры, команда выводила корректное значение?
Оказывается, char не такая-то и простая переменная. Название, явно произошло от слова character — что в переводе, может иметь значение символ. Отсюда следует некоторое исключение — все целочисленные типы, по умолчанию знаковые (signed или unsigned), но с char ситуация обстоит иначе, и стандарт описывает третий вариант, если не указан признак знака — тип для хранения базовых символов. Теперь попробуем от считанного значения в формате char, просто отнять 48, и посмотреть, что получится
{
keyboardResult = keyboardValue[r - 1][c - 1] - 48; // Отнимаем из массива 48, для приведения char к int
Serial.println(keyboardResult);
}
Уже намного лучше, числовые значения обрабатываются корректно. Правда * и # всё равно превратились в какую-то чепуху, но с другой стороны, какая разница программе, что из себя представляет * и #. Это функциональные клавиши, которые будут делать сброс, подтверждение, задание нового пароля и другие функции. Кстати, наверное вы задумались, почему, всё же 250 и 243? Если взглянуть на таблицу ASCii выше, то можно увидеть, что код * и # = 42 и 35 соответственно. Что будет, если отнять от них 48? По идее, должны уйти в минус? Но ведь переменная у нас беззнаковая, т.е. от 0 до 255, и мы как-бы прыгаем в минус от 255. Так и получились значения 250 и 243 в терминале, математику не обманешь

Теперь нам нужно эти нажатые цифры складывать в число. При каждой обработке нажатия кнопки — будем вызывать функцию void password_input (), где и будем вести обработку значений. Первым делом проверим, цифра это или символ, как видно выше звёздочка у нас 250, а решётка 243, значит отсеим эти значения.
if (keyboardResult >= 0 && keyboardResult <= 9)
Сформируем переменную число нашего кода, в начале нашей программы
int password = 0;
Теперь, чтобы превратить нажатые клавиши в число, нужно просто умножать предыдущее значение на 10 (так мы увеличиваем разрядность предыдущей цифры) и прибавляем новое значение к этой переменной.
password = password * 10 + keyboardResult;
Код прост и понятен, соберём всё это в одну функцию, и сделаем отправку в консоль, для наглядности
void password_input ()
{
if (keyboardResult >= 0 && keyboardResult <= 9) // Обрабатываем цифры
{
password = password * 10 + keyboardResult; // Прибавляем к значению password, новую нажатую клавишу, формируя число
Serial.print("Password = ");
Serial.println(password); //вывод на экран переменной password
} else {
//Здесь будем обрабатывать нажатие * и #
}
}
Запускаем симуляцию, смотрим, что после каждой нажатой клавиши, формируется переменная password, которая содержит все нажатые до этого клавиши.

Осталось сделать две важные функции — сброс, и подтверждение пароля. Сброс сделать легко — помним, что значение сброса у нас равно 250. Добавляем условие else if
else if (keyboardResult == 250)
{
password=0; // Cбрасываем пароль
Serial.println("Reset_password");
}
Где в теле условия просто приравниваем переменную password к нулю, и пишем для наглядности в терминал. Также я убрал из обработки нажатия клавиш строку терминала отдельно нажатой клавиши. Запускаем и проверяем

Мы не будем ограничивать длину пароля 4 символами, да и вообще не будем никак ограничивать. Пароль будем подтверждать кнопкой #. Для этого сделаем ещё одно условие else if, и впишем оттуда переход на функцию check_password
else if (keyboardResult == 243) // Если нажата #
{
check_password(); // Переходим на процедуру проверки пароля
}
Конечно, наш пароль должен храниться в энергонезависимой памяти. Мы обязательно реализуем это в следующих статьях, а пока процедуру проверки проведём с некой переменной correctPassword.
int correctPassword = 5467;
Создать функцию проверки на данном этапе не составит труда. Нужно всего лишь сравнить две переменные, и запустить функцию открытия замка в случае корректного пароля. Не забываем, во всех случаях сбрасывать переменную пароля password = 0;
void check_password() // Процедура проверки пароля
{
if (password == correctPassword) // Если введённый пароль совпал с тем, что сохранён в памяти
{
lock_open(); // Запускаем процедуру открытия замка
password = 0; // Сбрасываем пароль
Serial.println("Password_OK");
} else // Во всех других случаях, если пароль не совпал
{
Serial.println("Password_FAIL");
password = 0; // Сбрасываем пароль
}
}
Снова проверяем наш код

Проверка работает корректно. Но есть ещё один нюанс, о котором мы забыли. Если человек, начал вводить символы и ушёл, а другой подходит и вводит корректный пароль — он всё равно не заработает. Нужно по прошествии некоторого времени (пусть будет 3 секунды) — обнулять переменную password. Создадим функцию, которая будет делать проверку. Проверять нужно простой 3 секунды и значение. Если значение и так 0, сбрасывать смысла нет.
void reset_password() // Функция сброса пароля, при простое
{
if (millis() - resetPassTime > 3000 && password > 0) // Если пользователь начал вводить пароль, и прошло время более 3 секунд
{
password=0; // Сбрасываем пароль
Serial.println("Reset_password_3s");
}
}
А вот сбрасывать переменную времени будем в обработке клавиш. Каждая нажатая кнопка должна сбрасывать простой 3 секунды. Добавим в функцию key_scan
resetPassTime = millis(); // Сбрасываем переменную времени сброса пароля
Запускаем симуляцию, и смотрим, что случается после прошествии 3 секунд с момента нажатия клавиши.

Работает корректно, как обработка пароля, так и сброс спустя 3 секунды, если далее клавиши не нажимаются. Теперь мы можем не только обрабатывать клавиши с клавиатуры, а ещё считывать пароль и сравнивать его с корректным значением.
Ссылка на исходный код этой статьи
В следующей статье мы избавимся от мелких огрехов, у нас остался delay на 50мс, нет индикации запрещающего светодиода, и если зажать кнопку на клавиатуре, программа сканирования крутиться в бесконечном цикле. Всё это исправим, добавим buzzer и соберём проект в реальном железе, посмотрим, как всё работает