Forum: Mikrocontroller und Digitale Elektronik Grafik Display mit Grafik Controller ST7565R - Probleme bei Zeichenausgabe


von Reinhard O. (kristian_1955)


Angehängte Dateien:

Lesenswert?

Hallo,

ich benutze einen Atmega2560 zum Ansteuern eines grafischen LCD 
Displays. Das Display benutzt den folgenden Controller ST7565R.

Warum zeigt das Display die Null vernüftig an, wenn ich die Daten ohne 
for- Schleife nacheinander wie folgt schicke?
1
const unsigned char* Zeiger;
2
Zeiger = font1D;
3
lcdSendData(*(Zeiger+240+0));
4
lcdSendData(*(Zeiger+240+1));
5
lcdSendData(*(Zeiger+240+2));
6
lcdSendData(*(Zeiger+240+3));
7
lcdSendData(*(Zeiger+240+4));

Benutze ich eine for-Schleife wird die Null nicht richtig dargestellt:
1
for (uint8_t i = 0; i<=5;i++)
2
{  
3
  lcdSendData(*(i+Zeiger+240));
4
5
}

Die LCD Ausgabe ist auf dem Foto im Anhang gezeigt.

Vielen Dank für eure Hilfe! :)

von c-hater (Gast)


Lesenswert?

Reinhard O. schrieb:

> lcdSendData(*(Zeiger+240+4))
                           ^!!!

> for (uint8_t i = 0; i<=5;i++)
                        ^^!!!

Alles klar?

von Reinhard O. (kristian_1955)


Lesenswert?

c-hater schrieb:
> Reinhard O. schrieb:
>
>> lcdSendData(*(Zeiger+240+4))
>                            ^!!!
>
>> for (uint8_t i = 0; i<=5;i++)
>                         ^^!!!
>
> Alles klar?

Vielen Dank für deine Antwort. Habe es gerade korrigiert.
1
for (uint8_t i = 0; i<5;i++)
2
{  
3
  lcdSendData(*(i+Zeiger+240));
4
5
}

Leider immernoch die selbe Ausgabe wie auf dem Foto obwohl es ein 
offensichtlicher Fehler war.

von c-hater (Gast)


Lesenswert?

Reinhard O. schrieb:

> Leider immernoch die selbe Ausgabe wie auf dem Foto obwohl es ein
> offensichtlicher Fehler war.

Dann gibt es wohl noch andere Fehler. In dem Teil des Codes, den du 
nicht gepostet hast...

Wer hätte das ahnen können...

von Reinhard O. (kristian_1955)


Lesenswert?

Es muss wohl so sein. Ich kann es mir aber nicht erklären, weil die 
for-Schleife doch eigentlich genau das macht, was ich oben drüber 
machen. Dann dürfte das ohne for-Schleife ja auch nicht funktionieren.

Oder sehe ich das falsch?

von c-hater (Gast)


Lesenswert?

Reinhard O. schrieb:

> Oder sehe ich das falsch?

Ja. Diese Logik funktioniert leider nur, solange aller anderer Code 
fehlerfrei ist.

Wer clever ist, zieht den naheliegenden Umkehrschluss: wenn's nicht 
funktioniert, obwohl es nach den Gesetzen der Logik funktionieren 
müsste, MUSS der Fehler irgendwo anders stecken...

Alles andere wäre Voodoo...

von Reinhard O. (kristian_1955)


Lesenswert?

c-hater schrieb:
> Reinhard O. schrieb:
>
>> Oder sehe ich das falsch?
>
> Ja. Diese Logik funktioniert leider nur, solange aller anderer Code
> fehlerfrei ist.
>
> Wer clever ist, zieht den naheliegenden Umkehrschluss: wenn's nicht
> funktioniert, obwohl es nach den Gesetzen der Logik funktionieren
> müsste, MUSS der Fehler irgendwo anders stecken...
>
> Alles andere wäre Voodoo...

Da stimme ich dir zu.
Ich habe noch über ein timing Problem nachgedacht. Bei der for-Schleife 
liegt ja zwischen den einzelnen lcdSendData aufrufen mindestens 1 
Prozessortakt.
Im Gegensatz zu den lcdSendData Befehlen direkt untereinander.

