Grafik-Display‎ > ‎

2 Display-Grundlagen

http://sites.prenninger.com/arduino-uno-r3/grafik-display-1/2-display-grundlagen

http://www.linksammlung.info/

http://www.schaltungen.at/

                                                                                              Wels, am 2016-06-12

BITTE nützen Sie doch rechts OBEN das Suchfeld  [                                                              ] [ Diese Site durchsuchen]

DIN A3 oder DIN A4 quer ausdrucken
**********************************************************************************
DIN A4  ausdrucken
*********************************************************
Untergeordnete Seiten (4):

1 Grafik-Display
2 Display-Grundlagen
3 TextDisplay

4 GrafikDisplay
5 Projekte


2. DISPLAY - GRUNDLAGEN

Nun geht es um die grundlegende Ansteuerung des Displays.
Sie werden anhand erster Beispiele lernen, wie sich einfache Ausgaben mit dem Display realisieren lassen.


2.1  HelloWorld - das erste eigene Programm
Nachdem mit dem vorangegangenen Starter-Programm die Funktionalität des Displays und einige Grundlagen erläutert wurden, geht es nun darum, das erste eigene Programm zu schreiben.
Traditionsgemäß wird es „Hella World' genannt.
Mit einer einfachen Ausgabe tastet man sich langsam an ein neues System heran.
Dabei wird die Ausgabe möglichst einfach gehalten und auf besondere Ästhetik noch keinen Wert gelegt.
Es gibt zwei Möglichkeiten, wie Sie dieses und alle folgenden Beispiele durcharbeiten können.
Eine Option ist, das Beispiel mit dem Namen des Kapitels, also HelloWorld, zu öffnen.
Das ist das fertig programmierte Beispiel, das direkt auf den Controller geladen und ausprobiert werden kann.
Die andere Option ist, dass Sie das Beispiel HelloWorldDlY öffnen.
Dieses Beispiel enthält außer den beiden obligatorischen Funktionen loop und setup keinen Quelltext.
 Das Beispiel ist dazu gedacht, das Programm selbst zu entwickeln und unter einem anderen Namen abzuspeichern.
Gerade bei den ersten Beispielen ist das empfehlenswert.
Bei den meisten Beispielen werden die Schritte erklärt, die Sie machen müssen, um das Programm selbst zu konstruieren.
Sollten Sie auf Schwierigkeiten stoßen, können Sie jederzeit das fertige Beispiel zum Vergleich heranziehen.

                                                                                Seite 10


Tippen Sie also folgendes Programm ein und laden Sie es auf den Controller:

001 #include "Display.h"
002 #include "SPI.h"
003
004 Display lcd = Display();
005
006 void setup() {
007    lcd.init(20);    // 20 = Kontrast-Wert des LCD-Displays (12 bis 127)
008 }


Der Quelltext besteht aus mehreren Teilen.
Zu Beginn werden die 2 benötigten Libraries mittels include-Befehl eingebunden.
Direkt darunter wird eine neue Instanz des Typs Display erzeugt.
Sie können Ihr Display also nun unter dem Namen lcd ansprechen.
Dieser wird auch gleich in der setup-Prozedur genutzt, um das Display zu initialisieren.
Der Initialisierungsfunktion wird als Parameter der Kontrast mitgegeben.
In den runden Klammern, in denen hier eine Null steht, sollte also der von Ihnen im Starter-Beispiel ermittelte Kontrastwert eingetragen werden.
Alle bis hier genutzten Quelltextzeilen werden in fast jedem in diesem Buch vorgestellten Programm vorkommen.
Sie sind die ersten grundlegenden Schritte, um das Display erfolgreich anzusteuern.
Um sinnlose Wiederholungen zu vermeiden, wird dieser Quelltextteil allerdings nicht in jedem Kapitel abgedruckt werden.
Wenn Sie sich einmal bei der genauen Schreibweise nicht ganz sicher sind, können Sie einfach auf diesen Seiten nachschlagen oder den relevanten Text aus einem vorangegangen Programm kopieren.

001 void loop() {
002    lcd.resetRamAddress();
003    for (int i = 0; i<128; i++) {
004    lcd.writeData(0b00000000);
005    delay(10);  // 10ms Löschzeit
006    }
007
008    lcd.resetRamAddress();
009    for (int i = 0; i<128; i++) {
010    lcd.writeData(0b11111111);
011    delay(100);  // 100ms Schreibzeit
012      }
013  }



                                                                                Seite 11


MENU > Werkzeuge > Port: "COM5 (Arduino Uno)"
MENU > Datei > Sketchbook > DisplayBeispiele > HelloWorld.ino
Hackerl = Verifizieren
Pfeil = Hochladen

ORDNER HelloWorld    |  HelloWorld.ino  |  Display.cpp  |  Display.h  |

