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.