Simple Abfahrtsanzeige

Hier ist eine simple Zug- Abfahrtsanzeige. Sie erhebt keinen Anspruch darauf, originalgetreu dem österreichischen Vorbild zu sein, es ist eine nette, kleine Spielerei um die Modelleisenbahn um ein paar kleine Details zu ergänzen.

Dazu braucht man nur einen Arduino(Nano) und ein OLED Display mit IIC oder I2C Anbindung.

 

Features

Es ist absichtlich recht einfach gehalten, um auch Modellbahner mit wenig Programmier- und Interfaceerfahrung nicht abzuschrecken.

Die Abfahranzeige läuft in einer Schleife, mit einer vordefinierten Sequenz an Zugzielen, Bahnsteignummern und Wartezeiten. Diese Listen können vor dem Aufspielen auf den Arduino (fast) nach Belieben geändert werden, beim Programmablauf aber nicht beeinflußt oder geändert werden. Die Zeitabfolge läuft von unten nach oben, das heißt als Nächstes abfahrende Zug ist an 1. Stelle, der zuletzt abfahrende Zug an letzter Stelle. Je nachdem, wieviele Züge hier eingetragen wurden, ist die Liste insgesamt eher länger oder kürzer.

Beispiel: REX Zug nach Salzburg auf Gleis 3 erscheint in der letzten Zeile, Abfahrt in 9 Minuten. Wenn der nächste Zug abgefahren ist (er verschwindet aus der 1. Zeile) rückt der REX Zug nach Salzburg auf Gleis 3 in die vorletzte Zeile nach, Abfahrt in 6 Minuten. usw...

Die Zeit, nach wievielen Sekunden die Zeilen nachrücken sollen, kann natürlich geändert werden, Default sind hier 10 Sekunden.

Umlaute und Sonderzeichen können leider nicht dargestellt werden.

 

Schaltung

Wie eingangs schon erwähnt, die Schaltung ist simpel. In meinem Beispiel habe ich 2 Displays angeschlossen, die zeigen das exakt gleiche Bild. Wer mehr oder weniger Displays anhängen möchte, entfernt einfach die 2 Datenleitungen vom Arduino oder klemmt zusätzliche auf die beiden schon Vorhandenen. Es ist keine Umkonfiguration nötig.

 

 

Was wird benötigt?

Die Schaltung ist mehr als simpel, man benötigt nur:

  • einen Arduino (in meinem Fall ein chinesischer Nano Klon mit 5V Logikspannung)
  • ein OLED Display mit IIC bzw. I2C) Anschluß. Wichtig sind nur die folgenden Werte:
    • Logikspannung (5V oder 3,3V) muß mit dem Arduino übereinstimmen
    • Anschluß über IIC bzw. I2C (2 Pins + 2 Pins für Spannungsversorgung)

    • Info: Falls ein anderes OLED Display als 128X64 Pixel oder ein andere Treiber als SSD1306 verwendet wird, muss das im Sketch vorher angepaßt werden!
    • Suchtip für Aliexpress: 0.96 inch OLED 128X64 OLED Display IIC),

 

TIP: Arduino Displayansteuerungen

Sehr viel Info und libs für Monochromdisplays findet man bei olikraus.

https://github.com/olikraus/U8g2_Arduino

U8G2 Bibliothek

Die Bibliothek von olikraus muß zuvor noch eingebunden werden, macht die Arduino IDE eh scho von selbst, einfach Unter Sketch / Bibliothek einbinden / Bibliotheken verwalten nach 8g2 suchen und installieren.

 

Sketch

