Forum: Compiler & IDEs LCD will nicht


von Sven F. (skfink)


Lesenswert?

Hallo,
hab folgenden code mit dem ich das LCD mit HD44780 Controller
anspreche.

#include <avr/io.h>
#include <inttypes.h>
#include <avr/iom8.h>

int warte(void)
{
PORTC |= (1 << 2);  //RS auf 1
DDRB = 0x00;    //PORTB auf Eingang
while (PORTB & 0b10000000);  //BusyFlag gesetzt
DDRB = 0xFF;    //PORTB auf Ausgang
PORTC &= ~(1 << 2);  //RS auf 0
return 0;
}


int main(void)
{
    DDRB = 0xff;
  DDRC = 0xff;
  PORTC = 0x00;
  PORTD = 0x00;
  warte();
  PORTD |= (1 << 4) | (1 << 5); //Function Set
  warte();
  PORTD = 0x0E;  //Display ein
  warte();
  PORTD = 0x01;  //Display löschen
  warte();
  PORTD = 0x06;  //Cursor Autoinkrement
  warte();
  PORTC |= (1 << 3);  //RS auf 1
  warte();
  PORTB = 0xA3;  //# auf LCD ausgeben
  warte();
  PORTC |= (1 << 1);  //enable ein
  warte();
  PORTC &= ~(1 << 1);  //enable aus
  warte();
  while (1);
  return 0;
}


passieren tut gar nix. Ich benutze das im 8 Bit Modus, Wobeo PORTB die
8 Datenleitungen übernimmt und PORTC Pin 1: Enable, Pin2: RW, Pin3: RS

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Ohne das jetzt alles genau gelesen zu haben... (zumal du dein
Pin-Mapping nirgends beschrieben hast).

Hast du dir mal das Datenblatt eines HD44780 angeguckt?  Während
der Initialisierung musst du einiges an Timing einhalten, selbst
wenn du ansonsten ein BUSY-Flag lesen kannst und im 8-Bit-Modus
arbeitest.  Auf den Power-on-Reset würde ich mich nicht verlassen.

Ähem, eins fällt mir gerade noch auf:
1
DDRB = 0x00;    //PORTB auf Eingang
2
while (PORTB & 0b10000000);  //BusyFlag gesetzt
Du hast eben ein 0x00 nach PORTB geschrieben, was bringt dich zu
der Annahme, dass beim anschließenden Rücklesen da was anderes
als 0x00 stehen könnte?  Um die Eingänge zu lesen, musst du von
PINB lesen.

von Sven F. (skfink)


Lesenswert?

blöder Fehler, aber daran lags nicht. Pinmapping hab ich doch in meinem
letzten Satz geschrieben.

von johnny.m (Gast)


Lesenswert?

Wenn Du schreibst, dass Du für die Ansteuerung Port B und Port C
benutzt, im Programm aber Port D und Port C schreibst, solltest Du Dich
vielleicht erst mal entscheiden, was Du genau willst. Abgesehen davon
kann das Busy-Flag soweit ich mich erinnere am Anfang der Init noch gar
nicht gelesen werden. Und selbst wenn es das kann: Du liest es jeweils
nur ein einziges Mal. Da es bei diesem ersten Lesen gesetzt sein
dürfte, hängt sich Dein Programm in der Abfrage while(PINB &
0b00000001); auf. Du solltest wirklich das Datenblatt vom Display genau
durchlesen. Da steht meist ein Ablaufdiagramm für die Initialisierung
drin und auch, ab welchem Schritt Du das Busy-Flag lesen kannst.

Gruß

Johnny

von Sven F. (skfink)


Lesenswert?

nach etlichen Verbesserungen und lesen des Datenblattes des HD44780 komm
ich zu folgendem code der ein # ausgeben soll, dies aber immer noch
nicht tut. Stattdessen leuchten alle verfügbaren dots.

int warte(void)
{
PORTC |= (1 << 3);  //RS auf 1
PORTC &= ~(1 << 2);  //RW auf 0
DDRB = 0x00;    //PORTB auf Eingang
while (!(PINB & 0b10000000));  //BusyFlag gesetzt
DDRB = 0xFF;    //PORTB auf Ausgang
PORTC &= ~(1 << 3);  //RS auf 0
PORTC |= (1 << 2);  //RW auf 1
return 0;
}