Könnte das ein Ansatz sein? Welchen Code soll ich posten? Von der 
Funktion lcdSendData?

Danke dir und den zukünftigen Tippgebern!

von c-hater (Gast)


Lesenswert?

Reinhard O. schrieb:

> Ich habe noch über ein timing Problem nachgedacht. Bei der for-Schleife
> liegt ja zwischen den einzelnen lcdSendData aufrufen mindestens 1
> Prozessortakt.
> Im Gegensatz zu den lcdSendData Befehlen direkt untereinander.

Wenn das wirklich ein Problem sein sollte (was nicht vollkommen 
auszuschliessen ist), dann taugt der Code nix, den du verwendest.

Ich tippe aber eher auf Fehler, die DU SELBER gemacht hast. Wenn ich als 
c-hater mit Querlesen schon in ganz wenigen Zeilen von dir auf Anhieb 
einen Fehler finde, läßt das nämlich nix Gutes für die Qualität deines 
restlichen Codes ahnen, das sagt mir meine Erfahrung...

> Welchen Code soll ich posten? Von der
> Funktion lcdSendData?

Den ganzen natürlich, abgerüstet auf das Minimum, bei dem der Fehler 
noch auftritt. In compilierbarer Form. Sonst sitzen wir womöglich noch 
bis Sylvester 2030 an deinem Problem...

von Reinhard O. (kristian_1955)


Lesenswert?

c-hater schrieb:
> Den ganzen natürlich, abgerüstet auf das Minimum, bei dem der Fehler
> noch auftritt. In compilierbarer Form.
1
#include <avr/io.h>
2
#include <avr/pgmspace.h>
3
4
#define F_CPU 16000000
5
#define LCD_DATA      PORTC
6
#define LCD_DATA_DDR    DDRC
7
#define LCD_CONTROL      PORTA
8
#define LCD_CONTROL_DDR    DDRA
9
#define DB0        PC0
10
#define DB1        PC1
11
#define DB2        PC2
12
#define DB3        PC3
13
#define DB4        PC4
14
#define DB5        PC5
15
#define DB6         PC6
16
#define DB7         PC7
17
#define light_on_off PA5
18
#define  A0      PA2
19
#define al_RES    PA1
20
#define al_CS1    PA0
21
#define E      PA4
22
#define R_W      PA3
23
#define C86      PA6
24
#define P_S      PA7
25
26
const unsigned char zero[] PROGMEM = {
27
0x7C, 0x8A, 0x92, 0xA2, 0x7C,
28
};
29
30
void wait(uint16_t x)
31
{
32
  while (x>0)
33
  x--;
34
}
35
void lcdSendCmd(uint8_t data)
36
{
37
  LCD_CONTROL &=~(1<<A0);
38
  LCD_CONTROL &=~(1<<R_W);
39
  LCD_DATA=data;
40
  LCD_DATA_DDR=0xFF;
41
  LCD_CONTROL&=~(1<<al_CS1);
42
  LCD_CONTROL|=(1<<E);
43
  __asm volatile ("nop \n nop \n nop \n");
44
  LCD_CONTROL&=~(1<<E);
45
  LCD_CONTROL|=(1<<al_CS1);
46
}
47
48
void lcdSend(uint8_t data)
49
{
50
  LCD_CONTROL &=~(1<<R_W);
51
  LCD_DATA=data;
52
  LCD_DATA_DDR=0xFF;  
53
  LCD_CONTROL&=~(1<<al_CS1);
54
  LCD_CONTROL|=(1<<E);
55
  __asm volatile ("nop \n nop \n nop \n");
56
  LCD_CONTROL&=~(1<<E);
57
  LCD_CONTROL|=(1<<al_CS1);
58
}
59
60
61
void lcd_init( void )
62
{  LCD_DATA_DDR = 255;
63
  LCD_CONTROL_DDR = 255;
64
  wait(60000);
65
  LCD_CONTROL &= ~0b00100000;
66
  LCD_CONTROL |=(1<<al_CS1);
67
  LCD_CONTROL |=(1<<P_S);
68
  LCD_CONTROL |= (1<<C86);
69
  LCD_CONTROL&=~0b00000010;
70
  wait(10000);
71
  LCD_CONTROL|=0b00000010;
72
  wait(10000);
73
  lcdSendCmd(0xE2);
74
  lcdSendCmd(0xA0);
75
  lcdSendCmd(0x192);
76
  lcdSendCmd(0xA2);
77
  lcdSendCmd(0x2F);
78
  lcdSendCmd(0x26);
79
  lcdSendCmd(0xF8);
80
  lcdSendCmd(0x00);
81
  lcdSendCmd(0x81);
82
  lcdSendCmd(0x09);
83
  lcdSendCmd(0xE0);
84
  lcdSendCmd(0xAF);
85
  LCD_CONTROL |= 0b00100000;
86
  wait(100);
87
}
88
void lcdSendData(uint8_t data)
89
{
90
  LCD_CONTROL |=(1<<A0);
91
  lcdSend(data);
92
}
93
94
void lcdSetPos(uint8_t x, uint8_t y)
95
{
96
  lcdSendCmd(0b00010000+(x>>4));
97
  lcdSendCmd(0b00000000+(x&0x0f));
98
  lcdSendCmd(0b10110000+((y)>>3));
99
}
100
101
void lcdClear()
102
{
103
104
  for(uint8_t y=0;y<64;y+=8)
105
  {
106
    lcdSetPos(0,y);
107
    for(uint16_t x=0;x<128;x++)
108
    lcdSendData(0);
109
  }
110
  lcdSetPos(0,0);
111
}
112
113
int main(void)
114
{  
115
  lcd_init();
116
  lcdClear();
117
  
118
  lcdSetPos(0,56);
119
  const unsigned char* Zeiger;
120
  Zeiger = zero;
121
  lcdSendData(*(Zeiger+0));
122
  lcdSendData(*(Zeiger+1));
123
  lcdSendData(*(Zeiger+2));
124
  lcdSendData(*(Zeiger+3));
125
  lcdSendData(*(Zeiger+4));
126
127
  for (uint8_t i = 0; i<5;i++)
128
  {
129
    lcdSendData(*(i+Zeiger));
130
131
  }
132
    while (1) 
133
    {
134
    }
135
}

