Forum: Mikrocontroller und Digitale Elektronik Schiebeoperatoren


von Tobias D. (tobid)


Lesenswert?

Hallo zusammen.
Ich wollte mir ein Lauflicht programmieren, was von links nach rechts 
und von rechts nach links läuft. Allerdings bin ich auf ein etwas 
merkwürdiges Problem gestoßen.

Mein Lauflicht soll am Port 0 ausgegeben werden. Für das Lauflicht von 
rechts nach links sieht das ganze dann so aus:
1
  {   
2
    x=0x01;
3
4
    for(z=1;z<9;z++)
5
    {
6
      P0=x;
7
      x=x<<1;
8
      init();        // Timer für Pause aufrufen
9
    }
10
  }

Das funktioniert soweit auch noch einwandfrei.
Dann müsste das ganze von links nach rechts ja wie folgt aussehen:
1
  {  
2
    x=0x80;
3
4
    for(z=1;z<9;z++)
5
    {
6
      P0=x;
7
      x=x>>1;
8
      init();        // Timer für Pause aufrufen
9
    }
10
  }

Komischer Weise funktioniert dies aber nicht. Die "1" wird zwar um eine 
Stelle nach rechts weitergeschoben, aber die vorherige Stelle wird nicht 
mehr mit einer 0 aufgefüllt, sondern bleibt 1 . Ich habe also eine 
"auffüllendes Licht", sodass am Ende alle LED's an sind.

Kann mir jemand vielleicht sagen wo mein Fehler ist ?

MfG

von Yalu X. (yalu) (Moderator)


Lesenswert?

x ist wohl eine vorzeichenbehaftete 8-Bit-Variable. Nimm stattdessen
unsigned char bzw. uint8_t, dann sollte es funktionieren.

von Tobias D. (tobid)


Lesenswert?

Wow super! Es funktioniert.
Allerdings habe ich den Zusammenhang jetzt nicht wirklich verstanden. 
Könntest du mir den vielleicht etwas näher erklären? Wäre super !

von Klaus W. (mfgkw)


Lesenswert?

Ich bin zwar nicht "du:", aber:

Bei vorzeichenbehafteten Zahlen ist das höchste Bit gesetzt, wenn
die Zahl negativ ist.

Beispiel int8_t:
1
 dezimal  heaxdezimal  binär
2
    127   0x7F         01111111
3
    126   0x7E         01111110
4
    125   0x7D         01111101
5
     ...
6
7
    2     0x02         00000010
8
    1     0x01         00000001
9
    0     0x00         00000000
10
   -1     0xFF         11111111
11
   -2     0xFE         11111110
12
   -3     0xFD         11111101
13
    ...
14
   -126   0x82         10000010
15
   -127   0x81         10000001
16
   -128   0x80         10000000

Würde eine negative Zahl (gesetztes oberstes Bit einfach nur nach
rechts geschoben und eine 0 links eingefügt, dann würde sie dadurch
ja positiv werden (oberstes Bit jetzt 0).
Das ist nicht erwünscht, weil man das Rechtsschieben auch gerne als
schnelle Division durch 2 nimmt.

Deshalb wird eine 1 statt der 0 links eingefügt.

(Analog bei größeren Typen.)

Das heißt auch "arithmetisches Shiften".

Bei unsigned ist dagegen das oberste ja kein Vorzeichenbit,
also entfällt diese Begründung und es wird mit Nullen aufgefüllt.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Eigentlich ist der Rechts-Shift einer negativen Zahl im C-Standard gar
nicht genau festgelegt, er ist "implementation-defined". Möchte man
portablen Code schreiben, sollte man nur positve Zahlen nach rechts
schieben.

Die meisten C-Compiler übersetzen x>>n in eine ASR-Instruktion
(arithmetic shift right) des Prozessors, wenn x vorzeichenbehaftet ist,
und in eine LSR-Instruktion (logic shift right), wenn x vorzeichenlos,
also unsigned ist. Das ergibt dann das von Klaus beschriebene Verhalten.

von Joachim (Gast)


Lesenswert?

>Die meisten C-Compiler übersetzen x>>n in eine ASR-Instruktion

Hm. gibt's eigentlich 'rotieren' als C-Syntax? Glaube nicht, oder? Hab's 
bis jetzt noch nicht gebraucht...

von Rolf Magnus (Gast)


Lesenswert?

Joachim schrieb:
>>Die meisten C-Compiler übersetzen x>>n in eine ASR-Instruktion
>
> Hm. gibt's eigentlich 'rotieren' als C-Syntax?

Nein. Das wäre auch schwierig, denn das Rotieren bezieht üblicherweise 
das Carry-Flag mit ein, das es in C nicht gibt.

von Tobias D. (tobid)


Lesenswert?

Ah, alles klar ;)
Wunderbar. Danke an euch beide! Mir war nicht bewusst, dass bei einer 
negativen Zahl das oberste Bit gesetzt ist.

von Thorsten S. (whitejack)


Lesenswert?

..nur so als Hintergrundinfo, spiel mal mit /2 und *2 an deinem 
Lauflicht herum... :-)

T.

von (prx) A. K. (prx)


Lesenswert?

Rolf Magnus schrieb:

> Nein. Das wäre auch schwierig, denn das Rotieren bezieht üblicherweise
> das Carry-Flag mit ein, das es in C nicht gibt.

Die in Prozessoren oft vorhandene Rotation ohne Carry wäre in 
Verschlüsselungsfunktionen ganz praktisch, da die recht oft damit 
arbeiten.

von Thorsten S. (whitejack)


Lesenswert?

...ein weiterer Ansatz für ein Lauflicht wäre:

Nimm eine Zählvariable (von 0-255) und ordne jedem Pin 2 Defines zu 
(pinXan und pinXaus). Bei jedem Schleifendurchlauf könnte man gucken ob 
der jeweilige pin an oder aus sein soll indem man die Werte für die Pins 
jeweils in jedem Schleifendurchlauf entsprechend mit der Zählvariable 
vergleicht...

Für dein lauflicht Würde es dann so aussehen:

pin0an = 0
pin0aus = 31

pin1an = 32
pin1aus = 63

usw..

pin7an = 224
pin7aus = 255

Da man ja jedem Pin diese zeitliche "Position" zuordnen kann, kann man 
wirklich tolle effekte erreichen...

T.

von Andreas B. (Gast)


Lesenswert?

A. K. schrieb:
> Die in Prozessoren oft vorhandene Rotation ohne Carry wäre in
> Verschlüsselungsfunktionen ganz praktisch, da die recht oft damit
> arbeiten.

Es gibt kein direktes C-Konstrukt dafür. Das heißt aber nicht, dass der 
Compiler das nicht verwendet.

Beispiel für den ARM:
1
#include <stdint.h>
2
3
uint32_t rotate_right(uint32_t x, unsigned int pos)
4
{
5
  x = (x >> pos) | (x << (32 - pos));
6
  return x;
7
}

Resultierender Code (gcc mit Optimierung -O2):
1
rotate.o:     file format elf32-littlearm
2
3
4
Disassembly of section .text:
5
6
00000000 <rotate_right>:
7
   0:  e1a00170   ror  r0, r0, r1
8
   4:  e12fff1e   bx  lr

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.