Forum: Mikrocontroller und Digitale Elektronik LCD (HD44780) timing extrem träge


von Daniel R. (h3po)


Lesenswert?

Hallo,

Ich arbeite mit einem PIC18F2550 und dem Microchip C18 Compiler und 
möchte jetzt, nach einiger Einarbeitungszeit mein erstes LC-Display 
ansteuern.

Ich habe mir anhand des xlcd Sourcecodes eigene Routinen gestrickt, die 
das Display an den Ports (PORTA für Daten, PORTC für Controls) ansteuert 
wo ich es später (soll mal ein Labornetzteil werden) anschließen will. 
Die Standardeinstellung auf PORTB von der xlcd.h kann ich leider nicht 
nehmen, da ich die Interrupt-Pins brauche.

Mein Code hat auch nach einigen Stunden gefrickel funktioniert. Am Ende 
musste ich feststellen, dass die Probleme gar nicht von meinem Code 
herrührten, sondern von den Timings.
In der Standard xlcd werden zwischen den Befehlen 18TCY pause eingelegt, 
die "E cycle time" beträgt dann etwa 40cycles, bei meinem PIC mit 48MHz 
clock also etwa 40*83ns=3,3µs. Laut Datenblatt meines LCD muss ich 
mindestens 500ns einhalten - also sollte es damit gehen. In meinen 
ersten Versuchen habe ich der Einfachheit halber die standard Delays 
genommen und 20TCY Pause eingelegt.
Mein Problem ist nun, dass es erst ab etwa 2000TCY zuverlässig 
funktioniert, aber selbst das nur, wenn ich erst das Display und dann 
den PIC einschalte. auch ein Delay von 1s vor der Initialisierung hat da 
nicht geholfen.
Bei 2000TCY Pausen brauche ich dann knapp 8000 Cycles um ein einzelnes 
Byte zu übertragen.

Im Moment ist das nicht tragisch, aber später wird es den restlichen 
Code ganz schön ausbremsen wenn ich zB Periodisch meine Ampere & 
Volt-Messwerte à je 5 Zeichen auf dem Display updaten möchte.
Woher kann das kommen? Ich habe leider kein Oszilloskop um die Signale 
zu untersuchen. Meine Leitung zum Display ist im Moment ein 25cm 
Flachkabel, aber auch ein direktes aufstecken auf meine 
Experimentierboard hat nicht geholfen.

Ich bin dankbar für eure Hilfe.
Meinen Code werde ich noch posten, habe mir leider grade irgendwas 
kaputtgebastelt, sodass es selbst mit den langen Pausen nicht mehr 
funktioniert.
Liebe Grüße, Daniel a.k.a H3PO

_____________________
Edit:

Habe inzwischen rausgefunden, dass bloß meine prozedur zum Lesen nicht 
funktionierte und deshalb das Busy-Flag nie zurückgegeben wurde. Wenn 
ich stattdessen 10ms warte, funktioniert mein code.
Deshalb hier mal mein code zum lesen - sieht wer nen groben fehler?
1
//read byte from register x
2
char lcd_read (char rs)
3
{
4
  char byte = 0;
5
6
  LCD_LATCH &= 0xF0;    //reset
7
  LCD_TRIS |= 0x0F;    //input
8
9
  LCD_RS = rs;
10
  LCD_RW = 1;
11
  Delay10TCYx(delay);    //rs,rw,data setup delay
12
  LCD_E = 1;
13
  Delay10TCYx(delay);    //data setup delay,
14
15
  byte = (LCD_PORT << 4) & 0xF0;  //read high nibble
16
17
  LCD_E = 0;
18
  Delay10TCYx(delay);    //delay between E cycles
19
  LCD_E = 1;
20
  Delay10TCYx(delay);    //data setup delay,
21
  
22
  byte |= (LCD_PORT & 0x0F);  //read masked low nibble  
23
24
  LCD_E = 0;
25
  LCD_RS = 0;
26
  LCD_RW = 0;
27
}
28
29
//return busy bit7 as 0 or 1
30
char lcd_busy (void)
31
{
32
  //could save some cycles by not shifting  
33
  return ((lcd_read(0) & 0x80) >> 7);
34
}

von Thomas (kosmos)


Angehängte Dateien:

Lesenswert?

kannst du in deiner Entwicklungsumgebung die Zeit messen die du einzelne 
Pins aktivierst bzw. deaktivierst um mal zu prüfen ob die Pausen im 
Programm auch wirklich am Portpin anliegen, falls du kein Oszi zur Hand 
hast?

Ansonsten anbei mal ein sehr ausführliches Datenblatt mit sehr vielen 
Beispielen

von Daniel R. (h3po)


Lesenswert?

Hallo Thomas,