von c-hater (Gast)


Lesenswert?

Reinhard O. schrieb:

> [code]

Ja, das dürfte ein Timingproblem sein. Der Code nimmt auf zwei Ebenen 
nur unvollständig Rücksicht auf das Timing des Controllers.

Der Buscycle ist mindestens wacklig (hängt vom Takt des AVR ab, bei 
16MHz ist es grenzwertig).

Aber auch bei den Kommandos wird oft keine Rücksicht auf die 
Ausführungszeiten genommen. lcd_Init ist sehr wacklig, lcdSetPos ist bei 
16Mhz definitiv schon kaputt.

Wetten: wenn du das Teil mit 1MHz betreibst, läuft das Programm, genau 
wie du es dir vorstellst? Auch mit der Schleife...

Fazit: wenn du selber LowLevel programmieren willst (was ich nur 
begrüßen kann), musst du lernen, Datenblätter zu lesen und deren 
Restriktionen im Code umzusetzen. Es geht nicht anders.

von Reinhard O. (kristian_1955)


Lesenswert?

c-hater schrieb:
> Fazit: wenn du selber LowLevel programmieren willst (was ich nur
> begrüßen kann), musst du lernen, Datenblätter zu lesen und deren
> Restriktionen im Code umzusetzen. Es geht nicht anders.

Gut, dann weiß ich jetzt woran es liegt! Vielen Dank!
Dann werde ich die fuse bits mal so setzen, dass der atmega2560 
langsamer läuft und schauen ob das Problem dann weg ist.

Wie mache ich sowas im Code? Spontan fällt mir ein flag ein, dass im 
1Mhz takt gesetzt wird.

von c-hater (Gast)


Lesenswert?

Reinhard O. schrieb:

> Wie mache ich sowas im Code?