/*
  Initial idea from https://forum.arduino.cc/index.php?topic=512089.0https://phobien.ndesign.de Posting #13
  Display Library & Doku  https://github.com/olikraus/u8g2/wiki
  possibly helpful  https://arduino-projekte.info/schriftarten-fuer-oled-display/

  OLED Displays must use IIC bus but can be connected to an digital I/O Port of the Arduino.
  Please check that Voltage of displays and Arduino corresponds.

  Arduino Nano (china Clone, with 5V logic level Voltage) used
  OLED Display: 4pin 0.96" White/Blue/Yellow blue 0.96 inch OLED 128X64 OLED Display Module 0.96" IIC I2C Communicate for arduino
  Driver IC SSD1306, VCC 3-12V, Interface IIC/SPI
  
  OLED SSD1306 IIC  Display #1 - SCK - D2
  OLED SSD1306 IIC  Display #1 - SDA - D3

  There can be connected more Displays to those ports (of course they will show the same content)
  no further changes necessary
  OLED SSD1306 IIC  Display #2 - SCK - D2
  OLED SSD1306 IIC  Display #2 - SDA - D2

  FONTS used
  u8g2_font_crox3hb_tr               bold for Station name/direction, 12 pixel Font
  u8g2_font_crox2h_tf                common text 10 pixel Font
  Optional FONTS (not used here)
  u8g2_font_HelvetiPixelOutline_tr   Ads?, 12 pixel Font
  u8g2_font_tenstamps_mr             inverted (like website access counter) 12 pixel Font


  DISPLAY Options
  5 lines of Info for train lists plus Header
  
  per line following information is shown:
  a. Train Type 3 characters (REX, R, ICE, S50, CJX, ...)
  b. destination City e.g. "Salzburg" max 14 characters, if more chars are specified they will be cut off
  c. Track number, one digit e.g. 5
  d. Time [minutes] until train arrives, one digit e.g. 2

  optional:
  Clear display = screen_OLED[X]("", "", "", 0, 0);

  Example CALL:

  OLED1.firstPage();
  do {
    screen_OLED1_header();
    screen_OLED1_1stline("R  ", "Linz", 3, 0);
    screen_OLED1_2ndline("CJX", "Twinkydorf", 4, 2);
    screen_OLED1_3rdline("S45", "Zwettl", 5, 4);
    screen_OLED1_4thline("R  ", "Pressbaum", 1, 5);
    screen_OLED1_5thline("REX", "Wien West BHF", 2, 6);   
  } while ( OLED1.nextPage() );
  delay(std_delay);

*/

#include <U8g2lib.h>
#include <Wire.h>

//======== INIT Section =============
int std_delay = 10000;                //define standard delay between Screen steps

//Define Display type and Pins used (any digital IO pin possible) for every Display
U8G2_SSD1306_128X64_NONAME_1_SW_I2C OLED1(U8G2_R0, /* clock=*/ 2, /* data=*/ 3, /* reset=*/ U8X8_PIN_NONE);

//======== INIT Section END =========

void setup() {
  Serial.begin(9600);
  Serial.println("Setup started...");
  OLED1.begin();                                   //start Display1
  Serial.println("std_delay set to " + String(std_delay));
  Serial.println("Setup done...");
}

//Define subroutine for Display-Header
void screen_OLED1_header() {
  OLED1.setFont(u8g2_font_5x7_tr);
  OLED1.setCursor(0, 10);
  OLED1.println("ZUG ZIEL       GLEIS  MIN" );
}

//Define subroutine for 1st Display-line
void screen_OLED1_1stline(String Zug, String Ziel, byte Gleis, byte Min) {
  if (Ziel.length() < 15) {
    do {
      Ziel = Ziel + " ";
    } while (Ziel.length() < 14);
  } else {
    Ziel.remove(14);
  }

  OLED1.setFont(u8g2_font_5x7_tr);
  OLED1.setCursor(0, 20);
  OLED1.println(Zug + " " + Ziel  + " " + String(Gleis) + "   " + String(Min));
}

//Define subroutine for 2nd Display-line
void screen_OLED1_2ndline(String Zug, String Ziel, byte Gleis, byte Min) {
  if (Ziel.length() < 15) {
    do {
      Ziel = Ziel + " ";
    } while (Ziel.length() < 14);
  } else {
    Ziel.remove(14);
  }

  OLED1.setFont(u8g2_font_5x7_tr);
  OLED1.setCursor(0, 30);
  OLED1.println(Zug + " " + Ziel  + " " + String(Gleis) + "   " + String(Min));
}

//Define subroutine for 3rd Display-line
void screen_OLED1_3rdline(String Zug, String Ziel, byte Gleis, byte Min) {
  if (Ziel.length() < 15) { // fill Target name with blanks to 14  characters
    do {
      Ziel = Ziel + " ";
    } while (Ziel.length() < 14);
  } else { // shrink Target name to 14 or less characters
    Ziel.remove(14);
  }

  OLED1.setFont(u8g2_font_5x7_tr);
  OLED1.setCursor(0, 40);
  OLED1.println(Zug + " " + Ziel  + " " + String(Gleis) + "   " + String(Min));
}

