Спор у нас был дурацкий и вечный: кто быстрее. Друг уверял, что его реакция — сто пятьдесят миллисекунд, я отвечал, что двести пятьдесят, и мы оба не имели ни одного числа — только уверенность. Мерить было нечем, пока на столе не оказалась Uno, кнопка и светодиод. Час вечера — и у нас было табло: светодиод вспыхивает, ты жмёшь кнопку, Serial печатает твои миллисекунды. Первый же честный замер испортил спор: у обоих вышло за двести восемьдесят, и мы втроём со светодиодом выясняли отношения до полуночи.
Схема та же, что в уроке про кнопку: кнопка на пине 2 с внутренней подтяжкой, светодиод на пине 12. Но сам скетч — это маленький экзамен на понимание времени: случайная пауза до вспышки, ловля нажатия на каждом обороте loop, разность millis() в двух точках. И одна честная засада, которую мы добавили после того, как друг начал жать кнопку ДО вспышки и «реагировать» за сорок миллисекунд: фальстарт должен не только ловиться, но и наказываться.
В этом уроке собираем игру целиком: правила честного замера, цифры, с которыми можно сравнивать себя, и симулятор, где кнопка нажимается по-настоящему, а табло — настоящий Serial. Спорьте после урока: теперь у вас есть прибор.
Правила честного замера
Протокол из четырёх фаз. Ноль: скетч ждёт нажатия — заезд по желанию, а не по таймеру. Раз: после нажатия скетч произносит «Gotovs» и прячет вспышку в случайную паузу от 2 до 6 секунд — кубик бросит random(), про который у нас есть целый урок с сидом и генераторами. Два: светодиод зажигается, и именно в этот момент запоминается tStart = millis(). Три: любое нажатие даёт результат millis() − tStart, светодиод гаснет, фаза ноль. Случайность паузы — не украшение: человек на редкость хорош в ритмах, и на фиксированной пятисекундной паузе он жмёт за пятьдесят миллисекунд до вспышки, «реагируя» быстрее света. Мы это проверили на себе во второй вечер спора, и у друга вышло двенадцать миллисекунд — физиологический подвиг, то есть жульничество с предчувствием.
Отсюда фальстарт: нажатие в паузе сбрасывает попытку — скетч ругается строкой в Serial и заново прячет вспышку в новую случайную паузу. Заметьте, ничего не блокирует: ни пауза, ни ловля нажатий не живут на delay(). Поставь я delay(4000) в фазу ожидания — кнопка оглохла бы на четыре секунды, и фальстарт пролетел бы незаметно: скетч просто не спрашивал кнопку в это время. Ждём через millis(), как разобрано в уроке про millis вместо delay, — loop крутится непрерывно и не теряет ни одного нажатия.
Теперь ориентиры, с которыми можно сравнивать цифры. Взрослый бодрый человек реагирует на зрительную вспышку в среднем за 200-250 мс; полоса 150-300 — нормальный разброс для одного и того же человека в один вечер. Быстрее 150 — почти наверняка поймал ритм или повезло с предчувствием: у меня быстрее 160 не выходило никогда, сколько я ни старался. Медленнее 400 — усталость, второй час ночи или отвлёкся на сообщение в телефоне. Эти числа — не нормы ГТО, а полоса, в которой лежат честные замеры; у тренированных спортсменов на старте бывает быстрее, но у них и старт не зрительный.
И ещё одно направление для экспериментов: слуховая реакция. В психофизике звуковой сигнал обычно обрабатывается быстрее зрительного — по общецитируемым оценкам, на 50-80 мс: ориентируйтесь на 150-180 мс вместо 200-250. Поставьте рядом с вспышкой короткий tone() — и сравните свои две колонки цифр. Заодно проверите, что замер не зависит от того, ЧЕМ именно игроку сказали реагировать: механика фаз и разности millis() остаётся той же.
Пара слов про кнопку, потому что тут тоже числа. Внешних резисторов схема не требует: pinMode с INPUT_PULLUP включает внутреннюю подтяжку порядка 20-50 кОм, пин между нажатиями держится на HIGH, а кнопка замыкает его на GND — нажатие читается как LOW. Это та же разводка, что мы разбирали в уроке про кнопку, и она же в симуляторе ниже: никаких висящих входов, чьи показания зависят от погоды на проводе.
const int SVET = 12;const int KNOPKA = 2;int faza = 0;unsigned long tVspyshka = 0;unsigned long tStart = 0;int rekord = 30000;int zaedy = 0;int lastKn = HIGH; void setup() { pinMode(SVET, OUTPUT); pinMode(KNOPKA, INPUT_PULLUP); Serial.begin(9600); Serial.println("Knopka - start zaezda");} void loop() { int kn = digitalRead(KNOPKA); int nazhata = 0; if (kn == LOW) nazhata = 1; int byla = 0; if (lastKn == LOW) byla = 1; if (faza == 0) { digitalWrite(SVET, LOW); if (nazhata == 1 && byla == 0) { faza = 1; tVspyshka = millis() + random(2000, 6000); Serial.println("Gotovs... zhdite sveta"); } } else if (faza == 1) { if (nazhata == 1 && byla == 0) { tVspyshka = millis() + random(2500, 6000); Serial.println("Rano! Zhdi sveta"); } else if (millis() >= tVspyshka) { digitalWrite(SVET, HIGH); tStart = millis(); faza = 2; } } else if (faza == 2) { if (nazhata == 1 && byla == 0) { digitalWrite(SVET, LOW); int ms = millis() - tStart; zaedy = zaedy + 1; Serial.print("Reakciya_ms="); Serial.println(ms); if (ms < rekord) { rekord = ms; Serial.print("Rekord_ms="); Serial.println(rekord); } Serial.print("Zaedy="); Serial.println(zaedy); faza = 0; } } lastKn = kn;}В скетче два приёма, которые стоит унести с собой и в другие проекты. Первый — детектор фронта: считываю кнопку в kn, сравниваю с lastKn с прошлого оборота и реагирую только на переход HIGH → LOW; так одно нажатие — одно срабатывание, независимо от того, сколько оборотов loop пройдёт, пока вы держите кнопку. Второй — машина фаз: переменная faza, у каждой фазы своё поведение и свои переходы. Это упрощённый сосед конечного автомата на светофоре — когда фаз станет больше пяти, переезжайте на switch, там же и про грабли switch.
Про дребезг скажу честно и без истерики. Контакты настоящей кнопки дрожат первые 5-20 мс нажатия, и цифровой вход может увидеть серию импульсов — мы разбирали лечение в уроке про антидребезг. Но заметьте: для замера реакции дребезг почти безвреден — мы ловим ПЕРВЫЙ фронт, а он у дребезжащего контакта самый ранний; лишние импульсы после нажатия в фазе два уже ничего не измеряют. Дребезг важен на кнопке старта в фазе ноль: двойной фронт может запустить заезд дважды, но и там подвох виден по Serial. В симуляторе кнопка идеальная, на плате — настоящая: разницу вы почувствуете сразу, это полезное ощущение.
И про прерывания, потому что вопрос этот звучит на каждой вечеринке с Arduino: да, кнопку можно повесить на attachInterrupt и ловить фронт без опроса — раздел про это есть, прерывания. Для игры на реакцию с паузой в секунды опрос кнопки на каждом обороте loop избыточен по скорости, зато проще и без ловушек с разделяемыми переменными. Замер здесь стоит миллисекунды, а не микросекунды — выбирайте инструмент по цене задачи.
И про честность рекордов — последнее предостережение. Один лучший замер — худший вид статистики: он всегда ловит то самое предчувствие, которое мы только что вычистили фальстартом. Сыграйте серию из пяти-десяти заездов и берите медиану — среднее по порядку, а не по величине. У меня медиана за вечер стоит как вкопанная в пределах 20 мс, а рекорд скачет на сотню — по рекорду спорить весело, по медиане — честно.
| Табло | Что это значит | Что делать |
|---|---|---|
| меньше 150 мс | поймал ритм или предчувствие, а не реакция | проверьте фальстарт-логику и добавьте разброс пауз |
| 150-250 мс | отличная зрительная реакция | записывайте серию замеров — медиана честнее рекорда |
| 250-350 мс | норма для бодрого взрослого | сравнивайте себя с собой, а не с другом |
| больше 400 мс | усталость или отвлёкся | перерыв; вечером цифры ползут вверх у всех |
Симулятор: жмите по-настоящему
В симуляторе ниже макетка из двух компонентов: светодиод на пине 12 и кнопка на пине 2 — её надо удерживать зажатой, как настоящую. Нажмите кнопку — «Gotovs» в мониторе, случайная пауза 2-6 секунд, вспышка, ваше нажатие, и в Serial падает строка вида Reakciya_ms=237. Рекорд и счёт заездов ведутся между попытками, и их удобно смотреть на графике: пары имя=значение Serial Plotter строит в ряды, как в уроке про график.
Что попробовать первым: сделайте десять заездов подряд и посмотрите, как разброс растёт к вечеру — это самый честный эффект, который даёт самодельный прибор. Потом попросите кого-то жать в ритм «раз-два-три-ЧЕТЫРЕ» — и полюбуйтесь, как случайная пауза ломает предчувствие быстрее, чем любые увещевания играть честно.
- 1. Почему пауза до вспышки должна быть случайной, а не фиксированной?
- 2. С какой точки начинается отсчёт времени реакции?
- 3. Игрок нажал кнопку во время паузы, до вспышки. Что делает скетч?
- 4. Что сломается, если ожидание вспышки поставить на delay(4000)?
- 5. Какой диапазон — нормальная зрительная реакция бодрого взрослого?
- Сыграйте пять заездов и найдите своё среднее. Потом сыграйте пять после паузы в минуту: разброс между сериями покажет, почему рекорд — плохая статистика, а медиана — хорошая.
- Спровоцируйте фальстарт дважды подряд и посмотрите на паузы: каждая перезапускается заново с новым случайным сроком. Уберите случайность — random(2000, 6000) замените на 3000 — и попробуйте угадать вспышку: «реакции» быстрее 100 мс вам гарантированы.
- Сломайте отсчёт нарочно: перенесите tStart = millis() в фазу ноль и посмотрите на табло. Верните строку на место — это лучший способ запомнить, где живут замеры.
- Добавьте звуковой дубль на пине 8: перед вспышкой — один короткий tone(), после заезда — два. Задача с звуком и светом сразу меняет цифры — проверьте, насколько именно.
- Замер реакции — разность millis() в двух точках: вспышка и нажатие. Точка отсчёта — момент, когда пин реально уходит HIGH.
- Пауза до вспышки — только случайная (2-6 с): фиксированная ловится на ритм, и человек жмёт раньше вспышки.
- Нажатие до вспышки — фальстарт: скетч ругается и перезапускает паузу, попытка не засчитывается.
- Ориентиры: 150-250 мс — отлично, 250-350 — норма, быстрее 150 — поймал ритм, дольше 400 — устал или отвлёкся.
- delay() на паузе глушит кнопку — фальстарты пролетают. Ждём через millis(), детектор фронта lastKn/nazhata отличает нажатие от удержания.
Частые вопросы по теме урока
- Почему пауза до вспышки должна быть случайной, а не фиксированной?
- На фиксированной паузе человек предчувствует вспышку и жмёт до неё. Случайный диапазон 2-6 секунд ломает ритм — остаётся только настоящая реакция. Человек ловит ритм и жмёт раньше вспышки — замер станет выдумкой
- С какой точки начинается отсчёт времени реакции?
- Меряем только реакцию: разность millis() между вспышкой и нажатием. Отсчёт от паузы даёт «реакции» на секунды — классическая путаница новичка. От момента вспышки: tStart рядом с digitalWrite(SVET, HIGH)
- Игрок нажал кнопку во время паузы, до вспышки. Что делает скетч?
- Фальстарт наказывается перезапуском паузы с новым случайным сроком. Попытка не засчитывается — иначе предчувствие выдаёт себя за реакцию. Это фальстарт: скетч ругнётся и заново спрячет вспышку
- Что сломается, если ожидание вспышки поставить на delay(4000)?
- delay блокирует весь loop на четыре секунды: скетч физически не читает кнопку. Ждём через millis() — и опрос не прерывается ни на один оборот. loop замрёт: кнопка не опрашивается, фальстарты пролетят незаметно
- Какой диапазон — нормальная зрительная реакция бодрого взрослого?
- Средний честный замер лежит около 200-250 мс. Быстрее 150 — почти наверняка пойманный ритм; дольше 400 — усталость или отвлечение. 200-250 мс в среднем, полоса 150-300