Руководства · 11 мин чтения

Светофор на Arduino: схема, код, кнопка для пешеходов, перекрёсток

Собери светофор на Arduino: 3 светодиода и резисторы 220 Ω, код на delay и millis, кнопка для пешеходов, перекрёсток двух дорог, версии для Tinkercad, ESP32 и Pico.

Светофор на Arduino — классический первый проект в электронике: три светодиода, красный, жёлтый и зелёный, по очереди загораются и гаснут, как настоящий светофор. Нужны только Arduino Uno, три светодиода, три резистора 220 Ω и несколько перемычек, и первая рабочая версия готова за полчаса. Но этот простой проект многому учит: цифровые выходы, расчёт светодиода и резистора, отсчёт времени, чтение кнопки и конечный автомат — самый аккуратный способ организовать код — помещаются в эти несколько светодиодов.

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

Красный, жёлтый и зелёный светодиоды 5 мм для светофора на Arduino
Светодиоды 5 мм: для светофора хватит одного красного, одного жёлтого и одного зелёного; держи под рукой ещё несколько для пешеходного сигнала и перекрёстка.

Как работает настоящий светофор?

Светофор — это таймер, который по очереди даёт ход каждому потоку транспорта. В Великобритании, Германии и Турции цикл такой: красный → красный + жёлтый → зелёный → жёлтый → красный. Красный вместе с жёлтым означает «скоро зелёный, приготовься, но пока не езжай», а один жёлтый — «сейчас загорится красный, готовься остановиться». В США, Франции, Италии и Испании фазы «красный + жёлтый» нет: свет сразу переключается с красного на зелёный. Время жёлтого тоже выбрано не случайно: немецкие правила задают 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 UnoESP32Raspberry Pi Pico
Красный для машин (направление A)D10GPIO 5GP13
Жёлтый для машин (направление A)D9GPIO 18GP12
Зелёный для машин (направление A)D8GPIO 19GP11
Красный для пешеходовD7GPIO 21GP15
Зелёный для пешеходовD6GPIO 22GP14
Кнопка для пешеходов (другая ножка к GND)D2GPIO 23GP4
Направление B: красный / жёлтый / зелёныйD5 / D4 / D3GPIO 25 / 26 / 27GP8 / GP7 / GP6
Катоды светодиодов и кнопкаGNDGNDGND
Перевёрнутый светодиод просто не горит и не повреждается, но без резистора светодиод и пин перегружены, и светодиод может быстро потускнеть. На Arduino Uno старайся держать не больше 20 mA на пин (абсолютный максимум — 40 mA), а суммарный ток всех пинов — ниже 200 mA. Если не горит ни один светодиод, первым делом проверь GND: если синяя шина макетной платы не соединена с пином GND Arduino, цепь никогда не замкнётся.

Шаг 1: самый простой код светофора на delay()

C++ (Arduino IDE)
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.

Кнопка 6×6 мм с четырьмя ножками
Кнопка 6×6 мм: четыре ножки внутри попарно соединены; если использовать две ножки по диагонали, подключение всегда будет правильным.
C++ (Arduino IDE)
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
C++ (Arduino IDE)
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 бод, чтобы видеть имя каждого перехода; этот вывод помогает и при отладке, и в школьном отчёте.

Чтобы добавить функцию, сначала добавь строку в таблицу, а потом напиши case в коде. Например, можно добавить ночное состояние с мигающим жёлтым и переходить в него, когда фоторезистор (LDR) замечает темноту. Чтобы глубже разобраться в конечных автоматах, загляни в статью Конечный автомат на Arduino.

Шаг 4: перекрёсток двух дорог

На настоящем перекрёстке светофоры двух дорог связаны: пока у дороги A зелёный, у дороги B должен быть красный. В этой версии шесть светодиодов: D10, D9 и D8 для направления A и D5, D4 и D3 для направления B. Важная деталь — короткий интервал «всем красный»: когда одно направление переключается на красный, другое не получает зелёный сразу; контроллер ждёт 1 секунду, чтобы машины, ещё находящиеся на перекрёстке, успели его покинуть. На этот раз состояния хранятся не в switch, а в таблице (массиве): для каждого шага горящие лампы записаны битами в одном байте, а длительность — в миллисекундах.

