Forum: Compiler & IDEs Bit Manipulation mit Shift Right


von Tobi (Gast)


Lesenswert?

Hallo,

ich bin neu in der AVR Programmierung und versuche mir gerade klar zu 
machen, wie das ganze funktioniert. Bei der Implementierung eines LCD 
mit dem Code von dieser Seite bin ich auf folgende Zeile gestoßen:
1
PORTD &= ~(0xF0>>(4-PD0));

Kann mir bitte jemand erklären, was hier abgeht?
Ich verstehe, dass es hier um die Schaltung der Pins am Port D geht.
Auch die Operatoren sind mir alle bekannt.
Was ich nicht verstehe ist (4-PD0)...
Kann man mit den Pins bitweise rechnen und hier steht einfach nur
1
PORTD &= ~(0b11110000>>(0b00000100-0b00000001))
also
1
PORTD &= ~(0b11110000>>3)
???
Wenn ja, warum macht man sowas?

Ich danke Euch!
Tobias

von (prx) A. K. (prx)


Lesenswert?

PD0 dürfte nicht 1 sein, sondern 0.

von Tobi (Gast)


Lesenswert?

Sorry, sicherlich eine dumme Frage aber bezeichnet PD0 die Adresse im 
Port oder den Wert?

von (prx) A. K. (prx)


Lesenswert?

Armel benennt so die Portbits. PD0 ist Bit 0, zum Maske gehts mit 
1<<PD0.

von Mike (Gast)


Lesenswert?

Tobi schrieb:
> Sorry, sicherlich eine dumme Frage aber bezeichnet PD0 die Adresse im
> Port oder den Wert?

PD0 ist als 0 definiert und ist die Nummer des Bits. Bei einem 8 Bit 
breiten Port sind mögliche Werte alse PD0 .. PD7 entsprechend 0..7
Das kannst du direkt in der Headerdatei zu deinem Prozessor nachlesen.

von Karl H. (kbuchegg)


Lesenswert?

Tobi schrieb:
> Hallo,
>
> ich bin neu in der AVR Programmierung und versuche mir gerade klar zu
> machen, wie das ganze funktioniert. Bei der Implementierung eines LCD
> mit dem Code von dieser Seite bin ich auf folgende Zeile gestoßen:
>
>
1
PORTD &= ~(0xF0>>(4-PD0));

Wo hast du das her.
Du steht im Original sicher nicht PD0. Zumindest nicht in der 
Tutorialversion.

Was dort aber steht ist
1
#define DATA_PIN_0  PD0
2
3
...
4
5
   PORTD &= ~( 0xF0 >> (4 - DATA_PIN_0) )
(schlag mich jetzt nicht, wenn da anstelle von DATA_PIN_0 ein anderer 
Makro-Name benutzt wird. Auch mit einem anderen Namen geht es um 
dasselbe)

Und nein. Diesen Teil des Ausdrucks betrachtest du am besten nicht 
bitweise. Das ist eine ganz normale Subtraktion. 4 minus irgendwas.

Worum gehts denn? Was ist das Ziel.

Das Ziel ist es doch, den LCD Routinen die Vorgabe zu machen, an welchen 
4 Datenpins eines Ports die 4 Datenleitungen zum LCD hängen.
Das kann sein, wenn DATA_PIN_0 auf 0 definiert wird
1
#define DATA_PIN_0  0
2
3
  7  6  5  4  3  2  1  0
4
 +--+--+--+--+--+--+--+--+
5
 |  |  |  |  |D3|D2|D1|D0|     Dx sind die Datenleitungen zum LCD
6
 +--+--+--+--+--+--+--+--+
7
                       ^
8
                       | DATA_PIN_0 sagt uns, das D0 am Bit 0 vom Port zu finden ist

das kann aber auch sein, wenn DATA_PIN_0 auf zb 3 definiert ist
1
#define DATA_PIN_0  3
2
3
  7  6  5  4  3  2  1  0
4
 +--+--+--+--+--+--+--+--+
5
 |  |D3|D2|D1|D0|  |  |  |
