Forum: Mikrocontroller und Digitale Elektronik LCD an ATmega32 spinnt


von Jonas S. (jonebohne)


Lesenswert?

Ich habe einen ATmega32 und versuche seit einiger Zeit, das Display 
"DISPLAYTECH 204B" damit anzusteuern. Hier mein Code:
1
 lcd_init();
2
 
3
 set_cursor(0,0);
4
 lcd_puts("abcdefghijklmnopqrst");
5
 set_cursor(0,1);
6
 lcd_puts("uvwxyzABCDEFGHIJKLMN");
7
 set_cursor(0,2);
8
 lcd_puts("OPQRSTUVWXYZ01234567");
9
 set_cursor(0,3);
10
 lcd_puts("89{[()]}!?,.-_;:#+*%");
Soweit funktioniert das auch, ich kann die erste und die zweite Zeile 
problemlos beschreiben, aber die dritte und vierte Zeile sieht so aus:
"1234    XYZ0"
"_;:#    ?,.-"
Im obigen Code sieht das dann doch etwas anders aus...
Im Datenblatt vom Display und vom KS0066 habe ich bereits nachgesehen, 
kann aber nichts finden.
Das LCD wird größtenteils mit dem Code aus dem AVR-GCC-Tutorial 
angesteuert.

Nun weiß ich echt nicht mehr weiter. Kann mir jemand helfen?
Danke schonmal im vorraus!

von Klaus W. (mfgkw)


Angehängte Dateien:

Lesenswert?

In dem Code, den du übernommen hast, steht irgendwo
die Berechnung von Adressen im DD-RAM, also die
Zuordnung, welche Zeile wo im RAM anfängt.
Das ist vom Typ abhängig und unterscheidet sich
gelegentlich innerhalb gleicher Auflösungen.

Du musst das Datenblatt zu deinem LCD in die Hand nehmen
und kontrollieren, ob diese Zuordnung passt.

Als Hilfe, wonach du suchen musst, anbei eine solche Tabelle
für die LCD von EA. Ohne Gewähr, daß es bei dir dieselben
Werte sind (auch wenn es wahrscheinlich passt).

von L. J. (luke1)


Lesenswert?

Was auch sein kann ist folgendes: du benutzt den Code aus dem Forum. Der 
ist aber nur für ein Display mit 15 Zeichen. Kann sein, dass ich mich 
irre aber hat deins nicht 20? Wenn ja musst du hier was ändern:
1
void set_cursor(uint8_t x, uint8_t y)
2
{
3
  uint8_t tmp;
4
 
5
  switch (y) {
6
    case 1: tmp=0x80+0x00+x; break;    // 1. Zeile
7
    case 2: tmp=0x80+0x40+x; break;    // 2. Zeile
8
    case 3: tmp=0x80+0x10+x; break;    // 3. Zeile
9
    case 4: tmp=0x80+0x50+x; break;    // 4. Zeile
10
    default: return;                   // für den Fall einer falschen Zeile
11
  }
12
  lcd_command(tmp);
13
}

Hier wird nämlich festgelegt wieviel Zeichen in eine Zeile passen. Das 
könnte auch ein Grund sein glaub ich.

von Jonas S. (jonebohne)


Lesenswert?

Danke für die schnelle Antwort!
Dieselben Werten benutze ich schon. Im Datenblatt konnte ich noch nichts 
finden, aber ich werde es nochmal durchsuchen.

von Klaus W. (mfgkw)


Lesenswert?

PS: die anzupassenden Werte sind die, die in set_cursor()
jeweils hinter tmp=0x80+... stehen (jeweils einmal pro
Zeile).
Falls die Werte aus meiner Tabelle stimmen, sollten
das die Zahlen 0x00, 0x40, 0x14 und 0x54 sein (also
für die dritte und vierte LCD-Zeile aus dem Tutorial
anpassen):
1
    case 1: tmp=0x80+0x00+x; break;    // 1. Zeile