Danke erstmal, dass du mein Thema so weit hinten im Forum noch beachtet 
hast.
Ehrlich gesagt habe ich keine Ahnung, was die MPLAB IDE so alles kann. 
Der Editor hat mich schon nach kurzer Zeit dermaßen genervt, dass ich 
jetzt in Notepad++ programmiere und per batch an den Compiler übergebe. 
Ich werd mir die Debugger-Funktionen von dem Ding mal ansehen.

Oben hätte ich noch dabeischreiben sollen, das schreiben in den DDRAM 
funktioniert mit delay = 3, also 30TCY = 2,5µs. Das lesen allerdings 
funktioniert nie, egal wie lang ich die Pausen mache.
Deinem Datenblatt kann ich leider nicht viel mehr entnehmen, als ich 
schon weiß. Oben meine Routine ist ja quasi eine 1:1 Kopie von der 
xlcd-Funktion, welche erwiesenermaßen bei tausenden Leuten funktioniert.

In meiner Funktion oben muss also ein grober programmierfehler sein, ich 
kann aber nichts finden.

Für den Fall, dass mein Display einfach eigenwillig ist, habe ich auch 
schonmal zusätzliche Delays eingebaut, wo mein Display laut 
Timing-Diagramm gerne noch 10-20ns Zeit hätte. Da eine Instruction bei 
mir aber schon 83ns dauert, ist das ja eigentlich überflüssig.

Hier hat sich bestimmt doch schon der eine oder andere eine eigene 
LCD-Funktionensammlung gebaut, oder? Könnte bitte mal jemand etwas 
funktionierendes posten?

von Sebastian H. (sebihepp)


Lesenswert?

In deiner Funktion "char lcd_read(char)" fehlt das return!

von Daniel R. (h3po)


Lesenswert?

*kopf->tisch*
Danke Herr Hepp, das war einfach die Erleuchtung, die man nach ner 
durchprogrammierten Nacht braucht, wenn man den Wald vor lauter Bäumen 
nicht mehr sieht =)

Jetzt dachte ich grade "yay - Problem gelöst", aber es funktioniert 
immer noch nicht. Auch die methode aus xlcd, einfach RW zu setzen, den 
Port zu lesen, ein if(LCD_PORT & 0x08) zu machen und anschließend das 
untere Nibble auszuclocken, funktioniert nicht. 0x08 in diesem fall, 
weil am 4bit-Port ja das obere Nibble vom Statusbyte anliegt.

xlcd-methode inkl. seltsamen originalkommentaren:
1
char lcd_busy (void)
2
{
3
        LCD_RW = 1;                     // Set the control bits for read
4
        LCD_RS = 0;
5
        Delay10TCYx(delay);
6
        LCD_E = 1;                      // Clock in the command
7
        Delay10TCYx(delay);
8
                                        // 4-bit interface
9
                                        // Lower nibble interface
10
        if(LCD_PORT&0x08)
11
12
        {
13
                LCD_E = 0;              // Reset clock line
14
                Delay10TCYx(delay);
15
                LCD_E = 1;              // Clock out other nibble
16
                Delay10TCYx(delay);
17
                LCD_E = 0;
18
                LCD_RW = 0;             // Reset control line
19
                beep();
20
                return 1;               // Return TRUE
21
        }
22
        else                            // Busy bit is low
23
        {
24
                LCD_E = 0;              // Reset clock line
25
                Delay10TCYx(delay);
26
                LCD_E = 1;              // Clock out other nibble
27
                Delay10TCYx(delay);
28
                LCD_E = 0;
29
                LCD_RW = 0;             // Reset control line
30
                Delay10TCYx(12);  //10µs
31
                return 0;               // Return FALSE
32
        }  
33
}

auf http://www.sprut.de/electronic/lcd/index.htm#ks0066u habe ich 
übrigens gelesen, dass der KS006 Controller (den hat mein Display) das 
Busy-Flag etwas zu früh löscht. Deshalb füge ich vor dem 'return 0;' 
eine kleine Pause ein. Oben ist auch noch mein beep(); drin, damit ich 
mitkriege wenn das Busy-Flag endlich mal erkannt wird.

von Sebastian H. (sebihepp)


Lesenswert?

> *kopf->tisch*
> Danke Herr Hepp, das war einfach die Erleuchtung, die man nach ner
> durchprogrammierten Nacht braucht, wenn man den Wald vor lauter Bäumen
> nicht mehr sieht =)

Was glaubst du, wie oft ich schon solche Fehler fabriziert habe. =)
Deiner war übrigens auch nicht ganz leicht zu finden. ;)
Mir ist nur ein Rätsel, dass der Compiler nicht gemeckert hat.

Ich warte noch auf die Platine, dann kann ich mich auch endlich
mit einem 2x8 Zeichen KS0066 Controller herumschlagen. Mal sehen
was bei mir nicht geht. Lustig ist ja, dass ich bereits das Programm
fertig geschrieben habe, die Bauteile bei mir sind und ich jetzt
seit 3 Tagen auf dieses dämliche Stück Harz mit Kupfer warte,
um endlich loslegen zu können. XD

