Forum: Mikrocontroller und Digitale Elektronik womit füllt atmel bit shift (>>) die neuen bits auf?


von A. S. (rava)


Lesenswert?

hi,


ich arbeite mit atmega162v (8bit) unter Atmel Studio 6.1

woran kann es liegen, dass
1
uint8_t counter;
2
counter = a>>1;
3
counter &= 0b01111111;
andere Ergebnisse liefert als
1
uint8_t counter;
2
counter = a>>1;
???

Wobei a volatile ist und von einem Interrupt geschrieben wird, der einen 
Drehencoder an PORTA ausliest:
1
volatile uint8_t a = 0;
2
volatile uint8_t EncoderPollingIncrement[16] = {0,-1, 1, 0, 1, 0, 0,-1,-1, 0, 0, 1, 0, 1,-1, 0};
3
ISR (TIMER1_COMPA_vect)
4
{
5
  static uint8_t history = 0;
6
  history <<= 2;
7
  history |= PINA & 0b00000011;
8
  history &= 0b00001111;
9
  
10
  a+=EncoderPollingIncrement[history];
11
}

Kann es sein, dass da irgendwas aus dem carry-bit gezogen wird? Laut dem 
C-Standard müsste bei unsigned Werten ja eigentlich immer eine 0 kommen.

Beim Herunterzählen wird aber jedes zweite Mal eine 1 nachgezogen.
Das Verhalten ist deterministisch.

von Magic S. (magic_smoke)


Lesenswert?

Der Controller kann beides. Rotate through carry und logical shift mit 
auffüllen mit 0.

von Tim  . (cpldcpu)


Lesenswert?

Vergleiche doch mal die generierten Assemblercode.

von c-hater (Gast)


Lesenswert?

A. S. schrieb:

> woran kann es liegen, dassuint8_t counter;
> counter = a>>1;
> counter &= 0b01111111;
> andere Ergebnisse liefert alsuint8_t counter;
> counter = a>>1;

Das weiß ich auch nicht, normalerweise sollte das Gleiche rauskommen. 
Höchstwahrscheinlich hast du uns nicht die ganze schreckliche Wahrheit 
mitgeteilt (dein vollständiges Programm).

Bei einem deiner Beschreibung entsprechenden Minimalprogramm tritt 
jedenfalls der beschriebene Effekt nicht auf. Ergo: Die Ursache muß in 
dem Teil deines Gesamtwerkes liegen, den du uns nicht gezeigt hast.

> Wobei a volatile ist und von einem Interrupt geschrieben wird, der einen
> Drehencoder an PORTA ausliest:

[...]

Diese Routine ist allerdings völlig Scheiße. a ist falsch deklariert, 
EncoderPollingIncrement übrigens ebenfalls. De facto sollten die alle 
"signed" sein, denn ganz offensichtlich wird hier mit Vorzeichen 
operiert. Nur weil der AVR zufällig mit dem Zweierkomplement rechnet, 
kommt trotzdem was sinnvolles raus, jedenfalls wenn man a am Ende 
entsprechend castet. Was du übrigens nicht getan hast.

Deswegen vermute ich, daß die Routine geklaut ist, im von dir 
tatsächlich verwendeten Original die Deklarationen abweichend von der 
hier geposteten Variante korrekt sind und deswegen deine Operation a>>1 
eine "signed"-Operation ist. Und da können (und müssen) dann natürlich 
Einsen im höchstwertigen Bit auftauchen, nämlich immer dann, wenn a 
negativ ist.

Der Unterschied im Asm-Code wäre, daß der Compiler bei einem "signed" a 
ein "asr" verwenden würde, bei einem unsigned hingegen ein "lsr".

von Hoo (Gast)


Lesenswert?

Eben, es gibt arithmetic-shift und logic-shift. Vielleicht einen Blick 
ins Instruction manual...

von Bitflüsterer (Gast)


Lesenswert?

@ A. S

Mir fallen gerade nur ein paar Widersprüche auf.

Ist das Array nunr unsigned oder signed? Sollte da nicht 'ne Warnung 
ausgegeben werden?
1
volatile uint8_t EncoderPollingIncrement[16] = {0,-1, 1, 0, 1, 0, 0,-1,-1, 0, 0, 1, 0, 1,-1, 0};

