Эмулятор светофора на Arduino: проверьте логику без платы
Светофор — самый честный первый автомат: три лампы, таймер и правила переходов, которые видно глазами. В самоучителе он уже разобран по винтикам в уроке про конечный автомат — там скетч, схема с пинами и история про вечный жёлтый. Эта страница — не дубль, а предисловие к проверке: как прогнать логику светофора в браузере до того, как светодиоды встанут на макетку, что смотреть в мониторе порта и какие промахи вылезают чаще всего.
Эмуляция не требует установки: стенд собирается сам, а тайминги считаются той же парой millis() и lastSwitch, что и на плате. Числа из урока возьмём за эталон — зелёный 5000 мс, жёлтый 2000, красный 5000, при запросе пешехода зелёный укорачивается до минимума 2000 — и подставляйте свои, методике всё равно. Если таймеры пока туманна тема, сначала пробегите урок про millis вместо delay — без неблокирующего таймера светофор не оживает.
Что проверяет эмулятор и что остаётся железу
Светофор — хрестоматийный конечный автомат: всегда ровно в одном состоянии, переходы по таймеру и флагу, ни одной паузы в loop(). Вся сложность живёт в логике, а не в электричестве, поэтому в браузере она проверяется целиком: три светодиода на пинах 12, 11 и 10, кнопка пешехода на пине 2 с INPUT_PULLUP, флаг zapros. Схему на этом этапе можно не собирать вовсе: эмулятор отвечает на вопрос «верна ли логика», макетка — на вопрос «доживут ли детали».
| Состояние | Горит | Длительность | Переход |
|---|---|---|---|
| ZELEN (0) | зелёный, пин 10 | 5000 мс; при zapros — минимум 2000 | в ZHOLT, всегда через жёлтый |
| ZHOLT (1) | жёлтый, пин 11 | 2000 мс | в KRAS; жёлтый обязателен |
| KRAS (2) | красный, пин 12 | 5000 мс | в ZELEN; здесь пешеход идёт |
| zapros | лампы не трогает | живёт до включения красного | просьба, а не состояние |
Числа не выдуманы под страницу — это фазы из урока, и таблицу легко пересобрать под свой перекрёсток. Главное правило экрана: таймер каждого состояния перезапускается ровно в момент смены, одной строкой lastSwitch = millis() рядом с присваиванием состояния. Размазанная пара «сменил — перезапустил» — источник половины светофорных затыков; механику неблокирующих таймеров подробно разбирает урок про millis вместо delay.
Пять проверок фаз, которые я гоняю первым делом
- Жёлтый всегда между зелёным и красным: связки «зелёный сразу красный» и «жёлтый вместе с красным» — брак фаз, водители не успевают среагировать.
- Нажатие пешехода на зелёном старше двух секунд срезает зелёный немедленно, а моложе — досиживает минимум: машины успевают покинуть перекрёсток.
- Запрос во время красного текущий красный не сокращает: он доживает до зелёного и делает его укороченным — нетерпеливое тыканье цикл не ускоряет.
- Каждая смена фазы печатает пару t=… svet=…, и t каждого нового состояния равен t предыдущего плюс длительность: таймер перезапустился ровно раз.
- Жёлтый держит ровно 2000 мс при любом количестве нажатий: флаг, вставший на жёлтом, гаснет вместе с жёлтым.
const int PIN_KRAS = 12;const int PIN_ZHOLT = 11;const int PIN_ZELEN = 10;const int KNOPKA = 2; const unsigned long DL_ZELEN = 5000;const unsigned long DL_ZHOLT = 2000;const unsigned long DL_KRAS = 5000;const unsigned long DL_ZELEN_MIN = 2000; int svet = 0; // 0 зелёный, 1 жёлтый, 2 красныйint zapros = 0;unsigned long lastSwitch = 0; void stavit(int novy) { svet = novy; lastSwitch = millis(); // единственный перезапуск таймера Serial.print("t="); Serial.print(millis()); Serial.print(" svet="); Serial.println(svet);} void setup() { pinMode(PIN_KRAS, OUTPUT); pinMode(PIN_ZHOLT, OUTPUT); pinMode(PIN_ZELEN, OUTPUT); pinMode(KNOPKA, INPUT_PULLUP); Serial.begin(9600);} void loop() { if (digitalRead(KNOPKA) == LOW) { zapros = 1; // кнопка просит, не приказывает } unsigned long vozrast = millis() - lastSwitch; if (svet == 0) { // зелёный if (zapros ? vozrast >= DL_ZELEN_MIN : vozrast >= DL_ZELEN) stavit(1); } else if (svet == 1) { // жёлтый if (vozrast >= DL_ZHOLT) { zapros = 0; stavit(2); } } else { // красный if (vozrast >= DL_KRAS) stavit(0); }}Монитор читайте парами имя=значение: t — миллисекунды от старта, svet — номер фазы, по нажатию кнопки встанет zapros=1. Три строки подряд с одинаковым svet — признак того, что кнопка печатает на каждом обороте, а не по флагу; разделение фронтов и режимов по switch разобрано в уроке про switch и режимы, каркас автомата там же. Компоновку «опрос мира — возраст состояния — switch» я больше нигде не нарушаю: она переезжает на шлагбаумы, замки и поливалки без единой правки структуры.
Пешеходный цикл: кнопка просит, автомат решает
Разница между «кнопка переключает» и «кнопка просит» — то, что отличает светофор от игрушки. Нажатие ставит флаг zapros, а распорядок держит автомат: зелёный старше двух секунд заканчивается сразу, моложе — дорабатывает минимум, дальше жёлтый и полный красный ровно на пять секунд. В это окно пешеход переходит дорогу; при включении красного флаг гаснет — просьба обслужена. Мой тест на живое понимание: понажимайте кнопку во время красного. Скука — признак здоровья: пока красный не сменился, происходить ничего не должно.
Типовые промахи, которые я собирал на кружке
- Жёлтый одновременно с красным — «украшение» перехода склейкой двух состояний: перекрёсток читается как гололёд, фазы сменяются по одной.
- Вечный жёлтый — зажатая кнопка в ветке зелёного каждый оборот сбрасывает таймер: кнопка обязана лишь взводить флаг, решения принимает автомат.
- Фазы мелькают за миллисекунды — переход выполнен, а перезапуск lastSwitch забыт: условие остаётся истинным на каждом обороте.
- Кнопка опрашивается внутри ветки зелёного — на красном и жёлтом пешеход невидим: опрос мира стоит до switch и работает во всех фазах.
- Пешеходный цикл без жёлтого — запрос кидает зелёный прямо в красный: короткий жёлтый между ними обязателен даже при укороченном зелёном.
Что видит эмулятор, а что остаётся макетке
| Проверка | Эмулятор | Макетка |
|---|---|---|
| Фазы, тайминги, флаг запроса | целиком, с точностью до миллисекунды | то же |
| Перезапуск таймера состояния | затык виден в мониторе сразу | то же, но искать дольше |
| Дребезг кнопки пешехода | не эмулируется, флаг взводится чисто | без debounce — лишние взводы |
| Ток и яркость ламп | нет: светодиоды не потребляют | резисторы, драйверы, питание |
| Наводки и просадки питания | нет | всплывают на реальной сборке |
Ключ ко всем пяти проверкам — неблокирующий таймер: delay() замораживает loop вместе с кнопкой, и флаг пешехода не поднимается вовсе. Разницу полезно ощущать руками, а не только глазами: лаборатория ниже крутится прямо на странице — блокирующая пауза против millis(), и кнопка в обеих колонках отвечает по-разному.
Блокирующая пауза против неблокирующего таймера: нажмите кнопку и сравните.
Подгружаем интерактив…
Когда пять проверок зелёные, логика готова к переселению: соберите три светодиода на макетке — посадка показана в уроке про светодиод на breadboard, — вставьте скетч и сверьте с экраном. Полную сборку пешеходного перекрёстка со скетчем и раскладкой пинов ведёт урок про конечный автомат — светофор без delay(), у него же на странице живой сим с подключённой кнопкой. Проверка в браузере — минутное дело, а экономит вечер перепайки.