2
    case 2: tmp=0x80+0x40+x; break;    // 2. Zeile
3
    case 3: tmp=0x80+0x14+x; break;    // 3. Zeile ändern!
4
    case 4: tmp=0x80+0x54+x; break;    // 4. Zeile ändern!

Das Tutorial geht so offenbar nur für 4x16-LCDs.

von Klaus W. (mfgkw)


Lesenswert?

L. J. schrieb:
> für ein Display mit 15 Zeichen.

4x16

von Jonas S. (jonebohne)


Lesenswert?

Den Code für 20 Zeichen verwende ich schon. Danke trotzdem!

von L. J. (luke1)


Lesenswert?

Ach :D Entschuldige Klaus. In der schnelle hab ich 15 getippt. Ich 
meinte 4x16

von Jonas S. (jonebohne)


Angehängte Dateien:

Lesenswert?

Hier sind die Dateien zur Ansteuerung des LCD.

von Klaus W. (mfgkw)


Lesenswert?

BTW: ich finde es auch etwas verbesserungsfähig im Tutorial.
Bislang steht das etwas versteckt drin, und jeder fällt da
zum ersten Mal drauf rein.

Ich mache es bei mir etwas anders: Ausgehend vom Tutorial
habe ich mir eine Version gebastelt, die beim Initialisieren
den gewünschten Typ übergeben bekommt und dann daraufhin
die richtige Adressierung nimmt.
Kostet mindestens 7.456 Byte mehr Code, aber das ist
mir bislang egal.
Das ist exakt in meiner Form leider nicht mehrheitsfähig,
da es in C++ geschtrieben ist.

Aber wäre es nicht sinnvoll, das im Tutorial mal mit
ein paar #ifdef umzubauen?
Also an einer Stelle mit #define den Typ setzen, und
die Berechnung in set_cursor() dann davon abhängig
gestalten?
Ich glaube, das wäre etwas anfängerfreundlicher.

von Klaus W. (mfgkw)


Lesenswert?

Jonas S. schrieb:
> Hier sind die Dateien zur Ansteuerung des LCD.

Das ist aber nicht so aus dem Turial übernommen, oder?

So wären es eben halt die Werte für
1
#define LCD_LINE3    0x14      // Zeile 3
2
#define LCD_LINE4    0x54      // Zeile 4
in lcd.h.

Die Werte sehen ja vernünftig aus.
Hast du damit auch wirklich alles neu kompiliert und es
geht trotzdem nicht?
Und du bist sicher, daß du ein 4x20 hast und nicht 4x16?

von L. J. (luke1)


Lesenswert?

@Klaus
Hm ich weiß nicht. Ich denke wenn man es so programmierst wie du hat man 
zwar damit vielleicht weniger Probleme. Aber es soll ja auch für 
Anfänger verständlich sein. Und je mehr in den Code reinkommt umso 
undurchsichtiger kann das für einen Anfänger aussehen (ich selbst steh 
auch noch weit am Anfang). Und ich denke darauf wurde der Schwerpunkt 
gelegt.

von Jonas S. (jonebohne)


Lesenswert?

Der Großteil ist zwar aus dem Tutorial aber einiges habe ich auch selbst 
geschrieben oder aus einem anderen tutorial. Und wenn es doch ein 4x16 
sein sollte, dann hat Reichelt mich verar*cht :D und die erste Zeile 
würde nicht aufs display passen. Nee das ist 100%ig 4x20.

von Klaus W. (mfgkw)


Lesenswert?

Wenn ich mich jetzt nicht im Kopf verrechnet habe, klappt das
auch gar nicht mit deinem Code.
Ändere doch bitte mal die Zeilen in lcd.c:
1
  case 0: tmp=LCD_SET_CSR|LCD_LINE1|csr_x; break;  // 1. Zeile
2
  case 1: tmp=LCD_SET_CSR|LCD_LINE2|csr_x; break;  // 2. Zeile
