Forum: Compiler & IDEs Bitmanipulation + Maskieren


von Richard B. (rbrose)


Lesenswert?

Hallo,

ich habe wiedermal ein kleines Verständnis Problem.
Was wird hier im genau Code gemacht?
1
   unsigned char temp2 = temp1;
2
3
   temp1 = temp1 >> 4;              // oberes Nibble holen
4
   temp1 = temp1 & 0x0F;            // maskieren
5
   LCD_PORT &= 0xF0;
6
   LCD_PORT |= temp1;               // setzen
7
8
...
9
10
   temp2 = temp2 & 0x0F;            // unteres Nibble holen und maskieren
11
   LCD_PORT &= 0xF0;
12
   LCD_PORT |= temp2;               // setzen

wenn temp1 = 10101111 wäre ... dann ist es nach >> 4 = 00001010 
Richtig?
Wieso wird maskiert?

Und was macht:
1
   temp2 = temp2 & 0x0F;            // unteres Nibble holen und maskieren
2
   LCD_PORT &= 0xF0;
genau?


Danke

von risu (Gast)


Lesenswert?

Hallo,

> wenn temp1 = 10101111 wäre ... dann ist es nach >> 4 = 00001010
> Richtig?
Ja.

> Wieso wird maskiert?
Das ist tatsächlich redundant; durch ">>4" wird das höherwertige Nibble 
schon 0.

> Und was macht:
> temp2 = temp2 & 0x0F;            // unteres Nibble
>                                         holen und maskieren
>  LCD_PORT &= 0xF0;
Na, oben hattest Du es doch schon verstanden: Das obere Nibble wird 
durch das "&"-Skalpell wegoperiert, also auf 0 gesetzt. Das SFR, das als 
LCD_PORT bezeichnet wird, wird gelesen, das untere Nibble des gelesenen 
Werts wird auf 0 gesetzt, und dann wird der neue Wert zurück in das SFR 
geschrieben.

Gruß
  risu

von Peter Diener (Gast)


Lesenswert?

Hallo Richard,
1
temp1 = temp1 >> 4;              // oberes Nibble holen
2
temp1 = temp1 & 0x0F;            // maskieren

Die zweite Operation ist nur überflüssig, wenn temp1 vom typ char (oder 
anderem 8bit Typ) ist.
Dann hat die Bitshift-Operation zur Folge, dass die oberen 4 Bit mit 
Null aufgefüllt werden und der Inhalt von temp1 um 4 Bit nach rechts 
geschoben wird und Bit 0 bis Bit 3 des alten temp1 entsprechend nicht 
mehr existieren.

Aber (du hast nicht angegeben, von welchem typ temp 1 ist) wenn temp1 
ein int wäre, wäre folgendes denkbar:

Angenommen temp1 ist vorher 0b1110 0110 1101 1011
Dann ist es nach dem shift  0b0000 1110 0110 1101
Dann würde es durchaus Sinn machen, mit 0x0F == 0b0000 0000 0000 1111 zu 
undieren.
Das Ergebnis wäre dann 0b0000 0000 0000 1101.

Viele Grüße,

Peter

von risu (Gast)


Lesenswert?

Hallo Peter,

> ...du hast nicht angegeben, von welchem typ temp 1 ist ...

Richard schrieb als erste Zeile:
>   unsigned char temp2 = temp1;

Meine Erklärung bezog sich spezifisch darauf.

Viele Grüße

Fred

von risu (Gast)


Lesenswert?

Hallo Peter,

natürlich stimmt was Du sagst, und ich hätte nicht aus dem fehlenden 
"typecast" bei "unsigned char temp2=temp1;" schließen dürfen, dass 
"temp1" auch "unsigned char" ist.

Gruß

Fred

von Peter Diener (Gast)


Lesenswert?

Hallo nochmal,

ich hab noch eine kleine Ergänzung zum Programmierstil:
1
   temp2 = temp2 & 0x0F;            // unteres Nibble holen und maskieren
2
   LCD_PORT &= 0xF0;
3
   LCD_PORT |= temp2;               // setzen

Das sieht aus, wie eine sogenannte Read-Modify-Write Anweisung.
Dabei möchte man bestimmte Bits einer Variable ändern, die anderen aber 
unverändert lassen.

In deinem Beispiel möchtest du nur Bit 0 bis Bit 3 von temp betrachten
und diese in LCD_PORT an die Stellen von Bit 0 bis Bit 3 schreiben, 
wobei die anderen Bits von LCD_PORT so bleiben sollen, wie sie vorher 
waren.

Ich nehme an, LCD_PORT ist ein Portregister, schreibt also direkt auf 
die Hardwareports.

Dein Programmierstil ist hier suboptimal, denn die zweite Zeile
1
   LCD_PORT &= 0xF0;
schreibt auf die Bits 0 bis 3 von LCD_PORT tatsächlich null raus, wenn 
auch nur für einen kurzen Moment, bis mit
1
   LCD_PORT |= temp2;               // setzen
diese Bits mit etwas sinnvollem gefüllt werden.

