Jump to content
Schiffsmodell.net

Fahrzielanzeige der Hadag 2000 - eine Lösung in 1:25


Meinolf Höhler

Recommended Posts

Meinolf Höhler

In der nächsten Zeit werde ich hier meine Lösung für eine Fahrzielanzeige vorstellen, welche speziell an die Hadag Fähre von Kapitän Odin in 1:25 abgepasst ist. Mit anderen Teilen sollte sich eine ähnliche Lösung für andere Maßstäbe oder Anwendungen finden lassen.

 

Kleine Vorgeschichte, wie es dazu kam:

Der Baubericht von Kapitän Odins Hadag ist ja mit reichlich Bildern ausgestattet und hier im Forum sicher fast jeden bekannt. Die Fähre hat einige nette Funktionen bekommen mit dem beleuchteten Kiosk, den Monitoren in Fahrstand und was ich sonst noch so vergessen haben sollte.

 

Es begab sich dann zur Intermodellbau 2015, dass der Schiffsmodell.net Stand für einige Zeit keine Besucher zu versorgen hatte und wir uns über allen möglichen Unsinn unterhalten haben.

Die Fahrtzielanzeiger waren bislang Messingkisten mit einem Papierstreifen drin, der noch beleuchtet werden müsste. Alles irgendwie statisch und der optische Effekt, naaaaaja, es geht, aber noch nicht der Brüller.

Weitere Ideen, die hier im Forum herumgeisterten, waren alte Magnetbänder irgndwie so zu behandeln, dass zumindest die Zwichenstationen "animiert" sind. Ich kann mir vorstellen, was da gebaut werden sollte, der Effekt mag nett sein, aber irgendwie auch nicht der WOW-Effekt

.

Irgendwie sind wir auf die Monitore im Fahrstand gekommen, und dass es doch verschiedenste Displays in diversen Technologien auch in kleinen Größen gibt. Fernbedienungen, MP3 Player etc haben Miniaturdisplays.

Also hab ich die Idee verkündet, wenn ich so ein Display günstig finde, dass man doch so was in die Kästen einbauen kann, die Elektronik versteckt man dann tief im Rumpf...

 

ich und meine große Klappe! :ohno:

 

Naja, die Messe ging weiter vor sich hin, das Internet gab aber noch nicht die richtige Lösung her. Da Messwerkzeug während der Messe am Stand rar war, das Werkzeug Kreditkarte passt, aber nicht gerade präzise ist, sah ich die Lösung dafür am Stand von SteBa (Stefan Bauer Modellbau) liegen, ein Messschieber :) kurz ausgeliehen, abgemessen und zurück gebracht. Stefan Bauer kam gerade zurück und wir kamen ins Gespräch, warum ich gerade deren Messschieber brauchte.

 

Überaschende Antwort von ihm, schau mal bei Massoth und den LGB Bahnern, die haben sowas im Programm. :that: Äääh wer war das bitte? Muss ich mir direkt mal anschauen :mrgreen:

 

Gesagt getan, zu Massoth hin und das Display angesehen. 120€ für zwei Displays mit Ansteuerelektronik, die auf ein Digitalprotokoll für Modellbahnen hört? Plus dann nochmal einen Eigenbau, der dem Display als Sender sagt, was es machen soll? Neee, da geht mehr.

Nicht falsch verstehen, Massoth hat ein sehr interessantes Produkt gebaut. Der Preis ist sicher für die Entwicklungsarbeit angemessen, aber es war nicht DAS Ding, was wir wollten.

Der Anfang war aber gemacht, denn es existiert wirklich ein Display in der richtigen Größe, die wir für die Hadag brauchten und auch in gelb (naja ich würde sagen Orange, aber der Hersteller sagt es ist gelb).

 

Reaktion von Kapitän Odin, als ich ihm die Lösung präsentierte: "Ist mir egal, wie du das machst, ich will das für meine Fähre haben!"

 

Tja man finde also in den Weiten des Internet einen Hersteller für ein Display, wo man die Außenmaße hat und weiß, dass es OLED Technik ist...das braucht eine Weile, aber schließlich spuckte mir Onkel Google die Firma Visionox aus. China lässt grüßen, aber die hatten genau das was ich wollte.

 

Tja bestellt man sowas nun direkt in China, hat elend lange Versandzeiten und keine Garantien, dass der Hersteller an einen überhaupt versendet? Geht man über Alibaba mit ähnlichen Bedenken?

War mir alles zu windig, also habe ich nach einem Händler möglichst in Europa oder den USA gesucht.

 

Bei MARITEX in Gdinya, Polen bin ich fündig geworden...Farbe weiß, aber was solls, wofür gibt es Farbfolien.

Also Email austauschen, ob die an Privat versenden, was man noch alles braucht, insbesondere die Steckverbindung der Elektronik. Alles war bei Maritex zu bekommen, der Kontakt sehr freundlich und interessiert, was man denn mit den Displays anstellen will.

 

In der Zeit, wo die Bestellung unterwegs war, hab ich mich ran gesetzt, was ich sonst noch so an Bauteilen benötige, also Datenblatt studieren, planen und viel lesen, wie ich die Dinge miteinander verbinde, ohne mir Komponenten zu zerstören ;)