3
  case 2: tmp=LCD_SET_CSR|LCD_LINE3|csr_x; break;  // 3. Zeile
4
  case 3: tmp=LCD_SET_CSR|LCD_LINE4|csr_x; break;  // 4. Zeile
(in lcd_update()) um in:
1
  case 0: tmp=LCD_SET_CSR+LCD_LINE1+csr_x; break;  // 1. Zeile
2
  case 1: tmp=LCD_SET_CSR+LCD_LINE2+csr_x; break;  // 2. Zeile
3
  case 2: tmp=LCD_SET_CSR+LCD_LINE3+csr_x; break;  // 3. Zeile
4
  case 3: tmp=LCD_SET_CSR+LCD_LINE4+csr_x; break;  // 4. Zeile

Da wollte einer mal wieder schlauer sein als der Compiler,
aber das funktioniert mit den | nur, wenn die Offsets für die
Zeilen auf 0x...0 enden, also nicht bei 0x14 und 0x54.

Im Zweifelsfall lieber dem Tutorial vertrauen :-)

von Klaus W. (mfgkw)


Lesenswert?

Jonas S. schrieb:
> Und wenn es doch ein 4x16
> sein sollte, dann hat Reichelt mich verar*cht :D und die erste Zeile
> würde nicht aufs display passen. Nee das ist 100%ig 4x20.

Das kann man sehen, wenn man es passend ins Licht hält.
Dann sieht man die Vierecke, die kann man abzählen.
Aber wahrscheinlich liegt es eh an deinen |, möchte ich wetten.

von Jonas S. (jonebohne)


Lesenswert?

Hmm der Teil ist aus dem Tutorial "Erweiterte LCD-Ansteuerung". Aber mit 
+ funktionierts! Danke!

von Klaus W. (mfgkw)


Lesenswert?

L. J. schrieb:
> Aber es soll ja auch für
> Anfänger verständlich sein.

Ein Anfänger sollte bei einer Art Bibliothek gar nichts direkt
im Quelltext ändern, bis er mal übermütig wird.
Eben deshalb fände ich es geschickter, an einer Stelle den Typ
anzugeben und der Rest läuft automagisch.

von Klaus W. (mfgkw)


Lesenswert?

Jonas S. schrieb:
> Hmm der Teil ist aus dem Tutorial "Erweiterte LCD-Ansteuerung". Aber mit
> + funktionierts! Danke!

Dann ist das da falsch!

Nach dem Lerneffekt kannst du es ja gleich dort korrigieren...

von Klaus W. (mfgkw)


Lesenswert?

Aber meine Kritik am Quelltext für das Tutorial gilt ebenso
für die erweiterte Version.
Da steht weder in der lcd.h noch in der .c auch nur ein müder
Kommentar, daß man je nach Typ bestimmte Werte ändern muß,
geschweige denn welche Werte man nehmen muß.
M.E. gehört da der Inhalt der obigen Tabelle von EA zumindest
als Kommentar rein, oder eben gleich wie vorgeschlagen mit
#ifdef... eingebaut.
Wenn ich es nicht übersehen habe, steht in
http://www.mikrocontroller.net/articles/Erweiterte_LCD-Ansteuerung
nicht einmal, daß es für ein 4x16 ist.

Ich will gar nicht wissen, wieviele Änfanger zufällig kein 4x16
haben und dann scheitern.

von Peter D. (peda)


Lesenswert?

Klaus Wachtler schrieb:
> Ich mache es bei mir etwas anders: Ausgehend vom Tutorial
> habe ich mir eine Version gebastelt, die beim Initialisieren
> den gewünschten Typ übergeben bekommt und dann daraufhin
> die richtige Adressierung nimmt.
> Kostet mindestens 7.456 Byte mehr Code, aber das ist
> mir bislang egal.

Boah!
Du mußt die Berechnungen doch nicht in double float machen, der Befehl 
ist ja nur ein Byte groß.

