Forum: Mikrocontroller und Digitale Elektronik LC-Display am Mikrocontroller C-Code frage


von ThoSo (Gast)


Lesenswert?

Hallo,

ich beschäftige mich momentan mit Microkontrollern. Ich habe bereits 
erfolgreich ein 16x2 LC-Display zum laufen gebracht. Allerdings nicht 
selber programmiert sondern die LCD-Bibliothek aus dem Forum hier 
entnommen.

Meine Frage bezieht sich auf die Funktion lcd_out. Ich verstehe nicht 
ganz was dort passiert.

// Sendet eine 4-bit Ausgabeoperation an das LCD
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_enable();
}

Also den 4-Bit Modus habe ich glaube ich soweit verstanden aber wieso 
werden die oberen 4 Bit maskiert?

Die Maske von LCD_PORT wird gelöscht um sicherzugehen, dass die Portbits 
auf 0 gesetzt sind richtig?

Ich habe auch versucht herauszufinden was (4-LCD_DB) bedeutet aber 
erfolglos. Könnte mir jemand die Syntax dahinter erklären?

Grüße

ThoSo

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

ThoSo schrieb:
> Also den 4-Bit Modus habe ich glaube ich soweit verstanden aber wieso
> werden die oberen 4 Bit maskiert?
Weil in diesen 4 Bits die hier nötige Information steckt.

Zum Gesamtverständnis brauchst du also noch diejenige Funktion, die 
lcd_out() zweimal hintereinander aufruft. Dort steht dann vermutlich 
sowas:
1
:
2
lcd_out(data);     // obere 4 Bits übertragen
3
lcd_out(data<<4);  // untere 4 Bits übertragen
4
:

ThoSo schrieb:
> Ich habe auch versucht herauszufinden was (4-LCD_DB) bedeutet aber
> erfolglos. Könnte mir jemand die Syntax dahinter erklären?
Dort seht "4 minus dem Wert, der für LCD_DB definiert ist".
Wenn also irgendwo steht "#define LCD_DB 3", dann steht da ganz einfach 
"(4-3)" und das berechnet sich zu "(1)"...

von Georg G. (df2au)


Lesenswert?

Schau dir die Definition von LCD_DB an.
Die Datenbits liegen nicht notwendigerweise auf D3-D0 des Ports, auch, 
wenn das sinnvoll ist.

von ThoSo (Gast)


Lesenswert?

Hi,

cool so schnell schon eine Antwort.

Ja genau lcd_out wird 2 mal aufgerufen um erst die oberen bits und dann 
die unteren bits zu senden. Ich verstehe dass die oberen 4 bits von data 
diejenigen sind die übertragen werden aber warum müssen diese maskiert 
werden? Ich habe das maskieren in meinem Code einmal auskommentiert und 
es funktioniert genauso?



Lothar M. schrieb:
> Wenn also irgendwo steht "#define LCD_DB 3", dann steht da ganz einfach
> "(4-3)" und das berechnet sich zu "(1)"...

Genau es steht in der .h datei define LCD_DB PD2 bei mir.
Also steht dort (4-2) und das würde sich ja zu (2) berechnen. Es soll 
doch aber nicht nur der Port 2 angesprochen werden sondern Port 2 Port 3 
Port 4 und Port 5 oder nicht?

Grüße
ThoSo

von Besucher (Gast)


Lesenswert?

ThoSo schrieb:
> Ich verstehe dass die oberen 4 bits von data
> diejenigen sind die übertragen werden aber warum müssen diese maskiert
> werden?
Die restlichen vier Bits werden gelöscht damit sie keine 
unbeabsichtigten Seiteneffekte hervorrufen. Ohne die Maskierung würden 
bei der oder-Verknüpfung Bits von data - genauer gesagt eventuelle 
Einserbits an den Positionen 2-3 (also im nicht benötigten Teil von 
data) - in die Bits 0-1 von LCD_PORT mit übernommen werden. Solange die 
zugehörigen Anschlüsse nicht verwendet werden mag das egal sein, aber 
wenn dort irgendwelche weitere Elektronik dranhängt dann bekommt die 
halt diese "unbeabsichtigen Einsen" mit (was sich in Fehlfunktionen 
aller Art äußern kann). Sowas sollte man unbedingt vermeiden, sofern man 
nicht gerade auf stundenlange Fehlersuche steht..

> Genau es steht in der .h datei define LCD_DB PD2 bei mir.
> Also steht dort (4-2) und das würde sich ja zu (2) berechnen. Es soll
> doch aber nicht nur der Port 2 angesprochen werden sondern Port 2 Port 3
> Port 4 und Port 5 oder nicht?
Diese "Ports" korrespondieren ja mit jeweils einem Bit von LCD_PORT. 
Durch das Beschreiben von LCD_PORT können wir somit auch alle vier 
Leitungen gleichzeitig modifizieren.

von Tho S. (thoso805)


Lesenswert?