//Define subroutine for 4th Display-line
void screen_OLED1_4thline(String Zug, String Ziel, byte Gleis, byte Min) {
  if (Ziel.length() < 15) {
    do {
      Ziel = Ziel + " ";
    } while (Ziel.length() < 14);
  } else {
    Ziel.remove(14);
  }

  OLED1.setFont(u8g2_font_5x7_tr);
  OLED1.setCursor(0, 50);
  OLED1.println(Zug + " " + Ziel  + " " + String(Gleis) + "   " + String(Min));
}

//Define subroutine for 5th Display-line
void screen_OLED1_5thline(String Zug, String Ziel, byte Gleis, byte Min) {
  if (Ziel.length() < 15) {
    do {
      Ziel = Ziel + " ";
    } while (Ziel.length() < 14);
  } else {
    Ziel.remove(14);
  }

  OLED1.setFont(u8g2_font_5x7_tr);
  OLED1.setCursor(0, 60);
  OLED1.println(Zug + " " + Ziel  + " " + String(Gleis) + "   " + String(Min));
}

void loop(void) {
  Serial.println("loop started (again)");

  // ======================== STEP ===============================
  OLED1.firstPage();
  do {
    screen_OLED1_header();
    screen_OLED1_1stline("REX", "Salzburg", 5, 2);
    screen_OLED1_2ndline("S50", "Tweetydorf", 2, 3);
    screen_OLED1_3rdline(" R ", "Linz", 3, 5);
    screen_OLED1_4thline("CJX", "Twinkydorf", 4, 6);
    screen_OLED1_5thline("S45", "Zwettl", 5, 9);
  } while ( OLED1.nextPage() );
  delay(std_delay);

  //OLED2.firstPage();
  //  do {
  //    screen_OLED2("SALZBURG", "RJ 65", "", 1, 6);
  //  } while ( OLED2.nextPage() );
  //delay(std_delay);

  OLED1.firstPage();
  do {
    screen_OLED1_header();
    screen_OLED1_1stline("REX", "Salzburg", 5, 0);
    screen_OLED1_2ndline("S50", "Tweetydorf", 2, 1);
    screen_OLED1_3rdline(" R ", "Linz", 3, 3);
    screen_OLED1_4thline("CJX", "Twinkydorf", 4, 4);
    screen_OLED1_5thline("S45", "Zwettl", 5, 7);
  } while ( OLED1.nextPage() );
  delay(std_delay);

  OLED1.firstPage();
  do {
    screen_OLED1_header();
    screen_OLED1_1stline("S50", "Tweetydorf", 2, 0);
    screen_OLED1_2ndline(" R ", "Linz", 3, 1);
    screen_OLED1_3rdline("CJX", "Twinkydorf", 4, 2);
    screen_OLED1_4thline("S45", "Zwettl", 2, 6);
    screen_OLED1_5thline(" R ", "Pressbaum", 5, 8);
  } while ( OLED1.nextPage() );
  delay(std_delay);

  OLED1.firstPage();
  do {
    screen_OLED1_header();
    screen_OLED1_1stline("R  ", "Linz", 3, 0);
    screen_OLED1_2ndline("CJX", "Twinkydorf", 4, 2);
    screen_OLED1_3rdline("S45", "Zwettl", 2, 4);
    screen_OLED1_4thline("R  ", "Pressbaum", 5, 6);
    screen_OLED1_5thline("REX", "Wien West BHF", 2, 6);   
  } while ( OLED1.nextPage() );
  delay(std_delay);

  OLED1.firstPage();
  do {
    screen_OLED1_header();
    screen_OLED1_1stline("CJX", "Twinkydorf", 4, 0);
    screen_OLED1_2ndline("S45", "Zwettl", 2, 2);
    screen_OLED1_3rdline(" R ", "Pressbaum", 5, 4);
    screen_OLED1_4thline("REX", "Wien West BHF", 2, 5);   
    screen_OLED1_5thline("S50", "Loosdorf", 3, 9); 
  } while ( OLED1.nextPage() );
  delay(std_delay);

  OLED1.firstPage();
  do {
    screen_OLED1_header();
    screen_OLED1_1stline("S45", "Zwettl", 2, 0);
    screen_OLED1_2ndline(" R ", "Pressbaum", 5, 2);
    screen_OLED1_3rdline("REX", "Wien West BHF", 2, 3);
    screen_OLED1_4thline("S50", "Loosdorf", 3, 6);   
    screen_OLED1_5thline("RJ ", "Wien HBF", 2, 7); 
  } while ( OLED1.nextPage() );
  delay(std_delay);
  
  OLED1.firstPage();
  do {
    screen_OLED1_header();
    screen_OLED1_1stline(" R ", "Pressbaum", 5, 0);
    screen_OLED1_2ndline("REX", "Wien West BHF", 2, 1);
    screen_OLED1_3rdline("S50", "Loosdorf", 3, 5);
    screen_OLED1_4thline("RJ ", "Wien HBF", 2, 6);   
    screen_OLED1_5thline("REX", "Salzburg", 5, 9); 
  } while ( OLED1.nextPage() );
  delay(std_delay);
  
  OLED1.firstPage();
  do {
    screen_OLED1_header();
    screen_OLED1_1stline("REX", "Wien West BHF", 2, 0);
    screen_OLED1_2ndline("S50", "Loosdorf", 3, 2);
    screen_OLED1_3rdline("RJ ", "Wien HBF", 2, 2);
    screen_OLED1_4thline("REX", "Salzburg", 5, 7);   
    screen_OLED1_5thline("S50", "Tweetydorf", 2, 9); 
  } while ( OLED1.nextPage() );
  delay(std_delay);  

  OLED1.firstPage();
  do {
    screen_OLED1_header();
    screen_OLED1_1stline("S50", "Loosdorf", 3, 0);
    screen_OLED1_2ndline("RJ ", "Wien HBF", 2, 1);
    screen_OLED1_3rdline("REX", "Salzburg", 5, 4);
    screen_OLED1_4thline("S50", "Tweetydorf", 2, 7);   
    screen_OLED1_5thline(" R ", "Linz", 3, 8); 
  } while ( OLED1.nextPage() );
  delay(std_delay);  
  
  OLED1.firstPage();
  do {
    screen_OLED1_header();
    screen_OLED1_1stline("RJ ", "Wien HBF", 2, 0);
    screen_OLED1_2ndline("REX", "Salzburg", 5, 1);
    screen_OLED1_3rdline("S50", "Tweetydorf", 2, 5);
    screen_OLED1_4thline(" R ", "Linz", 3, 7);   
    screen_OLED1_5thline("CJX", "Twinkydorf", 4, 9); 
  } while ( OLED1.nextPage() );
  delay(std_delay); 
}

 