#include "Display.h"
#include "SPI.h"

Display lcd = Display();

void setup() {
  lcd.init(20);   // 20 = Kontrast-Wert des LCD-Displays (12 bis 127)
}

void loop() {
  lcd.resetRamAddress();
  for (int i = 0; i<128; i++) {
    lcd.writeData(0b00000000);
    delay(10);  // 10ms Löschzeit
  }
 
  lcd.resetRamAddress();
  for (int i = 0; i<128; i++) {
    lcd.writeData(0b11101101);  // unten 3, mitte 2, oben 1 Strich
    delay(100);  // 100ms Schreibzeit
  }
}





MENU > Werkzeuge > Port: "COM5 (Arduino Uno)"
MENU > Datei > Sketchbook > DisplayBeispiele > HelloWorldDIY.ino
Hackerl = Verifizieren
Pfeil = Hochladen

ORDNER HelloWorldDIY    |  HelloWorldDIY.ino  |  Bitmap.h  |  Display.cpp  |  Display.h  |  Font.h  |  TextDisplay.cpp  |  TextDisplay.h  | 

void setup() {
}

void loop() {
}




In der loop-Prozedur schließlich finden Sie den interessanten Teil für dieses individuelle Programm.
Bevor Sie allerdings die einzelnen Befehle verstehen können, müssen Sie Folgendes über das Display wissen:
Das Display besteht nicht nur aus einer bloßen Anzeigefläche, sondern verfügt auch über einen internen Speicher [RAM].
Alles, was Sie in diesen Speicher schreiben, wird schließlich eins zu eins auf die Anzeigefläche übertragen.
Das Display sowie der RAM-Bereich sind in mehrere Zeilen und Spalten aufgeteilt.
Insgesamt gibt es 8 Zeilen (Pages) mit jeweils 128 Spalten.
Schreiben Sie an eine bestimmte Stelle im RAM eine 1, wird das entspreche Pixel aktiviert.

                          DXDCG12864-4330  128x64 dots
http://tiny.systems/software/lcdProjekt/DXDCG12864-4330.pdf


Abb. 2.1: Das Display, aufgeteilt in Pages und Spalten


Mithilfe der Library wird das Schreiben in das Display RAM sehr einfach mit dem Befehl writeData realisiert.
In den Klammern hinter dem Befehl steht ein Byte, das angibt, welche Pixel geschrieben werden sollen.
An welche Stelle im RAM geschrieben wird, bestimmt der sogenannte Pointer (Zeiger).
Der Pointer wird noch vor dem writeData-Befehl auf die erste Position [oben links) gesetzt.
Zuständig dafür ist der Befehl resetRamAddress.
Jedes Mal, wenn Sie mit writeData etwas in das RAM schreiben, rückt der Pointer automatisch um eine Position weiter.
Somit müssen Sie nicht vor jedem Schreibbefehl die gesamte Adresse angeben.
Das Programm schreibt nun 128-mal acht schwarze Pixel und damit einen von links nach rechts wachsenden Balken am oberen Rand des Displays.

                                                                                Seite 12



Abb. 2.2: Der Balken des HelloWorld-Beispiels

Tipp:
Sie wundern sich vielleicht, warum das Display mehr als nur den Balken zeigt?
Das liegt vermutlich daran, dass Sie gerade das erste Starter-Beispiel ausprobiert haben.
Reste des alten Musters befinden sich noch im RAM des Displays und wurden noch nicht überschrieben.
Deswegen überlagern sich die beiden Bilder.


Nachdem Sie einiges über das Display und über die Library erfahren haben, liest sich der Quelltext in der loop-Schleife ganz einfach.
Zunächst wird der Pointer mit dem Reset-Befehl nach oben links gesetzt.
Anschließend wird in der for-Schleife Spalte für Spalte beschrieben.
In der ersten for-Schleife wird zunächst an allen Stellen eine Null geschrieben, die Zeile wird also gelöscht.
Ein kleines Delay in jeder Schleife sorgt dafür, dass man das auch gut sichtbar verfolgen kann.
Hat die for-Schleife alle 128 Spalten beschrieben, wird der Pointer wieder resetet und eine for-Schleife läuft durch, die in die Spalten jeweils eine binäre 255 schreibt, also alle Zeilen aktiviert.

Tipp
Sie können auch einmal ausprobieren, was passiert, wenn Sie das Byte im unteren Write-Befehl verändern.
Verschiedene Muster lassen sich auf einfache Weise schon im ersten Beispiel ausprobieren.
Besonders interessant sind die Ausgabe der Laufvariablen i und das Muster eines fortlaufenden Binärzählers.


                                                                                Seite 13




Die folgende Abbildung zeigt noch einmal den genauen Zusammenhang zwischen der Darstellung auf dem Display und dem übergebenen Wert in binärer Schreibweise.