int warte_ms(int ms)
{
int zahl = 0;
int zahl2;
for (zahl = 0; zahl < ms; zahl++)
  {
  zahl2 = 0;
  while (zahl2 < 1000)
    zahl2++;
  }
return 0;
}

int sende(void)
{
PORTC |= (1 << 1);  //enable ein
warte_ms(0);
PORTC &= ~(1 << 1);  //enable aus
return 0;
}

int main(void)
{
    DDRB = 0xff;
  DDRC = 0xff;
  PORTC = 0x00;
  PORTB = 0b00110000;
  sende();
  PORTB = 0b00110000;
  sende();
  PORTB = 0b00110000;
  sende();
  warte();
  PORTB = 0b00111000; //Function Set
  sende();
  warte();
  PORTB = 0b00001000;  //Display off
  sende();
  warte();
  PORTB = 0x01;  //Display löschen
  sende();
  warte();
  PORTB = 0x07;  //Entry Mode
  sende();
  warte();
  PORTB = 0b00001111;  //Display on
  sende();
  warte();
  PORTC |= (1 << 3);  //RS auf 1
  warte();
  PORTB = 0x23;  //# auf LCD ausgeben
  sende();
  while (1)
  {
  }
  return 0;
}

von johnny.m (Gast)


Lesenswert?

Du setzt beim Lese- und Schreib-Versuch das Enable nicht. Die Abfrage
vom Busy-Flag ist auch immer noch nicht OK. Die Daten werden nur in der
Zeit, in der Enable gesetzt ist, übertragen. Das gilt für lesen und
Schreiben. Das Busy-Flag muss in der Warteschleife immer wieder
eingelesen werden. Es reicht nicht, es nur einmal abzufragen! Außerdem
macht es wenig Sinn, zu warten, bis das Flag gesetzt ist, sondern eher
bis es gelöscht ist. Müsste wohl eher heißen
while(PINB & 10000000){BUSY_FLAG lesen}
Außerdem solltest Du Dir angewöhnen, Bezeichner für die Steuerbits zu
vergeben und diese dann auch zu benutzen. Ausdrücke wie PORTC |= 1 << 3
sind besser lesbar, wenn Du schreibst
#define RS 3
//Irgendwelcher Code....
PORTC |= 1 << RS;

Auch die Binärausdrücke solltest Du durch so was ersetzen. Macht das
Lesen für andere erheblich leichter!

Gruß

Johnny

von Sven F. (skfink)


Lesenswert?

also die Abfrage nach dem BF hab ich schonmal abgeändert:

int warte(void)
{
PORTC &= ~(1 << 3);  //RS auf 0
PORTC |= (1 << 2);  //RW auf 1
DDRB = 0x00;    //PORTB auf Eingang
while (PINB & 0b10000000)  //BusyFlag gesetzt
  sende();
DDRB = 0xFF;    //PORTB auf Ausgang
PORTC |= (1 << 3);  //RS auf 1
PORTC &= ~(1 << 2);  //RW auf 0
return 0;
}

während des enable wird ja nichts gesendet oder empfangen, sondern erst
bei der fallenden Flanke des enable Signals, was in der sende() funktion
gemacht wird. Von daher sehe ich da keinen Fehler.
Mit den Änderungen an der BF Abfrage hat sich an der Funktionalität
aber nichts getan.

von johnny.m (Gast)


Lesenswert?

>während des enable wird ja nichts gesendet
>oder empfangen, sondern erst bei der fallenden
>Flanke des enable Signals

Stimmt so nur beim Schreiben. Beim Lesen vom Display (also auch beim
Lesen des Busy-Flag) liegen die Display-Daten nur so lange am Port an,
wie Enable High ist! Du musst beim lesen den Port (in eine Variable)
sichern, während Enable High ist.