6
 +--+--+--+--+--+--+--+--+
7
              ^
8
              | DATA_PIN_0 sagt uns, das D0 am Bit 3 vom Port zu finden ist

Beachte: größer als 4 kann DATA_PIN_0 nicht sein. Denn dann würden die 4 
Datenleitungen zum LCD schon links aus dem Portregister raushängen.


Klar soweit?

Jetzt ist es aber auch so, dass die entsprechenden 4 Portbits auch mal 
auf 0 gesetzt werden müssen.
Wie machst du das in Form eines Ausdrucks, wenn du im Vorfeld nicht 
weißt, an welchen der 4 Portbits denn die 4 Datenleitungen nun wirklich 
hängen?

Wenn wir diese Zahl wüssten und sich die auch nicht ändern kann, dann 
wäre das einfach.
Sind die untersten 4 Datenbits betroffen (wenn also D0 immer am Portpin 
0 liegen würde), dann würdest du einfach schreiben
1
   PORTD &= ~ ( 0x0F );
Wenn die 4 Datenleitungen mit dem D0 am Bit 1 beginnen, dann könnte man 
das zb so schreiben.
1
   PORTD &= ~ ( 0x0F << 1 );
Liegt D0 am Portbit 2, dann könnte man das zb so formulieren
1
   PORTD &= ~ ( 0x0F << 2 );

da zeichnet sich ein Muster ab! Das Kochrezept lautet: nimm die 0x0F 
(0x0F deswegen, weil da 4 1 Bits drinnen liegen, die die 4 
Datenleitungen 'ansprechen'). Und dann wird dieses Muster um genau so 
viele Stellen nach links verschoben, so dass die rechteste 1 des Muster 
genau dort zu liegen kommt, wo auch D0 im Byte des Portregisters liegt.

Würde man also formulieren
1
  PORTD &= ~( 0x0F << DATA_PIN_0 );
dann entsteht mittels
1
#define DATA_PIN_0   0
bzw.
1
#define DATA_PIN_0   3
jeweils genau das richtige Muster an 1 Bits, so dass durch die Negierung 
und die Verundung genau diese Bits am Port definitiv auf 0 gesetzt 
werden.

Jetzt hat aber das Originalautor aus irgendeinem Grund die Sache 'von 
hinten' aufgezäumt. Warum auch immer.

Er legt seine ersten 4 Stück 1 Bits nicht nach rechts in den Port
1
  7  6  5  4  3  2  1  0
2
 +--+--+--+--+--+--+--+--+
3
 |0 |0 |0 |0 |1 |1 |1 |1 |
4
 +--+--+--+--+--+--+--+--+
sondern er legt diese nach links in den Port
1
  7  6  5  4  3  2  1  0
2
 +--+--+--+--+--+--+--+--+
3
 |1 |1 |1 |1 |0 |0 |0 |0 |
4
 +--+--+--+--+--+--+--+--+
Daher steht bei ihm 0xF0 im Code und nicht wie hier bei mir 0x0F

Um die Problemstellung, dass diese 4 Stück 1-Bits an die Bitposition 0 
kommen müssen, wenn vereinbart ist, dass D0 am Bit 0 zu liegen kommen 
soll, hat sich nichts geändert.
D.h. die Fragestellung lautet in diesem Fall:
Wenn ich das rechteste dieser 4 Stück 1 Bits an die Bitposition 0 haben 
will und ich dazu die Definition
1
#define DATA_PIN_0   0
habe, wie oft muss ich dann dieses 0xF0 nach rechts verschieben, damit 
genau dieses Ergebnis
1
  7  6  5  4  3  2  1  0
2
 +--+--+--+--+--+--+--+--+
3
 |0 |0 |0 |0 |1 |1 |1 |1 |
4
 +--+--+--+--+--+--+--+--+
rauskommt?

Na das ist einfach. Ich musste 4 mal nach rechts verschieben, damit aus 
der Ausgangssituation dieses Ergebnis entsteht.

Wenn ich wieder die Ausgangssituation
1
  7  6  5  4  3  2  1  0