2.2 Analoglnput - Messergebnisse am Display anzeigen
Im vorangegangenen Beispiel haben Sie bereits gelernt, wie Sie eine einfache Ausgabe auf dem Display realisieren.
Das Problem war allerdings noch das Löschen des Registers.
Aus diesem Grund wird in diesem Kapitel eine spezielle Funktion zum Löschen von Daten aus dem Display-RAM vorgestellt.
Ganz nebenbei lernen Sie außerdem eine Möglichkeit kennen, die Temperatur Ihres Mikrocontrollers auszulesen.


Abb. 2.3: Die Berechnung des Binärwerts

binär 01101001 = hexadezimal 69
Binär-Dezimal-Hexadezimal Umrechner
http://binaer-dezimal-hexadezimal-umrechner.miniwebapps.de/


Balken OBEN : Meßwert des pin-A0 (z.B. 3,3V)
Balken Mitte: Meßwert des pin-A1 (z.B. 5,0V)
Balken UNTEN: Aktuelle Prozessortemperatur



Abb. 2.4: Die Messergebnisse als 3 Balken-Anzeige

                                                                                Seite 14

Dargestellt werden die Messwerte der beiden analogen Eingänge A0 und Al sowie die aktuelle Prozessortemperatur als Balkendiagramm.
Dazu benötigen Sie zunächst eine Methode, die einen Querbalken auf das Display zeichnen kann.

001 void writeMeteringBar(byte page, byte data) {
002 lcd.setPageAddress(page);
003 lcd.setColumnAddress(0);
004 for (byte i = 0; i < data; i++)  {
005    lcd.writeData(0xff);
006   }
007
}


Wie Sie sehen, werden der Funktion zwei Werte übergeben.
Der Wert page steht für den Zeilenbereich im Display.
Dabei gehören acht Zeilen jeweils zu einer Page, und das Display besteht so aufgeteilt aus insgesamt 8 Pages [von 0-7).
Mit dem Befehl setPageAdress wird die zu beschreibende Page gesetzt.
Der darauf folgende Befehl setColumnAdress legt die Spalte fest.
In dieser Prozedur beginnen die Balken immer am linken Rand des Displays, weshalb der Wert immer 0 ist.
data enthält später die gemessenen Werte auf das Display skaliert.
Das bedeutet auch, dass data der Anzahl von geschriebenen Spalten entspricht.

Tipp:
An diesem Beispiel können Sie erkennen, dass die drei Befehle setPageAdress, setColumnAddress und writeData schon ausreichen, um an jede beliebige Stelle im Display etwas auszugeben.


001 int getTemperature(void)
002 {
003 ADMUX = (_BV(REFS1)  |  _BV(REFS0)    | _BV(MUX3));
004 ADCSRA  |=  _BV(ADEN);                 //Enable ADC
005 delay(10);                                         // wait for voltage stabilisation
006 ADCSRA  |= _BV(ADSC);                  // Start ADC
007 while(bit_is_set(ADCSRA, ADSC));     // wait for messurement
008 return ADCW;                                   // return value from ADCW register (ADCL and ADCH)
009
}


Der ATmega328p hat einen internen Temperatursensor

Quelle:
http://playground.arduino.cc/Main/InternalTemperatureSensor


                                                                            Seite 15




Die Funktion getTemperature dient dazu, die Temperatur des Mikrocontrollers auszulesen.
Das ist in Arduino von Haus aus nicht mit implementiert.

001 void loop() {
002    lcd.clearDisplayRAM ();
003  byte value;
004
005  value = analogRead(A0) >> 3;
006  writeMeteringBar(0, value);
007 writeMeteringBar(1, value);
008
009  value = analogRead(A1) >> 3;
010 writeMeteringBar(3, value);
011 writeMeteringBar(4, value);
012
013  value = getTemperature() & 0b00111111;    // Normalization
014  writeMeteringBar(6, value);
015  writeMeteringBar(7, value);
016
017  delay(100);
018 
}

Die vorgestellten Funktionen werden nun alle in der obligatorischen loop-Funktion nacheinander ausgeführt, doch nicht ohne zu Beginn der Funktion das komplette Display mit der clearDisplayRAM-Funktion zu bereinigen.
Im Folgenden werden nacheinander die beiden ADC-Kanäle in die Variable value eingelesen und dem Wertebereich angepasst.
Das Problem ist nämlich, dass der Analog-Digital-Converter Werte von 0 bis 1023 misst [er hat also eine Auflösung von 10 bit], das Display aber nur einen maximalen Wert von 127 anzeigen kann.
Die einfachste Methode, den Messwert anzupassen, ist, den Binärwert um drei Stellen zu verschieben, und zwar mit dem Befehl >>3.
Mit dieser Normierung erhält man aus einem 10-bit-Messwert einen 7-bit-Messwert, der sich komplett auf dem Display darstellen lässt.
Die folgende Abbildung verdeutlicht die Normierung.

                                                                               Seite 16





