[Mikrocontroller]

Eine einfache Ampelschaltung mit Arduino oder ESP32 – Hardware, Logik und sauberer Code

/arduino-esp32-ampelschaltung-zustandsmaschine-millis

Eine „Ampel“ ist eines der dankbarsten Einsteigerprojekte, weil man dabei fast alles lernt, was später bei größeren Projekten wiederkommt: sauberes Verdrahten, Vorwiderstände für LEDs, Zustände (State Machine), Timing und am Ende die Frage, ob man stumpf mit delay() arbeitet oder das Ganze vernünftig nicht-blockierend aufzieht. Dazu kommt: Das Projekt läuft praktisch identisch auf einem Arduino (z. B. Uno/Nano) und auf einem ESP32 – man muss nur ein paar elektrische Unterschiede beachten.

Wichtig vorab: Das hier ist eine Modell-Ampel für Bastelzwecke. Nicht für echte Verkehrssteuerung, nicht für sicherheitsrelevante Anwendungen.


Was die Ampel „können“ soll

Die klassische Kfz-Ampel hat in der Praxis je nach Land verschiedene Phasen. Für ein Modell reicht meist diese Sequenz:

  1. Rot
  2. Rot + Gelb (Ankündigung „gleich Grün“)
  3. Grün
  4. Gelb
  5. zurück zu Rot

Damit hat man schon eine vollständige zyklische Zustandsmaschine. Optional kann man eine Fußgängerampel ergänzen (Rot/Grün) und später auch Taster, damit die Fußgängerphase nur bei Bedarf kommt.


Bauteile und warum man sie braucht

Für die Minimalversion:

  • 1× Arduino Uno/Nano oder 1× ESP32 DevKit
  • 3× LEDs: Rot, Gelb, Grün (5 mm Standard)
  • 3× Vorwiderstände (typisch 220–330 Ω)
  • Breadboard + Jumperkabel
  • USB-Kabel zum Programmieren

Warum Vorwiderstände?
Eine LED ist keine Glühbirne. Ohne Vorwiderstand begrenzt nichts den Strom – das kann LED und Mikrocontroller-Pin zerstören. Der Widerstand sorgt dafür, dass aus „zu viel Strom“ ein definierter, ungefährlicher Strom wird.

Als grobe Rechnung (nur zum Gefühl):

  • Arduino-Pin: 5 V, LED rot ca. 2 V → 3 V am Widerstand. Bei 10 mA: 3 V / 0,01 A = 300 Ω → 330 Ω passt.
  • ESP32-Pin: 3,3 V, LED rot ca. 2 V → 1,3 V am Widerstand. Bei 5–8 mA sind 150–220 Ω typisch, 220 Ω funktioniert meist sehr gut.

Man kann also für beide Plattformen 220–330 Ω nehmen und ist auf der sicheren Seite.


Arduino vs. ESP32: die zwei wichtigsten Unterschiede

1) Logikpegel / Spannung:

  • Arduino Uno: 5 V Logik, Pins liefern 5 V (HIGH)
  • ESP32: 3,3 V Logik, Pins liefern 3,3 V (HIGH)

Für einzelne LEDs ist das unkritisch. Bei Modulen oder externen Schaltungen muss man das im Kopf behalten.

2) Pin-Auswahl beim ESP32:
Beim ESP32 sind nicht alle Pins gleich „unkompliziert“. Einige sind Boot-Strapping-Pins oder werden beim Starten in einer Weise genutzt, die Ärger macht, wenn man dort LEDs „falsch“ anschließt. Für eine Ampel nimmt man am besten „langweilige“ GPIOs, z. B. GPIO 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33 (je nach Board). Dann hat man weniger Überraschungen.


Verdrahtung: simpel, robust, leicht zu prüfen

Die Standardverdrahtung für jede LED:

GPIO-Pin → Widerstand → LED-Anode (langes Bein) → LED-Kathode (kurzes Bein) → GND

Damit liegt die LED zwischen Pin und Masse, und ein HIGH am Pin schaltet sie ein.

Beispiel-Pins (kann man beliebig ändern):

  • Rot: Pin 8 (Arduino) / GPIO 16 (ESP32)
  • Gelb: Pin 9 (Arduino) / GPIO 17 (ESP32)
  • Grün: Pin 10 (Arduino) / GPIO 18 (ESP32)

Wenn man sauber arbeitet, prüft man direkt beim Aufbau jede LED einzeln: Kurz ein Mini-Sketch, der eine LED blinken lässt. So findet man Verdrahtungsfehler sofort, statt später in der Logik zu suchen.


