Fahrgastinformationsanlagen mit OLED Displays

Dieser Bericht ist aktuell in Arbeit. Er wird demnächst fortgeführt.

Eine sehr interessante Verwendung von kleinen Displays im Modellbahnbereich sind funktionsfähige Fahrgastinformationsanlagen. Als Lesestoff vorweg empfehle ich diesen Thread im Stummi-Forum. Dort wird sehr rege und informativ zum Thema Bahnsteiganzeiger diskutiert. Ich habe mich mit drei verschiedenen Displaytypen eingedeckt:

0,91 Zoll 128 X 32 Pixel OLED Display (klassische für Bahnsteiganzeigen)
0,69 Zoll 96 x 16 Pixel OLED Display (kleinere Version für einzeilige Laufschriftanzeiger)
1,54 Zoll 128 x 64 Pixel OLED Display (größeres Display für Werbeanzeigen)

Folgende Experimente habe ich aktuell schon gemacht:

1. Den Beispielcode vom Stummi-Forum auf einen Arduino Nano gespielt um damit eine Bahnsteiganzeige zu generieren.

2. Einen eigenen Code für meine Straßenbahn geschrieben. Der erste Abfahrtsanzeiger steht am Hauptbahnhof und gibt in Echtzeit die nächsten Abfahrten wieder. Er ist mit den getakteten Abfahrten synchronisiert die der Arduino generiert und an die Digitalzentrale über S88 generiert. Daher stimmen diese Abfahrtsangaben wirklich!

Zum Vergleich habe ich den exakt selben Code einmal auf das 0,69 Zoll 96 x 16 Pixel gespielt. Dieses ist etwas kleiner und eignet sich z.B. für die dynamischen einzeiligen Schriftanzeiger an kleinen Haltepunkten.

Die Displays werden über I2C angesteuert. Da sie alle gleiche Adressen haben, benötigt man für mehrere Displays an einem Bus einen TCA9548A I2C 8 Kanal Multiplexer. Dazu werde ich auch noch Informationen weiter geben.

Zu den ersten Tests habe ich euch ein Video aufgenommen:

Anbei schon mal der Code für den Abfahrtsanzeiger am Bahnhof.

#include 
#include "TimerOne.h"

U8GLIB_SSD1306_128X32_2X u8g(U8G_I2C_OPT_NONE);

int Takt;
int Automatik = 0;
int Blinker = 0;
long Takt_WendeschleifeA;
long Takt_WendeschleifeB;
long Takt_SchattenbahnhofA;
long Takt_SchattenbahnhofB;
int ZeitbisAbfahrt_Ziel1A;
int ZeitbisAbfahrt_Ziel1B;
int ZeitbisAbfahrt_Ziel2A;
int ZeitbisAbfahrt_Ziel2B;
int Takt_Ziel1A;
int Takt_Ziel1B;
int Takt_Ziel2A;
int Takt_Ziel2B;
int Zeile_Ziel1A;
int Zeile_Ziel1B;
int Zeile_Ziel2A;
int Zeile_Ziel2B;

int Takt_Wendeschleife_Kontakt = 4;
int Takt_Schattenbahnhof_Kontakt = 3;
int Automatik_Eingang = 5;