Wenn Du ein Display von EA hast, kann es sein, dass die Beschreibung im
Datenblatt ein wenig zu kurz gefasst ist. Ich habe mal ein Display von
Sharp mit einem ebenfalls HD44780-kompatiblen Controller programmiert,
und da waren die erforderlichen Wartezeiten zwischen den
Initialisierungsschritten erheblich länger. Da gab es im Datenblatt
auch richtige Ablaufdiagramme für die Init inkl. der Zeitangaben, die
ich auch heute (für die EA-Displays) immer noch verwende. Habe damit
nie Probleme gehabt. Möglicherweise solltest Du einfach mal bei der
Initialisierung die Wartezeiten erhöhen (ruhig auf ein paar zig ms) und
schauen, ob es was bringt.

BTW, warum verwendest Du keine Bibliotheksfunktionen für die
Wartezeiten? Und wenn Du den Code schon änderst, dann ändere doch bitte
auch die '1 << irgendwas'-Ausdrücke. Es ist auch weniger
fehleranfällig.

Vorschlag:

#include <avr/delay.h>

#define EN 1
#define BUSY 7
unsigned char dd_in; //Variable für Datenportsicherung
//....
int sende(void)
{
    PORTC |= (1 << EN);  //enable ein
    delay_us(1);
    dd_in = PINB; //PORTB (Display-Daten) sichern
    PORTC &= ~(1 << EN);  //enable aus
}
//...
dd_in = 1 << BUSY; //Variable setzen f. Schleife
while(dd_in & (1 << BUSY))
{
    sende();
}

Das nur als Idee...

Gruß

Johnny

von johnny.m (Gast)


Angehängte Dateien:

Lesenswert?

Hab das LCD-Handbuch von Sharp mal ausgegraben (im Anhang). Ist zwar
schon was älter, aber es funktioniert. Da steht eigentlich alles sehr
schön detailliert beschrieben drin. Auf S. 13 die Beschreibung
(Ablaufdiagramm) der Init mit Timing. Ich habe mit der Beschreibung
schon vor Jahren ein 2x16-LCD (LM162KS von Sharp) an einem ST6-µC zum
laufen gebracht und verwende das Prinzip immer noch bei allen
HD44780-kompatiblen Display-Controllern. Versuchs einfach mal mit den
angegebenen Zeiten.

Gruß

Johnny

von Sven F. (skfink)


Lesenswert?

also ich hab das deinen Tipps zufolge umgeschrieben, das Manual auch
gelesen und mit meinem code verglichen. Mehr als einmal. Viel mehr als
einmal. Aber ich find den Fehler einfach nicht. Hätte nicht gedacht
dass es so schwer ist ein Zeichen auf ein LCD zu kriegen.

#include <avr/io.h>
#include <inttypes.h>
#include <avr/iom8.h>
#include <avr/interrupt.h>

#define EN 1
#define BUSY 7
#define RW 2
#define RS 3
volatile int temp;

int ReadChannel(int mux)
{
  int result = 0;         //Initialisieren wichtig, da lokale
Variablen
                               //nicht automatisch initialisiert werden
und
                               //zufällige Werte haben. Sonst kann
Quatsch rauskommen
  ADCSRA = (1<<ADEN) | (1<<ADPS1) | (1<<ADPS0);    // Frequenzvorteiler

                               // setzen auf 8 (1) und ADC aktivieren
(1)

  ADMUX = mux;                      // Kanal waehlen
  ADMUX |= (1<<REFS0) | (1<<ADLAR); // interne Referenzspannung nutzen


  /* nach Aktivieren des ADC wird ein "Dummy-Readout" empfohlen, man
liest
     also einen Wert und verwirft diesen, um den ADC "warmlaufen zu
lassen" */
  ADCSRA |= (1<<ADSC);              // eine ADC-Wandlung
  while ( ADCSRA & (1<<ADSC) );     // auf Abschluss der Konvertierung
warten

  /* Eigentliche Messung - Mittelwert aus 4 aufeinanderfolgenden
Wandlungen */
 // for(i=0;i<4;i++)
 // {
    ADCSRA |= (1<<ADSC);            // eine Wandlung "single
conversion"
    while ( ADCSRA & (1<<ADSC) );   // auf Abschluss der Konvertierung
warten
    result = ADCH;    // Wandlungsergebnisse aufaddieren
 // }
  ADCSRA &= ~(1<<ADEN);             // ADC deaktivieren (2)

 // result /= 4;                     // Summe durch vier teilen =
arithm. Mittelwert
  return result;
}

