Fußgängerampel als Arduino-Projekt

Fußgängerampel als Arduino-Projekt

· 1.083 Wörter · 6 Minuten Lesezeit Leben

Meine Tochter Jade (1. Klasse) hat auf ihrem Schulweg eine Straße mit einer Fußgängerampel zu überqueren. Damit sie das Konzept in allen Aspekten versteht, haben wir ein „Projekt“ daraus gemacht und die Ampel mit einem Arduino (Microcontroller) nachgebaut. So wurde für sie ein weniger interessantes Thema plötzlich richtig spannend.

Begonnen haben wir mit der Frage, was an der Ampel denn passiert, was der normale Ablauf dort ist. Der Schwerpunkt ihrer Erzählung lag zuerst auf den Aspekten, die für sie als Fußgänger relevant sind. Ich habe die Fragestellung dann erweitert: Warum gibt es eigentlich überhaupt einen Zebrastreifen und eine Fußgängerampel. So sind wir dann darauf gekommen, dass der Grund wohl eine Anforderung ist: "Fußgänger sollen trotz relativ schnell fahrender Fahrzeuge sicher von einer auf die andere Straßenseite gelangen". Die Fußgängerampel haben wir dann als eine mögliche Lösung der Anforderung erkannt, wobei wir auch andere Lösungsmöglichkeiten gefunden haben.

Durch die reine Überlegung alleine entsteht aber noch keine Fußgängerampel, sie muss erst noch gebaut werden und sie muss dann auch funktionieren. Wie sie zu funktionieren hat, hatte Jade zuvor erst in einer Art Prosatext dargelegt, aus dem wir dann zusammen die sechs verschiedenen Zustände und ihre Abfolge abgeleitet haben. In einer kleinen Skizze haben wir für jede Phase sowohl ein Bild der Anzeige der Fußgängerampel als auch der Autoampel aufgemalt. Und Jade hat anschließend noch den Zeitpunkt identifiziert, an dem sie auf den Knopf drückt, der dem Ampelsystem mitteilt, dass jetzt ein Fußgänger die Straße überqueren möchte.

Damit der Aufbau recht einfach und zügig möglich ist, habe ich einen Arduino und ein TinkerKit-Shield mit passenden LEDs, Schaltern und weiteren Komponenten besorgt. Wir hätten auch ein Breadboard mit Widerständen, LEDs und einem Schalter bestücken und verdrahten können, aber das erschien mir für ein erstes Projekt dieser Art mit meiner 7 Jahre alten Tochter zu komplex. Wir werden das bestimmt später in einem anderen Projekt machen.

Da Jade gerade erst lesen und schreiben lernt, und - außer ein wenig mit Kudo für die XBOX 360 - noch nicht programmiert hat, habe ich das Programm für das Ampelprojekt geschrieben. Natürlich nach ihren Vorgaben - der Skizze mit den verschiedenen Ampelphasen. Dazu habe ich mich an den Beispiel-Programmen, die bei der Arduino Entwicklungsumgebung dabei sind, orientiert. Herausgekommen ist eine Ampelsteuerung, die jeder Arduino-Anfänger so oder so ähnlich schreiben würde. Sie funktioniert und verwendet für die Dauer der verschiedenen Ampelphasen reichlich die delay() Funktion.

/*
 * Pedestrian light project (loop cycle slowed down by delay()), 2012
 * An Arduino 1.0 project
 * Copyright Jade Garcia Manalo and Thomas Weitzel
 * This work is licensed under a Creative Commons License
 * https://creativecommons.org/licenses/by/3.0/
 */

// Digital pin no.
#define CR     13 // CAR_RED
#define CY     12 // CAR_YELLOW
#define CG      8 // CAR_GREEN
#define PR      7 // PEDESTRIAN_RED
#define PG      4 // PEDESTRIAN_GREEN
#define BUTTON  2 // THE BUTTON

const int numLights = 5;
const int lights[] = { CR, CY, CG, PR, PG };
const int numStates = 6;
const int delays[] = { 0, 3000, 3000, 10000, 5000, 3000 };
const boolean states[][numLights] = {
  //  CR    CY    CG    PR    PG
  {  LOW,  LOW, HIGH, HIGH,  LOW },
  {  LOW, HIGH,  LOW, HIGH,  LOW },
  { HIGH,  LOW,  LOW, HIGH,  LOW },
  { HIGH,  LOW,  LOW,  LOW, HIGH },
  { HIGH,  LOW,  LOW, HIGH,  LOW },
  { HIGH, HIGH,  LOW, HIGH,  LOW },
};

boolean lastButton = LOW;
boolean currentButton = LOW;

void setup() {
  pinMode(BUTTON, INPUT);
  for (int i = 0; i < numLights; i++) {
    pinMode(lights[i], OUTPUT);
  }
}