void setup()
{
  Takt = 0;
  Takt_WendeschleifeA = random(580,600);
  Takt_WendeschleifeB = random(1480,1500);
  Takt_SchattenbahnhofA = random(640,740);
  Takt_SchattenbahnhofB = random(1540,1640);
  ZeitbisAbfahrt_Ziel1A = Takt_WendeschleifeA+250;
  ZeitbisAbfahrt_Ziel1B = Takt_WendeschleifeB+250;
  ZeitbisAbfahrt_Ziel2A = Takt_SchattenbahnhofA+280;
  ZeitbisAbfahrt_Ziel2B = Takt_SchattenbahnhofB+280;
  Zeile_Ziel1A = 7;
  Zeile_Ziel2A = 17;
  Zeile_Ziel1B = 27;
  Zeile_Ziel2B = 37;

  pinMode(Takt_Wendeschleife_Kontakt, OUTPUT);        // Kontakt für Rückmeldedecoder zur Abfahrtssteuerung von der Wendeschleife
  pinMode(Takt_Schattenbahnhof_Kontakt, OUTPUT);      // Kontakt für Rückmeldedecoder zur Abfahrtssteuerung aus dem Schattenbahnhof
  pinMode(Automatik_Eingang, INPUT);                  // Eingang vom Schalter um Automatikbetrieb manuell ein- oder auszuschalten
  digitalWrite(Takt_Wendeschleife_Kontakt, LOW);
  digitalWrite(Takt_Schattenbahnhof_Kontakt, LOW);

  Timer1.initialize(200000);                          // Interrupt alle 200 ms
  Timer1.attachInterrupt(Zeitkonstante);

  Serial.begin(9600);                                 // Serieller Monitor zu Kontrollzwecken
}