Und wieso "herunterzählen"? Da wird nirgendwo heruntergezählt.

Ansonsten sollte, bei vorzeichemlosen a, das rechts-schieben einer 
Division entsprechen.

von A. S. (rava)


Lesenswert?

haha geklaut?
da steckt man 2h rein, sich zu überlegen, wie man eine performante 
Encoderabfrage mit LUT machen kann und dann sowas kopfschüttel ...

da ist mal wieder einer extra früh aufgestanden, um zu pöbeln.
danke ich löse mein Problem selbst.


bitte schließen.

von Karl H. (kbuchegg)


Lesenswert?

A. S. schrieb:
> haha geklaut?
> da steckt man 2h rein, sich zu überlegen, wie man eine performante
> Encoderabfrage mit LUT machen kann und dann sowas kopfschüttel ...

Du musst aber auch zugeben, dass es etwas befremdlich wirkt, wenn du in 
ein unsigned Array dann Werte mit -1 reinschreibst. Das mag korrekt 
sein, weil es korrekt berücksichtigt wird. Aber naheliegender ist es nun 
mal, dass in einer Encoderaufsummierung signed gerechnet wird. Denn 
schliesslich muss man ja links/rechts drehen können, und ein linksdrehen 
über den Nullpunkt hinaus (und somit in negative Werte hinein) soll dann 
auch nicht so unüblich sein.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Karl Heinz schrieb:
> Aber naheliegender ist es nun
> mal, dass in einer Encoderaufsummierung signed gerechnet wird.

Die Aufsummierung selbst sollte schon in unsigned gerechnet werden,
damit das Verhalten bei Überläufen klar spezifiziert ist. Trotzdem hätte
man aber die Lookup-Tabelle der Logik wegen signed machen können. Ob die
Konvertierung von signed nach unsigned nun aber beim Aufsummieren oder
bereits bei der Array-Initialisierung erfolgt, ist eigentlich egal.

@A.S.:

Da alle beteiligten Variablen vom Typ uint8_t sind, sollten a>>1 und
(a>>1)&0b01111111 dasselbe Ergebnis liefern. Bist du sicher, dass bei
deinem Test zwischen den beiden Auswertungen nicht einfach a verändert
wurde?

Wie sehen überhaupt die beiden unterschiedlichen Werte von counter aus?

von (prx) A. K. (prx)


Lesenswert?

So lange kein reproduzierbarer Code vorliegt bleibt das Kaffeesatzlesen.

von A. S. (rava)


Lesenswert?

Yalu X. schrieb:
> Wie sehen überhaupt die beiden unterschiedlichen Werte von counter aus?

beim hochzählen (Rechtsdrehen des encoders) passt es. Der Encoder zählt 
ja wegen der Rasterung in zweierschritten. Die Divison (rechtsshift) 
sorgt dafür, dass in "counter"
0, 1, 2, 3, 4, 5, 6, 7
steht.
Sobald ich nach links drehe, sehe ich:
6, 5, 4, 3, 2, 1, 0, 255, 254, ...

Das passt also auch - mit der Ausnahme dass ich 2, 1, 0, 127, 126, ... 
erwartet hätte, wenn der Bitshit links 0 einfügt.


ABER: wenn man den Encoder so langsam dreht, dass die Zwischenschritt 
zum Vorschein kommen zählt counter
Aufwärts: 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7,
Abwärts: 134, 6, 133, 5, 132, 4, 131, 3, 130, 2, 129, 1, 128, 0, 127, 
255


ich finde die Erklärung mit dem "Rotate through carry shift" ziemlich 
einleuchtend: der atmega holt sich von links immer das carry bit und da 
beim unsigned decrement jedes Mal 255 drauf addiert werden, toggelt 
ebendieses Carrybit bei jedem Zählschritt.


