Светофор на Arduino: схема, код, кнопка для пешеходов, перекрёсток
Собери светофор на Arduino: 3 светодиода и резисторы 220 Ω, код на delay и millis, кнопка для пешеходов, перекрёсток двух дорог, версии для Tinkercad, ESP32 и Pico.
Светофор на Arduino — классический первый проект в электронике: три светодиода, красный, жёлтый и зелёный, по очереди загораются и гаснут, как настоящий светофор. Нужны только Arduino Uno, три светодиода, три резистора 220 Ω и несколько перемычек, и первая рабочая версия готова за полчаса. Но этот простой проект многому учит: цифровые выходы, расчёт светодиода и резистора, отсчёт времени, чтение кнопки и конечный автомат — самый аккуратный способ организовать код — помещаются в эти несколько светодиодов.
В этом руководстве ты сначала увидишь, в каком порядке и сколько времени переключаются настоящие светофоры, затем соберёшь схему и напишешь самый простой код на delay(). Потом добавишь кнопку для пешеходов, перепишешь ту же логику на millis() и конечном автомате, чтобы ничего не блокировалось, и научишься управлять перекрёстком двух дорог. В последних разделах — как опробовать проект в Tinkercad без всякой установки, версии для ESP32 и Raspberry Pi Pico (MicroPython) и советы по школьному отчёту о проекте.

Как работает настоящий светофор?
Светофор — это таймер, который по очереди даёт ход каждому потоку транспорта. В Великобритании, Германии и Турции цикл такой: красный → красный + жёлтый → зелёный → жёлтый → красный. Красный вместе с жёлтым означает «скоро зелёный, приготовься, но пока не езжай», а один жёлтый — «сейчас загорится красный, готовься остановиться». В США, Франции, Италии и Испании фазы «красный + жёлтый» нет: свет сразу переключается с красного на зелёный. Время жёлтого тоже выбрано не случайно: немецкие правила задают 3 секунды жёлтого на дорогах с ограничением 50 км/ч, 4 секунды при 60 км/ч и 5 секунд при 70 км/ч, ведь водитель, который в момент появления жёлтого уже слишком близко, чтобы остановиться, должен успеть безопасно проехать перекрёсток.
| Фаза | Горит | Значение | Время в этом руководстве |
|---|---|---|---|
| 1 | Красный | Стоп | 5 с |
| 2 | Красный + жёлтый | Скоро зелёный, приготовься | 1 с |
| 3 | Зелёный | Можно ехать | 5 с |
| 4 | Жёлтый | Скоро красный, остановись | 3 с |
| На перекрёстке | Красный во всех направлениях | Безопасная пауза, чтобы освободить перекрёсток | 1 с |
Список деталей и расчёт резистора 220 Ω
| Деталь | Кол-во | Примечание |
|---|---|---|
| Arduino Uno (или Nano) | 1 | Подойдут и ESP32 или Raspberry Pi Pico |
| Светодиоды 5 мм: красный, жёлтый, зелёный | По 1 | +1 красный и +1 зелёный для пешеходного светофора, +1 комплект для перекрёстка |
| Резистор 220 Ω | 3 | По одному на светодиод; 8 для всех версий |
| Кнопка (6×6 мм) | 1 | Только для пешеходной версии |
| Макетная плата | 1 | Хватит половинной (400 отверстий) |
| Перемычки | 10–20 | Папа-папа |
| USB-кабель | 1 | Для загрузки кода и питания платы |
Светодиоды сами не ограничивают свой ток: светодиод, подключённый прямо к 5 V, потребляет больше тока, чем выдерживают он сам и пин. Поэтому к каждому светодиоду ставят последовательный резистор. Его номинал находят по закону Ома: R = (напряжение питания − прямое напряжение светодиода) / ток. Для красного светодиода (5 V − 2,0 V) / 0,015 A ≈ 200 Ω; с ближайшим стандартным номиналом 220 Ω ток получается около 13,6 mA. Светодиод при этом горит достаточно ярко, а ток остаётся ниже рекомендованных для пина Arduino 20 mA. Резистор 220 Ω маркируется полосками красный-красный-коричневый-золотой на четырёхполосных деталях и красный-красный-чёрный-чёрный-коричневый на пятиполосных; для уверенности воспользуйся утилитой «Цветовой код резисторов», а для других цветов и напряжений — калькулятором резистора для светодиода.
| Цвет светодиода | Прямое напряжение (прибл.) | Ток при 5 V (220 Ω) | Ток при 3,3 V (220 Ω) |
|---|---|---|---|
| Красный | 1,8–2,2 V | ≈ 13,6 mA | ≈ 5,9 mA |
| Жёлтый | 1,9–2,2 V | ≈ 13,2 mA | ≈ 5,5 mA |
| Зелёный (желтовато-зелёный) | 2,0–2,4 V | ≈ 13,2 mA | ≈ 5,5 mA |
| Зелёный (яркий, чисто-зелёный) | 2,8–3,4 V | ≈ 9,1 mA | ≈ 1,4 mA (очень тускло) |
Сборка схемы и таблица пинов
Длинная ножка каждого светодиода (анод, +) идёт через резистор к цифровому пину Arduino, а короткая (катод, −) — к GND. Неважно, с какой стороны стоит резистор, со стороны анода или катода, лишь бы последовательно. На макетной плате аккуратнее всего соединить пин GND Arduino с синей (−) шиной и вывести короткие ножки всех трёх светодиодов на эту шину. В таблице ниже перечислены пины, которые используют все скетчи этого руководства; первому скетчу нужны только первые три строки. Расположение пинов своей платы можно посмотреть в утилите «Распиновка».
| Назначение | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| Красный для машин (направление A) | D10 | GPIO 5 | GP13 |
| Жёлтый для машин (направление A) | D9 | GPIO 18 | GP12 |
| Зелёный для машин (направление A) | D8 | GPIO 19 | GP11 |
| Красный для пешеходов | D7 | GPIO 21 | GP15 |
| Зелёный для пешеходов | D6 | GPIO 22 | GP14 |
| Кнопка для пешеходов (другая ножка к GND) | D2 | GPIO 23 | GP4 |
| Направление B: красный / жёлтый / зелёный | D5 / D4 / D3 | GPIO 25 / 26 / 27 | GP8 / GP7 / GP6 |
| Катоды светодиодов и кнопка | GND | GND | GND |
Шаг 1: самый простой код светофора на delay()
1// Arduino trafik lambası: delay() ile en basit sürüm / simplest version with delay()
2const int KIRMIZI = 10; // kırmızı LED / red LED
3const int SARI = 9; // sarı LED / yellow LED
4const int YESIL = 8; // yeşil LED / green LED
5
6// Üç LED'i tek satırda ayarla / set all three LEDs in one line
7void lambalar(int k, int s, int y) {
8 digitalWrite(KIRMIZI, k);
9 digitalWrite(SARI, s);
10 digitalWrite(YESIL, y);
11}
12
13void setup() {
14 pinMode(KIRMIZI, OUTPUT);
15 pinMode(SARI, OUTPUT);
16 pinMode(YESIL, OUTPUT);
17}
18
19void loop() {
20 lambalar(HIGH, LOW, LOW); // DUR: kırmızı / STOP: red
21 delay(5000);
22 lambalar(HIGH, HIGH, LOW); // HAZIRLAN: kırmızı + sarı / GET READY: red + yellow
23 delay(1000);
24 lambalar(LOW, LOW, HIGH); // GEÇ: yeşil / GO: green
25 delay(5000);
26 lambalar(LOW, HIGH, LOW); // YAVAŞLA: sarı / SLOW DOWN: yellow
27 delay(3000);
28}Скетч в setup() делает три пина выходами, а в loop() по порядку выполняет четыре фазы. Чтобы не писать снова и снова по три строки digitalWrite, используется небольшая функция lambalar() («лампы»): каждая фаза читается одной строкой, и ошибку легко заметить. Для загрузки выбери в меню «Инструменты» (Tools) плату Arduino Uno и правильный порт, затем нажми «Загрузка» (Upload). Если загрузка не проходит, пройдись по списку проверок в статье Код не загружается в Arduino. Чтобы изменить время, просто поменяй значения в миллисекундах внутри delay(): 5000 означает 5 секунд.
Шаг 2: светофор с кнопкой для пешеходов
Пешеходные переходы с кнопкой, которые встречаются в городе, держат для машин зелёный и дают ход пешеходам, только когда кто-то нажимает кнопку. В этой версии к светофору для машин добавляются пешеходный светофор из двух светодиодов (красного и зелёного) и кнопка на D2. Одна ножка кнопки идёт на D2, а противоположная по диагонали — на GND; поскольку в коде используется INPUT_PULLUP, внешний резистор не нужен, и пока кнопка нажата, пин читает LOW. О внутренних соединениях кнопки с четырьмя ножками и об антидребезге подробнее в статье Как подключить кнопку к Arduino.

