Forum: Mikrocontroller und Digitale Elektronik Byte verschieben und mit Eingang auffüllen.


von Philipp L. (viech)


Lesenswert?

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

von Stefan F. (Gast)


Lesenswert?

Ich glaube das ist die effizienteste Variante:
1
if (PIND & (1<<PD2))
2
{
3
    dccbyte = (dccbyte<<1);
4
}
5
else
6
{
7
    dccbyte = (dccbyte<<1)+1;
8
}

von Philipp L. (viech)


Lesenswert?

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?

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


Lesenswert?

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?

von Stefan F. (Gast)


Lesenswert?

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.

von Philipp L. (viech)


Lesenswert?

> 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.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Deine sbit.h kann ich nicht berücksichtigen, weil sie mir nicht 
vorliegt.

Unabhängig davon habe ich es ausprobiert:
1
void test1()
2
{
3
    uint8_t dccbyte=0;
4
    uint8_t dccbit;
5
    for (uint8_t i=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
void test2()
2
{
3
    uint8_t dccbyte=0;
4
    for (uint8_t i=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
void test3()
2
{
3
    uint8_t dccbyte=0;
4
    for (uint8_t i=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.

von Stefan F. (Gast)


Lesenswert?

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;

von foobar (Gast)


Lesenswert?

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_t test()
2
{
3
    uint8_t i,x;
4
    for (i = 0; i < 8; ++i)
5
    {
6
        x <<= 1;
7
        if (~PIND & (1<<PD2))
8
            x |= 1;
9
    }
10
    return x;
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 ;-)

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

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

von Mark B. (markbrandis)


Lesenswert?

Philipp L. schrieb:
> wie finde ich heraus, welcher Code effektiver ist?

Indem Du Dir den erzeugten Assembler-Code anschaust.

von Stefan F. (Gast)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

Philipp, würdest du bitte mal die Deklaration der Variable "dccbit" 
zeigen?

von my2ct (Gast)


Lesenswert?

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?

von Bernot (Gast)


Lesenswert?

@ Apollo M.

Warum beachtest du die Netiquette nicht?
1
Wichtige Regeln - erst lesen, dann posten!
2
3
Groß- und Kleinschreibung verwenden

von Dr. Sommer (Gast)


Lesenswert?

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.

Beitrag #5729275 wurde von einem Moderator gelöscht.
Beitrag #5729296 wurde von einem Moderator gelöscht.
von c-hater (Gast)


Lesenswert?

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?!

von foobar (Gast)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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.

von foobar (Gast)


Lesenswert?

> 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 ;-)

von Peter D. (peda)


Lesenswert?

Mit wieviel MHz kommen denn die Bits rein, daß Du über Mikrooptimierung 
nachdenken mußt?

von Mark B. (markbrandis)


Lesenswert?

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"...

von Stefan F. (Gast)


Lesenswert?

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.

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.