//ISR(TIMER0_OVF_vect)


int warte_ms(int ms)
{
int zahl = 0;
int zahl2;
for (zahl = 0; zahl < ms; zahl++)
  {
  zahl2 = 0;
  while (zahl2 < 1000)
    zahl2++;
  }
return 0;
}

int sende(void)
{
PORTC |= (1 << EN);  //enable ein
warte_ms(0);
PORTC &= ~(1 << EN);  //enable aus
return 0;
}

int empfange(void)
{
PORTC |= (1 << EN);  //enable ein
warte_ms(1);
temp = PINB;
PORTC &= ~(1 << EN);  //enable aus
return 0;
}

int warte(void)
{
PORTC &= ~(1 << RS);  //RS auf 0
PORTC |= (1 << RW);  //RW auf 1
DDRB = 0x00;    //PORTB auf Eingang
temp |= (1 << BUSY); //BusyFlag setzen
while (temp & BUSY)  //solange BusyFlag gesetzt
  empfange();
DDRB = 0xFF;    //PORTB auf Ausgang
PORTC |= (1 << RS);  //RS auf 1
PORTC &= ~(1 << RW);  //RW auf 0
return 0;
}

int main(void)
{
    DDRB = 0xff;
  DDRC = 0xff;
  PORTC = 0x00;
  warte_ms(100);
  PORTB = 0b00110000;
  sende();
  warte_ms(10);
  PORTB = 0b00110000;
  sende();
  warte_ms(10);
  PORTB = 0b00110000;
  sende();
  warte();
  PORTB = 0b00111000; //Function Set
  sende();
  warte();
  PORTB = 0b00001000;  //Display off
  sende();
  warte();
  PORTB = 0x01;  //Display löschen
  sende();
  warte();
  PORTB = 0x07;  //Entry Mode
  sende();
  warte();
  PORTB = 0b00001111;  //Display on
  sende();
  warte();
  PORTC |= (1 << RS);  //RS auf 1
  PORTC &= ~(1 << RW);  //RW auf 0
  warte();
  PORTB = 0x23;  //# auf LCD ausgeben
  sende();
  while (1)
  {
  }
  return 0;
}

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> #include <avr/iom8.h>

Bitte nicht.  #include <avr/io.h> zusammen mit -mmcu=atmega8
ist der richtige Weg.

von Sven F. (skfink)


Lesenswert?

in Bezug auf das nicht funktionieren des LCD hats aber nichts gebracht.

von johnny.m (Gast)


Lesenswert?

Warum weigerst Du Dich, die Bibliotheksfunktionen für die Wartezeiten zu
benutzen???? Ich weiß zwar nicht, mit welcher Taktfrequenz der µC läuft,
aber das was Du da programmiert hast gibt mit großer Wahrscheinlichkeit
keine Millisekunden Wartezeit.

von Sven F. (skfink)


Lesenswert?

weil ich dann haufenweise Fehlermeldungen kriege. Selbst wenn meine
Funktion nicht die Anzahl ms wartet die sie soll, dann wartet sie
höchstens mehr. Und dass das LCD nicht funktioniert wegen zulangen
Wartezeiten kann doch wohl nicht sein. Der µC ist übrigens mit 1MHz
getaktet.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> weil ich dann haufenweise Fehlermeldungen kriege.

Dann poste sie.

von Sven F. (skfink)


Lesenswert?

aber das hat doch mit dem LCD an sich nichts zu tun...aber naja, wer
weiss....ich nicht.

Warnungen:
E:/Programme/WinAVR/avr/include/util/delay.h:136:3: warning: #warning
"F_CPU not defined for <util/delay.h>"
../LCD.c: In function `sende':
../LCD.c:47: warning: implicit declaration of function `delay_us'