1// Yaya butonlu trafik lambası (delay ile) / pedestrian push-button light (with delay)
2const int KIRMIZI = 10, SARI = 9, YESIL = 8; // araç lambası / vehicle light
3const int YAYA_KIRMIZI = 7, YAYA_YESIL = 6; // yaya lambası / pedestrian light
4const int BUTON = 2; // diğer ucu GND / other leg to GND
5
6void setup() {
7 pinMode(KIRMIZI, OUTPUT);
8 pinMode(SARI, OUTPUT);
9 pinMode(YESIL, OUTPUT);
10 pinMode(YAYA_KIRMIZI, OUTPUT);
11 pinMode(YAYA_YESIL, OUTPUT);
12 pinMode(BUTON, INPUT_PULLUP); // basılınca LOW okunur / reads LOW when pressed
13 digitalWrite(YESIL, HIGH); // araçlar geçer / cars go
14 digitalWrite(YAYA_KIRMIZI, HIGH); // yayalar bekler / pedestrians wait
15}
16
17void loop() {
18 if (digitalRead(BUTON) == LOW) { // yaya butona bastı / pedestrian pressed
19 delay(2000); // araçlara kısa süre tanı / short grace time for cars
20 digitalWrite(YESIL, LOW);
21 digitalWrite(SARI, HIGH); // sarı / yellow
22 delay(3000);
23 digitalWrite(SARI, LOW);
24 digitalWrite(KIRMIZI, HIGH); // araçlar durur / cars stop
25 delay(1000); // tüm kırmızı güvenlik payı / all-red safety gap
26 digitalWrite(YAYA_KIRMIZI, LOW);
27 digitalWrite(YAYA_YESIL, HIGH); // yayalar geçer / pedestrians cross
28 delay(6000);
29 for (int i = 0; i < 6; i++) { // yaya yeşili 3 kez yanıp söner / green man flashes 3 times
30 digitalWrite(YAYA_YESIL, i % 2 == 0 ? LOW : HIGH);
31 delay(500);
32 }
33 digitalWrite(YAYA_YESIL, LOW);
34 digitalWrite(YAYA_KIRMIZI, HIGH);
35 delay(1000); // tüm kırmızı / all red
36 digitalWrite(SARI, HIGH); // kırmızı + sarı / red + yellow
37 delay(1000);
38 digitalWrite(KIRMIZI, LOW);
39 digitalWrite(SARI, LOW);
40 digitalWrite(YESIL, HIGH); // araçlar yeniden geçer / cars go again
41 delay(8000); // en kısa yeşil süresi / minimum green time
42 }
43}Этот скетч работает, но у него серьёзный недостаток: после нажатия кнопки Arduino около 25 секунд застревает в строках delay(). Повторное нажатие за это время не замечается, из последовательного порта ничего не читается, и никакой дисплей не обновляется. Нажатия во время 8-секундного ожидания в конце теряются полностью: пешеход нажимает и отпускает, ничего не происходит, и приходится нажимать снова. Если добавить звук или обратный отсчёт, с такой структурой работать ещё труднее. Решение — перестать ждать и начать измерять время.
Шаг 3: неблокирующий код на millis() и конечном автомате
Конечный автомат — это схема, в которой система всегда находится ровно в одном состоянии и переходит в следующее, когда выполняются определённые условия. Светофор — идеальный пример: «зелёный для машин», «жёлтый для машин» и «пешеходы переходят» — каждое из них отдельное состояние. Код больше не ждёт через delay(); при каждом проходе loop() задаёт всего два вопроса: в каком я состоянии и как долго в нём нахожусь? Прошедшее время измеряется как millis() - durumBaslangic (момент начала состояния), а когда время вышло, код переходит дальше. Если millis() для тебя в новинку, сначала прочитай статью millis() и delay() в Arduino. Если до написания кода занести состояния в таблицу, работа становится намного проще:
| Состояние | Светофор для машин | Пешеходный светофор | Длительность | Следующее состояние |
|---|---|---|---|---|
| ARAC_YESIL | Зелёный | Красный | Минимум 8 с, пока не поступит запрос | ARAC_SARI |
| ARAC_SARI | Жёлтый | Красный | 3 с | YAYA_BEKLE |
| YAYA_BEKLE | Красный | Красный | 1 с | YAYA_GECER |
| YAYA_GECER | Красный | Зелёный | 6 с | YAYA_FLAS |
| YAYA_FLAS | Красный | Мигающий зелёный | 3 с | YAYA_BITTI |
| YAYA_BITTI | Красный | Красный | 1 с | KIRMIZI_SARI |
| KIRMIZI_SARI | Красный + жёлтый | Красный | 1 с | ARAC_YESIL |
1// Yaya butonlu trafik lambası: millis() + durum makinesi / pedestrian light: millis() + state machine
2const int KIRMIZI = 10, SARI = 9, YESIL = 8; // araç lambası / vehicle light
3const int YAYA_KIRMIZI = 7, YAYA_YESIL = 6; // yaya lambası / pedestrian light
4const int BUTON = 2; // diğer ucu GND / other leg to GND
5
6enum Durum { ARAC_YESIL, ARAC_SARI, YAYA_BEKLE, YAYA_GECER, YAYA_FLAS, YAYA_BITTI, KIRMIZI_SARI };
7const char* const ISIM[] = { "ARAC_YESIL", "ARAC_SARI", "YAYA_BEKLE", "YAYA_GECER",
8 "YAYA_FLAS", "YAYA_BITTI", "KIRMIZI_SARI" };
9
10// Süreler (ms) / durations (ms)
11const unsigned long MIN_YESIL = 8000; // araçlara en az yeşil / minimum green for cars
12const unsigned long SARI_SURE = 3000; // sarı / yellow
13const unsigned long GUVENLIK = 1000; // tüm kırmızı / all red
14const unsigned long YAYA_SURE = 6000; // yaya yeşili / pedestrian green
15const unsigned long FLAS_SURE = 3000; // yanıp sönen yaya yeşili / flashing green man
16const unsigned long KS_SURE = 1000; // kırmızı + sarı / red + yellow
17
18Durum durum = ARAC_YESIL;
19unsigned long durumBaslangic = 0; // durumun başladığı an / when the state started
20bool yayaTalebi = false; // butona basıldı mı? / was the button pressed?
21
22void aracLamba(bool k, bool s, bool y) {
23 digitalWrite(KIRMIZI, k);
24 digitalWrite(SARI, s);
25 digitalWrite(YESIL, y);
26}
27
28void yayaLamba(bool k, bool y) {
29 digitalWrite(YAYA_KIRMIZI, k);
30 digitalWrite(YAYA_YESIL, y);
31}
32
33// Yeni duruma geç: lambaları ayarla, saati başlat / enter a state: set the lamps, restart the clock
34void gec(Durum yeni) {
35 durum = yeni;
36 durumBaslangic = millis();
37 switch (yeni) {
38 case ARAC_YESIL: aracLamba(0, 0, 1); yayaLamba(1, 0); break;
39 case ARAC_SARI: aracLamba(0, 1, 0); yayaLamba(1, 0); break;
40 case YAYA_BEKLE: aracLamba(1, 0, 0); yayaLamba(1, 0); break;
41 case YAYA_GECER: aracLamba(1, 0, 0); yayaLamba(0, 1); yayaTalebi = false; break;
42 case YAYA_FLAS: aracLamba(1, 0, 0); yayaLamba(0, 1); break;
43 case YAYA_BITTI: aracLamba(1, 0, 0); yayaLamba(1, 0); break;
44 case KIRMIZI_SARI: aracLamba(1, 1, 0); yayaLamba(1, 0); break;
45 }
46 Serial.println(ISIM[yeni]); // durum günlüğü / state log
47}
48
49void setup() {
50 Serial.begin(9600);
51 pinMode(KIRMIZI, OUTPUT);
52 pinMode(SARI, OUTPUT);
53 pinMode(YESIL, OUTPUT);
54 pinMode(YAYA_KIRMIZI, OUTPUT);
55 pinMode(YAYA_YESIL, OUTPUT);
56 pinMode(BUTON, INPUT_PULLUP);
57 gec(ARAC_YESIL);
58}
59
60void loop() {
61 unsigned long gecen = millis() - durumBaslangic; // bu durumda geçen süre / time spent in this state
62
63 // yaya yeşili yanarken basış yok sayılır / presses during the green man are ignored
64 if (digitalRead(BUTON) == LOW && durum != YAYA_GECER) {
65 yayaTalebi = true; // talebi hatırla / remember the request
66 }
67
68 switch (durum) {
69 case ARAC_YESIL:
70 if (yayaTalebi && gecen >= MIN_YESIL) gec(ARAC_SARI);
71 break;
72 case ARAC_SARI:
73 if (gecen >= SARI_SURE) gec(YAYA_BEKLE);
74 break;
75 case YAYA_BEKLE:
76 if (gecen >= GUVENLIK) gec(YAYA_GECER);
77 break;
78 case YAYA_GECER:
79 if (gecen >= YAYA_SURE) gec(YAYA_FLAS);
80 break;
81 case YAYA_FLAS:
82 digitalWrite(YAYA_YESIL, (gecen / 250) % 2 == 0); // 250 ms'de bir yanıp söner / toggles every 250 ms
83 if (gecen >= FLAS_SURE) gec(YAYA_BITTI);
84 break;
85 case YAYA_BITTI:
86 if (gecen >= GUVENLIK) gec(KIRMIZI_SARI);
87 break;
88 case KIRMIZI_SARI:
89 if (gecen >= KS_SURE) gec(ARAC_YESIL);
90 break;
91 }
92}Сердце скетча — функция gec() («перейти»): она запоминает новое состояние, перезапускает таймер, выставляет лампы для этого состояния и выводит имя состояния в монитор порта. Оператор switch в loop() только проверяет, вышло ли время. Нажатие кнопки ничего не запускает сразу: оно поднимает флаг yayaTalebi (запрос пешехода), а цикл начинается, когда у машин был зелёный хотя бы 8 секунд. Теперь нажатия никогда не теряются, а поскольку кнопка меняет только флаг, дребезг контактов тоже не мешает. Нажатия, когда зелёный человечек уже горит, игнорируются; иначе сразу после перехода пешеходов запустился бы новый, ненужный цикл. Открой монитор порта на скорости 9600 бод, чтобы видеть имя каждого перехода; этот вывод помогает и при отладке, и в школьном отчёте.
Шаг 4: перекрёсток двух дорог
На настоящем перекрёстке светофоры двух дорог связаны: пока у дороги A зелёный, у дороги B должен быть красный. В этой версии шесть светодиодов: D10, D9 и D8 для направления A и D5, D4 и D3 для направления B. Важная деталь — короткий интервал «всем красный»: когда одно направление переключается на красный, другое не получает зелёный сразу; контроллер ждёт 1 секунду, чтобы машины, ещё находящиеся на перекрёстке, успели его покинуть. На этот раз состояния хранятся не в switch, а в таблице (массиве): для каждого шага горящие лампы записаны битами в одном байте, а длительность — в миллисекундах.
1// İki yönlü kavşak: tablo tabanlı durum makinesi / two-way junction: table-driven state machine
2const byte A_KIRMIZI = 10, A_SARI = 9, A_YESIL = 8; // A yönü (ana yol) / direction A (main road)
3const byte B_KIRMIZI = 5, B_SARI = 4, B_YESIL = 3; // B yönü (ara yol) / direction B (side road)
4const byte PINLER[6] = { A_KIRMIZI, A_SARI, A_YESIL, B_KIRMIZI, B_SARI, B_YESIL };
5
6struct Adim {
7 byte maske; // bit sırası: A_K A_S A_Y B_K B_S B_Y / bit order: A_R A_Y A_G B_R B_Y B_G
8 unsigned long sure; // ms
9};
10
11const Adim PLAN[] = {
12 { 0b001100, 8000 }, // A yeşil, B kırmızı / A green, B red
13 { 0b010100, 3000 }, // A sarı, B kırmızı / A yellow, B red
14 { 0b100100, 1000 }, // tüm yönler kırmızı / all red
15 { 0b100110, 1000 }, // A kırmızı, B kırmızı + sarı / A red, B red + yellow
16 { 0b100001, 5000 }, // A kırmızı, B yeşil / A red, B green
17 { 0b100010, 3000 }, // A kırmızı, B sarı / A red, B yellow
18 { 0b100100, 1000 }, // tüm yönler kırmızı / all red
19 { 0b110100, 1000 }, // A kırmızı + sarı, B kırmızı / A red + yellow, B red
20};
21const byte ADIM_SAYISI = sizeof(PLAN) / sizeof(PLAN[0]);
22
23byte adim = 0;
24unsigned long adimBaslangic = 0;
25
26void uygula(byte maske) {
27 if ((maske & 0b001000) && (maske & 0b000001)) { // iki yeşil aynı anda yanamaz / never two greens
28 maske = 0b100100; // güvenli durum: tüm kırmızı / fail-safe: all red
29 }
30 for (byte i = 0; i < 6; i++) {
31 digitalWrite(PINLER[i], (maske >> (5 - i)) & 1); // soldaki bit A_KIRMIZI / leftmost bit is A_RED
32 }
33}
34
35void setup() {
36 for (byte i = 0; i < 6; i++) pinMode(PINLER[i], OUTPUT);
37 uygula(PLAN[0].maske);
38 adimBaslangic = millis();
39}
40
41void loop() {
42 if (millis() - adimBaslangic >= PLAN[adim].sure) {
43 adimBaslangic += PLAN[adim].sure; // kaymasız zamanlama / drift-free timing
44 adim = (adim + 1) % ADIM_SAYISI;
45 uygula(PLAN[adim].maske);
46 }
47 // Burada buton, sensör ya da ekran işleri çalışabilir / buttons, sensors or a display can run here
48}Каждая строка массива PLAN — это один шаг. В двоичных числах вроде 0b001100 биты слева направо означают: A красный, A жёлтый, A зелёный, B красный, B жёлтый и B зелёный; 1 — горит, 0 — не горит. Функция uygula() («применить») по порядку записывает эти шесть битов в пины. Проверка в начале функции не даёт опечатке зажечь зелёный в обоих направлениях сразу и вместо этого переводит перекрёсток в режим «всем красный». В настоящих контроллерах перекрёстков есть ещё отдельная защитная схема, которая ловит конфликтующие зелёные сигналы. Чтобы изменить время или добавить шаги, достаточно отредактировать таблицу, не трогая остальной код. Если по мере роста числа светодиодов не хватает пинов, сдвиговый регистр 74HC595 даёт восемь выходов с трёх пинов.
Попробуй в Tinkercad: симуляция без деталей
- Создай бесплатный аккаунт на tinkercad.com и начни новую схему в разделе Circuits («Схемы»).
- Перетащи из списка компонентов справа Arduino Uno R3, макетную плату, три светодиода и три резистора. Щёлкни по светодиоду, чтобы выбрать цвет, и по резистору, чтобы задать номинал 220 Ω.
- Проведи провода: аноды светодиодов через резисторы к D10, D9 и D8, катоды к GND. Если задать цвета проводов (например, чёрный для GND), схема легко читается.
- Нажми Code («Код»), выбери в выпадающем меню Text («Текст»), удали пример кода и вставь скетч из этого руководства.
- Нажми Start Simulation («Начать моделирование»); светодиоды начнут переключаться по очереди. Последовательный вывод появится в Serial Monitor внизу панели кода.
Tinkercad — самый простой способ проверить подключение и код перед сборкой из настоящих деталей; если забыть резистор, симулятор покажет на светодиоде предупреждение о слишком большом токе. Чтобы проверить пешеходную версию, просто щёлкни мышью по кнопке во время симуляции. На медленном компьютере симуляция может идти медленнее реального времени, поэтому интервалы могут казаться чуть длиннее. Для школьного задания можно сдать ссылку на проект в Tinkercad или скриншот схемы. Подробная инструкция — в статье Симуляция Arduino в Tinkercad, а альтернатива с поддержкой ESP32 и Pico — Wokwi.
Светофор на ESP32 с ночным режимом
Скетчи Arduino из этого руководства работают и на ESP32: выбери в Arduino IDE плату ESP32 Dev Module и замени константы пинов, как показано в таблице. Версия ниже добавляет одну функцию — ночной режим. В часы малого движения многие перекрёстки переходят на мигающий жёлтый. На платах ESP32 DevKit есть кнопка BOOT, подключённая к GPIO 0, так что дополнительная кнопка даже не нужна: после запуска платы её можно читать как обычный вход. Каждое нажатие переключает между обычным и ночным режимами.
1// ESP32 trafik lambası + gece modu (BOOT düğmesi) / ESP32 traffic light + night mode (BOOT button)
2const int KIRMIZI = 5; // GPIO 5
3const int SARI = 18; // GPIO 18
4const int YESIL = 19; // GPIO 19
5const int BOOT_BUTON = 0; // kart üzerindeki BOOT düğmesi / on-board BOOT button
6
7struct Adim { bool k, s, y; unsigned long sure; };
8const Adim PLAN[] = {
9 { true, false, false, 5000 }, // kırmızı / red
10 { true, true, false, 1000 }, // kırmızı + sarı / red + yellow
11 { false, false, true, 5000 }, // yeşil / green
12 { false, true, false, 3000 }, // sarı / yellow
13};
14const int ADIM_SAYISI = sizeof(PLAN) / sizeof(PLAN[0]);
15
16int adim = 0;
17unsigned long adimBaslangic = 0;
18bool geceModu = false;
19int sonOkuma = HIGH, kararli = HIGH; // debounce için / for debouncing
20unsigned long degisimZamani = 0;
21
22void lambalar(bool k, bool s, bool y) {
23 digitalWrite(KIRMIZI, k);
24 digitalWrite(SARI, s);
25 digitalWrite(YESIL, y);
26}
27
28void setup() {
29 Serial.begin(115200);
30 pinMode(KIRMIZI, OUTPUT);
31 pinMode(SARI, OUTPUT);
32 pinMode(YESIL, OUTPUT);
33 pinMode(BOOT_BUTON, INPUT_PULLUP);
34 lambalar(PLAN[0].k, PLAN[0].s, PLAN[0].y);
35 adimBaslangic = millis();
36}
37
38void loop() {
39 unsigned long simdi = millis();
40
41 // 50 ms kararlı kalan basışı kabul et / accept a press that stays stable for 50 ms
42 int okuma = digitalRead(BOOT_BUTON);
43 if (okuma != sonOkuma) {
44 sonOkuma = okuma;
45 degisimZamani = simdi;
46 }
47 if (simdi - degisimZamani >= 50 && okuma != kararli) {
48 kararli = okuma;
49 if (kararli == LOW) { // yeni basış / new press
50 geceModu = !geceModu;
51 Serial.println(geceModu ? "Gece modu / Night mode" : "Normal mod / Normal mode");
52 adim = 0; // normal mod kırmızıdan başlar / normal mode restarts on red
53 adimBaslangic = simdi;
54 lambalar(PLAN[0].k, PLAN[0].s, PLAN[0].y);
55 }
56 }
57
58 if (geceModu) {
59 lambalar(false, (simdi / 500) % 2 == 0, false); // yanıp sönen sarı / flashing yellow
60 return;
61 }
62
63 if (simdi - adimBaslangic >= PLAN[adim].sure) {
64 adimBaslangic = simdi;
65 adim = (adim + 1) % ADIM_SAYISI;
66 lambalar(PLAN[adim].k, PLAN[adim].s, PLAN[adim].y);
67 }
68}Чтение кнопки включает простой антидребезг: нажатие засчитывается, только если показание не меняется 50 мс. Таблица PLAN с состояниями ламп — более читаемый вариант идеи из скетча перекрёстка. Поскольку пины ESP32 выдают 3,3 V, светодиоды потребляют меньше тока, чем на Arduino, и выглядят чуть тусклее. Красный и жёлтый светодиоды с 220 Ω достаточно яркие, а яркий зелёный с прямым напряжением около 3 V может остаться очень тусклым; в таком случае уменьши резистор зелёного до 100 Ω. Если ESP32 для тебя в новинку, хорошая отправная точка по установке и безопасным пинам — статья Что такое ESP32?
Пешеходный светофор на Raspberry Pi Pico (MicroPython)
На Raspberry Pi Pico тот же конечный автомат пишется на MicroPython. На этот раз состояния хранятся в списке: каждая строка содержит имя состояния, значения пяти ламп и длительность. Кнопка работает через прерывание: когда GP4 падает в LOW, запускается функция buton_kesmesi() («прерывание кнопки»), которая только поднимает флаг запроса. Если сохранить код на Pico через Thonny как main.py, он будет запускаться автоматически при каждом включении платы.
1# Raspberry Pi Pico (MicroPython): yaya butonlu trafik lambası / pedestrian traffic light
2from machine import Pin
3import time
4
5yesil = Pin(11, Pin.OUT) # araç yeşil / vehicle green
6sari = Pin(12, Pin.OUT) # araç sarı / vehicle yellow
7kirmizi = Pin(13, Pin.OUT) # araç kırmızı / vehicle red
8yaya_yesil = Pin(14, Pin.OUT) # yaya yeşil / pedestrian green
9yaya_kirmizi = Pin(15, Pin.OUT) # yaya kırmızı / pedestrian red
10buton = Pin(4, Pin.IN, Pin.PULL_UP) # GP4, diğer ucu GND / other leg to GND
11
12# (durum, kırmızı, sarı, yeşil, yaya kırmızı, yaya yeşil, süre ms)
13# (state, red, yellow, green, ped red, ped green, duration ms)
14DURUMLAR = [
15 ("ARAC_YESIL", 0, 0, 1, 1, 0, 8000), # en az yeşil süresi / minimum green
16 ("ARAC_SARI", 0, 1, 0, 1, 0, 3000),
17 ("YAYA_BEKLE", 1, 0, 0, 1, 0, 1000), # tüm kırmızı / all red
18 ("YAYA_GECER", 1, 0, 0, 0, 1, 6000),
19 ("YAYA_FLAS", 1, 0, 0, 0, 1, 3000),
20 ("YAYA_BITTI", 1, 0, 0, 1, 0, 1000), # tüm kırmızı / all red
21 ("KIRMIZI_SARI", 1, 1, 0, 1, 0, 1000),
22]
23
24talep = False
25adim = 0 # şu anki durumun sırası / index of the current state
26
27def buton_kesmesi(pin): # kesmede yalnız bayrak kaldır / only set a flag in the IRQ
28 global talep
29 if DURUMLAR[adim][0] != "YAYA_GECER": # yaya yeşilinde basış yok sayılır / ignored during the green man
30 talep = True
31
32buton.irq(trigger=Pin.IRQ_FALLING, handler=buton_kesmesi)
33
34def uygula(i):
35 global talep
36 ad, k, s, y, yk, yy, _ = DURUMLAR[i]
37 kirmizi.value(k)
38 sari.value(s)
39 yesil.value(y)
40 yaya_kirmizi.value(yk)
41 yaya_yesil.value(yy)
42 if ad == "YAYA_GECER":
43 talep = False # talep karşılandı / request served
44 print("Durum / state:", ad)
45
46uygula(adim)
47baslangic = time.ticks_ms()
48
49while True:
50 gecen = time.ticks_diff(time.ticks_ms(), baslangic) # taşmaya karşı güvenli / wrap-safe
51 ad = DURUMLAR[adim][0]
52
53 if ad == "YAYA_FLAS":
54 yaya_yesil.value((gecen // 250) % 2 == 0) # yanıp sönen yaya yeşili / flashing green man
55
56 if gecen >= DURUMLAR[adim][6] and (ad != "ARAC_YESIL" or talep):
57 adim = (adim + 1) % len(DURUMLAR) # yeşil, talep gelene kadar sürer / green lasts until a request
58 uygula(adim)
59 baslangic = time.ticks_ms()
60
61 time.sleep_ms(10)В MicroPython вместо millis() используется time.ticks_ms(), а вместо обычного вычитания — time.ticks_diff(): счётчик ticks после определённого значения переполняется и начинает сначала, поэтому прямое вычитание двух показаний может дать неверный результат. Обработчик прерывания должен быть коротким: никаких print, sleep и долгих вычислений, только смена флага. Пины Pico тоже работают на 3,3 V, так что замечание о зелёном светодиоде из раздела про ESP32 относится и сюда. Как установить MicroPython и как нумеруются пины, читай в статье Что такое Raspberry Pi Pico?, а основы языка — в статье Основы MicroPython.
Частые ошибки и как их исправить
| Симптом | Вероятная причина | Решение |
|---|---|---|
| Не горит ни один светодиод | Не подключён GND, или шина питания макетной платы разорвана посередине | Соедини GND Arduino с синей шиной; на длинной макетной плате соедини две половины перемычкой |
| Один светодиод не горит | Светодиод перевёрнут или подключён не к тому пину | Длинная ножка (анод) через резистор к пину, короткая к GND; сверь номер пина с кодом |
| Загорается не тот цвет | В коде перепутаны пины красного и зелёного | Исправь провода или константы по таблице пинов |
| Светодиод очень яркий и греется | Не поставлен резистор | Добавь к каждому светодиоду последовательный резистор 220 Ω |
| Кнопка не работает или всегда будто нажата | Кнопка подключена к двум ножкам, которые уже соединены внутри, или забыт INPUT_PULLUP | Используй ножки по диагонали и напиши pinMode(BUTON, INPUT_PULLUP) |
| Нет мгновенной реакции на кнопку | Минимальное время зелёного для машин ещё не истекло | Это не ошибка; уменьши MIN_YESIL |
| Через некоторое время программа начинает сбоить | Переменная времени объявлена как int | Объявляй все переменные, связанные с millis(), как unsigned long |
Советы для школьного задания и отчёта о проекте
Светофор — одно из самых частых заданий по Arduino на уроках естествознания, технологии и робототехники. Учитель обычно хочет увидеть не только работающую схему, но и то, что ты её понимаешь и можешь объяснить. Если построить отчёт из следующих разделов, он будет выглядеть организованно, и ничего важного не потеряется:
- Цель: опиши в одном-двух предложениях, что показывает проект, например «управление временем светофора с помощью цифровых выходов».
- Список деталей: укажи название, количество и номинал (220 Ω) каждой детали, как в таблице выше.
- Схема: добавь скриншот из Tinkercad или Fritzing; если рисуешь от руки, используй правильные обозначения. В этом поможет статья Как читать электрическую схему.
- Расчёт резистора: покажи R = (5 − 2) / 0,015 ≈ 200 Ω и объясни, почему выбран 220 Ω.
- Таблица состояний или блок-схема: приложи таблицу состояний, длительностей и переходов; это самое убедительное доказательство, что код тебе понятен.
- Код и пояснения: приложи полный код и объясни важные строки своими словами.
- Испытания и наблюдения: засеки длительность фаз секундомером, измерь мультиметром ток одного светодиода и сравни с расчётом.
- Выводы и улучшения: опиши возникшие проблемы, как они решены и какие функции можно добавить (кнопка для пешеходов, звук, ночной режим).
Идеи, как развить светофор дальше
- Табло обратного отсчёта: показывай на 7-сегментном индикаторе, сколько секунд осталось до конца зелёного.
- Звуковой сигнал для слабовидящих пешеходов: пока у пешеходов зелёный, часто пищи зуммером, а пока красный — редко.
- Умный перекрёсток: определяй машину, ожидающую на второстепенной дороге, датчиком расстояния HC-SR04 и давай зелёный направлению B, только когда машина там есть.
- Автоматический ночной режим: с помощью фоторезистора переключайся на мигающий жёлтый, когда темнеет.
- Дистанционное управление: сделай веб-страницу на ESP32 и меняй режим и время с телефона.
- Железнодорожный переезд: используй ту же логику для светофора на переезде с двумя попеременно мигающими красными светодиодами; похожие идеи отсчёта времени есть в проектах Knight Rider и бегущие огни.
Светофор кажется маленьким проектом, но его идеи — конечные автоматы, неблокирующий отсчёт времени и безопасные переходы — лежат и в основе роботов, сигнализаций и устройств умного дома, которые ты соберёшь позже. Чтобы выбрать следующий проект, загляни в список идей проектов на Arduino. В приложении BlueGrays есть проекты светофора для Arduino, ESP32 и Raspberry Pi Pico, а также продолжения вроде светофора с четырьмя режимами на Pico, каждый с пошаговой схемой и готовым кодом, который можно опробовать на своей плате.
Частые вопросы
Как сделать светофор на Arduino?
Подключи длинные ножки красного, жёлтого и зелёного светодиодов к цифровым пинам Arduino (например, D10, D9 и D8) через резисторы 220 Ω, а короткие — к GND. В коде задай эти пины как OUTPUT и по очереди включай и выключай светодиоды в loop(). Для первой версии хватит delay(); для кнопок и дополнительных функций нужен millis().
Какой резистор нужен светодиоду светофора?
На Arduino Uno с 5 V распространённый и безопасный выбор — 220 Ω на светодиод; красный светодиод тогда потребляет около 13–14 mA. С 330 Ω свет тусклее, но всё равно хорошо заметен. 220 Ω подходит и для ESP32 и Pico с 3,3 V; только если яркий зелёный светодиод остаётся тусклым, уменьши резистор до 100 Ω.
Почему на светофоре красный и жёлтый горят вместе?
В таких странах, как Великобритания, Германия и Турция, красный вместе с жёлтым сообщает, что вот-вот загорится зелёный: водители готовятся, но не трогаются, пока не появится зелёный. В США, Франции, Италии и Испании этой фазы нет, и свет сразу переключается с красного на зелёный. В своём проекте на Arduino эту фазу можно включить или убрать по желанию.
Что использовать для светофора на Arduino: delay или millis?
Если светодиоды просто переключаются по очереди, delay() — самый простой вариант, и он отлично работает. Но как только добавляются кнопка, датчик, дисплей или звук, delay() приостанавливает программу, и эти входы пропускаются. Код на millis() и конечном автомате никогда не ждёт и может следить за всем сразу.
Можно ли собрать проект светофора в Tinkercad?
Да. В Tinkercad Circuits есть готовые компоненты Arduino Uno, светодиоды, резисторы и кнопки; нарисуй схему, вставь код в текстовый редактор (Text) и запусти симуляцию. Это идеальный способ проверить схему и код без настоящих деталей и получить скриншот или ссылку для задания.
Как работает пешеходный светофор с кнопкой?
Обычно у машин зелёный, а нажатие кнопки регистрирует запрос. Когда минимальное время зелёного для машин истекает, свет становится жёлтым, затем красным, и после короткой безопасной паузы у пешеходов загорается зелёный. В коде этого руководства зелёный человечек мигает перед окончанием, а затем цикл возвращается к машинам.
Что собрать после прочтения
Все проекты
Светофор
Крошечная копия светофора, который ты каждый день видишь на дороге. Красный горит две секунды, жёлтый мигает на мгновение, зелёный горит две секунды, затем через жёлтый снова возвращается красный, и т

Светофор на ESP32
Реалистичная симуляция светофора на 3 светодиодах — красном, жёлтом и зелёном. Тайминги близки к реальной логике движения: красный долго, жёлтый коротко, зелёный долго. Классический пример последовательного управления GPIO.

Симулятор светофора на Pico (3 светодиода по очереди)
Настоящий светофор на красном, жёлтом и зелёном светодиодах — первый проект серии Pico с 3 светодиодами! Зелёный 4 с «едем», жёлтый 1,5 с «внимание», красный 3 с «стоп» и красный + жёлтый 1 с «приготовиться» (европейская схема светофора). Изучи объявление функций (def all_off()), логику конечного автомата (последовательность) и синхронизацию нескольких GPIO. Простая схема: 3 светодиода на GP11 (зелёный), GP12 (жёлтый), GP13 (красный), у каждого токоограничивающий резистор 220Ω.

Два попеременно мигающих светодиода
Два светодиода быстро мигают по очереди: когда один загорается, другой гаснет, будто они перебрасывают друг другу мяч. Arduino держит каждый свет включённым пятую долю секунды, а затем передаёт очеред

Управление светодиодом кнопкой (удержание)
Как дверной звонок: свет горит, пока ты нажимаешь, и гаснет, когда отпускаешь. Arduino всё время слушает кнопку; пока она нажата, светодиод горит, а как только ты убираешь палец, он гаснет. Эта просте

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

Светофор на Pico с выбором 4 режимов кнопкой (конечный автомат)
4 режима светофора всего одной кнопкой! Каждое нажатие меняет режим: обычный цикл (красный → жёлтый → зелёный по очереди), только красный (сигнал стоп), мигающий жёлтый (внимание/ночной режим) и выключено (обслуживание). Понятия: конечный автомат (переменная mode определяет, какое поведение выполняется), цикл режимов по модулю ((mode + 1) % 4 → 0→1→2→3→0), неблокирующий тайминг (фазы светодиодов отсчитываются через time.ticks_ms() + time.ticks_diff()).
Другие статьи
Приложение BlueGrays
Собери эту схему шаг за шагом в телефоне
Схема подключения, код и список деталей на одном экране. 200+ проектов на Arduino, ESP32 и Pico готовы к сборке.
Наведи камеру телефона
Наведи камеру на код — откроется магазин приложений на телефоне.
или открыть магазин

