Hallo,
ich bin ein lernwilliger Anfänger, was die Programmierung und die
AVR-Mikrocontroller angeht.
Ich programmiere in C mit dem GCC Plugin für das AVR-Studio.
Als Lernprojekt habe ich mir vorgenommen ein Text LCD-Display
anzusteuern.
Leider klappt bei mir nicht einmal die Initialisierung.
Da ich schon seit dem Wochenende viel versucht habe (ich habe auch die
Suchfunktion benutzt), wende ich mich nun an euch.
Meine Hardware:
- Der AVR: ATMEGA16, interner Quarz 8Mhz
- Das Display: 4*20 Zeichen (Die Bezeichnung des LCD-Displays lautet:
YM-2004A Series)
Durch meine Recherche habe ich schon rausgefunden, dass das Display
entweder den Controller KS0066 oder den SPLC780 nutzt und diese
eventuell anders initialisiert werden.
Im Anhang ist einmal das kleine Datenblatt des Displays(welches ich beim
Kauf dabei bekommen habe).
Außerdem habe ich ein großes Datenblatt und ein Datenblatt für den
KS0066, welche ich gegoogelt habe angehängt.
Fehler:
Sobald das LCD-Diplay Strom bekommt sehe ich in der 1.und der 3. Zeile
Balken. Sonst passiert nichts - ich nehme stark an, dass die Balken nach
der Initialisierung verschwinden sollten.
Mein Programm:
Ich bin bei der Initialisierung nach dem Datenblatt des KS0066
vorgegangen (die Initialisierung vom HD44780 habe ich aber auch schon
versucht).
1
#ifndef F_CPU
2
#define F_CPU 8000000UL
3
#endif
4
5
#include<avr/io.h>
6
#include<util/delay.h>
7
8
#define RS PD4
9
#define RW PD5
10
#define DB7 PD3
11
#define DB6 PD2
12
#define DB5 PD1
13
#define DB4 PD0
14
#define E PD6
15
16
voidlcd_init(void);
17
18
// ***** Hauptprogramm *****
19
intmain(void){
20
21
lcd_init();// Display initialisieren
22
23
while();// Endlosschleife
24
25
}
26
27
voidlcd_init(void)
28
{
29
30
//************ power on *********************
31
//******** wait more than 30ms **************
32
_delay_ms(50);
33
34
//************* Function set ****************
35
// LOW
36
PORTD&=~(1<<RS)// RS LOW
37
|(1<<RW)// RW LOW
38
|(1<<DB7)// DB7 LOW
39
|(1<<DB6)// DB6 LOW
40
// DB5 HIGH
41
|(1<<DB4);// DB4 LOW
42
// HIGH
43
PORTD|=(1<<DB5);
44
//--------------------------------------------
45
// LOW
46
PORTD&=~(1<<RS)// RS LOW
47
|(1<<RW)// RW LOW
48
|(1<<DB7)// DB7 LOW
49
|(1<<DB6)// DB6 LOW
50
// DB5 HIGH
51
|(1<<DB4);// DB4 LOW
52
// HIGH
53
PORTD|=(1<<DB5);
54
//--------------------------------------------
55
// LOW
56
PORTD&=~(1<<RS)// RS LOW
57
|(1<<RW)// RW LOW
58
// DB7 HIGH
59
// DB6 HIGH
60
// DB5 HIGH
61
|(1<<DB4);// DB4 HIGH
62
// HIGH
63
PORTD|=(1<<DB7)
64
|(1<<DB6)
65
|(1<<DB5);
66
67
//*********** wait more than 39us **************
68
_delay_us(50);
69
70
//******** Display ON/OFF Control *************
71
// LOW
72
PORTD&=~(1<<RS)// RS LOW
73
|(1<<RW)// RW LOW
74
|(1<<DB7)// DB7 LOW
75
|(1<<DB6)// DB6 LOW
76
|(1<<DB5)// DB5 LOW
77
|(1<<DB4);// DB4 LOW
78
//--------------------------------------------
79
// LOW
80
PORTD&=~(1<<RS)// RS LOW
81
|(1<<RW);// RW LOW
82
// DB7 HIGH
83
// DB6 HIGH
84
// DB5 HIGH
85
// DB4 HIGH
86
// HIGH
87
PORTD|=(1<<DB7)
88
|(1<<DB6)
89
|(1<<DB5)
90
|(1<<DB4);
91
92
//********** wait more than 39us ****************
93
_delay_us(50);
94
95
//****************** Display Clear **************
96
// LOW
97
PORTD&=~(1<<RS)// RS LOW
98
|(1<<RW)// RW LOW
99
|(1<<DB7)// DB7 LOW
100
|(1<<DB6)// DB6 LOW
101
|(1<<DB5)// DB5 LOW
102
|(1<<DB4);// DB4 LOW
103
//--------------------------------------------
104
// LOW
105
PORTD&=~(1<<RS)// RS LOW
106
|(1<<RW)// RW LOW
107
|(1<<DB7)// DB7 LOW
108
|(1<<DB6)// DB6 LOW
109
|(1<<DB5);// DB5 LOW
110
// DB4 High
111
// HIGH
112
PORTD|=(1<<DB4);
113
114
//********* wait more than 1,53ms ****************
115
_delay_ms(5);
116
117
//************ Entry Mode Set ********************
Danke für den Hinweis.
Ich hatte versucht den Code zu kürzen um die Lesbarkeit zu verbessern.
Ich habe allerdings viele Versionen ausprobiert. Die Folgende
funktioniert auch nicht:
1
#ifndef F_CPU
2
#define F_CPU 8000000UL
3
#endif
4
5
#include<avr/io.h>
6
#include<util/delay.h>
7
8
#define RS PD4
9
#define RW PD5
10
#define DB7 PD3
11
#define DB6 PD2
12
#define DB5 PD1
13
#define DB4 PD0
14
#define E PD6
15
16
voidlcd_init(void);
17
18
19
// ***** Hauptprogramm *****
20
intmain(void){
21
22
_delay_ms(15);// wait min 15 mSec. after Power ON
23
lcd_init();
24
// Display initialisieren
25
for(;;);
26
27
}
28
29
voidlcd_init(void)
30
{
31
32
//************ power on *********************
33
34
//******** wait more than 30ms **************
35
_delay_ms(50);
36
37
//************* Function set ****************
38
// LOW
39
PORTD&=~(1<<RS);// RS LOW
40
PORTD&=~(1<<RW);// RW LOW
41
PORTD&=~(1<<DB7);// DB7 LOW
42
PORTD&=~(1<<DB6);// DB6 LOW
43
// DB5 HIGH
44
PORTD&=~(1<<DB4);// DB4 LOW
45
// HIGH
46
PORTD|=(1<<DB5);
47
//--------------------------------------------
48
// LOW
49
PORTD&=~(1<<RS);// RS LOW
50
PORTD&=~(1<<RW);// RW LOW
51
PORTD&=~(1<<DB7);// DB7 LOW
52
PORTD&=~(1<<DB6);// DB6 LOW
53
// DB5 HIGH
54
PORTD&=~(1<<DB4);// DB4 LOW
55
// HIGH
56
PORTD|=(1<<DB5);
57
//--------------------------------------------
58
// LOW
59
PORTD&=~(1<<RS);// RS LOW
60
PORTD&=~(1<<RW);// RW LOW
61
// DB7 HIGH
62
// DB6 HIGH
63
// DB5 HIGH
64
PORTD&=~(1<<DB4);// DB4 HIGH
65
// HIGH
66
PORTD|=(1<<DB7);
67
PORTD|=(1<<DB6);
68
PORTD|=(1<<DB5);
69
70
//*********** wait more than 39us **************
71
_delay_us(50);
72
73
//******** Display ON/OFF Control *************
74
// LOW
75
PORTD&=~(1<<RS);// RS LOW
76
PORTD&=~(1<<RW);// RW LOW
77
PORTD&=~(1<<DB7);// DB7 LOW
78
PORTD&=~(1<<DB6);// DB6 LOW
79
PORTD&=~(1<<DB5);// DB5 LOW
80
PORTD&=~(1<<DB4);// DB4 LOW
81
//--------------------------------------------
82
// LOW
83
PORTD&=~(1<<RS);// RS LOW
84
PORTD&=~(1<<RW);// RW LOW
85
// DB7 HIGH
86
// DB6 HIGH
87
// DB5 HIGH
88
// DB4 HIGH
89
// HIGH
90
PORTD|=(1<<DB7);
91
PORTD|=(1<<DB6);
92
PORTD|=(1<<DB5);
93
PORTD|=(1<<DB4);
94
95
//********** wait more than 39us ****************
96
_delay_us(50);
97
98
//****************** Display Clear **************
99
// LOW
100
PORTD&=~(1<<RS);// RS LOW
101
PORTD&=~(1<<RW);// RW LOW
102
PORTD&=~(1<<DB7);// DB7 LOW
103
PORTD&=~(1<<DB6);// DB6 LOW
104
PORTD&=~(1<<DB5);// DB5 LOW
105
PORTD&=~(1<<DB4);// DB4 LOW
106
//--------------------------------------------
107
// LOW
108
PORTD&=~(1<<RS);// RS LOW
109
PORTD&=~(1<<RW);// RW LOW
110
PORTD&=~(1<<DB7);// DB7 LOW
111
PORTD&=~(1<<DB6);// DB6 LOW
112
PORTD&=~(1<<DB5);// DB5 LOW
113
// DB4 High
114
// HIGH
115
PORTD|=(1<<DB4);
116
117
//********* wait more than 1,53ms ****************
118
_delay_ms(5);
119
120
//************ Entry Mode Set ********************
Weisst du was mich an deinem Code stört, außer das die Timings
teilweise fehlen? RW wird angesteuert (was man gar nicht braucht
wenn man RW auf Masse legt), Enable aber nicht.
Jörg schrieb:> Ich habe allerdings viele Versionen ausprobiert. Die Folgende> funktioniert auch nicht:
Meiner Meinung nach schneidest du dich mit so einem Schei.... nur ins
eigene Fleisch.
> void lcd_init( void )> {>> //************ power on *********************>> //******** wait more than 30ms **************> _delay_ms (50);>> //************* Function set ****************> // LOW> PORTD &= ~(1<<RS); // RS LOW> PORTD &= ~(1<<RW); // RW LOW> PORTD &= ~(1<<DB7); // DB7 LOW> PORTD &= ~(1<<DB6); // DB6 LOW> // DB5 HIGH> PORTD &= ~(1<<DB4); // DB4 LOW> // HIGH> PORTD |= (1<<DB5);> //--------------------------------------------> // LOW> PORTD &= ~(1<<RS); // RS LOW> PORTD &= ~(1<<RW); // RW LOW> PORTD &= ~(1<<DB7); // DB7 LOW> PORTD &= ~(1<<DB6); // DB6 LOW> // DB5 HIGH> PORTD &= ~(1<<DB4); // DB4 LOW> // HIGH> PORTD |= (1<<DB5);
usw. usw.
du musst jeden einzelnen Pin über 25 Statements verfolgen um zu wissen,
wann genau der jeweilige Pin auf 0 bzw auf 1 ist.
Mir ist schon klar, dass das Ziel darin besteht, die Initialisierung
über die Bühne zu bringen, ohne das die anderen Pins verändert werden.
Aber leg fürs erste nicht zuviel Gewicht auf dieses Ziel! Denn dadurch
verlierst du es erst mal aus den Augen.
Fürs erste ist es völlig ausreichend, wenn du die Initalisierung so
machst, dass du an den Port D zu Beginn jeden neues
Initialisierungsschrittes das anzulegende Bitmuster komplett anlegst.
Also einfach eine Zuweisung an PORTD. Dann hinen nach noch ein Toggeln
mit dem E-Pin und das wars.
Mach dir die Sache nicht unnötigerweise zu komplex. Zumindest nicht am
Anfang.
Kein Mensch stört sich bei einem Anfänger bei seinen ersten Schritten
daran, wenn du anstelle von ...
1
//******** wait more than 30ms **************
2
_delay_ms(50);
3
4
//************* Function set ****************
5
// LOW
6
PORTD&=~(1<<RS);// RS LOW
7
PORTD&=~(1<<RW);// RW LOW
8
PORTD&=~(1<<DB7);// DB7 LOW
9
PORTD&=~(1<<DB6);// DB6 LOW
10
// DB5 HIGH
11
PORTD&=~(1<<DB4);// DB4 LOW
12
// HIGH
13
PORTD|=(1<<DB5);
... das so machst:
1
//******** wait more than 30ms **************
2
_delay_ms(50);
3
4
PORTD=(1<<DB5);
Weil ichs gerade sehe: Wo wedelst du eigentlich mit dem Enable-Pin?
Ich denke, dass ich das auch noch nicht ganz verstanden habe.
Zu RW:
Ich habe RW an einen PORT-Pin angeschlossen, daher setzte ich diesen
immmer auf LOW.
Zu E:
Wann muss ich E denn ansteuern?
Im Datenblatt konnte ich in dem Flussdiagramm nicht finden, dass E
angesteuert wird. Daher steuere ich E auch nicht an.
Danke für deine Hilfe.
Jörg schrieb:> Im Datenblatt konnte ich in dem Flussdiagramm nicht finden, dass E> angesteuert wird.
Im Datenblatt gibt es sicherlich ein Diagramm, welches die elektrischen
Signale zeigt. Und dort taucht E auf
> Daher steuere ich E auch nicht an.> Wann muss ich E denn ansteuern?
Immer dann, wenn der Zustand deiner Port-Bits fertig eingestellt ist.
Das LCD kann ja nicht hellsehen, dass dein Code jetzt mit Manipulation
der Port-Bits fertig ist und es daher die anliegenden Signale übernehmen
soll.
Ich weiß, der Name "Enable" ist etwas schlecht gewählt. Aber im Grunde
ist das nichts anderes als die Leitung: "Alle Datenbits sind fertig
eingestellt, R/W hab ich eingestellt, Command/Data hab ich eingestellt;
jetzt bist du drann - mach was damit!"
>Wann muss ich E denn ansteuern?>Im Datenblatt konnte ich in dem Flussdiagramm nicht finden, dass E>angesteuert wird.
Mittleres Datenblatt Seite 4 und 5. Man muss schon
ziemlich blind sein um das zu übersehen.
Das auf Seite 4 und 5 im 2. Datenblatt hatte ich schon gesehen,
allerdings wusste ich nicht, dass ich auch bei der Initialisierung diese
Grafik beachten muss.
Ich dachte dies gilt nur, wenn ich nach der Initialisierung Daten in das
Display schreibe.
Nun weiss ich es besser. Danke!
Muss ich E nun also ansteuern wenn ich die PINS DB7-DB4 ändere, oder
immer erst nach einem "gesamten Initialisierungsschritt"
Mit Initialisierungsschritt meine ich die Schritte, welche im Datenblatt
3(das des KS0066) auf Seite 27 aufgeführt sind.
Jörg schrieb:> Das auf Seite 4 und 5 im 2. Datenblatt hatte ich schon gesehen,> allerdings wusste ich nicht, dass ich auch bei der Initialisierung diese> Grafik beachten muss.
Immer!
Wie gesagt: Das LCD kann ja nicht hellsehen, wann die restlichen Pins
ihren endgültigen Zustand angenommen haben.
> Muss ich E nun also ansteuern wenn ich die PINS DB7-DB4 ändere, oder> immer erst nach einem "gesamten Initialisierungsschritt"
Laut Datenblatt geht E auf High
dann ändern sich die Datenpins
und dann geht E wieder auf Low
Siehe Timingdiagramm
Mit dem Übergang von E von High auf Low übernimmt das LCD die Daten.
Wobei das nicht soooo heiß gegessen wird.
Du kannst auch vorher die Datenleitungen einstellen, dann geht E auf
High, wartet ein bischen und geht wieder auf Low. Entscheidend ist nur,
dass die Datenleitungen (+R/W +Command) mit dem Wechsel von E von High
auf Low übernommen werden und das E eine gewisse Mindestzeit auf High
gehalten werden muss.
Warum studierst du den nicht anderen Code für solche Details? Im
Tutorial gibt es einen Abschnitt über das LCD!
> Mit Initialisierungsschritt meine ich die Schritte, welche im Datenblatt> 3(das des KS0066) auf Seite 27 aufgeführt sind.
Das hat nichts mit der Initialisierung zu tun.
Das ist der generelle Mechanismus, der einzuhalten ist, wenn Daten zum
LCD übertragen (Write) oder vom LCD gelesen (Read) werden.
Ist wie beim Telefon: Du musst zuerst die Verbdinung herstellen und erst
dann reden, nicht umgekehrt. Das ist beim Telefon der grundsätzliche
Vorgang. Was du deinem Gegenüber mitteilst, ist davon unberührt.
Ich habe mir verschiedensten Code angeguckt, jedoch wollte ich für den
Anfang nur "das Nötigste" programmieren.
Für mich als Anfänger ist es noch etwas schwer durch den Code zu finden,
wenn dieser in vielen Funktionen (mit verschiedenen Parameterübergaben)
aufgeteilt ist.
Und ich muss jetzt feststellen, dass viel mehr code nötig ist als ich
dachte. UND VORALLEM VERSTEHE ICH WARUM.
Ich habe noch Probleme diesen Abschnitt deines Beitrages zu verstehen:
"Entscheidend ist nur,
dass die Datenleitungen (+R/W +Command) mit dem Wechsel von E von High
auf Low übernommen werden und das E eine gewisse Mindestzeit auf High
gehalten werden muss.
"
Heisst das die Leitungen RW und RS(also Command?) sind erst HIGH und
werden gleichzeitig mit E auf LOW gesetzt?
Und woher weiss ich wie lange ich E halten muss? Im Datenblatt steht da
glaub ich nichts zu?
Vielen Dank, dass Ihr auf meine Anfängerfragen nicht total genervt
reagiert.... ich merke selbst, dass die Fragen auf unterstem Niveau
sind.
Gruß
Jörg schrieb:> Ich habe mir verschiedensten Code angeguckt, jedoch wollte ich für den> Anfang nur "das Nötigste" programmieren.
Das ist das Problem:
Woher weißt du, was nötig ist und was Zugabe?
> Für mich als Anfänger ist es noch etwas schwer durch den Code zu finden,> wenn dieser in vielen Funktionen (mit verschiedenen Parameterübergaben)> aufgeteilt ist.
Dann musst du bei den einfachen Funktionen anfangen zu analysieren und
dir merken, was sie machen. Ein gut gewählter Funktionsname hilft dabei.
Was macht ?
1
staticvoidlcd_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
}
Aha. Das schickt einen Puls auf dem E-Pin raus. Also einmal auf 1, etwas
warten und wieder zurück auf 0. Jetzt weißt du schon, aus dem
Datenblatt, das du das immer brauchst, um eine Ausgabe abzuschliessen.
Die Erwartung ist daher, dass diese Funktion wohl am Ende einer Ausgabe
aufgerufen wird.
Schaun wir mal
1
staticvoidlcd_out(uint8_tdata)
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
}
Jup. Offenbar richtig. die Funktion lcd_out gibt etwas am Port aus und
hinten nach wird der Enable Puls gesetzt. lcd_out enthält alles was
notwendig ist um etwas an das LCD zu schicken.
Wo wird sie verwendet?
Na zb in der lcd_init. Anstelle sich in der lcd_init ständig mit den
Port Pins rumzuschlagen, wird einfach nur lcd_out aufgerufen. lcd_out
weiß dann schon, was da alles zu machen ist (inklusive Enable Pin
toggeln)
> "Entscheidend ist nur,> dass die Datenleitungen (+R/W +Command) mit dem Wechsel von E von High> auf Low übernommen werden und das E eine gewisse Mindestzeit auf High> gehalten werden muss.> ">> Heisst das die Leitungen RW und RS(also Command?) sind erst HIGH und> werden gleichzeitig mit E auf LOW gesetzt?
Nein
Das heisst dass du R/W und RS ebenfalls einstellen musst, bevor auf E
der Wechsel 1 - warten - 0 erfolgt.
> Und woher weiss ich wie lange ich E halten muss?
Schau ins Timing Diagramm. Da sind Pfeile drinnen, die jeweils die
Mindestzeiten angeben.
Ich habe nun mein Progreamm noch mehrmals komplett überarbeitet.
Leider bekomme ich das Display nicht initialisiert.
Es wäre super, wenn Ihr mir sagen könntet ob mein Code jetzt richtig ist
und ich vllt. einfach nur die falschen Kommandos zur Initialisierung
benutze? -Aber welche sind dann richtig? - kann ich irgend etwas an den
Fusebits falsch eingestellt haben, was nun Pins am Port D
blockiert(durch mehrfachbelegung der pins)?
Besonders interessieren würde es mich, ob folgende Stellen korrekt sind:
1
instruction&=0x0F;//ändere nur unteres Nibble(DB4-DB7)
2
PORTD=instruction;//schreibe in DB4-DB7 unteres Nibble von instruction
Und dann:
1
lcd_write_instruction(0x02);
Mit diesem Code soll an DB5(angeschlossen an PD1) eine 1 anliegen,
während die Pins PD 4,5,6,7 unverändert bleiben.
1
#ifndef F_CPU
2
#define F_CPU 8000000UL
3
#endif
4
5
#include<avr/io.h>
6
#include<util/delay.h>
7
8
voidlcd_write_instruction(uint8_tinstruction);
9
voidlcd_write_data(uint8_tdata);
10
voidlcd_init(void);
11
12
#define DB4 PD0
13
#define DB5 PD1
14
#define DB6 PD2
15
#define DB7 PD3
16
#define RS PD4
17
#define RW PD5
18
#define E PD6
19
20
21
// ***** Hauptprogramm *****
22
intmain(void){
23
//Ausgänge konfigurieren
24
DDRD=0xff;//D0-7 als Ausgänge
25
PORTD=0x00;//Ausgänge auf 0
26
27
_delay_ms(150);// wait min 15 mSec. after Power ON
Jörg schrieb:> void lcd_write_instruction(uint8_t instruction)> {>> PORTD &= ~(1<<RS); //RS=0> PORTD &= ~(1<<RW); //RW=0> _delay_us (0.06); //60ns(tSU1) warten> PORTD |= (1<<E); //E=1> _delay_us (0.255); //warte 255ns (450ns(tW)-195ns(tSU2))>> instruction &= 0x0F; //ändere nur unteres Nibble(DB4-DB7)> PORTD = instruction; //schreibe in DB4-DB74 unteres Nibble von> instruction
Damit ist dann die ganze schöne Einstellung, die du vorher für RS, RW
und E gemacht hast, zur Geschichte geworden. Gone with the wind
RS und RW sind hier noch egal, weil sie sowieso 0 sind. Aber E ist nicht
egal
> void lcd_write_data(uint8_t data)> {> PORTD |= (1<<RS); //RS=1> PORTD &= ~(1<<RW); //RW=0> _delay_us (0.06); //60ns(tSU1) warten> PORTD |= (1<<E); //E=1> _delay_us (0.255); //warte 255ns (450ns(tW)-195ns(tSU2))>> data &= 0x0F; //ändere nur unteres Nibble(DB4-DB7)> PORTD = data; //schreibe in DB4-DB7 unteres Nibble von data
... während hier dann auch das RS Bit auch noch gelöscht wurde
Schluck deinen Stolz runter und hol dir die LCD Routinen aus dem
Tutorial. Überpüf noch, ob die Initialisierung die Bytes schickt, die
bei dir laut Datenblatt vorgeschrieben sind und bring es erst mal zum
Laufen. Und dann studierst du den Tuorialcode im Detail.
Ich weiß schon, du willst das alles selbst schreiben, bla bla bal ...
und überhaupt. Aber solange dir solche läppischen Fehler passieren, bist
du einfach noch nicht soweit mehrer Leitungen simultan zu bedienen ohne
dass du ungewollt durch die Manipulation einer Leitung eine andere
irrtümlich veränderst ohne es zu merken. Ist nicht weiter schlimm, das
wird schon werden. Aber ein LCD ist ein denkbar ungeeignetes und
frustrierendes Objekt, um sich daran zu versuchen.
>data &= 0x0F; //ändere nur unteres Nibble(DB4-DB7)>PORTD = data; //schreibe in DB4-DB7 unteres Nibble von data
Und was ist mit dem oberen Nibble von data?
Willst du das nicht senden? Das musst du aber!