Achja, ich habe das Programm mit der PIC18 Simulator IDE getestet.
Und ich verlasse mich auch nicht mehr auf's BusyFlag. In dem
Datenblatt sollten maximale Zeitangaben stehen... diese warte ich
einfach ab.

Viele Grüße
Sebastian

von Sebastian H. (sebihepp)


Lesenswert?

Ich sehe was, was du nicht siehst. Und das heisst...

Enable (Fallende Flanke)

Du musst also Enable setzen und dann wieder löschen und erst dann
wird der Befehl abgearbeitet. In deinem Code setzt du es und frägst
dann gleich den Port ab... da bekommst du natürlich nicht wirklich
etwas verwertbares.

Sebihepp

von Daniel R. (h3po)


Angehängte Dateien:

Lesenswert?

Hm, das macht laut Timing-Diagramm (Anhang) weder Sinn, noch 
funktioniert es.
Ich warte doch nach dem Setzen von Enable eine Zeit ab (gefordert sind 
tD = 120ns, ich warte 30Cycles ~= 2490ns), lese den Port und lösche 
Enable wieder. Nach dem "Enable = fallende Flanke" liegen die Daten noch 
tDH = 20ns am Port an, also zu kurz um sie erfolgreich inneralb eines 
Cycles (83ns) zu lesen.

getesteter code:
1
char lcd_read (char rs)
2
{
3
  char byte = 0;
4
5
  LCD_LATCH &= 0xF0;      //reset
6
  LCD_TRIS |= 0x0F;      //input
7
8
  LCD_RS = rs;
9
  LCD_RW = 1;
10
  Delay10TCYx(delay);      //rs,rw,data setup delay
11
  LCD_E = 1;
12
  Delay10TCYx(delay);      //data setup delay,
13
  
14
  LCD_E = 0;
15
  byte = (LCD_PORT << 4);    //read high nibble
16
17
  Delay10TCYx(delay);      //delay between E cycles
18
  LCD_E = 1;
19
  Delay10TCYx(delay);      //data setup delay,
20
  
21
  LCD_E = 0;
22
  byte |= (LCD_PORT & 0x0F);  //read masked low nibble  
23
24
  LCD_RS = 0;
25
  LCD_RW = 0;
26
27
  return byte;
28
}
29
30
//return busy bit7 as 0 or 1
31
char lcd_busy (void)
32
{
33
  if (lcd_read(0) & 0x80)
34
  {
35
  beep();    //wenns piept, funzt der scheiss endlich!
36
  return 1;
37
  }
38
  Delay10TCYx(12);    //10µs für den fall, dass der ks006 sich zu früh freut
39
  return 0; 
40
}

Edit: Übrigens Danke trotzdem für deine Hilfe, Sebastian, und viel 
Erfolg mit deinem kommenden Projekt! Ich bastel hier noch alles auf nem 
Steckbrett :)

von Sebastian H. (sebihepp)


Angehängte Dateien:

Lesenswert?

Funktioniert überhaupt der Beep?

Ich hab' noch nen kleines Steckbrett. Morgen baue ich meine Schaltung
auch mal drauf auf. Mal sehen ob mein Code funktioniert. Der ist
übrigens im Anhang. Das ist zwar Assembler und ich habe ihn auch noch
nicht getestet, aber vielleicht willst du ja mal drüber schauen.
Die Ein oder Andere Eingebung bringt das ab und zu. ;)

Der Code ist für einen PIC18F4550 mit 40MHz Quarzoszillator.

schönen Abend noch.
Sebihepp

von Daniel R. (h3po)


Lesenswert?

Habe das Rätsel grade gelöst. Am Anfang von main.c beim setzen der Flags 
habe ich mich bei einem Kommentar verschrieben und dachte deshalb, 
'#pragma config PBADEN = OFF' würde die Analog-Inputs auf PORTA (PORTB 
wäre richtig gewesen) deaktivieren. Damit Digital Inputs auf PORTA 
gehen, muss man ADCON1 = 0x0F setzen. Das habe ich jetzt in die Init 
übernommen und meine lcd_read funktioniert jetzt.
Das auf sprut.de in einem Nebensatz erwähnte Phänomen, dass der KS006 
das Busy-Flag zu früh entfernt, tritt bei mir tatsächlich auf. Wenn das 
Flag 0 wird, muss ich trotzdem vor dem nächsten byte noch 20µs Pause 
machen, damit die komplette Übertragung klappt. Habe ich die Pause nicht 
drin, fehlen alle paar dutzend übertragene Bytes immer mal wieder 
mehrere am Stück.

Ich werde jetzt noch ein paar String-Funktionen bauen und meine fertige 
Library dann hier als Anhang hochladen. Timing-Probleme beim ansteuern 
von LCD sind ja keine Seltenheit, vielleicht hilft dieser Thread hier 
dann ein bisschen weiter.

Danke, Sebastian und Thomas.

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.