Forum: Mikrocontroller und Digitale Elektronik HD44780 LCD - Simulation OK, AVR bleibt in Funktionen stecken


von Simon V. (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

Mein LCD funktioniert immer noch nicht. Ich habe eine (angepasste) 
Displayansteuerung, mache nach der Init-Funktion aber nur einen 
Port-Test, dass ich messen kann, ob da etwas passiert.
Wenn ich die init Funktion auskommentiere, funktioniert mein Port-Test. 
Wenn sie nicht auskommentiert ist, passiert einfach nichts. Als würde 
der µC irgendwo drin "hängen".

In der Simulation mit AVR Studio funktioniert alles, die Ports ändern 
ihren Wert, nur nicht in echt, außer wenn ich init weg lasse. Das 
Programm kompiliert ohne Fehler.
Die Initialisierung an sich funktioniert (schwarzer Balken 
verschwindet), nur kann ich keinen Text anzeigen lassen, was mich dazu 
gebracht hat, erst einmal die Ports zu testen.

Ich benutze einen Atmega32.
Ich habe die lcd.c vorübergehend in main.c gepackt.

von Klaus (Gast)


Lesenswert?

>void lcd_data( uint8_t data ) {
>    LCD_CTRL |= (1<<LCD_RS);    // RS auf 1 setzen

>    lcd_out( data );            // zuerst die oberen,
>    lcd_out( data<<4 );         // dann die unteren 4 Bit senden

>   _delay_us( LCD_WRITEDATA_US );
>}

static void lcd_out( uint8_t data ) {
>//    data &= 0xF0;                       // obere 4 Bit maskieren
>//    LCD_PORT &= ~(0xF0>>(4-LCD_DB));    // Maske löschen
>//    LCD_PORT |= (data>>(4-LCD_DB));     // Bits setzen
>    LCD_PORT = data;     // Bits setzen
>    lcd_enable();
>}

Also ich denke da ist der Wurm drinne.

ich würde es so mal machen! Erst mal das Highnibble ins Low verschieben
und Ausmaskieren und dann Senden.
int data2

data2 = (data >> 4);
data2 &= 0x0F;
LCD_PORT = data2;

Danach einfach
data2 = (data & 0x0F);
und das Lowniblle senden,

Wenn du deine lcd_out und LCD_Data  so anpasst brauchst nur das 
Datenbyte übergeben.

von Klaus (Gast)


Lesenswert?

Ohh noch was endeckt! Warum benutzt du nicht einen PORT wenn du schon in 
4-Bit Modus arbeitest.




>// 4 Bit LCD Datenbus DB4-DB7, das unterste Bit DB4 kann auf den Portbits >0..4 
liegen

>//  LCD DB4-DB7 <-->  PORTC Bit PC0-PC3
>#define LCD_DB        PC4

>// LCD Steuersignale RS und EN

>//  LCD RS      <-->  PORTC Bit PC4     (RS: 0=Data, 1=Command)
>#define LCD_RS        PD3

>//  LCD EN      <-->  PORTC Bit PC5     (EN: 1-Impuls für Daten)
>#define LCD_EN        PD4

ich meine Du könntest es auch so definieren.


#define LCD_PORT        PORTB
#define LCD_DDR         DDRB
#define LCD_PIN         PINB

#define LCD_RS          0
#define LCD_RW          1
#define LCD_EN          2

#define LCD_D4          3
#define LCD_D5          4
#define LCD_D6          5
#define LCD_D7          6

von SpäterGast (Gast)


Lesenswert?

Was steht denn als letztes auf dem LCD_PORT?

von Klaus (Gast)


Lesenswert?

Hallo Simon!

Diese Anfangsadressen der 2 und 3 Zeile bitte auch noch anpassen

#define LCD_DDADR_LINE3         0x10      muss   0x14
#define LCD_DDADR_LINE4         0x50      muss   0x54   sein.

von Simon V. (Gast)


Lesenswert?

vielen Dank für die ausführlichen Antworten.
meine lcd_data sieht jetzt so aus:



void lcd_data( uint8_t data ) {
    LCD_CTRL |= (1<<LCD_RS);    // RS auf 1 setzen

int data2;

data2 = (data >> 4);
data2 &= 0x0F;
LCD_PORT = data2;

data2 = (data & 0x0F);
LCD_PORT = data2;

    _delay_us( LCD_WRITEDATA_US );
}



und meine lcd_out:

static void lcd_out( uint8_t data ) {

int data2;

data2 = (data >> 4);
data2 &= 0x0F;
LCD_PORT = data2;

data2 = (data & 0x0F);
LCD_PORT = data2;

}



War's so gemeint? Was ist mit lcd_command? Dort das gleiche auch nehme 
ich an.



Die Ports habe ich so definiert:

#define LCD_D4          4
#define LCD_D5          5
#define LCD_D6          6
#define LCD_D7          7

Ich benutze getrennte Ports für Daten und Steuerung, weil ich mir zu 
wenig Gedanken über die Software gemacht habe, als ich die Hardware 
entwickelt habe...
Die Anfangsadressen habe ich auch angepasst. Zwischenzeitlich hatte ich 
mal ein undefinierbares Zeichn und einen blinkenden Cursor, mehr tat 
sich noch nicht.

von wahlostfriese (Gast)


Lesenswert?

Moin Simon,

wo sind in den zuletzt geposteten Funktionen denn die Aufrufe von 
lcd_enable geblieben? Ohne die kriegt der Controller das Bitmuster am 
LCD_PORT ja gar nicht mit. Außerdem maskierst Du jetzt die oberen Bits 
weg statt die unteren. Ich glaube nicht, dass so noch irgendwelche Daten 
rüberkommen ;-)