Software-Ansatz 1: schnell und einfach mit delay()

Mit delay() kommt man sehr schnell zu einem Ergebnis. Der Nachteil: Während delay() läuft, ist der Controller „beschäftigt“ – Taster abfragen, Sensoren lesen, WLAN bedienen, Logging, alles steht. Für eine Demo ist das okay, für „echte“ Projekte nervt es früher oder später.

Trotzdem, als Einstieg:

// Funktioniert auf Arduino und ESP32 (Arduino-IDE)
// Pins anpassen!

#if defined(ESP32)
const int PIN_RED = 16;
const int PIN_YELLOW = 17;
const int PIN_GREEN = 18;
#else
const int PIN_RED = 8;
const int PIN_YELLOW = 9;
const int PIN_GREEN = 10;
#endif

void setup() {
  pinMode(PIN_RED, OUTPUT);
  pinMode(PIN_YELLOW, OUTPUT);
  pinMode(PIN_GREEN, OUTPUT);
}

void loop() {
  // Rot
  digitalWrite(PIN_RED, HIGH);
  digitalWrite(PIN_YELLOW, LOW);
  digitalWrite(PIN_GREEN, LOW);
  delay(5000);

  // Rot + Gelb
  digitalWrite(PIN_RED, HIGH);
  digitalWrite(PIN_YELLOW, HIGH);
  digitalWrite(PIN_GREEN, LOW);
  delay(1500);

  // Grün
  digitalWrite(PIN_RED, LOW);
  digitalWrite(PIN_YELLOW, LOW);
  digitalWrite(PIN_GREEN, HIGH);
  delay(5000);

  // Gelb
  digitalWrite(PIN_RED, LOW);
  digitalWrite(PIN_YELLOW, HIGH);
  digitalWrite(PIN_GREEN, LOW);
  delay(1500);
}

Das ist leicht zu lesen und perfekt, um die Verdrahtung zu verifizieren.


Software-Ansatz 2: „richtig“ mit Zustandsmaschine und millis() (nicht-blockierend)

Sobald man einen Taster (Fußgänger) oder später noch mehr Logik will, lohnt sich ein nicht-blockierender Ansatz. Das Prinzip: Man merkt sich den aktuellen Zustand und den Zeitpunkt, an dem man in diesen Zustand gewechselt hat. In jeder Loop-Runde prüft man, ob die Dauer abgelaufen ist – wenn ja, wechselt man weiter.

Das klingt theoretisch, ist aber praktisch extrem angenehm: Die Loop bleibt schnell, man kann jederzeit Eingaben prüfen oder andere Dinge parallel laufen lassen.

// Nicht-blockierende Ampel (State Machine) für Arduino & ESP32

#if defined(ESP32)
const int PIN_RED = 16;
const int PIN_YELLOW = 17;
const int PIN_GREEN = 18;
#else
const int PIN_RED = 8;
const int PIN_YELLOW = 9;
const int PIN_GREEN = 10;
#endif

enum State {
  RED,
  RED_YELLOW,
  GREEN,
  YELLOW
};

State state = RED;
unsigned long stateStartMs = 0;

// Zeiten in Millisekunden
const unsigned long T_RED       = 5000;
const unsigned long T_REDYELLOW = 1500;
const unsigned long T_GREEN     = 5000;
const unsigned long T_YELLOW    = 1500;

void setLights(bool r, bool y, bool g) {
  digitalWrite(PIN_RED, r ? HIGH : LOW);
  digitalWrite(PIN_YELLOW, y ? HIGH : LOW);
  digitalWrite(PIN_GREEN, g ? HIGH : LOW);
}

void enterState(State s) {
  state = s;
  stateStartMs = millis();

  switch (state) {
    case RED:        setLights(true,  false, false); break;
    case RED_YELLOW: setLights(true,  true,  false); break;
    case GREEN:      setLights(false, false, true ); break;
    case YELLOW:     setLights(false, true,  false); break;
  }
}

bool elapsed(unsigned long duration) {
  return (millis() - stateStartMs) >= duration;
}

void setup() {
  pinMode(PIN_RED, OUTPUT);
  pinMode(PIN_YELLOW, OUTPUT);
  pinMode(PIN_GREEN, OUTPUT);

  enterState(RED);
}