Das kommt auf die Zeiten an, um die es jeweils geht. Von taktabhängig 
implantierter Zahl von nop() über delay_us() bis hin zu asynchronem 
Warten kann alles nötig werden/sinnvoll sein.

Man muss halt einfach nur zwei Sachen wissen: wie lange muss die 
Verzügerung mindestens sein und wie lange braucht mein eigener Code bei 
gegebenem Takt. Die Differenz (sofern positiv) muß man halt möglichst 
effizent verwarten.

von foobar (Gast)


Lesenswert?

Nur mal so als Tipp: aus diesem Code:
1
void wait(uint16_t x)
2
{
3
  while (x>0)
4
  x--;
5
}
macht der gcc folgendes:
1
wait:
2
        ret

Ich vermute, die automatische "pos"-Erhöhung im Controller klappt nicht 
(aus was für Gründen auch immer, falsche Initialisierung, evtl wegen 
Timingfehler, s.o.) - die zweiten 5 Bytes werden falsch dargestellt. 
Kannst ja mal Schleife und Einzelstatements vertauschen.

von Der Versuch (Gast)


Lesenswert?

Sollte dies nicht :

lcdSendData(*(Zeiger+240+i));   sein

stat

lcdSendData(*(i+Zeiger+240));

Versuch wert...

von foobar (Gast)


Lesenswert?

> Sollte diess nicht
> lcdSendData(*(Zeiger+240+i));
> sein?

Das ist egal, macht das gleiche.  Da das so häufig vorkommt, haben die 
C-Erfinder dem sogar eine extra Syntax gegönnt:

  lcdSendData(Zeiger[240+i]);

;-)

von Reinhard O. (kristian_1955)


Lesenswert?

c-hater schrieb:
> Man muss halt einfach nur zwei Sachen wissen: wie lange muss die
> Verzügerung mindestens sein und wie lange braucht mein eigener Code bei
> gegebenem Takt. Die Differenz (sofern positiv) muß man halt möglichst
> effizent verwarten.

Um die Laufzeit des Codes zu bestimmen, würde ich einen Ausgang am 
Anfang auf high setzen und am Ende low und die Pulsbreite mit dem 
Oszilloskop messen oder? Dann muss ich das mal mit zur Arbeit nehmen. 
Besitze privat leider noch keins.

foobar schrieb:
> Kannst ja mal Schleife und Einzelstatements vertauschen.

Könntest du ein Code Beispiel posten?

P.S. Ich hatte schonmal ein ähnliches Problem als ich Daten über SPI in 
einer for-Schleife übertragen wollte. Da wurden falsche Bytes vom 
Controller ausgegeben sobald ich die untereinander ohne Schleife 
geschickt habe, hat es geklappt. Die Ursache habe ich damals nicht 
ausmachen können.

von c-hater (Gast)


Lesenswert?

Reinhard O. schrieb:

> Um die Laufzeit des Codes zu bestimmen, würde ich einen Ausgang am
> Anfang auf high setzen und am Ende low und die Pulsbreite mit dem
> Oszilloskop messen oder?

Kann man machen.

Ich selber programmiere Assembler und bin auf sowas nicht angewiesen, 
weil ich direkt Takte zählen kann. Dementsprechend habe ich auch einen 
Satz Makros für die Warterei, mit dem halbautomatisch Busywaits 
effizient in den Code eingebaut werden können.

Das ist aber natürlich nicht der Weisheit letzter Schluss. Die 
Entscheidung, ob Busywait oder asynchrones Warten sinnvoller ist, nimmt 
mir diese Halbautomatik nicht ab.

Beim 44780 (und kompatiblen) als Ziel und AVR8 als Quelle steht diese 
Entscheidung leider relativ häufig an, weil in dem weiten Taktbereich, 
in die AVR operieren können, viele der HD44780-Wartezeiten je nach 
Anwendung mal in diese Kiste gehören, mal in die andere...
Nur die Buszyklen sind immer mit Busywaits hinreichend versorgt, da 
lohnt asynchrones Warten nie.

von C-Lover (Gast)


Lesenswert?