Also ich würde die lcd_out so lassen, wie Du sie zuerst hattest, und die 
lcd_data und lcd_command auch. Und auch Deine lcd_init sieht für mich so 
weit ok aus, die extra ms-Delays nach dem dritten Soft-Reset und dem 
Switch auf 4bit können wohl entfallen, sollten aber nicht wirklich 
stören. Ich beziehe mich dabei einerseits auf das Datenblatt des 
HD44780U, das bei alldatasheet.com zu finden ist, und andererseits auf 
meine eigene Implementierung dieses Controllers, die ich vor Jahren in 
Assembler geschrieben habe, und mit der ich bisher jedes Character-LCD 
von 1 bis 4 Zeilen je 16 bis 40 Zeichen im 4bit-Modus auf Anhieb zum 
Spielen gekriegt habe.

Ich denke daher, dass Dein eigentliches Problem, nämlich dass Dein 
Porttest nicht funktioniert, nichts mit den Ausgaben an das LCD zu tun 
hat, sondern mit etwas anderem. Und da bleiben praktisch nur die gcc 
includes, die Du verwendest. Ich würde da mal nachsehen...

von Karl H. (kbuchegg)


Lesenswert?

wahlostfriese schrieb:

> Also ich würde die lcd_out so lassen, wie Du sie zuerst hattest,

aber bitte so, wie es im Original stand. Nicht so wie er es geschrieben 
hat.

Dafür darf dann in der lcd_init ruhig auch der Port für die 
Control-Signale auf Ausgang gestellt werden.
Aber bitte auch nicht wieder so, wie er es bei den Datenleitungen 
gemacht hat, sondern so wie es im Original stand. Keine 
Rundumschlag-Port Veränderungen, sondern mit der feinen Klinge nur die 
Bits beeinflussen, die tatsächlich gebraucht werden.

Es fragt sich, warum er da überhaupt geändert hat. So wie das aussieht, 
steht er noch auf der Stufe des Faustkeils und arbeitet noch nicht mit 
der feinen Klinge, die auch ein paar Eventualitäten berücksichtigt an 
die der Faustkeilträger nicht denkt.