Heraus gekommen ist eine Schaltung, die ich im nächsten Teil vorstelle und eine Liste mit Teilen von Conrad Elektronik (Kondensatoren, Transistoren, MosFET und Widerstände), und EXP-Tech (Spannungswandler und Microchip). Eine ausführliche Bestellliste mit den Bauteilen werde ich noch reinschreiben ;)

 

Als denn, auf zum fröhlichen Hardware-basteln...im nächsten Teil 8-)

  • Like 1
Link to post
Meinolf Höhler

Wie angedroht, wenden wir uns heute der Hardware zu.

Die Einkaufsliste sieht wie folgt aus:

Maritex Gdynia/Polen
2x    OLED-PM128MW    Visionox M00415 OLED-Matrix Display 256x32Pixel, weiß
2x    ZIFNZ0326CV           ZIF Verbinder, 26 polig

Exp-Tech (oder ein anderer Händler, wer die Teile führt):
1x    Adafruit Pro Trinket     3,3V 12MHz (VORSICHT es gibt auch eine 5V Version!)
2x    Pololu S10V2F12        Step-up/-down DC/DC Spannungsregler

Elektronik-Kleinteile vom Händler seiner Wahl:
2x    25/35V 4,7µF Elektrolytkondensator
1x    1kOhm Widerstand
1x    10kOhm Widerstand
1x    680kOhm Widerstand
1x    BC547 NPN Bipolartransistor
1x    IRF9540N P-Channel MOSFET

Lackdraht, Lötkolben, Lot etc. setze ich mal als vorhanden voraus.

Eine nette Liste an Teilen und die Verschaltung sieht im Bild schlimmer aus, als sie ist. Aber erst einmal zu den Teilen ein paar Erklärungen.

OLED-Display
Wie schon im ersten Teil des Berichts geschrieben, ist das Display bei Maritex in Polen zu finden. Für die Kästen in 1:25 hat es genau die richtige Größe des Glaskörpers von 55x11mm, nur der Anschluss an die Außenwelt ragt über die Maße heraus. Diese Flexible Verbindung nennt man FPC (Flexible Printed Circuit, engl. Flexibler, gedruckter Schaltkreis) und braucht leider auch spezielle Verbinder, denn die Folie und die Kupferauflage ist so dünn, dass man die mit einem Lötkolben wahrscheinlich zerstören würde.
Daher bekommt man bei Maritex auch die entsprechenden Leiterplattenverbinder zu kaufen.
Die sind etwas robuster und können von Hand mit einer feinen SMD-Lötspitze und viel Geduld mit feinen Drähten verlötet werden, wie Kapitän Odin in seinem Baubericht schon gezeigt hat. Am besten kauft man davon auch nicht nur 2, sondern vielleicht 10 Stück. Der Preis ist gering und man hat einen Spielraum für Fehler. Bei den ersten Lötversuchen in Kapitän Odins Werft haben wir 3 Stück "zerlötet", sodass die nicht mehr vernünftig zu gebrauchen waren.

Pololu-Spannungsregler
In der Folienverbindung zum Display ist ein Chip eingebaut, der die Datensignale in Spannung und Strom für die einzelnen Pixel umsetzt, und über integrierte Treiber (einfach gesagt Verstärker) die Pixel so mit Strom versorgt, dass diese anfangen zu leuchten. Dieser Treiber benötigt eine Spannung von 12V, mit einer relativ geringen Toleranz. Damit die Spannung auch bei verschiedenen Akkuladungen gleich bleibt kommt der Step-up/-down Spannungsregler zum Einsatz.

„Step-up/-down? Wat issn ditte?“ :weisnicht:

 

Für Gleichspannungen, wie von einer Batterie, kann man keinen Transformator verwenden, sondern braucht diese DC/DC Regler.
Step-up Spannungsregler können aus einer kleinen Eingangsspannung eine Höhere produzieren, also z.B. aus 3V von zwei Mignon Batterien macht der 12V. Dieser Schritt nach oben hat den Namen gegeben.
Step-down Regler sind im Prinzip das Umgekehrte, von einer höheren Gleichspannung kann der eine Kleinere erzeugen, also z.B. von 28V einer Flugzeug-Bordelektrik (was besseres fiel mir gerade nicht ein) auf 12V.
Sind die 4S-LiFePo Akkus voll, haben die eine Gesamtspannung von über 12V, leer fallen die aber unter 12V, daher auch Step-up/-down, damit da immer 12V raus kommen ;)