2
 +--+--+--+--+--+--+--+--+
3
 |1 |1 |1 |1 |0 |0 |0 |0 |
4
 +--+--+--+--+--+--+--+--+
habe und mein Ergebnis soll wegen
1
#define DATA_PIN_0   3
das hier sein
1
  7  6  5  4  3  2  1  0
2
 +--+--+--+--+--+--+--+--+
3
 |0 |1 |1 |1 |1 |0 |0 |0 |
4
 +--+--+--+--+--+--+--+--+
wie oft muss ich dann nach rechts schieben?

Simpel: sieh einfach hin. Offenbar musstest du 1 mal nach rechts 
verschieben.

Es ist also so, dass wenn ich von 0xF0 ausgehe und ich das rechteste 
1-Bit auf der Position x haben will, dann muss ich genau (4-x) mal nach 
rechts verschieben.

Genau das ist die Erklärung für den Teil
1
#define DATA_PIN_0  PD0
2
3
...
4
5
   PORTD &= ~( 0xF0 >> (4 - DATA_PIN_0) )
6
7
               ***********************
den du im Code siehst.

Das ist also nichts irgendwie trickreiches auf Bit-Ebene. Sondern es 
geht einfach nur darum, eine Abfolge von 1 Bits abhängig von einer 
Zahlen-Definition an genau die Stelle in einem Byte zu bugsieren, an die 
man sie haben will, wenn man die Position des rechtesten 1 Bits davon 
vorgeben können will.

> Wenn ja, warum macht man sowas?

Weil man allgemeine Funktionen haben will.
Je nachdem an welche der 4 aufeinanderfolgenden Portleitungen du die 4 
Datenleitungen deines LCD anschliesst, willst du lediglich
1
#define DATA_PIN_0    ....
auf den jeweils richtigen Wert setzen und den Rest soll der Compiler 
erledigen und den Code für dich anpassen.
Und genau deswegen hab ich am Anfang auch gefragt, wo du das her hast. 
Denn wenn man den entsprechenden Zahlenwert kennt und der sich auch 
nicht ändert und man auch nicht die Notwendigkeit hat, das leicht 
änderbar machen zu müssen, wird man diesen 'Aufwand' nicht treiben. Es 
sei denn natürlich, man hat bei den allgemeinen Funktionen geklaut, die 
für sich adaptiert und nicht verstanden, warum das im allgemeinen Fall 
genau so geschrieben wurde.

: Bearbeitet durch User
von Tobi (Gast)


Lesenswert?

Karl Heinz...

You are the man!

Ich danke Dir vielmals. Deine ausführliche Erklärung hat meinem 
Verständnis, besonders in Bezug auf die verschiedenen Betrachtungsweisen 
bei der Programmierung, sehr geholfen!
Ich bin bisher nur die Programmierung in "Hochsprachen" gewohnt und muss 
mir erst einen kleinen Sack mit Erfahrungen und Handwerkzeug für die 
Arbeit mit MC ansammeln. Die von Dir erklärte Maskierung und 
Bitmanipulation gehört von nun an fest zum Repertoire!

Gruß,
Tobias

von Joe F. (easylife)


Lesenswert?

Trotzdem wäre

PORTD &= ~(0x0F << DATA_PIN_0);

deutlich einfacher zu verstehen, und macht - wie Karl Heinz ja schon 
erläuterte - genau das gleiche.

von Markus (Gast)


Lesenswert?

Karl Heinz und seine Erklärungen sind einfach nur genial. Wie viel Zeit 
ich wohl schon damit verbracht habe diese langen mit viel 
Hintergrundwissen garniertern Erklärungen ohne jegliche Arroganz zu 
studieren...Fantastisch in einem Internet, in dem mir 90% einfach nur 
hingerotzt vorkommen, wo z.B. Spiegel Online seine Artikel scheinbar 
nicht mal mehr ein einziges mal zur Kontrolle durchlesen lässt...Aber 
ich schweife ab. Vielen Dank Karl-Heinz!

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.