Курс Arduino с нуля · Большие проекты
Урок 29. Проект: сейф с цифровым кодом
Три кнопки вместо клавиатуры, звёздочки на экране и сервопривод-засов. Сейф сам сравнивает набранное с секретом, а после трёх ошибок подряд блокирует кнопки — как настоящий.
Что понадобится
- Плата Uno из набора
- Кабель USB
- Макетная плата
- Экран LCD1602
- Потенциометр 10 кОм
- 3 тактовые кнопки
- Сервопривод SG90
- Картон и скотч для засова
- 22 провода «папа-папа»
Все детали есть в обучающем наборе, по которому идёт курс.
Начинается последний блок курса — большие проекты. Сегодня соберём сейф с цифровым кодом: три кнопки вместо клавиатуры, экран со звёздочками вместо окошка домофона и сервопривод вместо засова. Это первое наше устройство, которое решает само, пускать вас или нет.
Новых деталей снова не будет — всё из набора и всё уже знакомо: кнопки из урока 10, экран из урока 26, сервопривод из урока 22. Новой будет главная мысль: как программа хранит секрет и проверяет, что вы его знаете.
Что построим
Как это работает в жизни:
- нажимаете кнопки — на экране вместо цифр появляются звёздочки, как при вводе пароля, чтобы никто не подсмотрел;
- после четырёх нажатий сейф сам сравнивает набранное с секретным кодом;
- угадали — сервопривод убирает засов, идёт отсчёт, и сейф сам закрывается обратно;
- ошиблись три раза подряд — сейф обижается и перестаёт слушать кнопки: у настоящих дверных замков и телефонов такая же защита от подбора.
Как сейф хранит код и проверяет его
Секретный код — это четыре номера кнопок подряд, например «1 3 2 2». Лучший способ хранить такой список мы знаем с урока 6 — массив. Массивов будет два: в одном секрет, записанный прямо в программе, в другой складываются нажатия. Остаётся сравнить их — и вот тут новичков ждёт сюрприз.
Спросить у платы «равны ли эти два списка?» одним знаком равенства нельзя — так можно сравнивать только отдельные числа. Списки сравнивают циклом: берём пару клеток, сравниваем, переходим к следующей. Знакомый цикл for из урока 6 здесь займётся новой работой.
Кто на каком выводе
Деталей много, поэтому начинаем, как в уроке 28, — со списка мест. Экран забрал шесть выводов ещё в уроке 26, сервопривод сядет на вывод 9, как в шлагбауме из урока 24. Кнопкам достаются выводы 7, 8 и 10 — девятку между ними пропускаем, она занята.
Полный список: экран — 12, 11, 5, 4, 3, 2; сервопривод — 9; кнопки — 7, 8, 10. Свободными остались 6 и 13. Вывод 6 скоро пригодится — в задании со звёздочкой на него сядет зуммер.
Собираем схему
- Проверьте узел из урока 26: экран в нижнем ряду j, потенциометр в столбиках 21–23, все шестнадцать проводов на местах. Если разбирали схему — соберите по схеме урока 26 заново.
- Посадите три кнопки верхом на канавку: первую в столбики 9–11, вторую в 24–26, третью в 28–30. Ножки должны выходить вверх и вниз, а не влево и вправо — повёрнутая кнопка будет «нажата» всегда, это ловушка из урока 10.
- Сигнальные провода кнопок — с верхней стороны канавки: из столбика 9 на вывод 7, из 24 — на вывод 8, из 28 — на вывод 10.
- Земли кнопок — с нижней стороны канавки, и тянуться к шинам не нужно: провод из столбика 9 в столбик 7 и провода из столбиков 24 и 28 в столбик 18. Столбики 7 и 18 уже соединены с «минусом» — там сидят контакты 5 и 16 экрана. Это приём «возьми питание у соседа» из урока 28.
- Сервопривод: оранжевый провод на вывод 9, красный на верхнюю шину «+», коричневый на верхнюю шину «−». Если провода коротки, вставьте в разъём сервопривода три провода «папа-папа».
- Вырежьте из картона полоску-засов и прикрутите к качалке сервопривода — годится скотч. Пока засов пусть смотрит вдоль корпуса.
Проверьте перед включением: кнопки сидят на канавке ножками вверх-вниз; сигналы уходят с верхней стороны канавки, земли — с нижней; на выводе 9 именно оранжевый провод сервопривода; и все провода экрана на месте — их удобно пересчитать по схеме урока 26.
Шаг 1. Проверяем кнопки
Как всегда в больших проектах — сначала убеждаемся, что железо живо. Экран пусть называет номер нажатой кнопки:
// Урок 29. Шаг 1: проверяем кнопки — экран называет нажатую.
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
// Кнопки слева направо: 1, 2 и 3. Вывод 9 пропущен — он для сервопривода.
const int BUTTONS[3] = {7, 8, 10};
void setup() {
lcd.begin(16, 2);
for (int i = 0; i < 3; i++) {
pinMode(BUTTONS[i], INPUT_PULLUP);
}
lcd.print("Proverka knopok");
}
void loop() {
for (int i = 0; i < 3; i++) {
if (digitalRead(BUTTONS[i]) == LOW) {
lcd.setCursor(0, 1);
lcd.print("Nazhata: ");
lcd.print(i + 1); // человеку привычно считать с единицы
}
}
}
Кнопки лежат в массиве, как в пульте из урока 13, и работают через INPUT_PULLUP — нажатая кнопка даёт LOW. Понажимайте все три: на второй строке должны появляться 1, 2 и 3. Если какая-то кнопка молчит или «нажата» сама по себе — загляните в раздел «Не работает?».
Шаг 2. Копим нажатия и рисуем звёздочки
Теперь нажатия нужно запоминать. Заводим массив на четыре клетки, счётчик набранного и — важная деталь из урока 11 — ловим момент нажатия, а не само нажатие, иначе одна удержанная кнопка мгновенно заполнит весь код:
// Урок 29. Шаг 2: копим четыре нажатия и показываем звёздочки.
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
const int BUTTONS[3] = {7, 8, 10};
const int CODE_LEN = 4; // длина кода
int attempt[CODE_LEN]; // сюда складываем нажатия
int entered = 0; // сколько уже набрано
bool wasPressed[3] = {false, false, false};
void setup() {
lcd.begin(16, 2);
for (int i = 0; i < 3; i++) {
pinMode(BUTTONS[i], INPUT_PULLUP);
}
showInput();
}
void loop() {
for (int i = 0; i < 3; i++) {
bool pressed = (digitalRead(BUTTONS[i]) == LOW);
if (pressed && !wasPressed[i]) { // ловим именно момент нажатия
addPress(i + 1);
delay(30); // защита от дребезга
}
wasPressed[i] = pressed;
}
}
void addPress(int number) {
attempt[entered] = number; // записали номер кнопки в свободную клетку
entered = entered + 1;
showInput();
if (entered == CODE_LEN) { // набрали четыре — пока просто начинаем заново
delay(800);
entered = 0;
showInput();
}
}
void showInput() {
lcd.clear();
lcd.print("Vvedi kod:");
lcd.setCursor(0, 1);
for (int i = 0; i < CODE_LEN; i++) {
if (i < entered) {
lcd.print("* "); // это место уже набрано
} else {
lcd.print("_ "); // это место ещё пустое
}
}
}
Разберём главное:
attempt[entered] = number;— номер кнопки ложится в первую свободную клетку: счётчик набранного и есть её адрес;- звёздочки рисует отдельная функция: сколько клеток занято — столько звёзд, остальное — прочерки;
- после четвёртого нажатия пока просто пауза и набор заново — проверку подключим следующим шагом.
Прошейте и понажимайте: прочерки должны по одному превращаться в звёздочки. Подглядеть, что именно набрано, невозможно — всё как у взрослых.
Шаг 3. Сравниваем с секретом
Добавляем массив с секретом и функцию-судью. Она отвечает одним словом — да или нет, — а внутри работает цикл со схемы выше:
// Урок 29. Шаг 3: сравниваем набранное с секретным кодом.
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
const int BUTTONS[3] = {7, 8, 10};
const int CODE_LEN = 4;
const int SECRET[CODE_LEN] = {1, 3, 2, 2}; // секретный код сейфа
int attempt[CODE_LEN];
int entered = 0;
bool wasPressed[3] = {false, false, false};
void setup() {
lcd.begin(16, 2);
for (int i = 0; i < 3; i++) {
pinMode(BUTTONS[i], INPUT_PULLUP);
}
showInput();
}
void loop() {
for (int i = 0; i < 3; i++) {
bool pressed = (digitalRead(BUTTONS[i]) == LOW);
if (pressed && !wasPressed[i]) {
addPress(i + 1);
delay(30);
}
wasPressed[i] = pressed;
}
}
void addPress(int number) {
attempt[entered] = number;
entered = entered + 1;
showInput();
if (entered == CODE_LEN) { // код набран целиком — проверяем
checkCode();
}
}
void checkCode() {
lcd.clear();
if (codeCorrect()) {
lcd.print("OTKRYTO!");
} else {
lcd.print("Nevernyy kod");
}
delay(2000);
entered = 0; // в любом случае начинаем набор заново
showInput();
}
// Сравнить два списка можно только по одной клетке за раз.
bool codeCorrect() {
for (int i = 0; i < CODE_LEN; i++) {
if (attempt[i] != SECRET[i]) {
return false; // нашли расхождение — дальше смотреть незачем
}
}
return true; // все четыре клетки совпали
}
void showInput() {
lcd.clear();
lcd.print("Vvedi kod:");
lcd.setCursor(0, 1);
for (int i = 0; i < CODE_LEN; i++) {
if (i < entered) {
lcd.print("* ");
} else {
lcd.print("_ ");
}
}
}
if (attempt[i] != SECRET[i])— сравнивается пара клеток с одинаковым номером;return false;прекращает проверку на первом же несовпадении — ответ уже ясен, остальные клетки можно не смотреть;- до
return true;дело доходит, только если цикл прошёл все четыре пары и ни разу не споткнулся.
Наберите 1-3-2-2 — экран скажет «OTKRYTO!». Наберите что-нибудь другое — «Nevernyy kod». Сейф уже умеет главное, осталось приделать ему мышцы.
Шаг 4. Подключаем засов
Сервопривод из урока 22 будет запирать дверцу: 90 градусов — засов задвинут, 0 — убран. Открывшись, сейф ведёт отсчёт прямо на экране и через пять секунд запирается сам:
// Урок 29. Шаг 4: верный код убирает засов, потом сейф закрывается сам.
#include <LiquidCrystal.h>
#include <Servo.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
Servo zasov; // сервопривод, который запирает дверцу
const int BUTTONS[3] = {7, 8, 10};
const int SERVO_PIN = 9;
const int CODE_LEN = 4;
const int SECRET[CODE_LEN] = {1, 3, 2, 2};
const int ANGLE_LOCKED = 90; // засов задвинут
const int ANGLE_OPEN = 0; // засов убран
const int OPEN_SECONDS = 5; // сколько секунд сейф стоит открытым
int attempt[CODE_LEN];
int entered = 0;
bool wasPressed[3] = {false, false, false};
void setup() {
lcd.begin(16, 2);
for (int i = 0; i < 3; i++) {
pinMode(BUTTONS[i], INPUT_PULLUP);
}
zasov.attach(SERVO_PIN);
zasov.write(ANGLE_LOCKED); // при включении сейф заперт
showInput();
}
void loop() {
for (int i = 0; i < 3; i++) {
bool pressed = (digitalRead(BUTTONS[i]) == LOW);
if (pressed && !wasPressed[i]) {
addPress(i + 1);
delay(30);
}
wasPressed[i] = pressed;
}
}
void addPress(int number) {
attempt[entered] = number;
entered = entered + 1;
showInput();
if (entered == CODE_LEN) {
checkCode();
}
}
void checkCode() {
if (codeCorrect()) {
openSafe();
} else {
lcd.clear();
lcd.print("Nevernyy kod");
delay(2000);
}
entered = 0;
showInput();
}
void openSafe() {
zasov.write(ANGLE_OPEN); // засов уехал — дверцу можно открыть
// Пока сейф открыт, кнопки слушать не нужно — честно ждём с отсчётом.
for (int s = OPEN_SECONDS; s >= 1; s--) {
lcd.clear();
lcd.print("OTKRYTO!");
lcd.setCursor(0, 1);
lcd.print("Zakroyu cherez ");
lcd.print(s);
delay(1000);
}
zasov.write(ANGLE_LOCKED); // время вышло — заперли обратно
}
bool codeCorrect() {
for (int i = 0; i < CODE_LEN; i++) {
if (attempt[i] != SECRET[i]) {
return false;
}
}
return true;
}
void showInput() {
lcd.clear();
lcd.print("Vvedi kod:");
lcd.setCursor(0, 1);
for (int i = 0; i < CODE_LEN; i++) {
if (i < entered) {
lcd.print("* ");
} else {
lcd.print("_ ");
}
}
}
Пока сейф открыт, программа честно стоит в цикле с delay — и это правильно. В уроке 24 мы воевали с delay, потому что шлагбаум должен был слушать кнопку во время движения. А открытому сейфу кнопки слушать незачем — ему положено просто ждать. Блокирующее ожидание уместно, когда ждать — и есть вся работа: об этом был урок 21.
Проверьте: верный код — засов отъезжает, на экране «Zakroyu cherez 5… 4… 3…», потом засов возвращается. При включении сейф сразу запирается — это строка с zasov.write(ANGLE_LOCKED) в самом начале.
Шаг 5. Полный сейф: счётчик ошибок и блокировка
Осталась защита от подбора. Заводим счётчик неверных попыток: ошибка — плюс один, верный код — сброс в ноль. Три ошибки подряд — и сейф на пятнадцать секунд перестаёт замечать кнопки:
// Урок 29. Шаг 5: полный сейф — три ошибки подряд включают блокировку.
#include <LiquidCrystal.h>
#include <Servo.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
Servo zasov;
const int BUTTONS[3] = {7, 8, 10};
const int SERVO_PIN = 9;
const int CODE_LEN = 4;
const int SECRET[CODE_LEN] = {1, 3, 2, 2};
const int ANGLE_LOCKED = 90;
const int ANGLE_OPEN = 0;
const int OPEN_SECONDS = 5;
const int MAX_FAILS = 3; // сколько ошибок сейф терпит
const int LOCKOUT_SECONDS = 15; // и сколько потом не слушает кнопки
int attempt[CODE_LEN];
int entered = 0;
bool wasPressed[3] = {false, false, false};
int fails = 0; // счётчик неверных попыток подряд
void setup() {
lcd.begin(16, 2);
for (int i = 0; i < 3; i++) {
pinMode(BUTTONS[i], INPUT_PULLUP);
}
zasov.attach(SERVO_PIN);
zasov.write(ANGLE_LOCKED);
showInput();
}
void loop() {
for (int i = 0; i < 3; i++) {
bool pressed = (digitalRead(BUTTONS[i]) == LOW);
if (pressed && !wasPressed[i]) {
addPress(i + 1);
delay(30);
}
wasPressed[i] = pressed;
}
}
void addPress(int number) {
attempt[entered] = number;
entered = entered + 1;
showInput();
if (entered == CODE_LEN) {
checkCode();
}
}
void checkCode() {
if (codeCorrect()) {
fails = 0; // верный код прощает старые ошибки
openSafe();
} else {
fails = fails + 1;
if (fails >= MAX_FAILS) {
lockout();
} else {
lcd.clear();
lcd.print("Nevernyy kod");
lcd.setCursor(0, 1);
lcd.print("Popytka ");
lcd.print(fails);
lcd.print(" iz ");
lcd.print(MAX_FAILS);
delay(2000);
}
}
entered = 0;
showInput();
}
void lockout() {
// Сейф нарочно перестаёт слушать кнопки: жать бесполезно, надо ждать.
for (int s = LOCKOUT_SECONDS; s >= 1; s--) {
lcd.clear();
lcd.print("STOP! 3 oshibki");
lcd.setCursor(0, 1);
lcd.print("Zhdi ");
lcd.print(s);
lcd.print(" s");
delay(1000);
}
fails = 0; // наказание отбыто — счётчик обнуляется
}
void openSafe() {
zasov.write(ANGLE_OPEN);
for (int s = OPEN_SECONDS; s >= 1; s--) {
lcd.clear();
lcd.print("OTKRYTO!");
lcd.setCursor(0, 1);
lcd.print("Zakroyu cherez ");
lcd.print(s);
delay(1000);
}
zasov.write(ANGLE_LOCKED);
}
bool codeCorrect() {
for (int i = 0; i < CODE_LEN; i++) {
if (attempt[i] != SECRET[i]) {
return false;
}
}
return true;
}
void showInput() {
lcd.clear();
lcd.print("Vvedi kod:");
lcd.setCursor(0, 1);
for (int i = 0; i < CODE_LEN; i++) {
if (i < entered) {
lcd.print("* ");
} else {
lcd.print("_ ");
}
}
}
Что здесь нового:
- после ошибки экран показывает счёт: «Popytka 1 iz 3» — честное предупреждение;
- блокировка — тот же приём отсчёта, что и у открытого сейфа: пока идёт цикл, нажатия просто никто не читает;
- верный код прощает старые ошибки — счётчик обнуляется.
Это и есть готовый проект. Прошейте, позовите домашних и предложите угадать код: пусть сейф сам объяснит им, что торопиться не стоит.
Не работает? Разбираемся
- Экран пустой или два ряда квадратов. Это контраст: медленно покрутите потенциометр в обе стороны, как в уроке 26.
- Звёздочки сыплются сами, без нажатий. Какая-то кнопка стоит повёрнутой на 90 градусов и всегда замкнута. Выньте её и посадите так, чтобы ножки выходили вверх и вниз — про эту ловушку был урок 10.
- Одно нажатие — сразу две звёздочки. Дребезг сильнее обычного: увеличьте паузу после нажатия с 30 до 50–80 миллисекунд.
- Какая-то кнопка молчит. Проверьте её пару проводов: сигнал должен уходить с верхней стороны канавки, земля — с нижней. Если оба провода с одной стороны, кнопка не работает — обе ножки там соединены всегда.
- Сервопривод дрожит и жужжит у крайнего положения. Он упирается в край хода. Поменяйте углы с 0 и 90 на 10 и 80 — смыслу это не мешает.
- При повороте засова экран мигает или плата перезагружается. Сервопривод под нагрузкой берёт много тока. Уберите с качалки лишний вес, проверьте, что красный провод сидит на «+» плотно, и подключайте плату в порт компьютера напрямую, без длинных удлинителей.
- Сейф говорит «Nevernyy kod» на верный код. Проверьте порядок кнопок: левая должна сидеть на выводе 7, средняя на 8, правая на 10. Перепутанные провода — это другой код.
Задания для самостоятельной работы
- Задание 1. Смените код на свой и удлините его до пяти нажатий. Понадобится поменять длину кода в одном месте и добавить пятое число в секрет — всё остальное программа пересчитает сама. Заодно звёздочек станет пять.
- Задание 2. Пусть открытый сейф закрывается не по отсчёту, а по нажатию любой кнопки — «закрой за мной». Пригодится цикл ожидания из урока 21: стоять на месте, пока все три кнопки отпущены.
- Задание со звёздочкой ⭐. Озвучьте сейф зуммером: короткий щелчок при каждом нажатии, весёлая трель при открытии, низкий гудок при ошибке и сирена во время блокировки. Зуммер сядет на свободный вывод 6.
Разбор задания со звёздочкой
Зуммер ставится в свободную середину макетки: ножка со знаком «+» — в столбик 12 (ряд b), вторая — в столбик 14. Из столбика 12 провод идёт на вывод 6, а из столбика 14 — на верхнюю шину «−». Звуки — это tone из урока 19: у щелчка частота высокая и длительность крошечная, у гудка — наоборот. Сирена — два тона по очереди в каждом витке отсчёта блокировки.
// Урок 29. Разбор задания: сейф со звуком — щелчки, трель и сирена.
#include <LiquidCrystal.h>
#include <Servo.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
Servo zasov;
const int BUTTONS[3] = {7, 8, 10};
const int SERVO_PIN = 9;
const int BUZZER_PIN = 6; // зуммер поселился на свободном выводе 6
const int CODE_LEN = 4;
const int SECRET[CODE_LEN] = {1, 3, 2, 2};
const int ANGLE_LOCKED = 90;
const int ANGLE_OPEN = 0;
const int OPEN_SECONDS = 5;
const int MAX_FAILS = 3;
const int LOCKOUT_SECONDS = 15;
int attempt[CODE_LEN];
int entered = 0;
bool wasPressed[3] = {false, false, false};
int fails = 0;
void setup() {
lcd.begin(16, 2);
for (int i = 0; i < 3; i++) {
pinMode(BUTTONS[i], INPUT_PULLUP);
}
zasov.attach(SERVO_PIN);
zasov.write(ANGLE_LOCKED);
showInput();
}
void loop() {
for (int i = 0; i < 3; i++) {
bool pressed = (digitalRead(BUTTONS[i]) == LOW);
if (pressed && !wasPressed[i]) {
addPress(i + 1);
delay(30);
}
wasPressed[i] = pressed;
}
}
void addPress(int number) {
tone(BUZZER_PIN, 3000, 20); // короткий щелчок, как у домофона
attempt[entered] = number;
entered = entered + 1;
showInput();
if (entered == CODE_LEN) {
checkCode();
}
}
void checkCode() {
if (codeCorrect()) {
fails = 0;
tone(BUZZER_PIN, 800, 120); // трель открытия: две ноты вверх
delay(150);
tone(BUZZER_PIN, 1200, 200);
openSafe();
} else {
fails = fails + 1;
tone(BUZZER_PIN, 200, 400); // низкий гудок ошибки
if (fails >= MAX_FAILS) {
lockout();
} else {
lcd.clear();
lcd.print("Nevernyy kod");
lcd.setCursor(0, 1);
lcd.print("Popytka ");
lcd.print(fails);
lcd.print(" iz ");
lcd.print(MAX_FAILS);
delay(2000);
}
}
entered = 0;
showInput();
}
void lockout() {
for (int s = LOCKOUT_SECONDS; s >= 1; s--) {
lcd.clear();
lcd.print("STOP! 3 oshibki");
lcd.setCursor(0, 1);
lcd.print("Zhdi ");
lcd.print(s);
lcd.print(" s");
tone(BUZZER_PIN, 700, 220); // сирена: два тона каждую секунду
delay(500);
tone(BUZZER_PIN, 500, 220);
delay(500);
}
fails = 0;
}
void openSafe() {
zasov.write(ANGLE_OPEN);
for (int s = OPEN_SECONDS; s >= 1; s--) {
lcd.clear();
lcd.print("OTKRYTO!");
lcd.setCursor(0, 1);
lcd.print("Zakroyu cherez ");
lcd.print(s);
delay(1000);
}
zasov.write(ANGLE_LOCKED);
}
bool codeCorrect() {
for (int i = 0; i < CODE_LEN; i++) {
if (attempt[i] != SECRET[i]) {
return false;
}
}
return true;
}
void showInput() {
lcd.clear();
lcd.print("Vvedi kod:");
lcd.setCursor(0, 1);
for (int i = 0; i < CODE_LEN; i++) {
if (i < entered) {
lcd.print("* ");
} else {
lcd.print("_ ");
}
}
}
Обратите внимание: звук нигде не останавливает программу — у каждого tone задана длительность, и он заканчивается сам. А в цикле блокировки две ноты вместе с двумя паузами по 500 миллисекунд как раз складываются в одну секунду отсчёта.
Что мы узнали
- секрет удобно хранить массивом, а нажатия — копить во второй такой же;
- два списка сравнивают циклом, по одной паре клеток за раз;
- прервать проверку на первом несовпадении — нормально: ответ уже известен;
- счётчик ошибок и блокировка — та же защита от подбора, что в телефонах;
- блокирующее ожидание уместно, когда ждать — и есть вся работа устройства;
- детали из разных уроков собираются в устройство, которое хранит секрет.
В следующем уроке кнопки и зуммер останутся на макетке: соберём игру «Повтори за мной» — она сама придумывает мелодии-задания и проверяет вашу память на скорость.
Частые вопросы
Как сделать кодовый замок на Arduino из обычных кнопок?
Код — это последовательность нажатий, например 1-3-2-2. Секрет хранится в массиве, нажатия копятся во второй такой же массив, а после четвёртого нажатия программа сравнивает их циклом — пара за парой. На экране вместо цифр показываются звёздочки.
Почему нельзя сравнить два массива одним знаком равенства?
Знак сравнения в Arduino сравнивает два отдельных числа, а не списки целиком. Массивы сравнивают циклом: первая клетка с первой, вторая со второй. На первом же несовпадении проверку можно прерывать — ответ уже известен.
Как Arduino двигает засов сейфа?
Засов — сервопривод SG90 из набора на выводе 9: угол 90 градусов — заперто, 0 — открыто. К качалке скотчем крепится картонная полоска, она и держит дверцу. Через пять секунд после открытия программа сама возвращает засов на место.
Что происходит при неверном коде?
Сейф считает ошибки и честно показывает счёт: «Popytka 1 iz 3». После третьей ошибки подряд включается блокировка — пятнадцать секунд сейф не реагирует на кнопки и ведёт отсчёт на экране. Верный код обнуляет счётчик.
Можно ли сделать код длиннее четырёх нажатий?
Да. Длина кода задана одной константой: поменяйте её, допишите числа в секрет — и всё остальное программа пересчитает сама. С тремя кнопками и длиной пять получается 243 варианта кода.