Adafruit Pro Trinket
Der „Pro Trinket“ führt das Programm aus und berechnet, was auf dem Display dargestellt werden soll. Das Ergebnis wird dann über eine spezielle Schnittstelle an den Displaychip weitergegeben, der die Pixel entsprechend ansteuert. Er enthält einen ATMEL ATMega 328P Microcontroller, eine Mikro-USB-Buchse zur Programmierung und zwei Leuchtdioden zur Statusanzeige.
ATmega 328P hat der eine oder andere vielleicht mal gehört? Richtig, der gleiche Chip, der in den bekannten Arduino UNO Boards verbaut ist.
Der Pro Trinket ist Arduino kompatibel und bis auf zwei Pins, die für die USB Kommunikation benötigt werden, sind die gleichen Anschlüsse nach außen geführt, wie beim Arduino UNO.
Der größte Unterschied ist, wegen der Größe, dass der USB-Port nur für die Programmierung verwendet werden kann. Das UNO Board hat für die serielle Kommunikation und das Debugging einen separaten Atmel Chip „on-Board“ (mit einem ISP-Adapter kann man auch den Pro Trinket direkt Debuggen, aber das geht hier zu weit).
Durch die Kompatibilität zum UNO kann man mit der Arduino-Software das Programm für den UNO entwickeln und mit ein wenig Zusatzelektronik testen und ändert nachher „nur“ noch das Ziel des Programms auf den Pro Trinket.

Nun gibt es ja noch viele andere Boards, die auch Arduino-kompatibel sind und auch den ATmega 328P verwenden.
Warum gerade dieses Board?

Der Grund ist eigentlich einfach, es geht um Platz und um einen möglichst einfachen Aufbau.
Die meisten Boards nutzen eine 5V Logik, wie zum Beispiel das eben so kleine Arduino Nano Board, dass heißt, die Eingänge wollen möglichst +5V haben, damit das Signal als logisch 1 angesehen wird. Eventuell funktioniert es mit 3V, aber muss nicht. Die Ausgänge liefern auf jeden Fall 5V.

Das Display mag aber keine 5V Logik, das kann nur maximal 3,3V verarbeiten, höhere Spannungen können den Chip zerstören. Bei einem „normalen“ Arduino (kompatiblen) Board braucht man also Pegelwandler, die das Signal von 5V auf 3,3V umsetzt.
Bei einer Fehlersuche ist das eine mögliche Fehlerquelle mehr, die man untersuchen müsste, und es wird für die Pegelwandler Platz benötigt.
Der „Pro Trinket“ 3V braucht keine Pegelwandler, weil er die 3V direkt an seine Ausgänge liefert.

VORSICHT bei der Bestellung: es gibt auch eine 5V Variante mit 16 MHz Takt!

Der zweite Vorteil ist der eingebaute Spannungsregler. An BAT verkraftet der Pro Trinket bis 16V für seine eigene Versorgung. An dem 3V Pin kann der Regler bis zu 150mA ausgeben, was wir für die Versorgung der Displaylogik ausnutzen werden. Also noch ein separater Regler weniger und Platz gespart.

Transistoren
Im Datenblatt für das Display bzw für den Treiberchip ist beschrieben, dass man die Pins in einer gewissen Reihenfolge einschalten soll, unter anderem auch die 12V Stromversorgung. Da man mit 3V nicht direkt 12V schalten kann, muss man sich mit zwei Transistoren helfen.

 

Im nächsten Teil stelle ich die Schaltung vor und die Verbindung zum Display.

Link to post
  • 2 weeks later...
Meinolf Höhler

Heute werfen wir einen Blick auf die Schaltung, was welche Bauteile machen.

Zieldisplay_Schaltplan.png

Links unten ist der Akku (U2) und der Spannungsregler(U1) dargestellt. Da mir durch eigene Tollpatschigkeit mehrere Bauteile durchgebrannt sind, habe ich das Design nun so gebaut, dass die Spannung des Akku erst in den Spannungsregler geht, der stabile 12V erstellt, und die Spannung wird dann an den anderen Teilen weiter verwendet.

 

Das zentrale Bauteil ist natürlich der Pro Trinket, weil der die meisten Anschlüsse hat. Das Display ist im Schaltplan nicht dargestellt, weil das ein riesiges Bauteil wäre und den Plan unnötig verkompliziert.
Oben rechts sind drei Kondensatoren und dein Widerstand dargestellt. Diese kommen aus dem Datenblatt des Displays und dienen der Versorgung. Laut Datenblatt sollten für die Elektrolytkondensatoren Tantal-Elkos (4,7µF/25V) verwendet werden. Da Conrad die nicht in Düsseldorf vorrätig hatte, habe ich Standard-Elks mit 4,7µF/35V verwendet, hat auch funktioniert :)

Unten rechts sieht man zwei Transistoren und ein paar Widerstände. Wozu der Aufwand? Reicht nicht einer oder ein Relais?
Ob ein Transistor ausreicht, habe ich nicht direkt recherchiert. Der Haken war für mich, wie viel Leistung brauchen die Pixeltreiber. Da ich nicht großartig rechnen wollte und keine Angaben zum Stromverbrauch im Datenblatt gesehen habe, bin ich auf Nummer sicher gegangen und ein MOSFET zum Einschalten verwendet.

Für alle, die MOSFET nicht kennen, hier der Versuch einer einfachen Erklärung.
MOSFET ist die Abkürzung für Metal-Oxid-Halbleiter-Feldeffekt-Transistor. Die Anschlüsse werden „Source“ (Quelle), „Gate“ (Tor), und „Drain“ (Senke) genannt.
Im Gegensatz zum „normalen“ Transistor ist der MOSFET Spannungsgesteuert. Je größer die Spannung zwischen Gate und Source, um so mehr Strom kann zwischen Source und Drain fließen.
Als bildlichen Vergleich nehmen wir ein Tor an, was durch Federn geschlossen gehalten wird. Um so mehr ich an dem Tor ziehe, um es zu öffnen - also je mehr Spannung am Gate anliegt - um so breiter wird der Durchgang. Lässt man das Tor los, entfällt also die Spannung der Federn, die das Tor schließen, fließt auch kein Strom mehr. Das genaue Prinzip zu erklären würde den Rahmen sprengen. Das kann man besser bei Wikipedia nachlesen ;)