void draw()
{
  u8g.setFont(u8g_font_5x8);    // Schriftgröße
  u8g.setRot180();              // Rotation um 180°

  if (Automatik == 1)           // Wenn Automatikbetrieb eingeschaltet
  {
    // Anzeige Ziel 1 A
    if (ZeitbisAbfahrt_Ziel1A >= 1900 && Zeile_Ziel1A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1A, "1   Altstadt            7'");
    }
    if (ZeitbisAbfahrt_Ziel1A >= 1600 && ZeitbisAbfahrt_Ziel1A < 1900 && Zeile_Ziel1A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1A, "1   Altstadt            6'");
    }
    if (ZeitbisAbfahrt_Ziel1A >= 1300 && ZeitbisAbfahrt_Ziel1A < 1600 && Zeile_Ziel1A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1A, "1   Altstadt            5'");
    }
    if (ZeitbisAbfahrt_Ziel1A >= 1000 && ZeitbisAbfahrt_Ziel1A < 1300 && Zeile_Ziel1A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1A, "1   Altstadt            4'");
    }
    if (ZeitbisAbfahrt_Ziel1A >= 700 && ZeitbisAbfahrt_Ziel1A < 1000 && Zeile_Ziel1A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1A, "1   Altstadt            3'");
    }
    if (ZeitbisAbfahrt_Ziel1A >= 400 && ZeitbisAbfahrt_Ziel1A < 700 && Zeile_Ziel1A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1A, "1   Altstadt            2'");
    }
    if (ZeitbisAbfahrt_Ziel1A >= 200 && ZeitbisAbfahrt_Ziel1A < 400 && Zeile_Ziel1A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1A, "1   Altstadt            1'");
    }
    if (ZeitbisAbfahrt_Ziel1A >= 0 && ZeitbisAbfahrt_Ziel1A < 200 && Blinker <= 6 && Zeile_Ziel1A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1A, "1   Altstadt");
    }
    if (ZeitbisAbfahrt_Ziel1A >= 0 && ZeitbisAbfahrt_Ziel1A < 200 && Blinker > 6 && Zeile_Ziel1A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1A, "");
    }

    // Anzeige Ziel 1 B
    if (ZeitbisAbfahrt_Ziel1B >= 1900 && Zeile_Ziel1B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1B, "1   Altstadt            7'");
    }
    if (ZeitbisAbfahrt_Ziel1B >= 1600 && ZeitbisAbfahrt_Ziel1B < 1900 && Zeile_Ziel1B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1B, "1   Altstadt            6'");
    }
    if (ZeitbisAbfahrt_Ziel1B >= 1300 && ZeitbisAbfahrt_Ziel1B < 1600 && Zeile_Ziel1B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1B, "1   Altstadt            5'");
    }
    if (ZeitbisAbfahrt_Ziel1B >= 1000 && ZeitbisAbfahrt_Ziel1B < 1300 && Zeile_Ziel1B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1B, "1   Altstadt            4'");
    }
    if (ZeitbisAbfahrt_Ziel1B >= 700 && ZeitbisAbfahrt_Ziel1B < 1000 && Zeile_Ziel1B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1B, "1   Altstadt            3'");
    }
    if (ZeitbisAbfahrt_Ziel1B >= 400 && ZeitbisAbfahrt_Ziel1B < 700 && Zeile_Ziel1B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1B, "1   Altstadt            2'");
    }
    if (ZeitbisAbfahrt_Ziel1B >= 200 && ZeitbisAbfahrt_Ziel1B < 400 && Zeile_Ziel1B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1B, "1   Altstadt            1'");
    }
    if (ZeitbisAbfahrt_Ziel1B >= 0 && ZeitbisAbfahrt_Ziel1B < 200 && Blinker <= 6 && Zeile_Ziel1B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1B, "1   Altstadt");
    }
    if (ZeitbisAbfahrt_Ziel1B >= 0 && ZeitbisAbfahrt_Ziel1B < 200 && Blinker > 6 && Zeile_Ziel1B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel1B, "");
    }

    // Anzeige Ziel 2 A
    if (ZeitbisAbfahrt_Ziel2A >= 1900 && Zeile_Ziel2A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2A, "1   Am Sonnfeld         7'");
    }
    if (ZeitbisAbfahrt_Ziel2A >= 1600 && ZeitbisAbfahrt_Ziel2A < 1900 && Zeile_Ziel2A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2A, "1   Am Sonnfeld         6'");
    }
    if (ZeitbisAbfahrt_Ziel2A >= 1300 && ZeitbisAbfahrt_Ziel2A < 1600 && Zeile_Ziel2A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2A, "1   Am Sonnfeld         5'");
    }
    if (ZeitbisAbfahrt_Ziel2A >= 1000 && ZeitbisAbfahrt_Ziel2A < 1300 && Zeile_Ziel2A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2A, "1   Am Sonnfeld         4'");
    }
    if (ZeitbisAbfahrt_Ziel2A >= 700 && ZeitbisAbfahrt_Ziel2A < 1000 && Zeile_Ziel2A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2A, "1   Am Sonnfeld         3'");
    }
    if (ZeitbisAbfahrt_Ziel2A >= 350 && ZeitbisAbfahrt_Ziel2A < 700 && Zeile_Ziel2A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2A, "1   Am Sonnfeld         2'");
    }
    if (ZeitbisAbfahrt_Ziel2A >= 200 && ZeitbisAbfahrt_Ziel2A < 400 && Zeile_Ziel2A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2A, "1   Am Sonnfeld         1'");
    }
    if (ZeitbisAbfahrt_Ziel2A >= 0 && ZeitbisAbfahrt_Ziel2A < 200 && Blinker <= 6 && Zeile_Ziel2A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2A, "1   Am Sonnfeld");
    }
    if (ZeitbisAbfahrt_Ziel2A >= 0 && ZeitbisAbfahrt_Ziel2A < 200 && Blinker > 6 && Zeile_Ziel2A < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2A, "");
    }

    // Anzeige Ziel 2 B
    if (ZeitbisAbfahrt_Ziel2B >= 1900 && Zeile_Ziel2B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2B, "1   Am Sonnfeld         7'");
    }
    if (ZeitbisAbfahrt_Ziel2B >= 1600 && ZeitbisAbfahrt_Ziel2B < 1900 && Zeile_Ziel2B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2B, "1   Am Sonnfeld         6'");
    }
    if (ZeitbisAbfahrt_Ziel2B >= 1300 && ZeitbisAbfahrt_Ziel2B < 1600 && Zeile_Ziel2B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2B, "1   Am Sonnfeld         5'");
    }
    if (ZeitbisAbfahrt_Ziel2B >= 1000 && ZeitbisAbfahrt_Ziel2B < 1300 && Zeile_Ziel2B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2B, "1   Am Sonnfeld         4'");
    }
    if (ZeitbisAbfahrt_Ziel2B >= 700 && ZeitbisAbfahrt_Ziel2B < 1000 && Zeile_Ziel2B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2B, "1   Am Sonnfeld         3'");
    }
    if (ZeitbisAbfahrt_Ziel2B >= 400 && ZeitbisAbfahrt_Ziel2B < 700 && Zeile_Ziel2B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2B, "1   Am Sonnfeld         2'");
    }
    if (ZeitbisAbfahrt_Ziel2B >= 200 && ZeitbisAbfahrt_Ziel2B < 400 && Zeile_Ziel2B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2B, "1   Am Sonnfeld         1'");
    }
    if (ZeitbisAbfahrt_Ziel2B >= 0 && ZeitbisAbfahrt_Ziel2B < 200 && Blinker <= 6 && Zeile_Ziel2B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2B, "1   Am Sonnfeld");
    }
    if (ZeitbisAbfahrt_Ziel2B >= 0 && ZeitbisAbfahrt_Ziel2B < 200 && Blinker > 6 && Zeile_Ziel2B < 30)
    {
      u8g.drawStr(0, Zeile_Ziel2B, "");
    }
  }
  else
  {
    u8g.drawStr(0, 7, "Aktuell liegen keine");
    u8g.drawStr(0, 17, "Abfahrten vor");
  }
  
}