c-hater schrieb:
> Ich selber programmiere Assembler und bin auf sowas nicht angewiesen,
> weil ich direkt Takte zählen kann.

Willkommen im 20. Jahrhundert. Moment, C wurde (und andere Hochsprachen) 
wurden ja bereits vor 1975 erfunden.

Also: Willkommen im 21. Jahrhundert, wo wirlich niemand mehr - auch kein 
Profi - ganze Projekte in Assembler programmiert.

Wie kann man so in der Vergangenheit leben? Ich kann mir kaum vorstellen 
- solltest du im professionellen Umfeld agieren - dass du an Aufträge 
kommst.

von Crazy Harry (crazy_h)


Lesenswert?

@c-hater: ich kann verstehen, daß du in Assembler programmierst, denn 
nur so hat man einen Controller wirklich verstanden. Würde ich auch 
gerne können, aber ich nur mal vor 32 Jahren in der Ausbildung 
8085-Assembler programmiert und das schnell wieder vergessen. Schade 
drum.

von foobar (Gast)


Lesenswert?

>> Kannst ja mal Schleife und Einzelstatements vertauschen.
>
> Könntest du ein Code Beispiel posten?
1
int main(void)
2
{  
3
  lcd_init();
4
  lcdClear();
5
  
6
  lcdSetPos(0,56);
7
  const unsigned char *Zeiger;
8
9
  Zeiger = zero;
10
  for (uint8_t i = 0; i<5;i++)
11
    lcdSendData(Zeiger[i]);
12
13
  lcdSendData(Zeiger[0]);
14
  lcdSendData(Zeiger[1]);
15
  lcdSendData(Zeiger[2]);
16
  lcdSendData(Zeiger[3]);
17
  lcdSendData(Zeiger[4]);
18
19
  while (1)
20
  {
21
  }
22
}

Und wenn du das ausprobiert hast, kannst du mal dein wait fixen:
1
void wait(uint16_t x) // roughly 4*x clock cycles
2
{
3
    do
4
        asm volatile("");
5
    while (x--);
6
}

von c-hater (Gast)


Lesenswert?

C-Lover schrieb:

> Ich kann mir kaum vorstellen
> - solltest du im professionellen Umfeld agieren - dass du an Aufträge
> kommst.

Mein Vorteil ist wohl: ich kann nicht NUR C, aber ich kann auch C und 
noch einige Sprachen mehr...

Es hatte schon immer gewisse Vorteile, kein Schmalspur-Vollidiot zu 
sein...

von Reinhard O. (kristian_1955)


Angehängte Dateien:

Lesenswert?

foobar schrieb:
>>> Kannst ja mal Schleife und Einzelstatements vertauschen.

Dann sind die 0 und die fehlerhafte Anzeige vertauscht (s. Foto).

foobar schrieb:
> Und wenn du das ausprobiert hast, kannst du mal dein wait fixen:

Habe die wait Funktion durch deine ersetzt. Die Anzeige ist immer noch 
fehlerhaft.

Leider stehen im Datenblatt keine weiteren Erklärungen zum Timing nur 
Diagramme.

Warum wird sowas nicht als Flußdiagramm dargestellt in dem dann auch die 
Wartezeiten zwischen den einzelnen Befehlen angegeben werden?

von c-hater (Gast)


Lesenswert?

Reinhard O. schrieb:

> Warum wird sowas nicht als Flußdiagramm dargestellt in dem dann auch die
> Wartezeiten zwischen den einzelnen Befehlen angegeben werden?

Üblicherweise gibt es entweder bei jedem einzelnen Befehl eine Anmerkung 
zur benötigten Mindestdauer oder eine Tabelle, die das für alle Befehle 
in einer übersichtlichen Form zusammenfasst.

Man muss halt nur die Kompetenz haben, diese Informationen im DB zu 
finden...

von Reinhard O. (kristian_1955)


Lesenswert?

c-hater schrieb:
> Üblicherweise gibt es entweder bei jedem einzelnen Befehl eine Anmerkung
> zur benötigten Mindestdauer oder eine Tabelle, die das für alle Befehle
> in einer übersichtlichen Form zusammenfasst.