Gut, der MOSFET wird über die Spannung zwischen Gate und Source geschaltet, also ran an den Arduino, weil der 5V Spannung ausgibt!

:nono: Leider funktioniert das nicht. Es muss die richtige Spannung sein und es gibt zu allem Überfluss zwei Typen von MOSFET, je nach dem, was man schalten will.

Die zwei Varianten nennen sich N- und P-Kanal MOSFET. Der N-Kanal kann direkt mit dem Arduino betrieben werden und z.B. eine Pumpe einschalten oder etwas anderes Stromhungriges. Wichtig ist nur, dass man die Verbindung vom Verbraucher zum Minus-Pol der Batterie schaltet.

Blöd dass der Hersteller des Displaytreiber den Plus-Pol geschaltet haben möchte. Also verwenden wir dann eben den P-Kanal MOSFET.

Neues Problem: der N-Kanal MOSFET hat am Gate 0V, wenn er ausgeschaltet ist und es reichen die 5V des Arduino aus, um den MOSFET durchzuschalten.
P-Kanal MOSFET schalten durch, wenn die „Gate-Source-Spannung“ um einen gewissen Wert kleiner ist, als die Spannung an „Gate-Drain“. Der Arduino müsste dann aber zum Ausschalten mit 12V Ausgängen arbeiten, und die Gate-Source-Spannung Richtung 0V verändern.

Da das nicht möglich ist, helfen wir uns mit einem Standardtransistor aus. Der NPN-Transistor schaltet durch, wenn durch den Widerstand R4 mit den 5V vom Arduino ca. 5mA Strom erzeugt werden (Bipolartransistoren sind Stromgesteuert ;) ). Schaltet T1 durch, hat der MOSFET am Gate 0V Spannung anliegen, was diesen seinerseits schaltet. Schaltet man den Arduino-Pin aus, sperrt T1, was dazu führt, dass R5 das Gate auf 12V „hoch zieht“ und der MOSFET sperrt…PUUUUUH

 

Blöde Theorie, die ich auch mühsam suchen musste, aber so ist es nunmal :D

 

Die "Gabeln", die am rechten Rand der Schaltung zu sehen sind, sind die Verbindungen ins Display. Es sind nur die 11 Leitungen dargestellt, die die Schaltung verlassen. Einige der Kontakte sind mit Masse verbunden, was man aus dem Display Datenblatt herauslesen muss.

Noch ein Wort zu der LED aus der Schaltung, die ist nämlich nicht in der Einkaufsliste ;)
Die LED war während des Testens eine Hilfe, um zu sehen, ob das Programm läuft. Eine LED auf dem Arduino UNO wollte nicht so leuchten, wie geplant, also habe ich diese durch eine Separate mit Vorwiderstand ersetzt.
Auf dem ProTrinket funktioniert die geplante LED, nur hab ich die im Schaltplan und Programm belassen.

Zum Abschluss habe ich mit Fritzing aus dem Schaltplan ein Bild mit dem Aufbau auf einem Breadboard zeichnen lassen.

Zieldisplay_Steckplatine.png
Ganz schönes Strickmuster und der LiPo stimmt auch nicht ganz. Auf einer Lochrasterplatine oder selbst geätztem kann man das sicher schöner und übersichtlicher anordnen.
Soweit ist das die Hardware, sieht unübersichtlich aus, ist aber relativ einfach.

Der nächste Teil beschäftigt sich dann mit dem Programm.
Wer noch nie einen Arduino oder anderen Microchip mit der Programmiersprache C programmiert hat, dem empfehle ich das Internet nach entsprechenden Tutorials zu durchsuchen. Programmieren ist nicht besonders schwer (glaube ich jedenfalls) ich möchte hier aber keinen Programmierkurs C bzw C++ schreiben, weil es zu aufwendig wäre.
Als Voraussetzung für die nächsten Teile nehme ich an, dass man die Grundzüge von C kennt, also Syntax, Bedingungen, Schleifen, Datentypen und Präprozessorkommandos.

Link to post
  • 2 weeks later...
Meinolf Höhler

Dieser und die nachfolgenden Teile werden das Programm vorstellen, mit dem die Hadag-Zielanzeige betrieben wird.
Das Programm besteht aus einem Arduino-Projekt (Datei mit der Endung .ino) und einigen C-Dateien, damit das Projekt einigermaßen übersichtlich bleibt.

Die C-Dateien enthalten die anzuzeigenden Grafiken…äääh…UPS ich habe ja noch gar nicht genau beschrieben, was da angezeigt werden soll.