ich weiß nur bucgt, wie ich dem Ding klar mache, welche der beiden 
Shift-Operationen es zu verwenden hat. Ist aber auch nicht wichtig, da 
das nur fürs Debugging ist.
Ich könnte wohl einfach /2 schreiben und alles wäre gut. Bei Atmel geht 
das. Wenn man den xc8 free compiler von microchip verwendet, hat man das 
problem, dass der solche power-of-two-divisionen nicht optimiert. 
Deswegen habe ich mir angewöhnt, es manuell einzugeben.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Diese Philosophiererei bleibt sinnlos, wenn man keinen Code hat. 
Mindestens den vom Compiler erzeugten Code.

von Karl H. (kbuchegg)


Lesenswert?

A. K. schrieb:
> Diese Philosophiererei bleibt sinnlos, wenn man keinen Code hat.


Kompletten Code!
Inklusive der Angabe (Code), wie die gelisteten Werte ermittelt wurden.

von A. S. (rava)


Lesenswert?

okay,

nachdem wir die Ursache jetzt eh schon haben, lasst mich euch das 
Problem nochmal vorkauen. Euch ist schon klar, dass ihr es jetzt auch 
ausprobieren müsst!?

1
int main()
2
{
3
 uint8_t a=0;
4
 while(true)
5
 {
6
  a += 255;
7
  PORTA = a>>1;
8
  sleep_sec(1)
9
 }
10
}


Bei MIR tritt da der beschriebene Effekt auf. sleep_sec() und die 
intialisierung eurer µCs könnt ihr selbst schreiben.

von Tim  . (cpldcpu)


Lesenswert?

A. S. schrieb:
> nachdem wir die Ursache jetzt eh schon haben, lasst mich euch das
> Problem nochmal vorkauen. Euch ist schon klar, dass ihr es jetzt auch
> ausprobieren müsst!?

Wie wäre es, wenn Du einfach den entsprechenden Teil aus dem *.lss-file 
postest?

von Yalu X. (yalu) (Moderator)


Lesenswert?

Das Testprogramm in einem letzten Beitrag wird bei mir korrekt 
kompiliert, wenn ich vorher das fehlenden Semikolon anfüge. An Port A 
dürfte das höchstwertige Bit nie gesetzt sein, es sei denn, sleep_sec 
hat irgendwelche bösen Nebeneffekte.

Tim    schrieb:
> Wie wäre es, wenn Du einfach den entsprechenden Teil aus dem *.lss-file
> postest?

Genau, zeige uns den generierten Assembler-Code und nenne uns die 
verwendete GCC-Version.

: Bearbeitet durch Moderator
von Andreas M. (amesser)


Lesenswert?

1
 a+= 255;

255 ist für den Compiler ein 'int' (also int16_t). Die Addition wird 
also 16 bit signed ausgeführt und dann wieder zurück konvertiert. Ich 
denke das gleiche passiert beim shift. Geht
1
 a >> (uint8_t)1
 auch schief?

von Amateur (Gast)


Lesenswert?

>Euch ist schon klar, dass ihr es jetzt auch
>ausprobieren müsst!?

Jawolllll!

von A. S. (rava)


Lesenswert?

ok, ich wusste nicht, wo ich nachsehen muss, um an den assemblercode zu 
kommen. In meinem ursprünglichen Programm sieht's mittlerweile so aus:
1
...
2
a+=(uint8_t)255;
3
 3ce:  80 91 1c 01   lds  r24, 0x011C
4
 3d2:  90 91 1d 01   lds  r25, 0x011D
5
 3d6:  81 50         subi  r24, 0x01  ; 1
6
 3d8:  9f 4f         sbci  r25, 0xFF  ; 255
7
 3da:  90 93 1d 01   sts  0x011D, r25
8
 3de:  80 93 1c 01   sts  0x011C, r24
9
counter = a>>(uint8_t)1;
10
 3e2:  00 91 1c 01   lds  r16, 0x011C
11
 3e6:  10 91 1d 01   lds  r17, 0x011D
12
 3ea:  16 95         lsr  r17
13
 3ec:  07 95         ror  r16
14
 3ee:  10 2f         mov  r17, r16
15
//counter &= 0b01111111;    // Auffüllen von links geschieht nicht immer mit Nullen!
16
17
    OuputValue(counter);
18
...

er benutzt tatäschlich 2 Register für die Operationen. Datentypen sind 
wie gehabt uint8_t