void loop() {
  setLightsAndWait(0);
  currentButton = digitalRead(BUTTON);
  if (currentButton != lastButton) {
    for (int i = 1; i < numStates; i++) {
      setLightsAndWait(i);
    }
  } else {
    lastButton = currentButton;
  }
}

void setLightsAndWait(int s) {
  for (int i = 0; i < numLights; i++) {
    digitalWrite(lights[i], states[s][i]);
  }
  delay(delays[s]); // TODO: get rid of the delay() function
}

Jetzt wird's ein klein bisschen technischer - wir verlassen jetzt das eigentliche Ampelprojekt: ein Microcontroller kann, während sich das Programm in der delay() Funktion befindet, nichts machen - er wartet nur auf den Ablauf der beim Aufruf übergebenen Zeit (das ist nicht ganz korrekt: er kann auf Interrupts reagieren). Auf diese Art kann ein Microcontroller nur eine unabhängige Ampel steuern, aber keine weitere, die sich irgendwo anders befindet - oder einen gänzlich anderen Prozess. Dabei hätte er durchaus das Potential dazu, wenn ihn nur mein ungeschickt geschriebenes Programm nicht daran hindern würde. Die Verwendung der delay() Funktion ist letztlich daran schuld.

Bei einem Microcontroller kommt es darauf an, dass er seine loop() Funktion möglichst schnell durchläuft, am besten viele Male pro Sekunde. Nur so kann er schnell viele Zustandsänderungen erkennen und behandeln. Das erfordert jedoch einen Verzicht auf die delay() Funktion. Nachfolgendes Arduino-Programm implementiert diese Anforderung. Eine weitere Laufzeitverbesserung könnte man beispielsweise erreichen, wenn setLights() in der loop() Funktion nur bei einer Änderung der Variable state aufgerufen würde.

/*
 * Pedestrian light project (fastest loop cycle), 2012
 * An Arduino 1.0 project
 * Copyright Jade Garcia Manalo and Thomas Weitzel
 * This work is licensed under a Creative Commons License
 * https://creativecommons.org/licenses/by/3.0/
 */

// Digital pin no.
#define CR     13 // CAR_RED
#define CY     12 // CAR_YELLOW
#define CG      8 // CAR_GREEN
#define PR      7 // PEDESTRIAN_RED
#define PG      4 // PEDESTRIAN_GREEN
#define BUTTON  2 // THE BUTTON

const int numLights = 5;
const int lights[] = { CR, CY, CG, PR, PG };
const int numStates = 6;
const double stateSeconds[] = { 10.0, 3.0, 3.0, 10.0, 5.0, 3.0 };
const boolean states[][numLights] = {
  //  CR    CY    CG    PR    PG
  {  LOW,  LOW, HIGH, HIGH,  LOW },
  {  LOW, HIGH,  LOW, HIGH,  LOW },
  { HIGH,  LOW,  LOW, HIGH,  LOW },
  { HIGH,  LOW,  LOW,  LOW, HIGH },
  { HIGH,  LOW,  LOW, HIGH,  LOW },
  { HIGH, HIGH,  LOW, HIGH,  LOW },
};

boolean lastButton = LOW;
boolean currentButton = LOW;
boolean buttonPressed = false;
long savedMillis = millis();
int state = 0; // Initial state is state 0; it's also the default state

void setup() {
  pinMode(BUTTON, INPUT);
  for (int i = 0; i < numLights; i++) {
    pinMode(lights[i], OUTPUT);
  }
}

void loop() {
  setLights(state);
  if (0 == state) {
    if (!buttonPressed) {
      // Monitor button presses only in state 0
      currentButton = digitalRead(BUTTON);
      if (currentButton != lastButton) {
        buttonPressed = true;
      } else {
        lastButton = currentButton;
      }
    } else {
      state = nextState(state);
    }
  } else {
    buttonPressed = false;
    state = nextState(state);
  }
}

// Sets the lights according the state provided
void setLights(int s) {
  for (int i = 0; i < numLights; i++) {
    digitalWrite(lights[i], states[s][i]);
  }
}

// Returns the state for the next loop cycle
int nextState(int s) {
  long currentMillis = millis();
  if (currentMillis - savedMillis > 1000.0 * stateSeconds[s]) {
    savedMillis = currentMillis;
    s = (s + 1) % numStates;
  }
  return s;
}

Jade hat nach Abschluss der Implementierung noch einige Zeit mit der Ampel, Spiel-Autos und Spiel-Fußgängern gespielt. Es hat ihr anscheinend großen Spaß gemacht.

Kommentare

Kelk1ng, 2015-09-18 um 10:15 Uhr

Sehr interessant.