Hallo,
ich möchte gern ein Protokoll (DCC) auslesen.
Dazu frage ich bereits 87us nach der steigenden Flanke einen Eingang ab.
Der Ablauf soll wie folgt sein:
1: Eingang (PIN_D2) abfragen und negieren
2: Byte um eine Stelle nach links verschieben
3: rechte Stelle mit negiertem Eingang auffüllen
dccbit = ~(PIN_D2);
dccbyte <<= 1;
dccbyte |= dccbit;
Hier sind wohl ein paar Fehler enthalten, würde mich über eure Hilfe
freuen!!
Danke,
Philipp
Danke,
dieser Code sollte nun das gleiche machen, richtig?
dccbit = (~(PIN_D2) & (0x01));
dccbyte1 <<= 1;
dccbyte1 |= dccbit;
wie finde ich heraus, welcher Code effektiver ist?
Philipp L. schrieb:> dieser Code sollte nun das gleiche machen, richtig?
Nein. Du musst das PIND Register Lesen und davon das Bit 2
herausfiltern.
Und dann noch an die Position von Bit 0 verschieben, sonst klappt das
mit anschliessenden oder-Operation nicht. Diesen Schritt bin ich durch
den if() Ausdruck umgangen.
Du liest PIN_D2 (was auch immer das sein mag) und filterst davon das Bit
0 heraus.
Stefanus F. schrieb:> Ich glaube das ist die effizienteste Variante:
Bedingte Sprünge sind eher selten effizient... wie wäre es damit das
komplette Byte auf einmal zu negieren anstatt jedes Bit einzeln?
Dr. Sommer schrieb:> Bedingte Sprünge sind eher selten effizient...
Kannst du das am Assembler-Code des AVR konkretisieren?
> wie wäre es damit das komplette Byte auf einmal zu negieren> anstatt jedes Bit einzeln?
Ja, das könnte eine Abkürzung sein.
> Du liest PIN_D2 (was auch immer das sein mag) und filterst davon das Bit> 0 heraus.
PIN_D2 ist doch mittels sbit.h der Eingang PD2 ?
diese beiden Zeilen ergeben daher das gleiche, oder nicht?
1: dccbit = (~(PIN_D2) & (0x01));
2: dccbit = (~(PIND) & (0x03));
> wie wäre es damit das komplette Byte auf einmal zu negieren
ich muss aber nach jedem Einlesen auf das einzeln negierte Bit
reagieren.
Es reicht mir nicht das komplette Byte zu lesen und später als ganzes zu
negieren.
Deine sbit.h kann ich nicht berücksichtigen, weil sie mir nicht
vorliegt.
Unabhängig davon habe ich es ausprobiert:
1
voidtest1()
2
{
3
uint8_tdccbyte=0;
4
uint8_tdccbit;
5
for(uint8_ti=0;i<8;i++)
6
{
7
dccbit=~(PIND&(1<<PD2));
8
dccbyte<<=1;
9
dccbyte|=dccbit;
10
}
11
PORTA=dccbyte;
12
}
1
00000034 <test1>:
2
34: 28 e0 ldi r18, 0x08 ; 8
3
36: 90 e0 ldi r25, 0x00 ; 0
4
38: 80 b3 in r24, 0x10 ; 16
5
3a: 99 0f add r25, r25
6
3c: 84 70 andi r24, 0x04 ; 4
7
3e: 80 95 com r24
8
40: 98 2b or r25, r24
9
42: 21 50 subi r18, 0x01 ; 1
10
44: c9 f7 brne .-14 ; 0x38 <test1+0x4>
11
46: 9b bb out 0x1b, r25 ; 27
12
48: 08 95 ret
1
voidtest2()
2
{
3
uint8_tdccbyte=0;
4
for(uint8_ti=0;i<8;i++)
5
{
6
if(PIND&(1<<PD2))
7
{
8
dccbyte=(dccbyte<<1);
9
}
10
else
11
{
12
dccbyte=(dccbyte<<1)+1;
13
}
14
}
15
PORTA=dccbyte;
16
}
1
0000004a <test2>:
2
4a: 98 e0 ldi r25, 0x08 ; 8
3
4c: 80 e0 ldi r24, 0x00 ; 0
4
4e: 82 9b sbis 0x10, 2 ; 16
5
50: 02 c0 rjmp .+4 ; 0x56 <test2+0xc>
6
52: 88 0f add r24, r24
7
54: 02 c0 rjmp .+4 ; 0x5a <test2+0x10>
8
56: 88 0f add r24, r24
9
58: 8f 5f subi r24, 0xFF ; 255
10
5a: 91 50 subi r25, 0x01 ; 1
11
5c: c1 f7 brne .-16 ; 0x4e <test2+0x4>
12
5e: 8b bb out 0x1b, r24 ; 27
13
60: 08 95 ret
1
voidtest3()
2
{
3
uint8_tdccbyte=0;
4
for(uint8_ti=0;i<8;i++)
5
{
6
dccbyte=(dccbyte<<1)|(PIND&(1<<PD2));
7
}
8
PORTA=~dccbyte;
9
}
1
00000062 <test3>:
2
62: 28 e0 ldi r18, 0x08 ; 8
3
64: 80 e0 ldi r24, 0x00 ; 0
4
66: 90 b3 in r25, 0x10 ; 16
5
68: 88 0f add r24, r24
6
6a: 94 70 andi r25, 0x04 ; 4
7
6c: 89 2b or r24, r25
8
6e: 21 50 subi r18, 0x01 ; 1
9
70: d1 f7 brne .-12 ; 0x66 <test3+0x4>
10
72: 80 95 com r24
11
74: 8b bb out 0x1b, r24 ; 27
12
76: 08 95 ret
Meine angeblich kürzere Variante ist tatsächlich einen Befehl grösser
und enthält mehr Sprünge. Dr. Sommer hatte Recht.
Am Ende das ganze Byte zu negieren bringt keinen Vorteil.
Philipp L. schrieb:> dccbit = ~(PIN_D2);
Ich denke das tut nicht, was du willst, weil es keine echten
Bit-Variablen gibt. Du invertierst damit vermutlich alle 8 Bits.
Also 0b00000001 oder 0b11111110
Und hier passiert dann im zweiten Fall etwas völlig falsches:
> dccbyte |= dccbit;
Der avr-gcc (4.8) Codegenerator ist nicht gerade der Bringer - er
erzeugt häufig unnötig komplexen Code. Bei meinem "kanonischen" Ausdruck
"x = (x<<1) + !(PIND & (1<<PD2))" erzeugt er miesen Code. Nen einfaches
"if" bekommt er aber ganz gut hin:
1
uint8_ttest()
2
{
3
uint8_ti,x;
4
for(i=0;i<8;++i)
5
{
6
x<<=1;
7
if(~PIND&(1<<PD2))
8
x|=1;
9
}
10
returnx;
11
}
1
test:
2
ldi r25,lo8(8)
3
4
.L3: lsl r24
5
sbis 0x10,2
6
ori r24,lo8(1)
7
8
subi r25,lo8(-(-1))
9
brne .L3
10
ret
Bei höheren Optimierungsstufen (mit loop-unrolling) verkackt er's aber
wieder ;-)
Stefanus F. schrieb:> Ich denke das tut nicht, was du willst, weil es keine echten> Bit-Variablen gibt.
immer dieser unsinn, das ist raten auf niedrigem niveau!
vielleicht mal die doku auch lesen ...
z.b. 328p page 22
I/O Registers within the address range 0x00 - 0x1F are directly bit-
accessible using the SBI and CBI instructions. In these registers, the
value of single bits can be checked by using the SBIS and SBIC
instructions.
die port register bits lassen sich alle in einer struct exact mappen und
ansprechen!
viele kennen auch die avr gpioX register nicht und wie diese für flags
eingesetzt werden.
eure code size kann ich leicht unterbieten, aber ich lass euch das
selber rausfinden. für avr verwende ich gcc v8.2!
mt
Stefanus F. schrieb:>> dccbit = ~(PIN_D2);>> Ich denke das tut nicht, was du willst, weil es keine echten> Bit-Variablen gibt. Du invertierst damit vermutlich alle 8 Bits.Apollo M. schrieb:> immer dieser unsinn, das ist raten auf niedrigem niveau!> vielleicht mal die doku auch lesen ...> I/O Registers within the address range 0x00 - 0x1F are directly bit-> accessible using the SBI and CBI instructions. In these registers, the> value of single bits can be checked by using the SBIS and SBIC> instructions.
Ich habe von der Programmiersprache C (in der avr-gcc Variante)
geschrieben, nicht von diesen Registern.
> die port register bits lassen sich alle in einer struct> exact mappen und ansprechen!
Das ist aber etwas ganz anderes, als die Zeile Code auf die ich mich
bezog.
Weisst du: Wenn man einzelne Sätze aus dem Zusammenhang reisst, kann man
damit anstellen, was man will - völlig losgelöst von seiner
ursprünglichen Bedeutung. Man kann aber auch einfach einen Fantasy Roman
schreiben, da besteht immerhin die Möglichkeit, dass sich jemand daran
erfreut.
Philipp L. schrieb:> wie finde ich heraus, welcher Code effektiver ist?
Früher (tm) hat man dafür die Taktzyklen gezählt, die das Programm für
die Ausführung braucht (z.B. im Simulator). Guck dir den Maschinencode
an, dann siehst du es oder programmiere es gleich in Inline-Assembler.
Ist dein Prozessor am Limit oder warum ist Code-Effizienz bei den paar
Takten ein Thema?
Stefanus F. schrieb:> Kannst du das am Assembler-Code des AVR konkretisieren?
Es geht um AVR? Da ist der Effekt aufgrund der einfachen Pipeline
wahrscheinlich schwächer. Der Effekt ist jedenfalls hinlänglich bekannt,
das kannst du einfach googeln.
foobar schrieb:> Nen einfaches> "if" bekommt er aber ganz gut hin:
[...]
Da fehlt de facto noch eine Instruktion, damit der Code tut, was er
soll. Die Initialisierung von r24 auf null. Die dazugerechnet und den
Callframe abgezogen ergeben sich 7*6+1*7=49 Takte für den Nutzcode. Das
ist nicht gut, das ist schwach.
Der richtige Assemblerprogrammierer schreibt hier:
1
ldi R24,1
2
loop:
3
lsl R24
4
sbis PIND,PORTD2
5
ori R24,1
6
brcc loop
Das macht dann 8*5=40 Takte, ist also fast 20% schneller. Soviel zum
Thema: die C-Compiler sind heute so gut, dass man mit Assembler fast
nichts mehr sparen kann. 1/5 der Rechenzeit ist ja wohl doch ein wenig
mehr als "fast nichts".
> Bei höheren Optimierungsstufen (mit loop-unrolling) verkackt er's aber> wieder ;-)
Noch mehr?!
c-hater schrieb:
> Da fehlt de facto noch eine Instruktion, damit der Code tut, was er> soll. Die Initialisierung von r24 auf null.
Ist nicht nötig - der tut auch so, was er soll.
foobar schrieb:> Ist nicht nötig - der tut auch so, was er soll.
Stimmt. lsl schiebt unten natürlich immer eine Null rein. Mein Fehler.
OK, steht's also nur noch 40:48. Immer noch 16,7% Gewinn.
> OK, steht's also nur noch 40:48. Immer noch 16,7% Gewinn.
Eigentlich steht's 3:3, denn es ging um "Byte verschieben und mit
Eingang auffüllen". Die Schleife drum herum gehört da nicht zu ;-)
Peter D. schrieb:> Mit wieviel MHz kommen denn die Bits rein, daß Du über Mikrooptimierung> nachdenken mußt?
Wahrscheinlich ein klassischer Fall von "premature optimization"...
Peter D. schrieb:> Mit wieviel MHz kommen denn die Bits rein, daß Du über Mikrooptimierung> nachdenken mußt?
Ich glaube danach hat er gar nicht gefragt. Die Diskussion ist nur in
diese Richtung abgedriftet, nachdem ich den Begriff "effizienteste
Variante" einbrachte.