Das GCC kommt von WinAVR-20100110 - mir ist nicht ganz klar, wie ich die 
Version nachsehen kann.
1
-funsigned-char -funsigned-bitfields -DDEBUG  -O1 -ffunction-sections -fdata-sections -fpack-struct -fshort-enums -g2 -Wall -mmcu=atmega162 -c -std=gnu99 -MD -MP -MF "$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -MT"$(@:%.o=%.o)"

: Bearbeitet durch User
von Amateur (Gast)


Lesenswert?

Da haben sich die Atmel's so viel Arbeit gemacht und keiner schaut hin!

(AVR Instruction Set)
http://www.atmel.com/Images/doc0856.pdf

von Karl H. (kbuchegg)


Lesenswert?

Andreas Messer schrieb:

> denke das gleiche passiert beim shift.

Allerdings.
zzumindest konzeptionell.

Nur hat das keine Auswirkungen, da ein Wert von 255 problemlos in einen 
int passt und positiv ist. Womit ein rechts-shift 128 ergibt. Und der 
kann wieder in einen uint8_t verwandelt werden.

Ein guter Optimizer müsste das allerdings bemerken, dass auf die Art nie 
im int ein negatives Ergebnis entstehen kann und er kann daher die 
Operation als 8 Bit Operation unsigned implementieren.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

1
..... -O1 ....

und da wunderst du dich, dass der gcc nicht vernünftig optimiert?

von Karl H. (kbuchegg)


Lesenswert?

1
#include <avr/io.h>
2
3
int main()
4
{
5
 uint8_t a=0;
6
 while(1)
7
 {
8
  a += 255;
9
  PORTA = a>>1;
10
  ;
11
 }
12
}
1
int main()
2
{
3
  74:  90 e0         ldi  r25, 0x00  ; 0
4
 uint8_t a=0;
5
 while(1)
6
 {
7
  a += 255;
8
  76:  91 50         subi  r25, 0x01  ; 1
9
  PORTA = a>>1;
10
  78:  89 2f         mov  r24, r25
11
  7a:  86 95         lsr  r24
12
  7c:  82 b9         out  0x02, r24  ; 2
13
  7e:  fb cf         rjmp  .-10       ; 0x76 <main+0x2>
14
15
00000080 <_exit>:
16
  80:  f8 94         cli
17
18
00000082 <__stop_program>:
19
  82:  ff cf         rjmp  .-2        ; 0x82 <__stop_program>

Er nimmt einen LSR her.
Also einen logischen Shift. Nix mit Carry Flag.

Alles andere hätte mich auch gewundert.

von Karl H. (kbuchegg)


Lesenswert?

Moment.

Da stimmt was nicht
1
a+=(uint8_t)255;
2
 3ce:  80 91 1c 01   lds  r24, 0x011C
3
 3d2:  90 91 1d 01   lds  r25, 0x011D
4
 3d6:  81 50         subi  r24, 0x01  ; 1
5
 3d8:  9f 4f         sbci  r25, 0xFF  ; 255
6
 3da:  90 93 1d 01   sts  0x011D, r25
7
 3de:  80 93 1c 01   sts  0x011C, r24

da 'a' offenbar aus 2 Speicherzellen geladen bzw. gespeichert wird, KANN 
a kein uint8_t sein.
Ich denke es wird Zeit für den richtigen Code. So wie er ist und 
vollständig.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Das hier
1
counter = a>>(uint8_t)1;
ist Schwachsinn.

Der Datentyp der Angabe, um wieviele Bits geschoben werden soll, hat 
keinerlei Auswirkung darauf, wie die Operation implementiert wird.

In
1
   a << b  bzw
2
   a >> b
ist einzig und alleine der Datentyp von a herangezogen, um zu 
entscheiden auf wievielen Bits die Schiebeoperation gemacht werden muss.

Das ist anders als bei den anderen OPerationen, zb
1
   a + b
Hier haben beide Datentypen, der von a UND der von b, Einfluss auf die 
Operation.

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

A. S. schrieb:
> a+=(uint8_t)255;
>  3ce:  80 91 1c 01   lds  r24, 0x011C
>  3d2:  90 91 1d 01   lds  r25, 0x011D
>  3d6:  81 50         subi  r24, 0x01  ; 1
>  3d8:  9f 4f         sbci  r25, 0xFF  ; 255
>  3da:  90 93 1d 01   sts  0x011D, r25
>  3de:  80 93 1c 01   sts  0x011C, r24
> counter = a>>(uint8_t)1;
>  3e2:  00 91 1c 01   lds  r16, 0x011C
>  3e6:  10 91 1d 01   lds  r17, 0x011D
>  3ea:  16 95         lsr  r17
>  3ec:  07 95         ror  r16
>  3ee:  10 2f         mov  r17, r16
> //counter &= 0b01111111;    // Auffüllen von links geschieht nicht immer
> mit Nullen!

Dieser Code stammt aber nicht von einem uns bekannten Quellcode. Zeige
uns doch den Assembler-Code von exakt diesem Quellcode
1
int main()
2
{
3
 uint8_t a=0;
4
 while(true)
5
 {
6
  a += 255;
7
  PORTA = a>>1;
8
  sleep_sec(1);
9
 }
10
}

Die Versionsnummer des Compilers wird dir übrigens mit
1
avr-gcc --version

angezeigt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Damit optimiert wird, muss man den Optimizer auch aktivieren, was -O1 
nur halbherzig macht.

Wenn man die Syntaxfehler und fehlenden Deklarationen in obigem Beispiel 
korrigiert, erhält man etwa
1
#include <avr/io.h>
2
3
extern void sleep_sec (char);
4
5
void foo (void)
6
{
7
    uint8_t a=0;
8
    while (1)
9
    {
10
        a += 255;
11
        PORTA = a>>1;
12
        sleep_sec(1);
13
    }
14
}

Das übersetzt mit

$ avr-gcc foo.c -Os -S -mmcu=atmega162

liefert -- allerdings nicht mit einem Compiler, der schon 6 Jahre alt 
ist -- das:
1
foo:
2
  ldi r28,0
3
.L2:
4
  subi r28,lo8(-(-1))
5
  mov r24,r28
6
  lsr r24
7
  out 0x1b,r24
8
  ldi r24,lo8(1)
9
  call sleep_sec
10
  rjmp .L2

Ergo:

1) Es wird nur mit 8 Bits gerechnet, was allerdings der
   C-Spezifikation entspricht, die ja eine Promotion auf
   (unsigned) int vorschreibt.  Ich würd fast wetten daß auch
   avr-gcc 4.3.x (z.B. WinAVR-20010110) das schon so macht.

2) Es wird ein LSR gemacht, d.h. das Carry with nicht nachgezogen
   sondern das MSB auf 0 gesetzt.