Auf Wunsch von Kapitän Odin soll seine „Tollerort“ auf der Linie 62 zwischen den Landungsbrücken und Finkenwerder verkehren.
Die Zielanzeige zeigt die Liniennummer auf der linken Seite in voller Höhe. Rechts in ca 70% der Breite steht in der oberen Hälfte das Ziel, in der Unteren die Zwischenstationen, die mit einer Lautschrift animiert sind. Das Ganze soll etwa so aussehen, wie Arno es hier fotografiert hat: http://www.schiffsmodell.net/index.php?/topic/11269-fahrzielanzeige-der-hadag2000/?p=154789

Da der Mikrrochip und das Display keine Anzeige von Zeichen kennen, ist das der Kern des Programms, der gebraucht wird.
Eine Möglichkeit wäre fertige Bibliotheken zu verwenden. Diese waren aber nicht für die Größe des Displays gemacht. Die Zeile mit dem Ziel ist zudem fett geschrieben, die Zeile mit den Zwischenstationen etwas kleiner als das Ziel.
Alles in allem suboptimal, aber man kann sich ja abschauen, wie die Zeichen programmier sind und das nachbauen. :mrgreen:
Da für unsere Zwecke nur „schwarz-weiß“-Text verwendet wird, wird die Sache in sofern einfacher, als dass wir für jedes Pixel, das AN sein soll, das entsprechende Bit auf 1 setzen, für AUS entsprechend auf 0.
Schnell bin ich davon weg gegangen Zeichensätze für das Display zu erstellen. Die Anzeige ist im Moment nur für eine Linie gedacht und die Laufschrift der Zwischenstationen wäre erst einmal etwas komplizierter.
Als Grundprinzip habe ich die Bitmaps der Bibliotheken übernommen (z.B. die Grafikbibliothek von Adafruit).
Am Beispiel des Fahrtziels erkläre ich, wie es umgesetzt ist. Für die Liniennummer und die Zwischenstationen gilt das gleiche Prinzip, nur mit anderen Höhen und Breiten.

Das Fahrtziel ist 208 Pixel breit und 16 Pixel (2 Byte) hoch, macht in Bytes ausgedrückt 208*2 Byte = 416 Byte.
Die Daten, wie die „Bilder“ aufgebaut werden sollen, sind senkrecht zu lesen, das heißt in den ersten zwei Byte ist die erste Spalte der Anzeige, in Byte 3 und 4 die Zweite etc.

 

Tabelle3_1.png

 

Als Vergleich nehmen wir einen Fernseher. Das Fernsehbild wird zeilenweise übertragen. Ist eine Zeile vollständig, wird in die Nächste nach links gesprungen und wieder bis ans rechte Bildende die Punkte aufleuchten lassen.
Bei der Hadaganzeige ist die Kodierung nicht waagerecht sondern senkrecht, d.h. wir fangen oben links mit der ersten Spalte an, und arbeiten uns senkrecht, Spalte für Spalte durch die Daten durch.

Ehe jetzt die Elektronikspezialisten aufheulen, ich weiß auch, dass ein Fernsehbild mit zwei Halbbildern übertragen wird, aber es soll ja einfach bleiben ;) )

Nun bleibt noch die Frage, wo das LSB (Least Significant Bit, engl. Niederwertigstes Bit) im Byte liegt, damit die Anzeige richtig aussieht.
Durch das spaltenweise Vorgehen ist das LSB oben, damit sich das Programm konsistent von oben links nach unten rechts durch die Daten durcharbeiten kann.
Gespeichert werden die Daten byteweise, weil sonst noch die Schwierigkeit dazu käme, welches Byte das Niederwertigere ist (wer will, kann das im Internet unter dem Stichwort „Little Endian“ suchen).

So weit, so gut, schauen wir uns das Ganze nun im Code an.
 

static const unsigned char fontDestination[] PROGMEM = {

  //Finkenwerder

  0x00, 0x00, 0xFF, 0xFF,(…)

Bei der Menge der anzuzeigenden Daten reicht der Arbeitsspeicher nicht aus. ATMEL hat aber eine Möglichkeit geschaffen, die Daten in den Flachspeicher zu schreiben. Dies geschieht mit dem Schlüsselwort PROGMEM.
Als Datenformat verwenden wir ein Array, weil die Daten mit der Initialisierung des Array so hintereinander in den Speicher geschrieben werden, dass wie diese mit einfachen Berechnungen wieder auslesen können. Da wir Byte speichern und die Lesefunktion eine Byteadresse haben möchte, ist der Datentyp unsigned char.
Wie schon geschrieben sehen die Daten für die anderen Anzeigen ähnlich aus, es ist nur entsprechend mehr.
Einzige Ausnahme sind die Zwischenstationen. Die Datei fontStations.c enthält neben den Displaydaten noch ein Integer-Array, ebenfalls im Flash, welches die Längen der anzuzeigenden Daten enthält.

Im nächsten Teil wenden wir uns dann der Hauptdatei Zieldisplay.ino zu.

 

Link to post
  • 2 months later...
Meinolf Höhler

In Kürze geht es hier weiter.

Ich habe, wie im Baubericht von Kapitän Odin zu sehen ist, orange Displays bekommen und baue gerade das Programm noch einmal um. Es soll noch ein wenig optimiert werden, ehe ich das hier vorstelle.

Link to post
Meinolf Höhler

Die (fast) neuesten Wünsche von Kapitän Odin sind in die Anzeige eingeflossen. Fast, weil ich mal einen Stand hier fertig schreiben möchte. Der Rest kommt dann als Erweiterung ;)
 