> Ich benutze getrennte Ports für Daten und Steuerung, weil ich mir
> zu wenig Gedanken über die Software gemacht habe, als ich die
> Hardware entwickelt habe...

Solange du die 4 Datenleitungen an einem Port gelassen hast und die auch 
auf aufeinanderfolgenden Pins liegen, ist das kein Problem.
Die Steuerleitungen aus dem Originalcode 'rauszuoperieren' ist nicht das 
große Problem. Aber du musst das ein bißchen besser angehen, als du das 
getan hast. Du hast einfach nur draufgehauen und dabei mehr zerstört als 
du gut gemacht hast.

von Karl H. (kbuchegg)


Lesenswert?

Ich weiß jetzt nicht, was du an den Originalroutinen noch so alles 
geändert hast. Aber ich würde mal sagen: Geh zurück zu den 
Originalroutinen und pass die nochmal neu an. Zurück an den Start.

Aber diesmal mit etwas mehr Sachverstand!

Im Original steht da ....
1
#define LCD_PORT      PORTD
2
#define LCD_DDR       DDRD
... weil alle Leitungen über denselben Port abgewickelt werden. Das geht 
bei dir natürlich nicht. Die Steuerleitungen brauchen ihren eigenen 
Port. Also führen wir den gleich mal ein
1
#define LCD_PORT      PORTD
2
#define LCD_DDR       DDRD
3
4
#define LCD_CTRL_PORT PORTD
5
#define LCD_CTRL_DDR  DDRD

Wenn ein Port dazukommt, muss man sich sofort fragen: Wie und wo wird 
der auf die Datenrichtungen eingestellt, wo wird er initialisiert?