Vielen Dank ich habe es verstanden :)

Ach und jetzt verstehe ich auch die Logik hinter (4-PD2) im Endeffekt 
steht da ja dann (data>>(4-2)) also (data>>2) und das bedeutet, dass die 
Bits um 2 nach rechts verschoben werden damit am Ende 0b00111100 an 
PORTD übergeben wird. richtig?

Eine letzte Syntax Frage hätte ich noch zu:


void lcd_string( const char *data )
{
    while( *data != '\0' )
        lcd_data( *data++ );
}


was genau bedeutet der Ausdruck in:

while( *data != '\0' ) ?

von Frickelfritze (Gast)


Lesenswert?

Tho S. schrieb:
> was genau bedeutet der Ausdruck in:
>
> while( *data != '\0' ) ?

*data zeigt auf das nächste auszugebende Zeichen, wenn
dieses ungleich Null ist dann soll die Schleife wieder
einen Durchgang machen.

Null ist die Konvention in C zum Abschliessen eines Strings.
Anstatt '\0' kann man auch einfach 0 schreiben.

von Karl M. (Gast)


Lesenswert?

Tho S. schrieb:
> Eine letzte Syntax Frage hätte ich noch zu:
>
> void lcd_string( const char *data )
> {
>     while( *data != '\0' )
>         lcd_data( *data++ );
> }
>
> was genau bedeutet der Ausdruck in:
>
> while( *data != '\0' ) ?
Da fehlen Dir ja noch ca. 90% C Basiswissen.

Solange das Zeichen im Puffer data nicht 0 ist, gebe es über lcd_data() 
aus.
Wir nutzen in C, Null-terminierte Zeichenketten als String.

von Tho S. (thoso805)


Lesenswert?

Ist jedes Ende eines Strings also mit '/0'
gekennzeichnet?

Wie unterscheidet sich dann die 0 von keinem Zeichen?

Wo geschieht eigentlich die Umwandlung eines Zeichens in den binären 
Code?

von Frickelfritze (Gast)


Lesenswert?

Tho S. schrieb:
> Wo geschieht eigentlich die Umwandlung eines Zeichens in den binären
> Code?

Kauf dir ein Buch und lerne die Sprache C.

von Besucher (Gast)


Lesenswert?

Tho S. schrieb:
> Ist jedes Ende eines Strings also mit '/0'
> gekennzeichnet?
Die '\0' als Kennzeichnung für das Ende eines Strings ist eine allgemein 
akzeptierte Konvention (die auch in den Standard-Bibliotheken verwendet 
wird). Mann kann aber freilich auch andere Systeme verwenden, z.B. auf 
ein Terminalsymbol verzichten und stattdessen die Länge des Strings 
separat speichern.

> Wie unterscheidet sich dann die 0 von keinem Zeichen?
Pardon, was soll denn "kein Zeichen" sein? Ein Leerzeichen, ein nicht 
druckbares Zeichen, ein Loch im Speicher? Einen leeren String 
kennzeichnet man gewöhnlich dadurch das er nur ein '\0'-Zeichen enthält 
(dieses eine Zeichen, und mehr nicht). Ein nicht existierender String 
wird hingegen durch einen Null-Pointer repräsentiert. Aber auch das ist 
alles nur Konvention.

> Wo geschieht eigentlich die Umwandlung eines Zeichens in den binären
> Code?
Puh, das haben die Jungs und Mädels von der ISO irgendwann mal gemacht. 
Aber frag mich nicht mehr wo das war...

von Nop (Gast)


Lesenswert?

Tho S. schrieb:
> Wo geschieht eigentlich die Umwandlung eines Zeichens in den binären
> Code?

Normalerweise einfach mittels der ASCII-Tabelle. Außer Du willst auch 
noch Unicode, dann wird es ein bißchen aufwendiger.

von Tho S. (thoso805)


Lesenswert?

Vielen Dank für die Erklärungen.
Mit kein Zeichen meine ich z.B bei dem String ("abc")... nach dem c 
kommt ja nichts mehr. D.h der Pointer würde nach dem Zeichen"c",   '/0' 
sehen und weiß das ist das Ende der Zeichenkette.

Nop schrieb:
> Normalerweise einfach mittels der ASCII-Tabelle.

macht der Datentyp uint8_t aus einem Zeichen wie "a" einen Binärcode?
Bsp:

uint8_T data= a

Mir stellt sich diese Frage denn die Funktionen kriegen ja die Variable 
data übergeben.

von Nop (Gast)


Lesenswert?

Tho S. schrieb:
> uint8_T data= a

nein, das geht so nicht, weil a in C als Variable aufgefaßt wird. 
Wirklich, Du solltest ein Buch durcharbeiten, um C zu lernen.

Das ASCII-Zeichen a ist in C ein 'a' und vom Datentyp char. Dann aber 
IST das schon der Binärwert, was anderes geht ja in einem Computer 
nicht.
1
char my_char;
2
uint8_t my_byte;
3
4
my_char = 'a';
5
my_byte = (uint8_t) my_char; /*contains now the numerical value 97*/
6
/*or as well:*/
7
my_byte = (uint8_t) 'a';