Wie schon geschrieben zeigt das Display die Fahrt der Linie 62 an, mit den weiteren Haltestellen als Laufband, wie das Original auch.
Über zwei Taster oder Schalter (das müssen wir schauen, wie es am einfachsten ist), werden Sonderfunktionen ausgelöst.
Taster 1: Weiter schalten der Haltestelle
Taster 2: „BITTE NICHT MEHR EINSTEIGEN“ als Laufband

Schauen wir uns den Code dazu im Detail an, der in der Hauptdatei Zieldisplay.ino steht.
Trotzdem, dass der Code Kommentiert ist, werde ich die Datei in handliche Stücke zerlegen und vorstellen.

Ganz zu Anfang stehen die #include

#include <avr/pgmspace.h>

#include <SPI.h>

#include "fontLineNumber.c"
#include "fontDestination.c"
#include "fontStations.c"
#include „fontSpecial.c"

Um Daten aus dem Flash laden zu können, benötigen wir die pgmspace.h.
Die Kommunikation mit dem Display erfolgt über SPI, das „Serial-Peripheral-Interface“. SPI ist eine Vier-Draht-Verbindung, die einen Draht für den Takt (SCLK), Zwei für Daten (SDIN oder MOSI, SDOUT oder MISO), und Einen für die Chipauswahl (CS) braucht. Da wir nur in Richtung der Displays kommunizieren, fällt der MISO Draht für uns weg.
Für SPI sind auf dem Arduino einige Ausgänge vorgelegt:
- Pin 11: SDIN/MOSI
- Pin 13: SCLK
- Pin 12: MISO (Dateneingang, z.B. von einem Speicherbaustein, hier nicht verwendet)
CS ist frei wählbar, und Pin 5 kam dazu gerade recht :)

Auf die Bibliotheken folgen die vier eigenen C-Dateien.
 

#define ACTIVITY_LED                9

#define DO_NOT_ENTER                1
#define SWITCH_STOP                 8

#define SSD1326_VCC_ON              3
#define SSD1326_RES                 4
#define SSD1326_CS                  5
#define SSD1326_DC                  6

#define SSD1326_WIDTH               256
#define SSD1326_HEIGHT              32
#define SSD1326_HALF_HEIGHT         16
#define DISPLAY_BUFFER_WIDTH_BYTES  32

#define ANIMATIONSPEED        4

Um das Leben zu vereinfachen und Speicher zu sparen, sind einige Konstante Werte in #define abgelegt. Die Namen sollte selbsterklärend sein.

ANIMATIONSPEED bestimmt, wie schnell die Zwischenstationszeile durch das Display läuft. Der Wert ist die Anzahl der Pixel, die die Daten „weitergeschoben“ werden, dazu später mehr.


Natürlich werden verschiedene Variablen verwendet:

byte byStations = 1;

byStations speichert die aktuelle Stationsnummer. Auf der Linie 62 gibt es 12 Stationen (beide Richtungen zusammen natürlich;) ), also reicht ein Byte als Speicher völlig aus.
 

const long lBlinkIntervalActivityLED = 100;
int iActivityLEDstate = LOW;

iBlinkIntervalActivityLED ist eine Konstante für den Blinktakt der LED an Pin 9.
iActivityLEDstate speichert den Zustand der LED ab, ob diese AN (HIGH) oder AUS (LOW) ist.
 

unsigned int uiCalculatedAddressOffset = 0;
unsigned int uiStationsSize = 0;
unsigned int uiAnimationCounter = 0;

uiCalculatedAddressOffset, uiStationsSize und uiAnimationCounter sind Zwischenspeicher, die in jedem Programmzyklus des Chips benötigt werden.
 

const unsigned long ulBufferMask[32] = {
  0x00000001, (…), 0x80000000
};
static unsigned char buffer[1024] = {
  0x00, 0x00, 0x00,(…)
};

ulBufferMask[ ] wird an einigen Stellen als Bitmaske verwendet und buffer[ ] ist der Zwischenspeicher, in den wir die anzuzeigenden Daten schreiben. Es ist ein Array mit 32x32=1024 Byte (das Display hat 32 Zeilen á 32 Byte).
 

SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);

Zu letzt noch SPIsettings. Dies definiert die Übertragungseinstellungen auf dem SPI. Es wird mit 4MHz Takt übertragen, das MSB zuerst und Mode0 verwendet. Die 4 MHz habe ich aus dem Datenblatt des Display errechnet, in dem die minimale Zykluszeit mit 250ns angegeben ist. Die anderen Werte habe ich ausprobiert.


Kommen wir nun zu den Funktionen. Zwei davon sind für die Programmausführung vorgeschrieben ( setup() und loop() ), die Anderen sind dazu programmiert wegen der Übersichtlichkeit.

setup() enthält alles, was der Chip für die Ausführung von loop() wissen muss, besonders, welcher Pin welche Aufgabe hat.
 