Das ist ein unnötiger Peripheriezugriff, der Port könnte auch gleich so 
gesetzt werden, wie er sein soll. Eventuell kannst du duch das 
(unnötige) hochfrequente Schalten zwischen Zeile 2 und Zeile 3 sogar 
mehr Störaussendung deiner Schaltung erwarten. Das ist jedoch nur 
relevant, wenn dadurch der Rest deiner Schaltung störempfindlich ist, 
oder das Gerät verkauft werden soll und dafür eine CE-Prüfung notwendig 
ist.

Besser wäre folgende Vorgehensweise:
1
   temp2 &= 0x0F;             //Bit 4 bis 7 von temp2 sind jetzt 0
2
   temp2 |= LCD_PORT & 0xF0;  //Bit 4 bis 7 von LCD_PORT werden in temp2 zusätzlich mitgespeichert
3
   LCD_PORT = temp2;          //und jetzt alles auf ein Mal auf die Hardware schreiben
4
5
//wenn temp2 danach noch mit Bit 4 bis 7 == 0 gebraucht wird, noch folgendes:
6
7
   temp2 &= 0x0F;

So wird nur einmal auf LCD_PORT geschrieben.

Viele Grüße,

Peter

von Richard B. (rbrose)


Lesenswert?

Alles klar. Hab's jetzt verstanden. Danke!

von Peter Diener (Gast)


Lesenswert?

Hallo,

hab mir das Tutorial angesehen, die Routinen sind einfach nicht sauber 
ausprogrammiert. Sie sollten von der Logik her schon funktionieren, aber 
wie ich bereits geschrieben habe, entstehen durch die mehrfachen 
Read-Modify-Write Zugriffe auf den Port - ja es ist ein port, steht in 
dem entsprechenden Headerfile - Hochfrequente Flanken, die nicht jedes 
Display mag. Man muss beachten, dass die AVRs viel schneller sind, als 
die meisten Paralleldisplays betrieben werden dürfen, da ist es fehl am 
Platz gewünschten Bits der Reihe nach in verschiedenen Anweisungen auf 
den Port zu odern.

> Aber beim unteren Nibble
> LCD_PORT |= temp2;  ist es dann 00001010 ! Es muss aber 00001000 sein.

Das setzt das untere Nibble auf Null:
   LCD_PORT &= 0xF0;

Das hast du wohl übersehen. Die Routine funktioniert rein mathematisch 
schon.

Probier es doch mal so:
1
void lcd_command(unsigned char command)
2
{
3
   unsigned char command_shadow = command;
4
 
5
   command = command >> 4;           // oberes Nibble holen
6
   LCD_PORT = command;
7
   _delay_us(100);
8
   lcd_enable();
9
 
10
   command_shadow &= 0x0F;           // unteres Nibble holen und maskieren
11
   LCD_PORT = command_shadow;        // setzen
12
   _delay_us(100);
13
   lcd_enable();
14
   
15
   _delay_us(42);
16
}

Schließ dabei nur das Display an den Port an, ich schreibe hier immer 
uaf den ganzen Port. Das R/W und C/D Flag sind hier implizit drin.

Wenn du schon unbedingt den 4-Bit modus verwenden musst, ließ doch mal 
das Manual von deinem Display oder einem kompatiblen von EA 
(www.lcd-module.de). Da stehen Hinweise zum Timing und auch die 
entsprechenden Bits, die für die einzelnen Kommandos gesetzt werden 
müssen. So ein Displaytreiber ist gleich selber geschrieben, ich hätte 
auch einen, der ist aber für 8-Bit Anschluss.

Im Prinzip sind das nicht viele Kommandos, die man wirklich braucht.
Init();
SetCursor(x,y);
Cursor_sichtbar(bool);
WriteCharacter(char);

Das wars eigentlich. Die kann man einfach aus dem manual abschreiben, 
und dann funktionierts auch.

Grüße,

Peter

von Sebastian C. (basti79)


Lesenswert?

Hallo zusammen,
1
temp1 = temp1 >> 4;              // oberes Nibble holen
2
temp1 = temp1 & 0x0F;            // maskieren

Mit dem rechts Schieben ist es nicht ganz so intuitiv wie mit dem links 
Schieben. Während beim Schieben nach links (<<) jeweils von rechts mit 0 
aufgefüllt wird, hängt die beim rechts Schieben (>>) vom Typ der 
Variablen ab. Bei unsigned Typen wird ebenfalls eine 0 eingeschoben, bei 
signed wird jedoch das MSB dupliziert. Wenn wir hier temp1 ebenfalls als 
unsigned annehmen, macht es weiterhin keinen Unterschied, man sollte 
jedoch trotzdem dran denken ;)

Greets
  Basti.

von Peter D. (peda)


Lesenswert?

Peter Diener wrote:
> hab mir das Tutorial angesehen, die Routinen sind einfach nicht sauber
> ausprogrammiert.

Das stimmt.
Wenn z.B. die 2 freien Pins des Ports in Interrupts verwendet werden, 
gibts das schönste Chaos.

Am besten ist es daher, wenn man die atomaren Pin-setz/clear Befehle 
nimmt.
Dann ist man auch nicht mehr an einen Port oder an eine bestimmte 
Pinreihenfolge gebunden.

Es gehen völlig beliebige 6 Pins und wesentlich lesbarer wird die Sache 
obendrein:

Beitrag "Re: LCD nicht nur für einen Port in C"


Peter

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.