Nur, wie schon angedeutet, das ist das Simpel-Beispiel für plain ASCII. 
Sobald man mit Umlauten oder so arbeitet, muß man entweder über 
ASCII-Codepages gehen, oder, was zeitgemäßer ist, z.B. UTF-8 verwenden.

Obiges Code-Beispiel krankt auch daran, daß der Datentyp "char" je nach 
Plattform manchmal vorzeichenbehaftet ist und manchmal nicht. Ist also 
echt nur als Beispiel zu sehen, das hier geht, weil der ASCII-Wert von 
'a' unter 127 ist.

von Tho S. (thoso805)


Lesenswert?

Ich werde defintiv noch so ein Buch durcharbeiten. Danke für eure Mühe.

in den Funktionen, die den Befehl zum schreiben ausführen, wird ja immer 
uint8_t data als Übergabeparameter übergeben.


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_enable();
}

void lcd_data( uint8_t data )
{
    LCD_PORT |= (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 );
}

Der Befehl lcd_data("a") übergibt damit doch ein "a" an die Funktionen 
welches in der Variable data als Datentyp uint8_t gespeichert wurde oder 
nicht?
Mich verwirrt das weil z.B bei einer printf Funktion der Datentyp am 
Ende noch mitgegeben wird.

von Nop (Gast)


Lesenswert?

Tho S. schrieb:
> Der Befehl lcd_data("a") übergibt damit doch ein "a" an die Funktionen
> welches in der Variable data als Datentyp uint8_t gespeichert wurde oder
> nicht?

lcd_data("a") geht da gar nicht, weil "a" in C ein String ist. Das ist 
ein array of char mit zwei Einträgen, dessen erster Wert ein 'a' und 
dessen zweiter eine binäre 0 ist ('\0'). Völlig anderer Datentyp.

Das geht hier mit 'a' trotzdem, weil es an der Stelle egal ist, ob das 
byte nun vorzeichenbehaftet ist oder nicht, denn worauf es ankommt, sind 
die Bits, die auf Datenleitungen gemappt werden. Was sie bedeuten 
(vorzeichenbehafteter char, uint8_t oder sonstwas), ist egal dafür, daß 
die richtigen Datenleitungen gezogen werden müssen.

Übrigens geht auch dieses LCD-Beispiel nur für plain ASCII. Das macht 
aber dann nichts, wenn das LC-Display selber auf dieselbe ASCII-Codepage 
eingestellt ist und ohnehin kein UTF-8 kann. Oder wenn es keine Umlaute 
nutzt bzw. gar nicht erst Umlaute unterstützt.

von Ralph S. (jjflash)


Lesenswert?

Tho S. schrieb:
> Mit kein Zeichen meine ich z.B bei dem String ("abc")... nach dem c
> kommt ja nichts mehr. D.h der Pointer würde nach dem Zeichen"c",   '/0'
> sehen und weiß das ist das Ende der Zeichenkette.

Der String "abc" belegt im Speicher 4 (in Worten vier) Bytes:

97 98 99 00

97 ist der Ascii Code für 'a'
98 ist der Ascii Code für 'b'
99 ist der Ascii Code für 'c'

Demnach ist ein String in C immer genau ein Byte länger als es 
Buchstaben sind.

Ein solcher String wird auch als AsciiZ (für Ascii-Zero) bezeichnet.

Ascii steht für "American standard code for information interchange", 
kannst du auch gerne mal googeln.

In diesem Code sind 127 Zeichen definiert, die seit Urzeiten (genau seit 
1963 !!! ).

Seither sind diese Zeichen auf jedem Computer in Gebrauch und dadurch 
können Daten von noch so unterschiedlichen Rechnern ausgetauscht werden.

von M. K. (sylaina)


Lesenswert?

Frickelfritze schrieb:
> Null ist die Konvention in C zum Abschliessen eines Strings.
> Anstatt '\0' kann man auch einfach 0 schreiben.

Und in diesem Fall kann man auch schreiben
1
while( *data ) {
2
...

denn eine while-Schleife wird solange ausgeführt, solange ihr Argument 
wahr ist. In C ist jeder Ausdruck wahr, der nicht gleich null ist. Daher 
ist ein
1
while( *data != '\0' ) {
2
...

gleich einem
1
while( *data != 0 ) {
2
...

gleich einem
1
while( *data ) {
2
...

solange *data auch ein Zeiger auf einen korrekten String ist.
1
while( *data != '\0' ) {
2
...

schreibt man idR nur, um den Code lesbarer zu gestalten und auch na fünf 
Jahren sofort zu erkennen, wie lange die Schleife lebt. Der Compiler 
macht aus allen oben genannten Varianten denselben Code (sofern er, also 
der Compiler, auch gut ist).

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.