void setup()
{
  SPI.begin();

Mit SPI.begin() wird die SPI Klasse initialisiert und für die Datenübertragung vorbereitet. Die Übertragung ist in einer anderen Funktion implementiert.
 

pinMode(SSD1326_VCC_ON, OUTPUT);
pinMode(SSD1326_RES, OUTPUT);
pinMode(SSD1326_CS, OUTPUT);
pinMode(SSD1326_DC, OUTPUT);

pinMode(ACTIVITY_LED, OUTPUT);
pinMode(DO_NOT_ENTER, INPUT_PULLUP);
pinMode(SWITCH_STOP, INPUT_PULLUP);

Die Pins für die diversen Leitungen zum Display und der Aktivitäts-LED werden als Ausgänge gesetzt.
Die Pins für die Umschaltung der Haltestelle und der Anzeige „BITTE NICHT MEHR EINSTEIGEN“ als Eingang. Die Besonderheit ist das Schlüsselwort INPUT_PULLUP, im Gegensatz zu einem normalen Eingang, der mit INPUT definiert wird. INPUT_PULLUP aktiviert einen Widerstand im Mikroprozessor. Die Eingänge werden hierbei mit einem Schalter oder Taster auf Masse (GND) geschaltet. Öffnet der Schalter, zieht der Widerstand den Eingang auf 3,3V hoch. Warum wird dieser Mechanismus genutzt? Es hat keinen speziellen Grund, mir war gerade danach :)
 

digitalWrite(SSD1326_VCC_ON, HIGH);
delay(10);

digitalWrite(SSD1326_VCC_ON, LOW);
delay(10);

digitalWrite(SSD1326_RES, HIGH);
delay(10);

digitalWrite(SSD1326_RES, LOW);
delay(10);

digitalWrite(SSD1326_RES, HIGH);
delay(10);

digitalWrite(SSD1326_VCC_ON, HIGH);
delay(10);

Laut des Display-Datenblatts soll man die Display-Versorgung in einer bestimmten Weise einschalten. Dies wird durch diese Zeilen durchgeführt. Die 10ms Verzögerung nach jedem Schaltvorgang sind mehr als ausreichend, 1ms hätte auch genügt.
 

SPIsend(0xFD, true); // OLED Treiberchip für Kommandos freischalten
SPIsend(0x12, true);
SPIsend(0xAE, true); // Display ausschalten
SPIsend(0x15, true); // Daten-Spalten einstellen
SPIsend(0x00, true); // Startspalte
SPIsend(0x1F, true); // Endspalte
SPIsend(0x75, true); // Daten-Zeilen einstellen
SPIsend(0x00, true); // Startzeile
SPIsend(0x1F, true); // Endzeile
SPIsend(0x81, true); // Kontraststrom einstellen
SPIsend(0x80, true); 
SPIsend(0x87, true); // OLED Strombereich
SPIsend(0xA0, true); // Datenmapping und S/W Modus
SPIsend(0x12, true); // kein Mapping, S/W
SPIsend(0xA1, true); // Display Startzeile
SPIsend(0x00, true);
SPIsend(0xA2, true); // Display Offset
SPIsend(0x00, true);
SPIsend(0xA8, true); // MUX-Verhältnis
SPIsend(0x1F, true);
SPIsend(0xB1, true); // OLED-Phasenlänge
SPIsend(0x71, true);
SPIsend(0xB3, true); // CLK-Divider/OSC
SPIsend(0x31, true);
SPIsend(0xB7, true); // standard Grauwerttabelle
SPIsend(0xBB, true); // OLED-preCharge setup
SPIsend(0x35, true);
SPIsend(0xFF, true);
SPIsend(0xBC, true); // OLED-preCharge Spannung
SPIsend(0x1F, true);
SPIsend(0xBE, true); // VCOMH
SPIsend(0x0F, true);
SPIsend(0xAF, true); // Display einschalten

Diese Codesequenz gehört zur Initialisierung des Display. Sie sind weitgehend aus dem Datenblatt entnommen. Die einzige Änderung ist der Befehl 0xA0, der auf schwarz/weis-Darstellung angepasst wurde.
 

digitalWrite(ACTIVITY_LED, HIGH);
SPIsend(0xA5, true);
delay(500);

digitalWrite(ACTIVITY_LED, LOW);
SPIsend(0xA6, true);
delay(500);

SPIsend(0xA4, true);

caculateAddressOffset(0);

digitalWrite(ACTIVITY_LED, HIGH);
delay(2000);
digitalWrite(ACTIVITY_LED, LOW);

Zum Schluss wird das Display zur Anzeige des Initialisierungsende kurz aufblinken lassen. Dafür gibt es das Kommando 0xA5, welches alle Pixel einschaltet, und 0xA6 welches alle Pixel ausschaltet. Zeitgleich wird die Aktivitäts-LED im gleichen Takt ein- und ausgeschaltet. Dies dient nur einer Kontrolle, sollte das Display mal nicht mitspielen wollen.

Nach diesen Kommandos darf man nicht vergessen das Display mit 0xA4 in den normalen Modus zurückzusetzen, denn nur dann liest der Treiber die Daten auch wieder aus dem internen Grafikspeicher!