void loop() {
  switch (state) {
    case RED:
      if (elapsed(T_RED)) enterState(RED_YELLOW);
      break;

    case RED_YELLOW:
      if (elapsed(T_REDYELLOW)) enterState(GREEN);
      break;

    case GREEN:
      if (elapsed(T_GREEN)) enterState(YELLOW);
      break;

    case YELLOW:
      if (elapsed(T_YELLOW)) enterState(RED);
      break;
  }

  // Hier kann man jetzt jederzeit Dinge tun:
  // - Taster abfragen
  // - Sensoren lesen
  // - Serial-Logging
  // - beim ESP32: WLAN/MQTT usw.
}

Der Code hat drei große Vorteile:

  • Die Ampel-Logik ist als Zustandsmaschine klar strukturiert.
  • Zeiten sind zentral als Konstanten definiert.
  • Man hat „Platz“ in der Loop für Erweiterungen, ohne dass alles stehen bleibt.

Typische Fehler – und wie man sie schnell findet

LED leuchtet gar nicht:
Meist ist die LED falsch herum drin. Das lange Bein ist normalerweise die Anode (+). Außerdem: GND muss wirklich GND sein – Breadboards haben manchmal geteilte Schienen.

LED leuchtet dauerhaft schwach oder flackert:
Pin eventuell falsch gewählt (ESP32 Boot-Pins), oder ein Kontakt steckt nicht richtig. Beim ESP32 hilft: andere GPIOs nehmen.

Controller startet nicht mehr (ESP32):
Sehr häufig liegt eine LED oder ein Modul an einem Pin, der beim Booten eine bestimmte Pegelbelegung erwartet. Lösung: „sichere“ GPIOs verwenden und nichts an die typischen Strapping-Pins hängen.

Widerstände vergessen:
Dann wird es im besten Fall nur heiß und im schlechtesten Fall stirbt der Pin. Immer Widerstände.


Sinnvolle Upgrades: von der Demo zum kleinen Projekt

Wenn die Grundampel läuft, kommen die typischen nächsten Schritte fast automatisch:

1) Fußgängerampel mit Taster
Man ergänzt eine zweite LED-Gruppe (Rot/Grün) und einen Taster. In der Zustandsmaschine führt ein Tastendruck dazu, dass nach der nächsten passenden Stelle eine Fußgängerphase eingeschoben wird. Mit millis() ist das sauber machbar, ohne dass man die komplette Logik „verklebt“.

2) Realistischere Zeiten und Sperrzeiten
Man kann z. B. nach „Gelb“ kurz „Alles Rot“ einführen (Räumzeit), damit es realistischer wird.

3) Mehr LEDs mit wenig Pins (Shift Register / I²C Expander)
Wenn aus einer Ampel eine Kreuzung wird, sind Pins schnell knapp. Dann sind Bausteine wie 74HC595 (Shift Register) oder I²C-Portexpander (z. B. MCP23017) sehr praktisch.

4) Leistungsstufe für echte Lampen
Sobald keine LEDs mehr geschaltet werden, sondern größere Lasten (Relais, 12 V Lampen, LED-Stripes), braucht man Transistoren/MOSFETs und eine saubere Versorgung. Der Mikrocontroller-Pin ist keine Stromquelle für „richtige“ Verbraucher.

5) ESP32: Web-Interface oder MQTT
Mit einem ESP32 kann man Zeiten live konfigurieren, per Weboberfläche den Status anzeigen oder per MQTT in ein Smarthome einbinden. Die nicht-blockierende Logik ist dafür praktisch Pflicht.


Fazit

Eine Arduino/ESP32-Ampel ist mehr als drei LEDs blinken zu lassen: Das Projekt zwingt zu sauberer Verdrahtung, bringt Timing-Grundlagen und ist der perfekte Einstieg in Zustandsmaschinen. Wenn man direkt mit millis() arbeitet, hat man später kaum Umbauarbeit, sobald Taster, Sensoren oder Kommunikation dazukommen. Und genau deshalb ist die Ampel so ein Klassiker: simpel genug zum Loslegen, aber „ehrlich“ genug, um gute Technikgewohnheiten zu lernen.

Wenn du willst, kann ich als nächsten Schritt eine Variante mit Fußgängertaster (inkl. Entprellung) und optionaler „Alles-Rot“-Räumzeit schreiben.

Anzeige

/comments0 Einträge

Kommentare

> NO_COMMENTS_FOUND

> INITIATE_COMMENT_PROTOCOL

MARKDOWN_SUPPORT: ENABLED
CHARS: 0