Man könnte für jeden Displaytyp die 4 Zeilenoffsets als Tabelle machen, 
aber das sind dann ja nur mickrige 4 Byte pro Typ.

Du mußt mal zeigen, wie man dafür fast 8kB verschwenden kann.
Ich schaffs beim besten Willen nicht.


Peter

von Jonas S. (jonebohne)


Lesenswert?

Wie wäre es mit einer Wiki-Seite zur Auflistung aller Display-Typen mit 
den dazugehörigen Zeilenoffsets?

von Klaus W. (mfgkw)


Lesenswert?

Peter Dannegger schrieb:
> Boah!
> Du mußt die Berechnungen doch nicht in double float machen, der Befehl
> ist ja nur ein Byte groß.

Sorry, es kam vielleicht nicht klar raus: die 7 Komma irgendwas
Byte waren geschätzt und ein unverbindliches Beispiel :-)
In float würde ich das bestimmt auch nicht machen.

Mir kommt es auf ein paar Byte nun nicht an, aber erfahrungsgemäß
kommt bei jeder Änderung mit mehr als einem Byte irgendwo ein
Aufschrei.

Da die LCD-Geschichte irgendwie ja von dir kommt und du hier
noch aktiv bist, würde ich vor einer Änderung natürlich erstmal
deine Meinung dazu hören, evtl. gibt es ja noch mehr sinnvolle
Kommentare dazu.

Meine beiden Vorschläge wären wie gesagt:
- in lcd.h mit Compileroption -D... oder mit #define festlegen,
  welchen Typ man hat und den Rest in lcd.c mit #ifdef erledigen
  (neutral bzgl. Laufzeit und Codegroesse, bzw. sogar
  platzsparend, weil 1- und zweizeilige Versionen weniger zu tun
  haben)
- oder gleich ein paar Byte Code spendieren und bei der
  Initialisierung übergeben, welchen Typ man hat (ein Wert
  aus einer enum) und daraus dann dynamisch die Adresse berechnen
  (bzw. wird der Compiler das vermutlich eh wieder wegoptimieren,
  sodaß es auch nichts kostet).
  Diese Version fände ich eleganter, weil dann lcd.c und lcd.h
  nicht mehr geändert werden müssen und für jedes Projekt
  einfach der richtige Wert beim Aufrufer genommen wird.

Wenn niemand ernsthafte Einwände hat, mache ich das gerne.

Peter Dannegger schrieb:
> Man könnte für jeden Displaytyp die 4 Zeilenoffsets als Tabelle machen,
> aber das sind dann ja nur mickrige 4 Byte pro Typ.

ACK, zumal es ja zwar etliche Typen gibt, die aber zu wenigen
Gruppen zusammengefasst werden können.

>
> Du mußt mal zeigen, wie man dafür fast 8kB verschwenden kann.
> Ich schaffs beim besten Willen nicht.

Ich meinte nicht 8 kB, sondern 7 Komma ... Byte als doofe
Umschreibung für "wenige Byte".
Aber wenn du darauf bestehst, kann ich es auch mit 8 kB, ich
muss nur wirklich in float rechnen!

von L. J. (luke1)


Lesenswert?

Ich hatte auch noch ein 4x20 Zeichen Display hier liegen und habe es 
angesteuert. Das funktioniert auch ohne Probleme. Nur zu der Tabelle 
habe ich noch eine Frage. Da steht ja die Anfangs- und Endadresse. Aber 
wieso wird im Tutorial eigentlich die Anfangsadresse mit 0x80 addiert?

Danke schonmal für die Antwort/en :)

von Klaus W. (mfgkw)


Lesenswert?

Das ist der Code des Befehls, um die Adresse zu setzen
(DD-RAM Address set: oberstes Bit gesetzt, in den unteren Bits
steckt die gewünschte Adresse).
Siehe Datenblatt.

von L. J. (luke1)


Lesenswert?

Ah ich hab's gesehen ja. DB7 muss high sein :)

Danke Klaus

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.