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.
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".
Und wieso "herunterzählen"? Da wird nirgendwo heruntergezählt.
Ansonsten sollte, bei vorzeichemlosen a, das rechts-schieben einer
Division entsprechen.
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.
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.
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?
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.
A. K. schrieb:> Diese Philosophiererei bleibt sinnlos, wenn man keinen Code hat.
Kompletten Code!
Inklusive der Angabe (Code), wie die gelisteten Werte ermittelt wurden.
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
intmain()
2
{
3
uint8_ta=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.
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?
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.
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
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.
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.
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.
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.
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
externvoidsleep_sec(char);
4
5
voidfoo(void)
6
{
7
uint8_ta=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_tfun(uint8_ta)
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.
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.