Fehlermeldungen:
../LCD.c:47: undefined reference to `delay_us'
LCD.o: In function `empfange':
../LCD.c:55: undefined reference to `delay_us'
LCD.o: In function `main':
../LCD.c:80: undefined reference to `delay_us'
../LCD.c:83: undefined reference to `delay_us'
../LCD.c:86: undefined reference to `delay_us'

F_CPU hab ich gleich am anfang auf 1000000 festgesetzt. Warum der
delay_us jetzt nicht kennt...keine Ahnung, steht in der delay.h drin.

Aber wie gesagt, dürfte mit dem eigentlichen Problem nichts zu tun
haben.

von Stefan (Gast)


Lesenswert?

> F_CPU hab ich gleich am anfang auf 1000000 festgesetzt.

Vor (richtig) oder nach (falsch) dem #include <util/delay.h>

von Sven F. (skfink)


Lesenswert?

?
also ganz am anfang der main()
ich kann ja nicht einfach so irgendwo irgendwelche Befehle
hinschreiben.

von Sven F. (skfink)


Lesenswert?

hat vielleicht jemand ein fertiges hex file das ein Zeichen auf ein lcd
schreibt? Müsste dann natürlich noch die Pinbelegung wissen.
Um zu sehn ob das LCD vielleicht nicht kaputt ist.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> also ganz am anfang der main()

Falsch.

> ich kann ja nicht einfach so irgendwo irgendwelche Befehle
> hinschreiben.

C hat keine ,Befehle'.

F_CPU ist ein Makro für den C-Präprozessor.  Damit die Funktionen von
<util/delay.h> so funktionieren wie designt, muss dieser Makro vor
dem #include <util/delay.h> definiert sein.  Das kann man entweder so
machen:
1
#define F_CPU 1000000ul
2
#include <util/delay.h>

oder aber du reichst F_CPU gleich von der Kommandozeile aus durch,
indem du im Makefile ein

-DF_CPU=1000000ul

mit in die CFLAGS aufnimmst.  Letzteres ist die Methode, die der
Makefile-Generator des GCC-Plugins im AVR Studio macht (und dabei hat
er einen Bug, dass er bei jedem Aufmachen des entsprechenden
GUI-Fensterchens noch ein neues "UL" mit anhängt).

> ../LCD.c:47: warning: implicit declaration of function `delay_us'
> ../LCD.c:47: undefined reference to `delay_us'

Die Funktionen heißen _delay_us() usw., mit einem führenden
Unterstrich, damit sie nicht mit dem application name space
kollidieren.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> hat vielleicht jemand ein fertiges hex file das ein Zeichen auf ein
> lcd schreibt?

Zumindest Bibliotheken dafür gibt's zur Genüge: Procyon AVRlib, Peter
Fleury's Codeschnipsel.  Die avr-libc kommt auch mit einem Demo, das
ein Beispiel für eine LCD-Ansteuerung hat.

von Sven F. (skfink)


Lesenswert?

jetzt hat ers tatsächlich ohne Warnungen und Fehler kompiliert. LCD
funktioniert natürlich immer noch nicht.

<Zumindest Bibliotheken dafür gibt's zur Genüge: Procyon AVRlib,
Peter
Fleury's Codeschnipsel.
Problem ist dass die alle die stdio.h benutzen. Wenn ich die einbinde
stürtzt AVRStudio ab. Soll wohl einen Patch geben, hab aber nichts
dergleichen gefunden.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> Problem ist dass die alle die stdio.h benutzen.

Ach wo, du musst doch kein stdio benutzen.  Kann ja sein, dass die in
ihren Beispielen vielleicht ein sprintf() drin haben, aber das musst
du doch nicht nachmachen.  Auch eine ,echte' printf-Einbdindung (also
fdevopen() oder fdev_setup_stream()) musst du nicht haben, wenn du die
Funktionalität (und den Overhead) von stdio nicht nehmen willst.

> Wenn ich die einbinde stürtzt AVRStudio ab. Soll wohl einen Patch
> geben, hab aber nichts dergleichen gefunden.

Oh weh...  Installier' dir bitte den letzten Servicepack von AVR
Studio.  Da sind einige Bugs repariert worden.

von johnny.m (Gast)


Lesenswert?

Hallo,

habe grad noch mal kurz Deine letzte verbesserte Version überflogen und
zumindest einen fatalen Fehler gefunden:

while(temp & BUSY) muss heißen
while(temp & (1 << BUSY)).

Gruß

Johnny

von Sven F. (skfink)


Lesenswert?

ups, danke, wars aber nicht. hier nochmal die aktuelle nicht
funktionierende Version mit soweit allen berücksichtigten
Verbesserungsvorschlägen:

#include <avr/io.h>
#include <inttypes.h>
#include <avr/interrupt.h>
#define F_CPU 1000000ul
#include <util/delay.h>

#define EN 1
#define BUSY 7
#define RW 2
#define RS 3
volatile int temp;

int sende(void)
{
PORTC |= (1 << EN);  //enable ein
_delay_us(1);
PORTC &= ~(1 << EN);  //enable aus
return 0;
}

int empfange(void)
{
PORTC |= (1 << EN);  //enable ein
_delay_us(1);
temp = PINB;
PORTC &= ~(1 << EN);  //enable aus
return 0;
}

int warte(void)
{
PORTC &= ~(1 << RS);  //RS auf 0
PORTC |= (1 << RW);  //RW auf 1
DDRB = 0x00;    //PORTB auf Eingang
temp |= (1 << BUSY); //BusyFlag setzen
while (temp & (1 << BUSY))  //solange BusyFlag gesetzt
  empfange();
DDRB = 0xFF;    //PORTB auf Ausgang
PORTC |= (1 << RS);  //RS auf 1
PORTC &= ~(1 << RW);  //RW auf 0
return 0;
}

int main(void)
{
    DDRB = 0xff;
  DDRC = 0xff;
  PORTC = 0x00;
  _delay_us(100000);
  PORTB = 0b00110000;
  sende();
  _delay_us(10000);
  PORTB = 0b00110000;
  sende();
  _delay_us(10000);
  PORTB = 0b00110000;
  sende();
  warte();
  PORTB = 0b00111000; //Function Set
  sende();
  warte();
  PORTB = 0b00001000;  //Display off
  sende();
  warte();
  PORTB = 0x01;  //Display löschen
  sende();
  warte();
  PORTB = 0x07;  //Entry Mode
  sende();
  warte();
  PORTB = 0b00001111;  //Display on
  sende();
  warte();
  PORTC |= (1 << RS);  //RS auf 1
  PORTC &= ~(1 << RW);  //RW auf 0
  warte();
  PORTB = 0x23;  //# auf LCD ausgeben
  sende();
  while (1)
  {
  }
  return 0;
}

von johnny.m (Gast)


Lesenswert?

Zitat aus der Definition der _delay_us-Funktion in der Header-Datei:
The maximal possible delay is 768 us / F_CPU in MHz.
D.h. bei 1 MHz maximal 768 µs. 10000 bzw. 100000 ist zu viel. Dafür
gibts _delay_ms (max. 262.14 ms / F_CPU in MHz).

von Sven F. (skfink)


Lesenswert?

hilft auch nichts, LCD wird immer noch komplett voll geschrieben.

von Ale (Gast)


Lesenswert?

So es noch nicht funktioniert ?

Ein Paar Jahre vorher ich habe die gleiche mit ein 68HC11 gemacht aber
in assembler.

Bist du sicher, daß R/W* *RS und E haben die richtiche status ?
Zu Schreiben "command":

R/W* = 0
E = 1
*RS = 0

So in sende ich wurde schreben:
void sende (unsigned char b)
{
unsigned char portc = PORTB & 0xf8;
    PORTC = portc;
    PORTB = b;
    PORTC = portc | EN;
    // delay
    b = b + 1;
    b = b + 1;
    b = b + 1;
    PORTC = portc;
}

(Bist du sicher, daß die LCD und die PORTB/C sind in Ordnuing ?)

Ich mache die gleiche "circuit" zwischen Heute und Morgen, aber ich
wurde in assembler programmieren. When meine funktioniert ich kann die
Programme senden.

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.