Abb. 2.5: Der Rechts-Shift um drei Stellen  value = analogRead(A0) >> 3;

Zusammengefasst nehmen Sie die 7 höherwertigen Bits und schieben sie 3 Stellen nach rechts.
Mathematisch gesehen entspricht das einer Division durch 8, denn 8 = 2 x 2 x 2.

Im nächsten Schritt werden die Werte mit der bereits bekannten write-MeteringBar-Funktion ausgegeben, und zwar doppelt, um den Balken auf dem Display breiter darzustellen.
Die Ausgabe des internen Temperaturwerts funktioniert ganz ähnlich - bis auf die Anpassung des Werts.
 In diesem Fall werden nämlich einfach die oberen beiden Bits abgeschnitten.
Dadurch wird der Fokus mehr auf kleinere Wertänderungen gelegt. D
ie Darstellung ist also empfindlicher.
Das ist in diesem Fall sehr sinnvoll, denn Sie können die Temperatur des Controllers nur minimal beeinflussen.

Nun kennen Sie alle entscheidenden Quelltextteile und können das Programm vervollständigen.
Das HelloWorldDlY-Beispiel kann auch hier wieder als Vorlage benutzt werden.
Vergessen Sie aber nicht den im vorangegangenen Kapitel vorgestellten Setup-Teil und die Initialisierung des Displays.
Sie können natürlich auch das fertige Beispiel verwenden.
Wenn Sie das Programm auf Ihren Arduino Uno geladen haben, sollten Sie auf dem Display drei Balken sehen.
Der obere Balken stellt den analogen Eingang A0 dar.

                                                                            Seite 17



Mit diesem Beispiel haben Sie ein einfaches Voltmeter programmiert und können im Bereich zwischen 0 und 5,0 V Spannungen messen.
Die Ausgänge GND, 3,3 V oder auch 5,0 V eignen sich bestens für eine erste Messung.
Der darunter liegende Kanal A1 kann, wenn er nicht verbunden ist (offener Eingang), mit beeinflusst werden.
Der unterste Balken stellt die Temperatur des Mikrocontrollers dar.
Durch Berühren mit dem Finger können Sie auch diesen Wert verändern, allerdings nur minimal.
Mit diesem Beispiel sind Sie bereits in der Lage, verschiedene Messwerte auf einfache Weise zu visualisieren.



Abb. 2.6: Eine Drahtbrücke vom 3,3-V-Pin zu pin-A0


MENU > Werkzeuge > Port: "COM5 (Arduino Uno)"
MENU > Datei > Sketchbook > DisplayBeispiele > AnalogInput.ino
Hackerl = Verifizieren
Pfeil = Hochladen

ORDNER AnalogInput    |  AnalogInput.ino  |  Display.cpp  |  Display.h  |

#include "Display.h"
#include "SPI.h"

Display lcd = Display();