Wenn man allerdings nicht über eine Zwischenvariable geht, sondern
1
uint8_t fun (uint8_t a)
2
{
3
    return (a + 255) >> 1;
4
}

dann *muss* die Rechnung per 16 Bits ausgeführt werden:
1
fun:
2
  ldi r25,0
3
  subi r24,1
4
  sbci r25,-1
5
  asr r25
6
  ror r24
7
  ret

"a + 255" wird als int berechnet, wiederum konform mit dem 
Sprachstandard. Mit "a + 255U" wird die berechnung als unsigned 
ausgeführt, d.h. die Shiftsequenz wird dann zu LSR + ROR.

von Karl H. (kbuchegg)


Lesenswert?

Johann L. schrieb:

> $ avr-gcc foo.c -Os -S -mmcu=atmega162
>
> liefert -- allerdings nicht mit einem Compiler, der schon 6 Jahre alt
> ist -- das:

Der mit dem ich dasselbe Experiment weiter oben gemacht habe, hat dieses 
Alter. Bin jetzt nicht mehr auf dieser Maschine, aber der WinAvr stammt 
aus 2008 oder 2009.
Das Ergebnis unterscheidet sich allerdings nur maginal von deinem, das 
Assembler Listing ist anders strukturiert. Das Assembler Ergebnis ist 
aber im wesentlichen Identisch, was mich aber auch nicht groß verblüfft, 
denn derartige Optimierungen sind schon lange gut verstanden.

: Bearbeitet durch User
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.