void Zeitkonstante()
{
  if (Automatik == 1)     // wenn Automatikbetrieb eingeschaltet ist, wird der Takt gezählt
  {
    Takt++;
    ZeitbisAbfahrt_Ziel1A--;
    ZeitbisAbfahrt_Ziel1B--;
    ZeitbisAbfahrt_Ziel2A--;
    ZeitbisAbfahrt_Ziel2B--;
    Takt_Ziel1A--;
    Takt_Ziel1B--;
    Takt_Ziel2A--;
    Takt_Ziel2B--;
  }

  Blinker++;

  Serial.println(ZeitbisAbfahrt_Ziel1A);       // Serieller Monitor zu Kontrollzwecken
  Serial.println(Takt_Ziel1A);                 // Serieller Monitor zu Kontrollzwecken
}

void loop()
{
  // Prüfen ob Automatik aktiv
  Automatik = digitalRead(Automatik_Eingang);

  // Display Schleife
  u8g.firstPage();  
  do
  {
    draw();
  }
  while( u8g.nextPage() );

  // Blinkgeber Display
  if (Blinker > 12)
  {
    Blinker = 1;
  }

  // Taktgeber
  if (Takt >= 1800)
  {
    Takt = 0;
  }

  // Auslösung Kontakte für PC Steuerung zur Abfahrt der Straßenbahn von der Wendeschleife
  if (Takt == Takt_WendeschleifeA)
  {
    digitalWrite(Takt_Wendeschleife_Kontakt, HIGH);
  }
  if (Takt == Takt_WendeschleifeA+10)
  {
    digitalWrite(Takt_Wendeschleife_Kontakt, LOW);
  }
  if (Takt == 610)
  {
    Takt_WendeschleifeA = random(580,600);                // generiere eine Zufallszeit für die Abfahrt von der Wendeschleife
  }
  if (Takt == Takt_WendeschleifeB)
  {
    digitalWrite(Takt_Wendeschleife_Kontakt, HIGH);
  }
  if (Takt == Takt_WendeschleifeB+10)
  {
    digitalWrite(Takt_Wendeschleife_Kontakt, LOW);
  }
  if (Takt == 1510)
  {
    Takt_WendeschleifeB = random(1480,1500);              // generiere eine Zufallszeit für die Abfahrt von der Wendeschleife
  }

  // Auslösung Kontakte für PC Steuerung zur Abfahrt der Straßenbahn aus dem Schattenbahnhof
  if (Takt == Takt_SchattenbahnhofA)
  {
    digitalWrite(Takt_Schattenbahnhof_Kontakt, HIGH);
  }
  if (Takt == Takt_SchattenbahnhofA+10)
  {
    digitalWrite(Takt_Schattenbahnhof_Kontakt, LOW);
  }
  if (Takt == 750)
  {
    Takt_SchattenbahnhofA = random(640,740);              // generiere eine Zufallszeit für die Abfahrt aus dem Schattenbahnhof
  }
  if (Takt == Takt_SchattenbahnhofB)
  {
    digitalWrite(Takt_Schattenbahnhof_Kontakt, HIGH);
  }
  if (Takt == Takt_SchattenbahnhofB+10)
  {
    digitalWrite(Takt_Schattenbahnhof_Kontakt, LOW);
  }
  if (Takt == 1650)
  {
    Takt_SchattenbahnhofB = random(1540,1640);            // generiere eine Zufallszeit für die Abfahrt aus dem Schattenbahnhof
  }

  // Synchronisierung der Abfahrtsanzeige mit dem Takt für die kommende Abfahrt
  if (Takt == 700)
  {
    Takt_Ziel1A = Takt_WendeschleifeA+1350;               // Zeitstempel für die die generierte Zufallszeit wird für den Anzeiger initialisiert
  }
  if (Takt == 1600)
  {
    Takt_Ziel1B = Takt_WendeschleifeB+450;                // Zeitstempel für die die generierte Zufallszeit wird für den Anzeiger initialisiert
  }
  if (Takt == 800)
  {
    Takt_Ziel2A = Takt_SchattenbahnhofA+1280;             // Zeitstempel für die die generierte Zufallszeit wird für den Anzeiger initialisiert
  }
  if (Takt == 1700)
  {
    Takt_Ziel2B = Takt_SchattenbahnhofB+380;              // Zeitstempel für die die generierte Zufallszeit wird für den Anzeiger initialisiert
  }

  // Wenn die Straßenbahn der obersten Zeile abgefahren ist, rücke nachfolgende Zeilen nach und generiere einen Zeitstempel für die Abfahrt der Straßenbahn der untersten Zeile
  if (ZeitbisAbfahrt_Ziel1A == 0)
  {
    ZeitbisAbfahrt_Ziel1A = Takt_Ziel1A;                  // Übernehme den generierten Zeitstempel in die aktuelle Anzeige
    Zeile_Ziel1A = 37;
    Zeile_Ziel2A = 7;
    Zeile_Ziel1B = 17;
    Zeile_Ziel2B = 27;
  }
  if (ZeitbisAbfahrt_Ziel1B == 0)
  {
    ZeitbisAbfahrt_Ziel1B = Takt_Ziel1B;                  // Übernehme den generierten Zeitstempel in die aktuelle Anzeige
    Zeile_Ziel1A = 17;
    Zeile_Ziel2A = 27;
    Zeile_Ziel1B = 37;
    Zeile_Ziel2B = 7;
  }
  if (ZeitbisAbfahrt_Ziel2A == 0)
  {
    ZeitbisAbfahrt_Ziel2A = Takt_Ziel2A;                  // Übernehme den generierten Zeitstempel in die aktuelle Anzeige
    Zeile_Ziel1A = 27;
    Zeile_Ziel2A = 37;
    Zeile_Ziel1B = 7;
    Zeile_Ziel2B = 17;
  }
  if (ZeitbisAbfahrt_Ziel2B == 0)
  {
    ZeitbisAbfahrt_Ziel2B = Takt_Ziel2B;                  // Übernehme den generierten Zeitstempel in die aktuelle Anzeige
    Zeile_Ziel1A = 7;
    Zeile_Ziel2A = 17;
    Zeile_Ziel1B = 27;
    Zeile_Ziel2B = 37;
  }
}

Das Thema wird zeitnah mit weiteren Informationen fortgesetzt!