Könntest du mal in das Datenblatt schauen und mir sagen an welcher 
Stelle ich gucken muss?

c-hater schrieb:
> Man muss halt nur die Kompetenz haben, diese Informationen im DB zu
> finden...

Genau aus dem Grund bin ich hier. In der Hoffnung diese Kompetenz mit 
der Hilfe aus dem Forum aufzubauen.

von *** (Gast)


Lesenswert?

Der TO sollte erstmal die Schaltung offen legen, wie er
das Interface  mit dem µC beschaltet hat.
Da gibts nämlich mehrere Möglichkeiten.
Die Block-Diagramme für die passenden Routinen sind
im Datasheet beschrieben.

von S. R. (svenska)


Lesenswert?

Also meine spontane Vermutung liegt hier:

> const unsigned char zero[] PROGMEM = {
> 0x7C, 0x8A, 0x92, 0xA2, 0x7C,
> };

Das Array liegt also in PROGMEM.

> const unsigned char* Zeiger;
> Zeiger = zero;
> lcdSendData(*(Zeiger+0));
> ...

Da kannst du nicht einfach einen normalen Zeiger drauf basteln und 
erwarten, dass du die richtigen Daten bekommst.

Für PROGMEM brauchst du die pgm_read_zeug-Funktionen, sonst bekommst du 
falsche Werte. Alles, was du mit PROGMEM markierst, liegt im Flash und 
damit im Flash-Adressraum, aber Zeiger zeigen grundsätzlich ins RAM und 
damit den RAM-Adressraum. Auf einem AVR sind das getrennte Welten.

von foobar (Gast)


Lesenswert?

> Für PROGMEM brauchst du die pgm_read_zeug-Funktionen, sonst bekommst du
> falsche Werte.

Stimmt.  Und wie ich gerade sehe, kommt noch obendrauf, dass es ein AVR 
mit mehr als 64k Flash ist - viel Spaß mit diesen Missgeburten ...

von Reinhard O. (kristian_1955)


Lesenswert?

*** schrieb:
> Der TO sollte erstmal die Schaltung offen legen, wie er
> das Interface  mit dem µC beschaltet hat. Da gibts nämlich mehrere
> Möglichkeiten.
Hallo,

Ich steuere das LCD über die 6800 Schnittstelle an.
Wenn das also Info nicht reicht, poste ich später den Schaltplan.

von Reinhard O. (kristian_1955)


Lesenswert?

S. R. schrieb:
> Daten bekommst.
>
> Für PROGMEM brauchst du die pgm_read_zeug-Funktionen

Ich teste es später mal mit der Funktion.

Vielen Dank für den Hinweis!

von Reinhard O. (kristian_1955)


Lesenswert?

S. R. schrieb:
> Also meine spontane Vermutung liegt hier:
>
>> const unsigned char zero[] PROGMEM = {
>> 0x7C, 0x8A, 0x92, 0xA2, 0x7C,
>> };
>
> Das Array liegt also in PROGMEM.
>
>> const unsigned char* Zeiger;
>> Zeiger = zero;
>> lcdSendData(*(Zeiger+0));
>> ...
>
> Da kannst du nicht einfach einen normalen Zeiger drauf basteln und
> erwarten, dass du die richtigen Daten bekommst.

Ich habe einfach mal "PROGMEM" weggenommen und jetzt funktioniert es.
Super! Vielen Dank!
Im nächsten Schritt werde ich PROGMEM wieder hinzufügen und gucken ob 
ich es mit den Fubktionen pgm_read_*** zum Laufen bringe.

von S. R. (svenska)


Lesenswert?

Reinhard O. schrieb:
> Im nächsten Schritt werde ich PROGMEM wieder hinzufügen und gucken ob
> ich es mit den Fubktionen pgm_read_*** zum Laufen bringe.

Wenn du in C (nicht C++) unterwegs bist, dann kannst du das Array auch 
einfach mal mit "__flash" markieren. Das hat den gleichen Effekt wie 
PROGMEM, braucht aber keine umständlichen Zugriffsfunktionen.

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.