Wie man sieht, ist das einfach eine Aneinandereihung von Display-Aufrufen:

  OLED1.firstPage();
  do {
    screen_OLED1_header();
    screen_OLED1_1stline("RJ ", "Wien HBF", 2, 0);
    screen_OLED1_2ndline("REX", "Salzburg", 5, 1);
    screen_OLED1_3rdline("S50", "Tweetydorf", 2, 5);
    screen_OLED1_4thline(" R ", "Linz", 3, 7);   
    screen_OLED1_5thline("CJX", "Twinkydorf", 4, 9); 
  } while ( OLED1.nextPage() );
  delay(std_delay); 
}

Jede Zeile in diesem Konstrukt ist inhaltlich folgendermaßen aufgebaut:

  1. String: Zugname 3 Stellen fix, Leerzeichen müssen manuell gesetzt werden
  2. String: Zielbahnhof maximal 14 Stellen, Weniger oder mehr Zeichen werden mit Blanks aufgefüllt oder abgeschnitten
  3. Byte: Bahnsteig, einstellig, Zahlen von 0-9
  4. Byte: Zeit bis zur Abfahrt, einstellig, Zahlen von 0-9 

 

Ich habe hier absichtlich keine Automatismen eingebaut, könnte natürlich die Zugziele in ein Array reinschreiben und das dann Stück für Stück auslesen,  aber das wäre für eine simple Lösung zu kompliziert.

Was ich eingebaut hab, ist eine Prüfung auf die Stellenanzahl beim Zielnamen auf 14, bei weniger werden Leerzeichen hinzugefügt, bei mehr wird der Name abgeschnitten. Zugname mit 3 Zeichen, Bahnsteignummer und Zeit mit je einer einstelligen Zahl angeben!