Der letzte Befehl berechnet einmal einige Werte, damit die erste Station angezeigt wird, und die Aktivitäts-LED leuchtet noch einmal für zwei Sekunden auf.


Schauen wir nun loop() an. Diese Funktion wird, wie der Name sagt, in einem Loop - einer Schleife - ausgeführt. Sofern das Programm nicht abstürzt oder auf andere Weise den Chip auslastet, ist loop() eine Endlosschleife, die alle Variablen innerhalb von loop() beim nächsten Durchlauf „vergisst“. Daher sind Werte, wie die Stationsdummer oder der letzte Wert der gemessenen Zeit, lokale Variablen.
 

void loop()
{
 unsigned long ulCurrentMillis = millis();
  if (ulCurrentMillis - lPreviousMillis > lBlinkIntervalActivityLED)
  {
    lPreviousMillis = ulCurrentMillis;

    if (iActivityLEDstate == LOW)
      iActivityLEDstate = HIGH;
    else
      iActivityLEDstate = LOW;

    digitalWrite(ACTIVITY_LED, iActivityLEDstate);
  }

Wenn an Pin 9 eine LED angeschlossen ist, lässt dieser Code die LED blinken (hat man keine dran, sieht man das Blinklicht halt nicht ;) ). Zuerst speichern wir in ulCurrentMillis die aktuelle Zeit. Zu beachten ist, dass millis() keine Uhrzeit liefert, sondern die Anzahl der Millisekunden, seit der Chip eingeschaltet wurde. Ist die aktuelle Zeit, abzüglich der Zeit bei der letzten Änderung des Blinkers, größer, als das Blinkintervall, wird die aktuelle Zeit gespeichert, die LED abhängig vom bisherigen Zustand umgeschaltet und der neue Zustand auf den Ausgang geschrieben.
 

  iButtonSwitchStopState = digitalRead(SWITCH_STOP);
  
  if (iButtonSwitchStopState != iLastButtonSwitchStopState)
  {
    if (iButtonSwitchStopState == LOW)
    {
      switchToNextStation();
    }
  }
  
  iLastButtonSwitchStopState = iButtonSwitchStopState;

Bei der Auswertung des Signals für die Weiterschaltung der Haltestellen interessiert uns nicht direkt der Zustand des Signals, sondern nur wann sich das Signal ändert. Die Änderung des Signals von LOW auf HIGH nennt man „positive Flanke, der entgegengesetzte Wechsel „negative Flanke“. Mit der Flankenerkennung kann zum Beispiel umgesetzt werden, dass an der Gangway ein Mikrotaster angebracht ist, der das Signal schaltet, wenn die Gangway heruntergelassen wird. Wird die Gangway wieder hoch geklappt, verschwindet das Signal und die nächste Haltestelle wird angezeigt.

Wie funktioniert das nun? Also zuerst lesen wir mit digitalRead das aktuelle Signal.
Danach wird das Signal mit dem gespeicherten Zustand verglichen. Ist ein Unterschied da, hat es sich offensichtlich seit dem letzten Programmzyklus geändert. Ist das der Fall, so schauen wir nach, ob das Signal nun LOW (für die negative Flanke) oder HIGH (positive Flanke) ist.
In unserem Fall nehmen wir die negative Flanke als Trigger und schalten damit zur nächsten Station.

Zuletzt wird noch der aktuelle Signalzustand abgespeichert, damit wir die nächste Flanke erkennen können.
 

iButtonDoNotEnterState = digitalRead(DO_NOT_ENTER);
  
  if (iButtonDoNotEnterState != iLastButtonDoNotEnterState)
  {
    uiAnimationCounter = 0;  
  }
  
  if (iButtonDoNotEnterState == LOW)
  {
    drawSpecial();
  }
  else
  {
    drawLineNumber();
    drawDestination(byStations / 6);
    drawStations();
  }
  
  iLastButtonDoNotEnterState = iButtonDoNotEnterState;

Das gleiche System der Flankenerkennung wenden wir für die Anzeige „BITTE NICHT MEHR EINSTEIGEN“ an. Hier verwenden wir aber nicht nur eine Flanke, sondern beide.
Die eine schaltet die Anzeige „BITTE…“ mit drawSpecial() ein, die andere Flanke schaltet zurück auf die Linienanzeige.

 

  display();

  if (uiAnimationCounter <= uiStationsSize)
    uiAnimationCounter += ANIMATIONSPEED;
  else
    uiAnimationCounter = 0;

Was nun noch bleibt, ist die generierte Anzeige mit display() aus dem Puffer an das Display zu schicken.
Das letzte „if“ in loop() sorgt dafür, dass die Animation der Zwischenstationen am Ende der Anzeige auch wieder vorne anfangen kann. Abhängig von uiStationsSize wird uiAnimationCounter entweder um das ANIMATIONSPEED weitergezählt oder auf 0 gesetzt.

WOW, viel zu schreiben gehabt, und schon die wichtigen Funktionen erklärt. Beim nächsten Mal gehts mit den Funktionen weiter, um die verschiedenen Aufrufe in diesem Teil mit Leben zu füllen.

Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.