C++ (Arduino IDE)
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: симуляция без деталей

  1. Создай бесплатный аккаунт на tinkercad.com и начни новую схему в разделе Circuits («Схемы»).
  2. Перетащи из списка компонентов справа Arduino Uno R3, макетную плату, три светодиода и три резистора. Щёлкни по светодиоду, чтобы выбрать цвет, и по резистору, чтобы задать номинал 220 Ω.
  3. Проведи провода: аноды светодиодов через резисторы к D10, D9 и D8, катоды к GND. Если задать цвета проводов (например, чёрный для GND), схема легко читается.
  4. Нажми Code («Код»), выбери в выпадающем меню Text («Текст»), удали пример кода и вставь скетч из этого руководства.
  5. Нажми Start Simulation («Начать моделирование»); светодиоды начнут переключаться по очереди. Последовательный вывод появится в Serial Monitor внизу панели кода.

Tinkercad — самый простой способ проверить подключение и код перед сборкой из настоящих деталей; если забыть резистор, симулятор покажет на светодиоде предупреждение о слишком большом токе. Чтобы проверить пешеходную версию, просто щёлкни мышью по кнопке во время симуляции. На медленном компьютере симуляция может идти медленнее реального времени, поэтому интервалы могут казаться чуть длиннее. Для школьного задания можно сдать ссылку на проект в Tinkercad или скриншот схемы. Подробная инструкция — в статье Симуляция Arduino в Tinkercad, а альтернатива с поддержкой ESP32 и Pico — Wokwi.

Светофор на ESP32 с ночным режимом

Скетчи Arduino из этого руководства работают и на ESP32: выбери в Arduino IDE плату ESP32 Dev Module и замени константы пинов, как показано в таблице. Версия ниже добавляет одну функцию — ночной режим. В часы малого движения многие перекрёстки переходят на мигающий жёлтый. На платах ESP32 DevKit есть кнопка BOOT, подключённая к GPIO 0, так что дополнительная кнопка даже не нужна: после запуска платы её можно читать как обычный вход. Каждое нажатие переключает между обычным и ночным режимами.

C++ (Arduino IDE)
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, он будет запускаться автоматически при каждом включении платы.

MicroPython
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 Ω.
  • Таблица состояний или блок-схема: приложи таблицу состояний, длительностей и переходов; это самое убедительное доказательство, что код тебе понятен.
  • Код и пояснения: приложи полный код и объясни важные строки своими словами.
  • Испытания и наблюдения: засеки длительность фаз секундомером, измерь мультиметром ток одного светодиода и сравни с расчётом.
  • Выводы и улучшения: опиши возникшие проблемы, как они решены и какие функции можно добавить (кнопка для пешеходов, звук, ночной режим).
Если хочешь выделиться, сравни первую версию на delay() с версией на millis(): объясни, почему в первой нажатия кнопки теряются, а во второй — никогда. Видео работающей схемы на 20–30 секунд и скриншот вывода монитора порта сделают отчёт ещё сильнее.

Идеи, как развить светофор дальше

  • Табло обратного отсчёта: показывай на 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) и запусти симуляцию. Это идеальный способ проверить схему и код без настоящих деталей и получить скриншот или ссылку для задания.

Как работает пешеходный светофор с кнопкой?

Обычно у машин зелёный, а нажатие кнопки регистрирует запрос. Когда минимальное время зелёного для машин истекает, свет становится жёлтым, затем красным, и после короткой безопасной паузы у пешеходов загорается зелёный. В коде этого руководства зелёный человечек мигает перед окончанием, а затем цикл возвращается к машинам.

Все проекты
Светофор
#3Новичок

Светофор

Крошечная копия светофора, который ты каждый день видишь на дороге. Красный горит две секунды, жёлтый мигает на мгновение, зелёный горит две секунды, затем через жёлтый снова возвращается красный, и т

4.8(131)
396
Светофор на ESP32
#107Новичок

Светофор на ESP32

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

4.4(21)
44
Симулятор светофора на Pico (3 светодиода по очереди)
#150Новичок

Симулятор светофора на Pico (3 светодиода по очереди)

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

4.0(1)
12
Два попеременно мигающих светодиода
#2Новичок

Два попеременно мигающих светодиода

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

4.6(105)
343
Управление светодиодом кнопкой (удержание)
#9Новичок

Управление светодиодом кнопкой (удержание)

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

4.8(113)
229
Полицейская мигалка на светодиодах
#4Новичок

Полицейская мигалка на светодиодах

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

4.7(103)
258
Светофор на Pico с выбором 4 режимов кнопкой (конечный автомат)
#158Средний

Светофор на Pico с выбором 4 режимов кнопкой (конечный автомат)

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

5.0(1)
15