Im Original ist das in der init() Funktion. Da steht:
1
void lcd_init( void )
2
{
3
    // verwendete Pins auf Ausgang schalten
4
    uint8_t pins = (0x0F << LCD_DB) |           // 4 Datenleitungen
5
                   (1<<LCD_RS) |                // R/S Leitung
6
                   (1<<LCD_EN);                 // Enable Leitung
7
    LCD_DDR |= pins;
8
    ...

d.h. da werden alle am Port LCD_DDR benutzen Leitungen (inklusive 
Steuerleitungen) auf Ausgang gestellt.
Das geht bei dir natürlich nicht mehr. Bei dir sind die Steuerleitungen 
getrennt und es gibt ein #define dafür. Also trennst du die Dinge auf. 
Alles was mit den Datenleitungen zu tun hat, bleibt so wie es ist, nur 
die Steuerleitungen werden rausoperiert.
1
void lcd_init( void )
2
{
3
    LCD_DDR |= (0x0F << LCD_DB);
4
    LCD_CTRL_DDR |= (1<<LCD_RS) | (1<<LCD_EN);
5
    ...
so geschrieben passiert immer noch das gleiche, wenn Datenport und 
Controlport identisch sind. Wenn sie es aber nicht sind, dann 
berücksicht das dieser Code.
Wie gehts in der FUnktion weiter?
1
    // initial alle Ausgänge auf Null
2
    LCD_PORT &= ~pins;
ok. normalerweise braucht das keiner. Es schadert aber auch nichts. Aber 
auch hier wieder. Den Anteil der Datenbits von den Steuerleitungen 
trennen. Die Datenbits werden behandelt wie bisher, die Steuerleitungen 
gehen auf ihren eigenen Port
1
    // initial alle Ausgänge auf Null
2
    LCD_PORT &= ~(0x0F << LCD_DB);
3
    LCD_CTRL_PORT &= ~((1<<LCD_RS) | (1<<LCD_EN));

Damit ist die Intialisierung soweit geändert, dass die Steuerleitungen 
auf ihren eigenen Port laufen.

Wo muss noch geändert werden?
Zu diesem Zwecke geht man jetzt alle Zugriffe auf LCD_PORT durch und 
stellt sich die Frage: Womit haben wir es hier zu tun? Geht es hier um 
die Datenleitungen oder um die Steuerleitungen. Alles was mit 
Datenleitungen zu tun hat, BLEIBT SO WIE ES IST! Alles was mit 
Steuerleitungen zu tun hat, wird geändert. Falls es einen Zugriff auf 
LCD_PORT gibt, in dem beide Teilbereiche gleichzeitig beeinflusst 
werden, muss man die Dinge auseinanderdividieren.
Aber schaun wir mal. Erste Funktion
1
static void lcd_enable( void )
2
{
3
    LCD_PORT |= (1<<LCD_EN);     // Enable auf 1 setzen
4
    _delay_us( LCD_ENABLE_US );  // kurze Pause
5
    LCD_PORT &= ~(1<<LCD_EN);    // Enable auf 0 setzen
6
}
Datenleitungen oder Steuerleitungen?
Ganz klar Steuerleitungen. Also wird geändert
1
static void lcd_enable( void )
2
{
3
    LCD_CTRL_PORT |= (1<<LCD_EN);     // Enable auf 1 setzen
4
    _delay_us( LCD_ENABLE_US );  // kurze Pause
5
    LCD_CTRL_PORT &= ~(1<<LCD_EN);    // Enable auf 0 setzen
6
}

Nächste Funktion
1
static void lcd_out( uint8_t data )
2
{
3
    data &= 0xF0;                       // obere 4 Bit maskieren
4
 
5
    LCD_PORT &= ~(0xF0>>(4-LCD_DB));    // Maske löschen
6
    LCD_PORT |= (data>>(4-LCD_DB));     // Bits setzen
7
    lcd_enable();
8
}
Daten oder Steuer?
Ganz klar Daten. Und zwar nur Daten.
Ergo: Die Funktion bleibt so wie sie ist!

Nächste Funktion
1
void lcd_data( uint8_t data )
2
{
3
    LCD_PORT |= (1<<LCD_RS);    // RS auf 1 setzen
4
 
5
    lcd_out( data );            // zuerst die oberen, 
6
    lcd_out( data<<4 );         // dann die unteren 4 Bit senden
7
 
8
    _delay_us( LCD_WRITEDATA_US );
9
}
Der direkte Portzugriff: Daten oder Steuer?
Ganz klar Steuer. Also wird geändert
1
void lcd_data( uint8_t data )
2
{
3
    LCD_CTRL_PORT |= (1<<LCD_RS);    // RS auf 1 setzen
4
 
5
    lcd_out( data );            // zuerst die oberen, 
6
    lcd_out( data<<4 );         // dann die unteren 4 Bit senden
7
 
8
    _delay_us( LCD_WRITEDATA_US );
9
}

Nächste
1
void lcd_command( uint8_t data )
2
{
3
    LCD_PORT &= ~(1<<LCD_RS);    // RS auf 0 setzen
4
 
5
    lcd_out( data );             // zuerst die oberen, 
6
    lcd_out( data<<4 );           // dann die unteren 4 Bit senden
7
 
8
    _delay_us( LCD_COMMAND_US );
9
}
same type, selbes Spiel. Ganz klar Steuer. Also...
1
void lcd_command( uint8_t data )
2
{
3
    LCD_CTRL_PORT &= ~(1<<LCD_RS);    // RS auf 0 setzen
4
 
5
    lcd_out( data );             // zuerst die oberen, 
6
    lcd_out( data<<4 );           // dann die unteren 4 Bit senden
7
 
8
    _delay_us( LCD_COMMAND_US );
9
}

Nächster direkter Portzugriff ... es gibt keinen mehr.

Damit wurden im Code die Datenleitungen von den Steuerleitungen 
getrennt. Wenn dein LCD nicht noch im Timingbereich ein Problem hat, 
müsste es jetzt reichen, wenn du im Header File hier
1
// Konfiguration für die Datenleitungen
2
#define LCD_PORT      PORTD
3
#define LCD_DDR       DDRD
4
5
//  LCD DB4-DB7 <-->  LCD_PORT Bit PD0-PD3
6
#define LCD_DB        PD0
7
 
8
// Konfiguration für die Steuerleitungen
9
#define LCD_CTRL_PORT PORTD
10
#define LCD_CTRL_DDR  DDRD
11
12
//  LCD RS      <-->  LCD_CTRL_PORT Bit PD4     (RS: 1=Data, 0=Command)
13
#define LCD_RS        PD4
14
 
15
//  LCD EN      <-->  LCD_CTRL_PORT Bit PD5     (EN: 1-Impuls für Daten)
16
#define LCD_EN        PD5
deine korrekte Konfiguration einträgst und alles ist paletti.



-> Wenn du in deinen Programmier-Studien weiterkommen willst, dann MUSST 
du weg von direkten kompletten Portzugriffen! Einzelbits setzen und 
Einzelbits löschen dürfen dir kein Kopfkratzen verursachen! Du musst 
wissen, wie man mit |= bzw. &= einzelne Bits in einem Register gezielt 
beeinflusst, ohne das restliche Register zu zerstören. Das muss im 
Schlaf um 3 Uhr morgens nach dem Aufwecken zu 100% sicher klappen! Und 
wenn es sein muss auch unter Sauerstoffmangel im Handstand auf dem 
Gipfel des Mt. Everest. Sowohl lesend (bei der Analyse von fremdem Code) 
als auch schreibend (wenn man selbst programmiert). Alle Vorübungen in 
Büchern und Tutorien mit LED (LED ein, LED aus, blinkende LED, 
LED-Muster) bzw. Tastern zielen nur darauf ab, dem Einsteiger in diesem 
Bereich eine gewisse Grundfertigkeit zu vermitteln. Die LED ist nicht 
das Interessante, das Interessante ist die Bitmanipulation, mit der man 
dem einen Portpin seinen Willen aufzwingt. Und zwar nur diesem EINEN 
Portpin.

von Karl H. (kbuchegg)


Lesenswert?

Und wenn du dann in main die DDR Register, die zum LCD gehören erst mal 
in Ruhe lässt, und nicht mit Rundumschlag aktionen ala


  while(1)
  {
...
DDRC = 0xf0;


eingreifst, dann funktioniert das auch.


Im AVR-Studio hast du die Taktfrequenz deines µC korrekt eingetragen 
(bzw. im Makefile korrekt gesetzt) und auch kontrolliert ob der µC auch 
wirklich mit dieser Frequenz läuft? Wenn diese Werte nicht 
zusammenstimmen, dann arbeiten alle _delay_xx Funktionen nicht richtig 
und das Timing stimmt nicht mehr.

Und da du gesagt hats, du benutzt einen Mega32: Das JTAG Interface am 
Port C hast du per Fuse abgeschaltet?

von Simon V. (Gast)


Lesenswert?

Hallo zusammen,

vielen Dank an alle Helfer. Ich habe meinen Atmega32 ausgewechselt mit 
einem Atmega644p (der war noch da). Denn, als ich mit dem Oszi gemessen 
habe und festgestellt habe, dass wenn ich an einem Pin messe und 
gleichzeitig die Spitze berühre, die Ausgangsspannung sich verändert, 
obwohl die Pins (im Gegensatz zu mir) niederohmig sein müssen. Musste 
also am µC liegen! Da ich ohne Berührung korrekte Signale messen konnte, 
habe ich das nie bemerkt.
Und siehe da, es funktioniert mit einem Atmega644p! :)

Wenn ich den Code etwas aufgeräumt habe, kann ich ihn auch gerne nochmal 
posten, falls jemand Interesse hat.

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.