void setup() {
  lcd.init(20);                                                               // 20 = LCD-Display Kontrast 
(12 bis 127)

void writeMeteringBar(byte page, byte data) {
  lcd.setPageAddress(page);
  lcd.setColumnAddress(0);
  for (byte i = 0; i < data; i++) {
    lcd.writeData(0xff);
  }
}

int getTemperature(void)
{
  ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
  ADCSRA |= _BV(ADEN);                                            // Enable ADC
  delay(10);                                                                  // wait for voltage stabilisation
  ADCSRA |= _BV(ADSC);                                            // Start ADC
  while(bit_is_set(ADCSRA, ADSC));                             // wait for messurement
  return ADCW;                                                            // return value from ADCW register (ADCL and ADCH)
}

void loop() {
  lcd.clearDisplayRAM();
  byte value;

  value = analogRead(A0) >> 3;
  writeMeteringBar(0, value);
  writeMeteringBar(1, value);

  value = analogRead(A1) >> 3;
  writeMeteringBar(3, value);
  writeMeteringBar(4, value);

  value = getTemperature() & 0b00111111;                         // Normalization
  writeMeteringBar(6, value);
  writeMeteringBar(7, value);

  delay(100);                                                                    // Pause  100ms
}






2.3  Blick in die Library 1
Eine Library (Programmbibliothek) ist dazu gedacht, bereits programmierte Funktionen über die Verwendung in einem einzelnen Programm hinaus zugänglich zu machen.
Das bedeutet, dass Sie sich durch die Auslagerung wiederkehrender oder bestehender Programmteile auf Dauer eine ganze Menge Arbeit ersparen.
Eine Library schafft Struktur, spart Zeit und macht Ihren Code für weitere Projekte wiederverwendbar und teilbar.

                                                                     Seite 18




In diesem Buch geht es darum, alle Aspekte im Umgang mit dem Display zu beleuchten.
Damit Sie tatsächlich verstehen, was im Hintergrund abläuft, ist es nötig, von Zeit zu Zeit einen Blick in die Library zu werfen.
Aus diesem Grund finden Sie an geeigneten Stellen Erklärungen dazu, was in der Display Library, also im Hintergrund, abläuft.
Die Library kann aber auch den Zweck erfüllen, Ihnen die Möglichkeit zu geben, sich erst an das Thema Displayprogrammierung heranzutasten, ohne sich mit Detailfragen zu belasten.
Das ist eine sinnvolle Herangehensweise für Einsteiger, für die eine Erarbeitung dieses Kapitels zu einem späteren Zeitpunkt geeignet ist.
Sehen Sie die Library als Gerätetreiber für das angeschlossene Display, für dessen genaue Funktionsweise Sie sich interessieren können, aber nicht müssen.



Abb. 2.7: Die Library als Bindeglied zwischen Hard- und Software


2.3.1 Header-Datei
Eine Arduino-Library besteht mindestens aus zwei Dateien: einer CPP-Datei (der eigentlichen Implementierung, Umsetzung der Programmlogik) und einer H-Datei.
Die H-Datei ist die sogenannte Header-Datei.
Sie enthält die Definition der Klasse, also vor allem die Namen der Methoden und Variablen.
Wenn Sie das AnalogInput-Beispiel in IhrerArduino-Oberfläche geöffnet haben und auf den Reiter Display.h klicken, können Sie die gesamte Header-Datei überblicken.
Sie enthält also alle bis dahin genutzten Methoden, die hier kurz vorgestellt werden sollen.
Im Lauf des Buchs wird sich die Library weiterentwickeln.
Dazu mehr in den späteren Kapiteln.

001 /*
002    Display.h - Library to control a ST7565 based 12864 LCD.
003    Thomas Baum, mailto:th.baum@online.de, 10.05.2014
004 */


                                                                            Seite 19


Diesen Teil finden Sie zu Beginn der Header-Datei.
Der Text ist von Kommentarklammern umgeben und wird somit nicht mit kompiliert.
Einen ähnlichen Text beinhalten viele andere Libraries, denn hier befinden sich wichtige Informationen wie Hardwaretyp, Art der Library und Name des Autors sowie das Datum der letzten Änderung.
Falls Sie später einmal eine eigene Library schreiben, sollte dieser Teil nicht fehlen.

001 #ifndef _DISPLAY_H_
002 #def ne _DISPLAY_H_


Mit diesen beiden Zeilen wird eine Mehrfachdefinition verhindert.
Es handelt sich um eine if-Abfrage, auch wenn Sie anders aussieht, als Sie es von einem gewöhnlichen Arduino-Programm kennen.
Man könnte den Quelltext in etwa so verstehen:
Wenn _DISPLAY_H_ nicht definiert wurde, definiere _DISPLAY_H_ hier.
Diese Mehrfachdefinitionen können vorkommen, wenn eine Klasse vererbt wird oder eine ähnliche Library ebenfalls benutzt wird.
Nicht vergessen sollten Sie auch das #endif ganz am Ende der Header-Datei.
Der gesamte Quelltext dazwischen wird also ausgelassen, wenn bereits eine Klasse mit gleichen Namen verwendet wird.

001 #if ARDUINO >= 100
002 #inc ude "Arduino.h"
003 #else
004 #include "WProgram.h"
005 #endif

Auch bei diesen Zeilen handelt es sich um eine if-Abfrage aus Sicherheitsgründen.
Dabei geht es darum, dass in jede Library die Haupt-Library von Arduino eingebunden werden muss.
Allerdings hat sich der Name dieser Library im Lauf der Zeit geändert.
Deswegen wird hier die verwendete Arduino-Version abgefragt.
Ist diese größer als v1.0, muss die Arduino.h-Datei eingebunden werden.
Benutzen Sie eine ältere Version, wird die WProgramm.h-Datei verwendet.

001 class Display {
002   public:
003     Display( ) { }
004
005 void init(byte contrast);
006 void writeCommand(byte command);
007 void writeData(byte data);
008 void resetRamAddress(void);
009 void setPageAddress(byte pageAddress);
010 void setColumnAddress(byte columnAddress);
011 void clearDisplayRAM(void);
012 
} ;

                                                                            Seite 20



Hier kommt der tatsächlich spannende Teil.
In diesen Zeilen wird die Klasse Display inklusive Methoden definiert.
Sollten Sie einmal eine unbekannte Library in die Finger bekommen, können Sie in diesem Teil nachsehen, welche Funktionen diese Library enthält und welche für Sie zugänglich sind.
Frei zugänglich sind für Sie die Methoden oder Variablen, die unter public zu finden sind.
Das sind in diesem Fall alle hier abgebildeten.
Allerdings gibt es auch Privatdefinitionen, die nur innerhalb der Library selbst benutzt werden können.
Als Erstes wird der Standardkonstruktor der Klasse Display definiert.
Es folgen die bereits bekannten Methoden.
Steht vor der Funktion ein void, werden keine Daten zurückgegeben.
In den Klammern stehen die Daten, die der Funktion übergeben werden, auch als Parameter bezeichnet.
Dabei heißt void, dass keine Parameter angegeben werden.
Allerdings sieht man z. B. bei der writeData-Mehtode, dass der Funktion ein Byte übergeben werden muss.

Das war bereits das Wichtigste aus der Header-Datei, die immer praktisch ist, um sich einen kleinen Überblick über die öffentlichen Methoden zu verschaffen.
Wenn Sie allerdings wissen möchten, was diese Methoden genau machen, müssen Sie die CPP-Datei öffnen. In unserem Fall liegt die Implementierung [*. CPP) offen,
d. h., der Programmcode ist für alle sichtbar [Open Source].
Unter Umständen kann es aber einmal vorkommen, dass Sie in Ihren Arduino-Programmen APIs verwenden, deren Programmcode nicht offen liegt.
In diesem Fall steht Ihnen der Programmcode nur als Objektcode zur Verfügung.
Die Header-Datei stellt dann die Schnittstelle dazu dar.



2.3.2 CPP-Datei

001 #include <SPI.h>
002 #include "Display.h"

                                                                            Seite 21


Als Erstes finden Sie den bereits bekannten Kommentartext, der für Sie aber nichts Neues enthält.
Deswegen wird es erst bei den oben abgebildeten Zeilen interessant.
Hier wird nämlich zunächst einmal die SPI-Library eingebunden.
Diese Library ist in Arduino integriert und muss somit nicht heruntergeladen werden.
SPI steht für Serial Peripheral Interface, einem Bussystem, das einst von Motorola entwickelt wurde.
Über dieses Bussystem wird das Display angesteuert.
Ebenfalls eingebunden wird die Display-Header-Datei.
Dabei müssen Sie Folgendes unterscheiden:
Steht die Libaray in Anführungszeichen wie die im Display.h-Datei, befindet sich die Datei im selben Arbeitsordner.
Sollte sich die Datei in dem Library-Ordner befinden, muss der Name der Header-Datei in spitzen Klammern geschrieben werden.

001 const int LCD_A0 = 8;
002 const int LCD_RST = 9;
003 const int LCD_CS = 10;


In diesen 3 Zeilen werden Konstanten definiert, die für die Ansteuerung des Displays nötig sind.
Zum einen wäre da die A0-Leitung, die Kommandoleitung, nicht zu verwechseln mit dem analogen pin-A0 des Entwicklungsboards.
Es folgt die Resetleitung und zuletzt die sogenannte Chip-Select-Leitung.
Da es sich bei der SPI-Schnittstelle um einen Bus handelt, können theoretisch mehrere Peripheriegeräte (SPI-Slaves) angebunden sein.
Mit der CS-Leitung wird dem Display signalisiert, dass die ankommenden Daten für es bestimmt sind.
Damit kennen Sie alle notwendigen Leitungen für den Betrieb des Displays.
Die folgende Abbildung zeigt die fünf Kommunikationsleitungen (bestehend aus SPI-Schnittstelle und Steuerleitungen) für das Display.



Abb. 2.8: Die fünf Kommunikationsleitungen

                                                                     Seite 22



2.3.3 Funktion init()
Die init-Funktion wird in jedem Programm benötigt.
init steht für Initialisierung und bedeutet, bestimmte Anfangswerte und Einstellungen festzulegen, ohne die die Funktionalität nicht gewährleistet wäre.
In diesem Fall betrifft das Einstellungen zur SPI-Schnittstelle, Pegel an den Steuerleitungen und einige Befehle und Parameter, die vorher an das Display gesendet werden müssen.

001 void Display::init(void) {
002 SPI.begin();
003 SPI.setBitOrder(MSBFIRST);
004 pinMode(LCD_AO, OUTPUT);
005 pinMode(LCD_RST, OUTPUT);
006 pinMode(LCD_CS, OUTPUT);


In der ersten Zeile des oben abgebildeten Quelltextauszugs lässt sich gut ablesen, wie eine Methode allgemein in einer Library geschrieben werden muss.
Diese Schreibweise unterscheidet sich durchaus von der aus der Arduino-Umgebung bekannten Schreibweise.
Wichtig ist nämlich, dass die zugehörige Klasse immer beim Methodennamen mit angeben wird.
Der Klassenname und der Funktionsname sind durch zwei Doppelpunkte getrennt.
Die beiden darauf folgenden Zeilen behandeln die Initialisierung der SPI-Schnittstelle mit der Einstellung, dass das „Most Signifikant Bit" vorn steht.
In den letzten 3 Zeilen werden die Steuerleitungen schlicht als Ausgänge definiert.

001 digitalWrite(LCD_RST, LOW);
002 digitalWrite(LCD_CS, LOW);
003 delay(50);
004 digitalWrite(LCD_RST, HIGH);

Damit das Display richtig initialisiert wird, müssen zunächst einmal der Resetpin sowie der ChipSelect-Pin auf LOW gesetzt werden.
Nach einer kurzen Wartezeit kann der Resetpin wieder High gesetzt werden. Das Display ist nun resetet und kann Daten empfangen.

                                                                            Seite 23


001  writeCommand(0xa0);
002  writeCommand(0xc8);
003  writeCommand(0xa6);
004 writeCommand(0xa2);
005 writeCommand(0x2f);
006 writeCommand(0xf8) ;
007 writeCommand(0x00);
008 wri teCommand(0x27) ;
009 writeCommand (0x81) ;
010 writeCommand(contrast);
011 writeCommand(0xac);
012 writeCommand(0x00) ;
013 writeCommand (0xa4)
014 writeCommand(0xaf) ;
015 }

Was nun folgt, ist eine Reihe von Kommandos, die direkt an das Display gerichtet sind.
Die Funktion writeCommando ist keine Funktion der SPI-Library, sondern wird im nächsten Abschnitt erklärt.
Die Kommandos werden als Hexadezimalzahlen übergeben.
Der Reihenfolge nach passiert Folgendes:

Befehl als Hexcode   Befehl als Byte  Funktion
AO                   1010 0000        Anzeige horizontal umdrehen aus
C8                   1100 1000        Anzeige vertikal umdrehen an
A6                   1010 0110        Anzeige invertieren aus
A2                   1010 0010        LCD-Treiberspannung setzen 1/9 biss (Teilerverhältnis)
2F                   0010 1111        LCD-Booster: an, Regulator: an, Follower an
F8                   1111 1000        Boosterrate ändern
00                   0000 0000        Boosterrate setzen 00
27                   0010 0111        LCD-Spannungsregler 7
81                   1000 0001        Kontrastspannung ändern
Kontrastwert, z.B.18 0001 1000        Kontrastspannung setzen auf 24 (0x18) (Wertebereich 0-63)
AC                   1010 1100        Static Indicator ändern [Blinkmodus)
00                   0000 0000        Blinkmodus aus
A4                   1010 0100        Alle LCD-Pixel im Normalmodus
AF                   1010 1111        LCD aktivieren

  
Binär-Dezimal-Hexadezimal Umrechner
http://binaer-dezimal-hexadezimal-umrechner.miniwebapps.de/

                                                                     Seite 24


Tipp
Sie können einmal ausprobieren, was passiert, wenn Sie die Einstellungen Anzeige invertieren (Kommando A7), Anzeige horizontal umdrehen (Kommando Al) und Anzeige vertikal umdrehen (Kommando CO) verändern.
Eine genaue Auflistung, welches Kommando was bewirkt, finden Sie im Kapitel „HardwarePlayground".


Die init-Funktion ist natürlich nicht die einzige Funktion dieser Library.
Da Sie sie aber bereits kennen, werden die restlichen Funktionen ein Kinderspiel sein.

001 void Display::writeCommand(byte command) {
002    digitalWrite(LCD_A0, LOW);
003    SPI.transfer(command);
004    digitalWrite(LCD_A0, HIGH);
005  }


Als Nächstes wird die writeCommand-Funktion genauer unter die Lupe genommen.
Bei der Ansteuerung des Displays muss zwischen Kommando- und Datenmodus unterschieden werden.
Das Display muss schließlich wissen, ob es mit dem nächsten Byte Pixel auf dem Display aktivieren soll oder vielmehr interne Einstellungen verändert werden müssen.
Damit diese beiden Funktionen klar unterschieden werden können, gibt es die Kommandoleitung (A0).
Ist diese LOW, wird das nächste Byte als Kommando für das Display interpretiert.
Ist es HIGH, behandelt das Display das nächste Byte als Daten, die direkt ins aktuelle Register geschrieben und auf dem Display angezeigt werden.
Mit der writeCommand-Funktion werden nur Kommandos gesendet.
Also wird der Kommando-Pin zunächst auf LOW gesetzt und anschließend das Byte mit der SPI-Funktion transfer übertragen.
Schließlich muss der Kommandopin wieder HIGH gesetzt werden, für den Fall, dass als Nächstes ein Datenbyte folgt.

001 void Display::writeData(byte data) {
002 SPI.transfer(data);
003 }


Die Funktion writeData ist der Funktion writeCommand recht ähnlich, nur einfacher.
Da der Standortzustand des Kommandopins HIGH ist, muss einfach nur das Datenbyte mit der SPI-Funktion übertragen werden.

                                                                     Seite 25



001 void Display::resetRamAddress(void)  {
002 writeCommand(0x10);
003 writeCommand(0x00):
004 writeCommand(0xb0):
005 }


Die Funktion resetRamAddress haben Sie in den vorangegangenen Beispielen ebenfalls benutzt.
Da das Display nach jedem empfangenen Datenbyte den Pointer auf das Adressregister automatisch um eine Adresse weiter setzt, ist es notwendig, die RAM-Adresse manuell auf 0 (erste Stelle) zu setzen, um an den Anfang des Displays etwas schreiben zu können.
Die ersten beiden Kommandobytes setzen die Spaltenadresse fest.
Da die Adresse Werte von 0-127 annehmen kann, dem Kornmandobyte aber nur vier Stellen als Parameter zur Verfügung stehen, wird zunöchst die höherwertigen bits des Bytes übertragen und schließlich die niederwertigen vier bits des Bytes.
Der letzte Befehl stellt die Zeilenadresse ein, in diesem Fall also auf den Wert 0.
Damit Sie den Aufbau der Befehle genauer verstehen können, ist an dieser Stelle ein Auszug aus der Tabelle abgebildet:

Page address set                0b10110iii    Zeilenadresse einstellen [0-7]
Colurnn address set upper bit   0b0001iiii    Spaltenadresse einstellen [höherwertige Bits)
Column address set lower bit    0b0000iiii    Spaltenadresse einstellen (niederwertige Bits]


Wenn Sie die resetRamAddress-Funktion kennen, sind die beiden folgenden Funktionen leicht zu verstehen.

001 void Display::setPageAddress(byte pageAddress) {
002              writeCommand((byte) pageAddress | 0xb0);
003  }
004
005 void Display::setColumnAddress(byte columnAddress) {
006               byte highNibble = (byte)columnAddress >> 4;
007               writeCommand(0x10 | highNibble);
008              byte lowNibble = (byte)columnAddress & 0x0f;
009    writeCommand(lowNibble);
010  }


Die beiden Funktionen setPageAdress und setColumnAdress dienen dazu, den Pointer auf eine bestimmte Stelle im Display zu setzen.

                                                                     Seite 26



Wie die entsprechenden Kommandos lauten, ist Ihnen bereits aus der resetRamAddress-Funktion bekannt.
Interessant ist hier allerdings, wie die entsprechenden Werte an das Byte übergeben werden.
In der Funktion zur Einstellung der Page-Adresse, also der Displayzeile, wird das Kommandobyte mit einer Oder-Funktion mit dem Adressenbyte verknüpft.
Beim Festlegen der Column, also der Displayspalte, ist der Fall wie zuvor etwas schwieriger.
Das Adressbyte muss nämlich zunächst in High- und Lowbyte aufgeteilt werden.
Das Highbyte ergibt sich, indem das gesamte Byte um vier Stellen nach rechts verschoben wird.
Das Lowbyte bekommt man, indem man das Adressbyte mit einem Byte des Werts 0b00001111 (oder Hexadezimal 0x0F] und-verknüpft.
Die oberen vier Bytes fallen also automatisch weg.
Im Grunde werden also durch einfache Bytemanipulationen und Maskierung einfach hochwertige und niederwertige Stellen (auch high und low nibble genannt) des Bytes getrennt und jeweils wieder mit der Oder-Funktion in den Parameterbereich des Kommandobytes geschrieben.

001 void Display::clearDisplayRAM() {
002             for(int i = 0; i < 8; i++)  {
003             setPageAddress(i);
004             setColumnAddress(0);
005              for(int j = 0; j < 128; j++) {
006                  writeData(0x00);
007                   }
008            }
009  }


Die letzte in diesem Kapitel zur Library vorgestellte Funktion ermöglicht das Löschen des Displayinhalts.
Da es leider keine displayeigene Funktion gibt, die das Löschen selbstständig erledigt, muss man sich anders behelfen.
Der einfachste Weg ist, dass in jede Stelle des Registers der Wert 0 geschrieben wird.
Genau das geschieht in diesen for-Schleifen, die nacheinander alle 8 Zeilen [Pages] und pro Zeile alle 128 Spalten durchgehen und somit die komplette Löschung des Displays ermöglichen.

                                                                            Seite 27

ZeilenxSpalten
8x21 ASCII-Zeichen
8x8=64 dots
21x6=126+2=128 dots



DIN A4 ausdrucken
*********************************************************

Impressum: Fritz Prenninger, Haidestr. 11A, A-4600 Wels, Ober-Österreich, mailto:schaltungen@schaltungen.at
ENDE








Comments