Guten Abend liebe Community,
ich habe ein kleines Verständnisproblem...
Vor 3 Tagen habe ich mir das LCD DEM16217 gekauft, ich verstehe nur
leider nicht welche Schritte zur Initialisierung nötig sind und in
welcher Reihenfolge diese abgearbeitet werden müssen. Ziel ist der 8-Bit
Modus.
Programmierkenntnisse in C sind vorhanden.
Als Mikrocontroller setze ich den ATmega8515 ein.
Vielen Dank für Euere Hilfe im Voraus,
Christian :)
P.s: Dies ist mein erste Versuch mit einem LCD, also bitte seid etwas
nachsichtig mit mir:)
Das Tutorial hier im Forum zum Thema LCD habe ich bereits
durchgearbeitet.
Noch eine Frage: Wenn ich ein Byte an das Display übertrage, muss währen
der Übertragungszeit das LCD auf Write "stehen" und sobald die Pegel am
LCD stimmen der EN-Pin auf H gesetzt werden. Theoretisch sollte doch
dann ein Buchstabe auf dem LCD angezeigt werden. Wie kann ich dem LCD
aber mitteilen, an welcher Stelle, der Buchstabe erscheinen soll?
Christian Karle schrieb:> Schiebt das LCD die Daten immer automatisch ein Feld weiter?
Ja.
Ein derartiges LCD hat ein Konzept ähnlich einem Cursor, der angibt, wo
das nächste Zeichen hinkommt und der nach der Ausgabe eines Zeichens um
1 Stelle weitergestellt wird.
Ich denke, du hast die Datenblätter und/oder Tutorien und deren Code
studiert?
Wenn du das LCD erst mal initialisiert hast, ist der wichtigste
Abschnitt im oben verlinkten Datenblatt das Kapitel 9, in dem alle
Kommandos beschrieben sind, die das LCD versteht. 'Den Cursor an eine
bestimmte Position setzen' heißt dort 'Set DDRAM Address'. Das ist aber
eigentlich auch schon die einzige Falle in den Kommandos, die man ev.
nicht gleich versteht.
picaxe schrieb:> Miese Peter ;-)
Ein Pessimist ist nur ein Optimist mit Erfahrung. Wenig beschäftigt
Anfänger mehr, als ein vermeintlich popeliges "Standard-LCD" Display.
So...
Ich habe gerade die Zeit gefunden die Initialisierung zu schreiben.
Ich habe sie mit Absicht extrem ausführlich geschrieben und wollte euch
nun fragen, ob diese Initialisierung so funktionieren wird.
Vielen Dank im Voraus,
Christian :)
Christian Karle schrieb:> So...>> Ich habe gerade die Zeit gefunden die Initialisierung zu schreiben.> Ich habe sie mit Absicht extrem ausführlich geschrieben und wollte euch> nun fragen, ob diese Initialisierung so funktionieren wird.
Viellicht, vielleicht auch nicht.
Teil der Initialisierung ist auch, dass du dich an Timings halten musst,
die von dieser Seite des Bilschirms nur schlecht zu kontrollieren sind.
Was aber auf jeden Fall in deinem Code ziemlicher Mist ist, das ist,
dass du zb hier
die Pinnummern direkt reingeschrieben hast.
Logisch, es ist schon klar, dass es in letzter Konsequenz darauf hinaus
läuft, dass irgendwelche Pins an irgendeinem Port wie hier auf Ausgang
geschaltet werden müssen, oder das Pins toggeln müssen.
Aber: Auch wenn es letzten Endes auf bestimmte Prozessorpins hinaus
läuft, so ist die viel wichtigere Information die, welche Bedeutung
dieser µC-Pin aus Sicht des LCD hat. Das heißt als Programmierer ist für
dich wesentlich wichtiger, dass die den E-Anschluss vom LCD toggelst und
nicht, dass die E-Leitung mit dem Pin PE1 am Port E verbunden ist.
Letzteres ist ein technisches Detail, dass man beim Lesen und Verstehen
des Codes nicht wissen muss.
D.h wenn du das so schreibst
dann hat das eine ganz andere Codequalität. Jetzt steht im Code selber
unmissverständlich und eindeutig, dass hier die Leitungen auf Ausgang
gestellt werden, an denen die RS, RW und E Leitung vom LCD hängt. Das
ist die viel wesentlichere Information, als die Information, dass die am
Port E an irgendwelchen Pins hängen. Letzters ist ein notwendiges
technisches Übel, aber im Grunde insofern uninteresant, als es auch
jeder andere Port und jeder andere Pin an diesem Port sein könnte.
1
_delay_ms(50);
2
// Wait for more than 50 ms after VDD rises to 4.5V.
nein.
Du hast eines noch nicht verstanden.
Die Reihenfolge ist: zuerst stellst du alle Daten und Steuerleitungen
auf den Pegel den du brauchst. Sind die alle eingestellt, dann wird der
E-Pin vom LCD einmal hoch gezogen und wieder auf 0 zurückgestellt.
Das lCD kann ja nicht wissen, wann du mit der Einstellung der Daten und
der beiden Steuerleitungen fertig bist. Es muss also einen eindeutigen
Zustand, ein eindeutiges Kennzeichen geben, an dem man dem LCD mitteilt:
Jetzt gilt es - das was jetzt eingestellt ist, das ist dein nächstes
Kommando.
Die E-Leitung erfüllt genau diesen Zweck.
Es ist wie die Befehlausgabe bei der Bundesweer. Alle stehen um den
Befehlshaber rum. Der gibt seine Orders aus, revidiert die vielleicht
noch ein paar mal, irgendjemand macht einen Einwand, dann drehen sich
ein paar Details noch mal um. Aber irgendwann kommt der Ausspruch:
"Alles fertig - durchführen!". Und erst dann legen alle los.
Hier genau das gleiche. Du kannst an den Datenleitungen umstellen so oft
und so viel du willst, du kannst RS setzen oder löschen, du kannst RW
umstellen, so oft und so viel du lustig bist. Erst wenn der Puls an der
E-Leitung kommt - dann gilt die dann vorliegende Einstellung. Davor und
danach ist alles uninteressant. Da kannst du die Dtaenleitungen auch im
halbe Stunde Abstand nacheinander auf die richtigen Pegel bringen. Das
interessiert das LCD nicht die Bohne. Aber wenn der E-Puls kommt, dann
muss alles richtig eingestellt sein. Das ist dein 'Alles fertig -
durchführen!'
In deinem Code sehe ich nicht, dass du mal an E toggeln würdest. Teil
des Problems ist natürlich auch, dass du hier keine Abstaktionsebene (im
obigen Sinne) eingebaut hast, sondern alles mit direkten
Portbezeichnungen versehen hast. Und das ist nun mal unklug, weil man
die wesentliche Information im Code nicht sieht. Die wesentliche
Information ist nicht, dass am Port E der Pin PE2 auf 1 und wieder auf 0
geht. Die wesentliche Information ist, dass das die E-Leitung vom LCD
ist. Das will ich im Code sehen.
Ich sehe auch in deinem Code nicht, dass du dich um den Rest kümmern
würdest.
Du du sowieso 8 Bit Ausgabe machen willst, brauchst du auch die
eigentliche Ausgabe jedesmal aufs neue auskodieren. Du kannst dir ein
für alle mal genau EINE Funktion schreiben, die einen bestimmten Wert an
das LCD ausgibt. D.h. deine Init-Funktin wird vielleicht am Anfang an
den Portbits ein wenig rumpfriemeln um den 8 Bit Modus sicher zu
stellenb. Aber danach: Ob du das LCD löschen willst, oder einen anderen
Font einstellst: immer läuft es darauf hinaus, einen 8 Bit Wert auf das
LCD auszugeben. D.h. das lässt sich wunderbar trennen. Auf der einen
Seite gibt es eine Funktion, die einen (beliebigen) WErt als Kommando an
das LCD ausgibt, auf der anderen Seite ist die Init-Sequenz, die
mithilfe genau dieser Funktion die Initialisierung durchführt, indem es
die entsprechenden Kommandos zusammenstellt und mit Hilfe dieser
Funktion an das LCD absetzt. Die Funktion kümmert sich also um die
'Transportebene' und die logisch etwas höher angesiedelte Funktionalität
teilt der Funktion mit, was sie zu transportieren hat.
Hast du denn keinen anderen LCD Code studiert, wie der aufgebaut ist?
Man kann viel lernen, wenn man anderer Leute Code (allerdings sollten es
schon die richtigen Leute sein) im Detail studiert. So Dinge wie
Coding-Strategie lernt man so ganz gut. Vor allen Dingen am Anfang, wenn
man selber noch wenig Erfahrung hat, wie man Code in Schichten aufbaut
und sinnvoll in Baugruppen unterteilt.
Und nein: Code 3 mal von der Ferne überfliegen und 'schön' sagen, hat
nichts mit Codestudium zu tun.
Und eines ist auch klar: Das alles setzt vorraus, das man mit seinem
Werkzeug - der Programmiersprache - umgehen kann. Wer die Arbeiten eines
Michelangelo studiert weil er Bildhauer werden will, und sich von ihm
Details abschauen will, wie er bestimmte Dinge gemacht hat, von dem wird
vorausgesetzt, dass er mit Hammer und Meissel umgehen kann. Wer gerade
mal ein Ölbild anfertigen kann, wenn er in vorgefertigte Flächen auf der
Leinwand nach einem Nummernschema Farben reinmalt, der muss erst mal aus
dieser Malen nach Zahlen Phase rauskommen, ehe es Sinn macht, sich mit
der Pinselführung eines Rembrandt zu beschäftigen.
Neuer (überarbeiteter) Code:
Das Display hat sich auf den 2-line mode umgestellt und ein Cursor
blinkt in der 1.Zeile links auf.
Ist das Display somit richtig Initialisiert und kann ich auf dieser
Initialisierung weiter aufbauen?
Ich werde den Code natürlich noch kompakter schreiben.
LG Christian :)
Christian Karle schrieb:> Neuer (überarbeiteter) Code:>> Das Display hat sich auf den 2-line mode umgestellt und ein Cursor> blinkt in der 1.Zeile links auf.>> Ist das Display somit richtig Initialisiert und kann ich auf dieser> Initialisierung weiter aufbauen?
Sobald der defaultmässige dunkle Balken verschwindet, IST dein LCD
sauber initialisiert.
> Ich werde den Code natürlich noch kompakter schreiben.
Tip: machs es doch gleich richtig. Du sparst dir eine Menge Arbeit und
eine Menge Ärger.
Eine Funktion
1
voidLCD_command(uint8_tCommand)
2
{
3
LCD_DATA_PORT=command;
4
5
LCD_CONTROL_PORT|=((1<<LCD_EN));
6
_delay_ms(1);
7
LCD_CONTROL_PORT&=~((1<<LCD_EN));
8
}
ist keine Hexerei, die dann in der Init benutzt wird
(das einzige was man jetztr ankreiden könnte, das ist dass die Datenbits
in aufsteigender Folge am Datenport angeordnet sein müssen. Mag sein,
ist aber kein wirklicher Beinbruch, den wer das anders macht gehört
sowieso mit dem nassen Fetzen erschlagen. Es ist allerdings auch nicht
schwierig, in der Funktion LCD_command eine entsprechende Umsortierung
noch nachzurüsten. Das Löschen und Setzen von einzelnen Bits kannst du
dir allerdings in deinem Code sparen, denn auch in deinem Code müssen
alle 8 Datenpins an ein und demselben Port angeschlossen sein. D.h. wenn
diese Annahme nicht mehr hält, dann ist dein Code genauso im Arsch wie
wenn du einfach das Byte an den Port ausgibst. So ein Fall würde so und
so massive Codeänderungen nach sich ziehen)
So hat der Code doch gleich eine ganz andere Codequalität. Lies dir die
Init Funktion durch. Da brauchst du kein Datenblatt um aus dem Code
heruas zu verstehen was in welcher Reihenfolge wie initialisiert wird.
Das steht alles sonnenklar und schon fast im Klartext dort.
Da braucht es auch keine Kommentare, weil alles wesentliche sowieso im
Code selber steht. Deine Kommentare kannst du hingegen in der Pfeife
rauchen. Wer bei
1
LCD_CONTROL_PORT|=((1<<LCD_EN));
einen Kommentar
1
// Enable auf High.
braucht, um zu sehen, dass hier ein Pin auf High gestellt wird, der soll
sich sein C-Lehrgeld wiedergeben lassen - er hat offensichtlich seine
Programmiersprache nicht gelernt. Wenn in einem Kommentar dasselbe wie
im Code steht (nur in anderen Worten), dann ist das ein sinnloser
Kommmentar. Er erzählt mir nichts neues. Das ein _delay_ms(5) eine
Wartezeit von (mindestens) 5 Millisekunden realisiert, dazu brauch ich
keinen Kommentar. Der wird höchstens falsch, wenn sich am Zahlenwert mal
was ändert und vergessen wird, den Kommentar nachzuziehen
1
_delay_ms(10);
2
// Wait for more then 35 ms
was stimmt denn nun? Sind die 10ms richtig und ist der Kommentar falsch.
Oder ist der Kommentar richtig und die 10 im Code stimmen nicht?
Lass so einen Kommentar weg. Das hier gewartet wird, sehe ich im Code
genauso gut und auch dass es 10ms sind. Da bringt der Kommentar keine
neuen Erkenntnisse.
PS: Dafür vermisse ich in deinem Code die Behandlung des RS Bits.
Das ist jetzt noch kein Beinbruch, solange du keine Texte ausgibst. Aber
irgendwann wird es soweit sein.
Daher: Besser jetzt gleich behandeln.
Mit einer 'command' Funktion ist das ja auch kein Problem
1
voidLCD_command(uint8_tCommand)
2
{
3
LCD_CONTROL_PORT&=~(1<<LCD_RS);
4
5
LCD_DATA_PORT=command;
6
7
LCD_CONTROL_PORT|=((1<<LCD_EN));
8
_delay_ms(1);
9
LCD_CONTROL_PORT&=~((1<<LCD_EN));
10
}
fettich. Ab sofort ist sicher gestellt, das bei allen Kommando-Ausgaben,
das RS Bit auf 0 steht.
(Gleich eine Funktion gemacht -> wenig Arbeit bei der Behebung von
Fehlern und was viel wichtiger ist: ich bin sicher, dass bei ALLEN
Kommandos das RS Bit gelöscht ist und ich keine vergessen oder übersehen
habe)
Ok, die Übersichtlichkeit sollte nur für mich sein um z.B.
Leichtsinnsfehler besser zu vermeiden.
Das ist jetzt so ca. mein 10. Projekt in C :) und deshalb lieber erst
ausführlich und unkompliziert :)
Zur weiteren Vorgehensweise:
Ich müsste nun ein Byte an das Display senden, LCD_EN auf High setzten
und der Buchstabe, die Zahl oder das Zeichen würde auf dem Display
erscheinen.
Ich möchte wirklich alles Schritt für Schritt durchgehen...
Christian Karle schrieb:> Ok, die Übersichtlichkeit sollte nur für mich sein um z.B.> Leichtsinnsfehler besser zu vermeiden.>> Das ist jetzt so ca. mein 10. Projekt in C :) und deshalb lieber erst> ausführlich und unkompliziert :)
Sieh dir deinen Code an und dann sieh dir meinen COde an
versus das ganze Portgefrickel in deinem Code.
Welcher Code ist komplizierter? Welcher ist leichter zu verstehen?
Welcher ist einfacher zu erfassen? Welcher ist leichter zu verändern?
Dein Code ist eben nicht unkompliziert. Dein Code ist eben nicht so,
dass man kaum Leichtsinnsfehler machen kann. Ganz im Gegenteil. Dein
Code ist in die Länge gezogen und er versteckt die wesentliche
Information hinter einem Wust an technischen Details, die sich in den
Vordergrund drängen und alles andere überdecken. Da drinnen findest du
keine Fehler mehr, weil du bei den ganzen Port-Manipulationen den
Überblick verlierst, was wann und wo gesetzt bzw. gelöscht wird.
> Ich müsste nun ein Byte an das Display senden, LCD_EN auf High setzten> und der Buchstabe, die Zahl oder das Zeichen würde auf dem Display> erscheinen.
Geht genau so, wie ein Kommando an das LCD ausgeben. Nur das die RS
Leitung auf 1 sein muss und nicht auf 0, wie bei Kommandos.
Hast du also eine Funktion die ein Kommando ausgeben kann
1
voidLCD_command(uint8_tCommand)
2
{
3
LCD_CONTROL_PORT&=~(1<<LCD_RS);
4
5
LCD_DATA_PORT=command;
6
7
LCD_CONTROL_PORT|=((1<<LCD_EN));
8
_delay_ms(1);
9
LCD_CONTROL_PORT&=~((1<<LCD_EN));
10
}
dann hast du mit einer kleinen Modifikation auch sofort eine 2.te
Funktion, die ein Zeichen ausgeben kann
1
voidLCD_data(uint8_tData)
2
{
3
LCD_CONTROL_PORT|=(1<<LCD_RS);
4
5
LCD_DATA_PORT=Data;
6
7
LCD_CONTROL_PORT|=((1<<LCD_EN));
8
_delay_ms(1);
9
LCD_CONTROL_PORT&=~((1<<LCD_EN));
10
}
Wenn man wollte, könnte man beide Funktionen sogar zusammenziehen auf
einen gemeinsamen Kern. Denn die eigentliche Ausgabe ist ja in beiden
Fällen dieselbe. Lediglich die Behandlung des RS Bits ist
unterschiedlich:
1
voidLCD_out(uint8_tvalue)
2
{
3
LCD_DATA_PORT=value;
4
5
LCD_CONTROL_PORT|=((1<<LCD_EN));
6
_delay_ms(1);
7
LCD_CONTROL_PORT&=~((1<<LCD_EN));
8
}
9
10
voidLCD_Command(uint8_tCommand)
11
{
12
LCD_CONTROL_PORT&=~(1<<LCD_RS);
13
LCD_out(Command);
14
}
15
16
voidLCD_Data(uint8_tData)
17
{
18
LCD_CONTROL_PORT|=(1<<LCD_RS);
19
LCD_out(Data);
20
}
>> Ich möchte wirklich alles Schritt für Schritt durchgehen...
Was denkst du, warum ich dir das alles zeige?
Sicher nicht, weil ich denke das es sinnvoll ist, mit nicht mehr als
Grundkentnissen in den Grundrechenarten in Integralrechnung
einzusteigen.
Gewöhn dich daran. Funktionen zu schreiben und Funktionalität in
Funktionen zu kapseln ist eine der Grundfertigkeiten in der
Programmierung. Du kannst natürlich einen großen Bogen darum machen.
Aber du wirst dann in der Programmierung nicht weit kommen. Genau wie
jemand, der nicht multiplizieren will, weil er das ja durch eine
entsprechende Anzahl an Additionen ersetzen kann. Ja, stimmt schon. Man
kann anstelle von 2*3 auch 2+2+2 rechnen. Aber mach das mal bei
1865*5623!
Das hier, ein LCD, ist leicht in Funktionen zu kapseln. Da passiert
nirgends irgendetwas aussergewöhnliches. Das ist stink normales
Handwerk, wie es jeder Programmierer beherrschen muss. ... Und
irgendwann muss man eben auch damit anfangen, das zu tun. Noch ist kein
Meister vom Himmel gefallen. Es bringt aber auch nichts,
Grundfertigkeiten immer und immer wieder hinauszuzögern. Das bringt dir
im Endeffekt nur Mehrarbeit, ohne das es dich in der Fertigkeit schult,
von vorne herein gleich den einigermassen richtigen Programmansatz zu
erkennen und auch durchzuziehen.