Forum: Mikrocontroller und Digitale Elektronik Byte auf verteilten Ausgängen ordnen


von Philipp L. (viech)


Lesenswert?

Hallo zusammen,

ich habe Layoutbedingt 8 Ausgänge auf diversen uC Pins verteilt.

Nun möchte ich aber ein Byte in der richtigen Reihenfolge auf den 
gemischten Ausgängen ausgeben.

Gibt es dazu eine effizientere Programmierung als diese:


if (Testbyte & 0b00000001) PORT_D7 = 1;
else
PORT_D7 = 0;
if (Testbyte & 0b00000010) PORT_D6 = 1;
else
PORT_D6 = 0;
if (Testbyte & 0b00000100) PORT_D5 = 1;
else
PORT_D5 = 0;
if (Testbyte & 0b00001000) PORT_D3 = 1;
else
PORT_D3 = 0;
if (Testbyte & 0b00010000) PORT_C3 = 1;
else
PORT_C3 = 0;
if (Testbyte & 0b00100000) PORT_C2 = 1;
else
PORT_C2 = 0;
if (Testbyte & 0b01000000) PORT_C1 = 1;
else
PORT_C1 = 0;
if (Testbyte & 0b10000000) PORT_C0 = 1;
else
PORT_C0 = 0;

ich frue mich auf eure Hilfe !!

von Paul (Gast)


Lesenswert?

PORT_D7 = Testbyte.0    ;Bit 0
PORT_D6 = Testbyte.1    ;Bit 1
usw.

von Wolfgang (Gast)


Lesenswert?

Philipp L. schrieb:
> ich habe Layoutbedingt 8 Ausgänge auf diversen uC Pins verteilt.

Da wäre es doch naheliegend, das Layout etwas gerade zu ziehen, bevor 
man sich solche Verrenkungen in der Software einhandelt.

Oder guck dir an, wie andere das machen, z.B. bei Arduino für die 4 Byte 
LCD Daten.
https://github.com/arduino-libraries/LiquidCrystal/blob/master/src/LiquidCrystal.cpp
1
void LiquidCrystal::write4bits(uint8_t value) {
2
  for (int i = 0; i < 4; i++) {
3
    digitalWrite(_data_pins[i], (value >> i) & 0x01);
4
  }
5
  ...
6
}

von Lisa-Marie (Gast)


Lesenswert?

Für die 4 Bits der jeweiligen zwei Ports könnte man jeweils eine 
Translation table anlegen und dann 4 Bits zugleich schreiben, statt alle 
Bits einzeln.

von Lisa-Marie (Gast)


Lesenswert?

Wolfgang schrieb:
> Philipp L. schrieb:
>> ich habe Layoutbedingt 8 Ausgänge auf diversen uC Pins verteilt.
>
> Da wäre es doch naheliegend, das Layout etwas gerade zu ziehen, bevor
> man sich solche Verrenkungen in der Software einhandelt.
>
> Oder guck dir an, wie andere das machen, z.B. bei Arduino für die 4 Byte
> LCD Daten.
> 
https://github.com/arduino-libraries/LiquidCrystal/blob/master/src/LiquidCrystal.cpp
>
1
void LiquidCrystal::write4bits(uint8_t value) {
2
>   for (int i = 0; i < 4; i++) {
3
>     digitalWrite(_data_pins[i], (value >> i) & 0x01);
4
>   }
5
>   ...
6
> }

Das ist alles, aber sicher nicht effizient.

von Master S. (snowman)


Lesenswert?

z.B.

if (Testbyte & 0b00000100)
   PORT_D5 = 1;
else
   PORT_D5 = 0;

wird eifacher zu

PORT_D5 = (Testbyte >> 2) & 1;


ev. geht auch:

PORT_D5 = Testbyte & 0b00000100;
das hängt aber vom compiler & und mikrocontroller ab

von Lisa-Marie (Gast)


Lesenswert?

Master S. schrieb:
> PORT_D5 = (Testbyte >> 2) & 1;
>
>
> ev. geht auch:
>
> PORT_D5 = Testbyte & 0b00000100;
> das hängt aber vom compiler & und mikrocontroller ab

Das ist beides wahrscheinlich nicht effizienter als die Lösung des 
Threadstarters. Insbesondere, wenn die CPU keinen Barrel-Shifter hat.

von Thomas S. (selli69)


Lesenswert?

Lass es ganz einfach so. Wenn dir den Assembler-Code ansiehst, der sich 
daraus ergibt ist das wirklich nicht die Welt an Anweisungen. Und lass 
dir von Leuten, die dir (d)ein optimiertes Layout madig reden wollen 
keinen Scheiß erzählen. Die ganze Industrie optimiert Layouts und passt 
in der Software an.

von abc.def (Gast)


Lesenswert?

Die Varianten werden wohl durch den Compiler herausoptimiert, in 
Assembler ist alles dasselbe. Also kommt nur auf gute Lesbarkeit an.
Ich würde die Einzelbit-verarbeitung bevorzugen weil:
Im nächsten Projekt brauchst du bestimmte Pins wegen Sonderfunktionen. 
Wenn das LCD dann ein komplettes Port belegt, und nur hier und da 
einzelne Pins zur Verfügung stehen, fängst du schon wieder von vorne an. 
Besser wär, einzelne Pins zu verwenden und im h-file als #define 
einzugeben.

von Thomas S. (selli69)


Lesenswert?

abc.def schrieb:
> Besser wär, einzelne Pins zu verwenden und im h-file als #define
> einzugeben.

Amen!

von Philipp L. (viech)


Angehängte Dateien:

Lesenswert?

PORT_D7 = Testbyte.0    ;Bit 0

Bring den Fehler im Anhang...

von K. S. (the_yrr)


Lesenswert?

Philipp L. schrieb:
> PORT_D7 = Testbyte.0    ;Bit 0
>
> Bring den Fehler im Anhang...

kann der Compiler das so direkt?
ich wäre jetzt von einer Struct mit 8 * 1 Bit mit dem Namen 0-7 
ausgegangen. Eventuell muss noch ne union mit einem uint8_t außen drauf, 
wenn du nicht immer jedes Bit einzeln setzen/löschen willst.

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Philipp L. schrieb:
> PORT_D7 = Testbyte.0    ;Bit 0
>
> Bring den Fehler im Anhang...

Darfst halt nicht jeden Blödsinn glauben...

Abgesehen davon ist es egal was du fabrizierst, performant wird das in 
keinem Fall. Nicht nur das die Bits auf zwei Ports aufgeteilt sind, sie 
sind auch noch unzusammenhängend und was noch schlimmer ist, in 
umgekehrter Reihenfolge.

: Bearbeitet durch User
von Philipp L. (viech)


Lesenswert?

> Nicht nur das die Bits auf zwei Ports aufgeteilt sind, sie
> sind auch noch unzusammenhängend und was noch schlimmer ist, in
> umgekehrter Reihenfolge.

Das ist mir bewusst, das Layout ist aber nur geringfügig größer als das 
MLF-package des uC´s.
Da sind die 0,15mm (Abstand+Breite) der Leiterbahn schon groß.
Leiterbahnlayout war da wichtiger als die Software.

: Bearbeitet durch User
von K. S. (the_yrr)


Lesenswert?

Tim T. schrieb:
> Philipp L. schrieb:
>> PORT_D7 = Testbyte.0    ;Bit 0
>>
>> Bring den Fehler im Anhang...
>
> Darfst halt nicht jeden Blödsinn glauben...

ist von der Idee her (ist nicht meine gewesen) nicht so schlecht, da 
Fehlt halt noch dass er ne Struct braucht:

struct st_name{
  unsigned 0:1;
  unsigned 1:1;
... wobei ich nicht probiert habe ob 0,1,... gültige namen sind

dann noch ne union
union un_name{
  struct st_name abc;
  uint8_t abcd; };
Ohne Gewähr und ungetestet, wird zwar nicht viel verändern am ASM/Hex 
der rauskommt, aber wird übersichtlicher (und denke ich war so gemeint, 
wie gesagt nicht meine Idee, nur meine Interpretation von dieser).

: Bearbeitet durch User
von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Also ich sehe bei dir so einige Defizite, da solltest du dir nich 
unbedingt nen Kopf um die Performance beim Bitschubsen machen, zumal der 
Compiler da eh aus fast allem Genannten am Ende das Gleiche zaubert.

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Naja, übersichtlicher als 8 Statements ala:

PORT_D5 = (Testbyte >> 2) & 1;

wirds nicht, und schneller auch nicht, eher das Gegenteil.

von K. S. (the_yrr)


Lesenswert?

wenn du einige zusammenhängende Pins und Bytes hast, die nur wild 
umsortiert werden müssen, könntest du eine kleine Lookup Tabelle 
erstellen.

z.b. über D5,6,7 eine 3bit, oder über D3,5,6,7 eine 5 bit
und für C0-3 eine 4bit lookup

4 bit sind 16 byte flash/ram was auch immer, das wird übrig sein

maskier und shifte die entsprechenden bits
für C0-3:
(Testbyte & 0xF0) >> 4, und das als Index für ein array als deine Lookup 
Tabelle, das array legst du schön in den Flash und fertig.

den Wert aus dem Array/Lookuptabelle veroderst du dann mit Port_C
also Port_C = (Port_C & 0xf0) | Lookup_wert
das & 0xf0 um C0-3 zu löschen, dass | um die neuen zu setzen, wenn du 
C4-7 nicht beeinflussen willst.


die Frage ist halt auch welcher µC und ob z.b. die Ausgänge anders 
getaktet sind als der Core, dann wäre es eventuell gut möglichst wenig 
Zugriffe auf die Pins zu haben, und wie schnell/gleichzeitig die gesetzt 
werden müssen und worauf hin du optimierst.

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

Master S. schrieb:
> ev. geht auch:
>
> PORT_D5 = Testbyte & 0b00000100;
> das hängt aber vom compiler & und mikrocontroller ab

Wie?

von abc.def (Gast)


Lesenswert?

Normalerweise mache ich das so:

Am Anfang:

#define bitSet(a,b) (a |= (1<<b))
#define bitClear(a,b) (a &= ~(1 << b))
#define bitClr(a,b) (a &= ~(1 << b))
#define bitTest(a,b) (a & (1 << b))
#define true  (0==0)
#define false (0!=0)

// display.h
// segment a
#define dispaOn  bitClear(PORTB,2)
#define dispaOff bitSet  (PORTB,2)
#define dispaDDR bitSet  ( DDRB,2)

damit ist die Zuordnung von Signal zu Pin an einer eindeutigen Stelle 
getroffen, und später kann bei Bedarf auch noch in low-aktiv oder 
high-aktiv getauscht werden.

Vielleicht würde dir das hier helfen:
#define bitTransfer(a,b,c,d) if(bit( c , d )){bitSet( a , b 
);}else{bitClear( a , b );}

Du gibst deine Signale nicht auf PORT aus, sondern byteweise in 
Variablen. Von dort aus über bitTransfer zu den realen Ausgängen, die 
aber auch beliebig verteilt sein können. Je nach Erfordernis beim 
Routing oder wegen µC-Sonderfunktionen.


Generell ist es für mich lesbarer, wenn da steht:
bitSet  (PORTB,2);
oder
if (bitTest(PINC,1)) { /* tuwas */ }
als wie ein Geschwurbel von << und !~

Warum macht man das Geschwurbel eigentlich? Finde ich überall.
( Das ist eine ernstgemeinte Frage an die Community )

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Weil der Shift-Operator zum Sprachumfang von C gehört, die 
bitSet-Funktion/Makro nicht.
Und wer mit ner einfachen Verschiebung samt Bitmaskierung schon Probleme 
hat, macht irgendwas falsch.

: Bearbeitet durch User
von abc.def (Gast)


Lesenswert?

Es gibt hier gefühlt wöchentlich einen Thread, wo ein Anfänger nicht 
damit zurechtkommt. Muß man die Anfänger vergraulen?

Gehört der uint16_t zum Sprachumfang von C? Ich brauche ein #include 
dafür. Also muß ich mit dem int weiterwursteln, das auf jeder Plattform 
eine andere Länge hat.

Ja, ich habe manchmal Probleme mit C. Ich mache hobbymäßig 1 Projekt pro 
Jahr, und das auch abends zwischen 20:00 und 23:00 . Da bin ich nicht 
mehr so fit, und es ist mir lieber, wenns übersichtlich wär. Eine 
Tabelle aus BitSet kann ich da noch eher verstehen. Mit einer LookUp 
table, geschrieben vor 5 Jahren, die einer kleinen Änderung bedarf, wird 
es schon schwerer.
Mache ich da etwas falsch, oder wie kann ich das besser machen?

Wenn ich es nerdig wollte, würde ich Z80 Assembler nehmen (nicht AVR, 
der ist nur grottig).

von Fred (Gast)


Lesenswert?

abc.def schrieb:
> Gehört der uint16_t zum Sprachumfang von C? Ich brauche ein #include
> dafür.

Ja. Du brauchst auch für Funktionen aus der Standardbibliothek ein 
#include.

abc.def schrieb:
> Also muß ich mit dem int weiterwursteln, das auf jeder Plattform
> eine andere Länge hat.

Quatsch

von hauptschüler (Gast)


Lesenswert?

abc.def schrieb:
> ... 1)) { /* tuwas */ }

Ja. Bei so hochkonzentrierten Arbeiten muß man auch mal infantil sein 
dürfen. Keine Ahnung wie oft ich so etwas schon im Forum gelesen habe. 
Man kann aber auch zB. "Aktion" schreiben. Das klingt dann nicht so 
ausgefallen. Ist aber nur meine Meinung.

von Zeno (Gast)


Lesenswert?

@TO
Mach es so wie von Thomas vorgeschlagen - laß es wie es ist. Eine kleine 
Optimierung wäre bei Deinem Code schon drin.
Setze erst mal alle Ports auf 0 dann fällt schon mal der else Zweig weg 
(machst Du ja in DEinem 2. Codeansatz so), oder mach es so wie von Tim 
vorgeschlagen:
1
PORT_D5 = (Testbyte >> 2) & 1;

Dann werden die Ports entsprechend dem Ergebnis der logischen Operation 
gesetzt.

Wenn alle Ports gleichzeitig schalten sollen, dann kommst Du nicht um 
einen Puffer (IC) herum, aber dann kannst Du auch gleich ein neues 
Layout machen und alle Ausgänge auf einenPort legen.

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

abc.def schrieb:
> Es gibt hier gefühlt wöchentlich einen Thread, wo ein Anfänger nicht
> damit zurechtkommt. Muß man die Anfänger vergraulen?

Fällt dir nicht auch auf das sich bei den Anfängern was verändert hat? 
Früher war die Erwartungshaltung eine Andere, da hat man sich noch 
wirklich mit der Materie auseinander gesetzt und wenn mans dann immer 
noch nicht hin bekam eventuell auch gelassen. Heute wird wegen wirklich 
jedem Furz nachgefragt, aber immer nur für die aktuelle 
Problemestellung, bloß nicht an sich selbst arbeiten, könnte ja in 
Arbeit ausarten. Arduino und co haben ihr übriges dazu getan das jeder 
der ne LED irgendwie zum blinken bekommen hat, danach meint ein 
Spaceshuttle zu bauen. Mit den Grundlagen anfangen, warum, er hat ja 
schon ne Led blinken lassen, das hat er nicht nötig...

> Gehört der uint16_t zum Sprachumfang von C? Ich brauche ein #include
> dafür. Also muß ich mit dem int weiterwursteln, das auf jeder Plattform
> eine andere Länge hat.

Das empfinde ich auch als eine der größten Unzulänglichkeiten von C. 
Aber es hält dich auch keiner davon ab festzustellen wie groß ein short 
auf deiner Zielplattform ist und den dann benutzen.

> Ja, ich habe manchmal Probleme mit C. Ich mache hobbymäßig 1 Projekt pro
> Jahr, und das auch abends zwischen 20:00 und 23:00 . Da bin ich nicht
> mehr so fit, und es ist mir lieber, wenns übersichtlich wär. Eine
> Tabelle aus BitSet kann ich da noch eher verstehen. Mit einer LookUp
> table, geschrieben vor 5 Jahren, die einer kleinen Änderung bedarf, wird
> es schon schwerer.
> Mache ich da etwas falsch, oder wie kann ich das besser machen?

Das ist eben deine Sichtweise, für mich gehört der Shift und das 
Bitweise Und genauso zum Programm wie die geschweiften Klammern um einen 
Funktionsblock. Wirklich aktiv wahr nehm ich die nicht mehr.

> Wenn ich es nerdig wollte, würde ich Z80 Assembler nehmen (nicht AVR,
> der ist nur grottig).

Also ich hab mit dem AVR Assembler überhaupt keine Probleme, benutz den 
aber auch nicht mehr sonderlich oft weil C selbst auf den kleinen Tinys 
das macht was ich will. Nur wenns mal wirklich ein exaktes Timing 
braucht wo ich keine Takte zu verschenken habe, oder ich irgendwelche 
schweinereien mache die man so nicht tun sollte (z.B. Missbrauch von ISR 
mit verändertem Rücksprungziel) hol ich den Assembler raus.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Zeno schrieb:
> Setze erst mal alle Ports auf 0 dann fällt schon mal der else Zweig weg

 Sicher.
 Auch wenn sich ein bestimmtes bit in Testbyte nicht ändert, bzw.
 dauernd 1 ist, wird am entsprechendem PortPin munter gewackelt...

 P.S.
 8 Abfragen, 8 rjmps und 8 Mal setzen oder rücksetzen ist auf jeden Fall
 dabei, ein guter Compiler macht das ohne Probleme und (fast) immer
 auf dieselbe Art und Weise, egal wie du die Abfrage in C schreibst.

: Bearbeitet durch User
von Markus (Gast)


Lesenswert?

Zum umordnen von bits kann evtl. auch dieses schöne GCC built-in 
praktisch sein:


https://gcc.gnu.org/onlinedocs/gcc-7.2.0/gcc/AVR-Built-in-Functions.html#AVR-Built-in-Functions
1
unsigned char __builtin_avr_insert_bits (unsigned long map,
2
                                         unsigned char bits,
3
                                         unsigned char val)

Der generierte Code ist nicht zwingend minimal (verwendet bst/bld 
Instruktionen), aber man bekommt einen relativ gut lesbaren C-Code ohne 
unendliche if-else und mask-shift Sequenzen.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Markus schrieb:
> Instruktionen), aber man bekommt einen relativ gut lesbaren C-Code ohne
> unendliche if-else und mask-shift Sequenzen.

 Auch deswegen gibt es funktionen wo man solch verwurschtelten Code
 ablegen und dokumentieren kann, ohne dass die main() Bettlakenlänge
 erreicht.

von Jörg B. (jbernau)


Lesenswert?

Hallo zusammen,

ein anderer Ansatz wäre einen Ports auf Variablen zu spiegeln:

static volatile uint8_t outRegister;

isr timerX_ovf_vect{
    // suche bit auf Vektor und schreibe asoziierten Ausgang
}


int main(void){
    // nutze outRegister wie ein Port Register
}

Das funktioniert, wenn die Ausgaben Zeit unktitisch sind und der TimerX 
schnell genug ist.

VG

Jörg

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Machen wir jetzt einen Wettbewerb um die unsinnigste Variante zu finden 
das miese Design noch mit dem schlechtesten Code zu verschlimmbessern?

von Hannes J. (hannes_j)


Lesenswert?

Wenn es unbedingt schneller sein soll, mach dir eine Lookup-Table.

von HildeK (Gast)


Lesenswert?

abc.def schrieb:
> Warum macht man das Geschwurbel eigentlich? Finde ich überall.
> ( Das ist eine ernstgemeinte Frage an die Community )

Man muss es nicht machen.
Im Forum gibt es von Peter D. die Datei sbit.h. Hier z.B.:
Beitrag "Re: Frage zu Struktur für IO-Port bei AVRs"
bzw. avrfreaks.net: 
https://www.avrfreaks.net/comment/711155#comment-711155

Ähnlich zu deinem Vorschlag wird dort dieses "Geschwurbel" dann sehr 
viel freundlicher in der Anwendung.

Ein eigenes Beispiel hatte ich mal hier genannt:
Beitrag "Re: Attiny85 Pin als Eingang"

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

HildeK schrieb:
> Ein eigenes Beispiel hatte ich mal hier genannt:
> Beitrag "Re: Attiny85 Pin als Eingang"
1
  if(LED1 == EIN) LED0 = EIN;  // auch Abfragen sind möglich

 Das ist genau dieselbe Abfrage die der TO hat.
 Und wenn man else dazuschreibt (was man sowieso muss), wird es
 auch genauso übersetzt.
 Und dass man anstatt 0b00000001 Pin_D7 und anstatt 0b10000000
 Pin_C0 (oder Taster_1) definieren (und schreiben) kann ist auch klar.

Philipp L. schrieb:
> Gibt es dazu eine effizientere Programmierung als diese:

 Nein.
 Als Erstes: Es ist überhaupt nicht unübersichtlich und unverständlich.
 Als Zweites: Es ist am effizientestem und wird vom Compiler zu
 kürzestem Code übersetzt.

: Bearbeitet durch User
von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Hab es mal zum Spaß zusammengebaut, alle Varianten bis auf die LUT 
landen bei einer SBRS, RJMP, SBI, RJMP, CBI Kette und brauchen 42 Takte, 
die mit 2 LUT braucht 25 Takte und auch minimal weniger Flash (6 Byte):
1
#include <avr/io.h>
2
#include <avr/pgmspace.h>
3
#include <stdint.h>
4
5
const uint8_t lut1[16] PROGMEM = { 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8 };
6
const uint8_t lut2[16] PROGMEM = { 0x00, 0x08, 0x04, 0x0C, 0x02, 0x0A, 0x06, 0x0E, 0x01, 0x09, 0x05, 0x0D, 0x03, 0x0B, 0x07, 0x0F };
7
8
9
static void portout2( const uint8_t byte ) {
10
11
  PORTD = (PORTD & 0x17) | pgm_read_byte( &lut1[ (byte & 0x0F) ] );
12
13
  PORTC = (PORTC & 0xF0) | pgm_read_byte( &lut2[ (byte >> 4) ] );
14
15
}
16
 
17
static void portout1( const uint8_t byte ) {
18
19
  if (byte & 0x01) PORTD |= (1 << PD7);
20
  else PORTD &= ~(1 << PD7);
21
  
22
  if (byte & 0x02) PORTD |= (1 << PD6);
23
  else PORTD &= ~(1 << PD6);
24
25
  if (byte & 0x04) PORTD |= (1 << PD5);
26
  else PORTD &= ~(1 << PD5);
27
28
  if (byte & 0x08) PORTD |= (1 << PD3);
29
  else PORTD &= ~(1 << PD3);
30
31
  if (byte & 0x10) PORTC |= (1 << PC3);
32
  else PORTC &= ~(1 << PC3);
33
  
34
  if (byte & 0x20) PORTC |= (1 << PC2);
35
  else PORTC &= ~(1 << PC2);
36
37
  if (byte & 0x40) PORTC |= (1 << PC1);
38
  else PORTC &= ~(1 << PC1);
39
40
  if (byte & 0x80) PORTC |= (1 << PC0);
41
  else PORTC &= ~(1 << PC0);
42
43
}
44
45
int main( void ) {
46
47
  while( 1 ) portout2( PINB );
48
49
  return 0;
50
51
}

Trotzdem würde ich eher die 1. Variante nehmen, da die 2. bei 
asynchronen Änderungen der übrigen Pins von Port C und D nicht sicher 
ist.

: Bearbeitet durch User
von Crazy Harry (crazy_h)


Lesenswert?

PortD:=(PinD and %11111011) or ((Testbyte and %00000001) SHL 2);

Bit2 ist das zu setzende/löschende Bit.
Das SHL um das Bit dem richtigen Bit des Ports zuzuordnen.

If ... then dauert doch relativ lange.

..... ich liebe Pascal :-)

: Bearbeitet durch User
von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Tim T. schrieb:
> Hab es mal zum Spaß zusammengebaut, alle Varianten bis auf die LUT
> landen bei einer SBRS, RJMP, SBI, RJMP, CBI Kette und brauchen 42 Takte,
> die mit 2 LUT braucht 25 Takte und auch minimal weniger Flash (6 Byte):

 Ja, aber nur weil der Compiler das ziemlich schlecht optimiert hat...
 if...then Variante wird 8 Mal zu folgenden Code übersetzt:
1
         sbrs  r25, 0
2
         rjmp  .+4        
3
         sbi  0x0b, 7
4
         rjmp  .+2    
5
         cbi  0x0b, 7       // 10 Bytes
 Das sind 8*10 Bytes = 80 Bytes.
 + 2(mov am Start) + 2(rjmp am ende) ergibt 84 Bytes.
 Ergibt 8*5 Takte bei setzen und 8*4 Takte bei rücksetzen des bits.

 Mit besserer Optimierung könnte das zu folgendem Code werden:
1
         sbrs  r25, 0
2
         cbi  0x0b, 7
3
         sbrc  r25, 0
4
         sbi  0x0b, 7        // 8 Bytes
 Das sind 8*8 Bytes = 64 Bytes.
 + 2(mov am Start) + 2(rjmp am ende) ergibt 68 Bytes.
 Und ergibt immer 8*4 Takte = 32 Takte.

 Dass die lut Tabellen kompliziert und auch kompliziert zu ändern sind,
 ist wohl klar.

Tim T. schrieb:
> Trotzdem würde ich eher die 1. Variante nehmen, da die 2. bei
> asynchronen Änderungen der übrigen Pins von Port C und D nicht sicher
> ist.

 Genau.
 Bei der if..then Variante können sich die übrigen Pins beliebig ändern.
 Zeitunterschied zwischen PORTD.7 und PORTC.0 beträgt max.
 7*4 Takte = 28 Takte oder 1.75us bei 16MHz, falls das in diesem Fall
 relevant sein sollte.

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Marc V. schrieb:
> Tim T. schrieb:
>> Hab es mal zum Spaß zusammengebaut, alle Varianten bis auf die LUT
>> landen bei einer SBRS, RJMP, SBI, RJMP, CBI Kette und brauchen 42 Takte,
>> die mit 2 LUT braucht 25 Takte und auch minimal weniger Flash (6 Byte):
>
>  Ja, aber nur weil der Compiler das ziemlich schlecht optimiert hat...
>  if...then Variante wird 8 Mal zu folgenden Code übersetzt:
>
1
>          sbrs  r25, 0
2
>          rjmp  .+4
3
>          sbi  0x0b, 7
4
>          rjmp  .+2
5
>          cbi  0x0b, 7       // 10 Bytes
6
>
>  Das sind 8*10 Bytes = 80 Bytes.
>  + 2(mov am Start) + 2(rjmp am ende) ergibt 84 Bytes.
>  Ergibt 8*5 Takte bei setzen und 8*4 Takte bei rücksetzen des bits.
>  Mit besserer Optimierung könnte das zu folgendem Code werden:
>
1
>          sbrs  r25, 0
2
>          cbi  0x0b, 7
3
>          sbrc  r25, 0
4
>          sbi  0x0b, 7        // 8 Bytes
5
>
>  Das sind 8*8 Bytes = 64 Bytes.
>  + 2(mov am Start) + 2(rjmp am ende) ergibt 68 Bytes.
>  Und ergibt immer 8*4 Takte = 32 Takte.

Hmm, ich komm da auf andere Werte:

Angenommen das Bit ist gesetzt, dann braucht sbrs 2 Takte, das cbi wird 
übersprungen, also 0 Takte, sbrc überspringt nicht, also 1 Takt und das 
sbi hat 2 Takte, macht bei mir in Summe 5 Takte, bei 8 Bits also 8 * 5 = 
40 Takte (Andersrum sinds natürlich auch 40 Takte). Wenn ich dann noch 
das mov am Anfang und den rjmp mitrechne, bin ich bei 43 Takten.

>  Dass die lut Tabellen kompliziert und auch kompliziert zu ändern sind,
>  ist wohl klar.

Jop, hatte nur mal bock es zu machen.

> Tim T. schrieb:
>> Trotzdem würde ich eher die 1. Variante nehmen, da die 2. bei
>> asynchronen Änderungen der übrigen Pins von Port C und D nicht sicher
>> ist.
>
>  Genau.
>  Bei der if..then Variante können sich die übrigen Pins beliebig ändern.
>  Zeitunterschied zwischen PORTD.7 und PORTC.0 beträgt max.
>  7*4 Takte = 28 Takte oder 1.75us bei 16MHz, falls das in diesem Fall
>  relevant sein sollte.

Nicht ganz (s.o.) aber vom Prinzip her richtig.

von Markus (Gast)


Lesenswert?

Mit __builtin_avr_insert_bits ist es IMHO gut lesbar und braucht nur 20 
Takte. PORTC/D lassen sich auch gut relativ synchron zuweisen (einfach 
statt direkter Zuweisung in temp. variable speichern)

avr-gcc -mmcu=attiny88 -O3 -Wall -Wextra -c -o foo.o foo.c:
1
#include <avr/io.h>
2
#include <inttypes.h>
3
4
void
5
pinout(uint8_t out)
6
{
7
    PORTD = __builtin_avr_insert_bits(0x0123ffff, out, PORTD);
8
    PORTC = __builtin_avr_insert_bits(0xffff4567, out, PORTC);
9
}

avr-objdump -d foo.o:
1
00000000 <pinout>:
2
   0:  9b b1         in  r25, 0x0b  ; 11
3
   2:  80 fb         bst  r24, 0
4
   4:  97 f9         bld  r25, 7
5
   6:  81 fb         bst  r24, 1
6
   8:  96 f9         bld  r25, 6
7
   a:  82 fb         bst  r24, 2
8
   c:  95 f9         bld  r25, 5
9
   e:  83 fb         bst  r24, 3
10
  10:  94 f9         bld  r25, 4
11
  12:  9b b9         out  0x0b, r25  ; 11
12
  14:  98 b1         in  r25, 0x08  ; 8
13
  16:  84 fb         bst  r24, 4
14
  18:  93 f9         bld  r25, 3
15
  1a:  85 fb         bst  r24, 5
16
  1c:  92 f9         bld  r25, 2
17
  1e:  86 fb         bst  r24, 6
18
  20:  91 f9         bld  r25, 1
19
  22:  87 fb         bst  r24, 7
20
  24:  90 f9         bld  r25, 0
21
  26:  98 b9         out  0x08, r25  ; 8
22
  28:  08 95         ret

Insgesamt 42 Byte Flash.

von spess53 (Gast)


Lesenswert?

Hi

>...bin ich bei 43 Takten.

Mit acht mal

bst Rd,b
bld Rd,b

bin ich bei 16 Takten. Dazu noch ein out/sts mit 1/2 Takten ist man bei 
15/16 Takten.

MfG Spess

von spess53 (Gast)


Lesenswert?

Hi

Korrektur:

Dazu noch ein out/sts mit 1/2 Takten ist man bei
17/18 Takten.

MfG Spess

von Jacko (Gast)


Lesenswert?

Na und?
ICH würde mich an hand-optimiertem ASM-Code erfreuen, aber ist
das nötig?

Entweder kann man sich "umständlich" leisten, dann darf jedes
Bit einzeln in grenzwertig schlechter Hochsprachen-Syntax
abgehandelt werden, oder es wird knapp, dann muss es "richtig
optimiert" worst case in ? µs erledigt sein.

Schau doch erst mal ganz gelassen, ob "umständlich" nicht
schnell genug ist.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Tim T. schrieb:
> Angenommen das Bit ist gesetzt, dann braucht sbrs 2 Takte, das cbi wird
> übersprungen, also 0 Takte, sbrc überspringt nicht, also 1 Takt und das
> sbi hat 2 Takte, macht bei mir in Summe 5 Takte, bei 8 Bits also 8 * 5 =
> 40 Takte (Andersrum sinds natürlich auch 40 Takte). Wenn ich dann noch
> das mov am Anfang und den rjmp mitrechne, bin ich bei 43 Takten.

 Du rechnest falsch.
1
                          bit=1 / bit=0
2
         sbrs  r25, 0       2   /   1
3
         cbi  0x0b, 7       0   /   1
4
         sbrc  r25, 0       1   /   2
5
         sbi  0x0b, 7       1   /   0


spess53 schrieb:
> Dazu noch ein out/sts mit 1/2 Takten ist man bei
> 17/18 Takten.

 2 * 10 Takte = 20 Takte + 2(mov) + 2(rjmp) = 24 Takte
 Und das Problem mit asynchronen Änderungen der übrigen Pins
 bleibt bestehen.


 Die ganze Diskussion ist aber umsonst.
 Die Variante mit if..else ist übersichtlich und schnell genug, alles
 andere ist entweder komplizierter, länger oder langsamer.

von Markus (Gast)


Lesenswert?

Jacko schrieb:
> Na und?
> ICH würde mich an hand-optimiertem ASM-Code erfreuen, aber ist
> das nötig?
>
> Entweder kann man sich "umständlich" leisten, dann darf jedes
> Bit einzeln in grenzwertig schlechter Hochsprachen-Syntax
> abgehandelt werden, oder es wird knapp, dann muss es "richtig
> optimiert" worst case in ? µs erledigt sein.
>
> Schau doch erst mal ganz gelassen, ob "umständlich" nicht
> schnell genug ist.

Also ich finde ja, dass zwei Zeilen C mit builtin sehr gut lesbar sind. 
Wird in diesem Fall sehr wahrscheinlich nichts effizienteres geben.

Man muss halt mal die Dokumentation zur Toolchain gelesen haben 
(RTFM...), damit man so was findet :-)

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Sbi und cbi sind 2 Takt Instruktionen, komisch, ist aber so...
1
                           bit=1 / bit=0
2
          sbrs  r25, 0       2   /   1
3
          cbi  0x0b, 7       0   /   2
4
          sbrc  r25, 0       1   /   2
5
          sbi  0x0b, 7       2   /   0

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Tim T. schrieb:
> Sbi und cbi sind 2 Takt Instruktionen, komisch

So komisch ist das nicht, es wird intern read-modify-write gemacht.
Ein OUT ist dagegen nur ein write.

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Tim T. schrieb:
>> Sbi und cbi sind 2 Takt Instruktionen, komisch
>
> So komisch ist das nicht, es wird intern read-modify-write gemacht.

Und genau das finde ich ja komisch, warum sind die Bits nicht in einzeln 
beschreibbaren FF?

> Ein OUT ist dagegen nur ein write.

Das ist auch klar.

: Bearbeitet durch User
von Zeno (Gast)


Lesenswert?

Marc V. schrieb:
> Zeno schrieb:
>> Setze erst mal alle Ports auf 0 dann fällt schon mal der else Zweig weg
>
>  Sicher.
>  Auch wenn sich ein bestimmtes bit in Testbyte nicht ändert, bzw.
>  dauernd 1 ist, wird am entsprechendem PortPin munter gewackelt...

Er hatte einen zweiten Ansatz gepostet, wo er vor jedem if auf Null 
setzt -warum auch immer. DAs hatte ich als Basis für meinen Post 
genommen.

Besser ist natürlich der Ansatz von Tim, da spart man sich if 
Gewurschtel und die Ports wackeln nur wenn sie wackeln sollen.

von Rangi J. (rangi)


Lesenswert?

Es wird vermutlich nicht viel besser gehen, aber es gibt noch ne Lösung 
mit einer LookUpTable. 2 x 256 Byte, eins für Port C und eins für Port 
D. Wenn du noch genügend Flash übrig hast.
Ich hatte mal das Problem, aber da waren die Bits innerhalb eines Ports 
verdreht. Da ging das sehr gut und es war nichtmal maskieren nötig. Das 
müsstes du hier allerding machen wenn die anderen Bits der Ports noch 
anderweitig in Verwendung sind.

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Tim T. schrieb:
> Peter D. schrieb:
>> Tim T. schrieb:
>>> Sbi und cbi sind 2 Takt Instruktionen, komisch
>>
>> So komisch ist das nicht, es wird intern read-modify-write gemacht.
>
> Und genau das finde ich ja komisch, warum sind die Bits nicht in einzeln
> beschreibbaren FF?

Ja, manchmal sollte man eventuell mal nen Moment länger drüber 
Nachdenken, dann wäre mir direkt aufgefallen das so eine Schaltung 
natürlich aufwändiger ist und mehr Platz im Chip braucht. Ich gehe mal 
davon aus das ein SBI intern nur den Port mit einem Register verODERt 
worin das entsprechende Bit gesetzt ist und ein CBI es mit einem 
invertiert gesetztem Register verUNDet?!

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

Rangi J. schrieb:
> Es wird vermutlich nicht viel besser gehen, aber es gibt noch ne Lösung
> mit einer LookUpTable. 2 x 256 Byte, eins für Port C und eins für Port
> D. Wenn du noch genügend Flash übrig hast.
> Ich hatte mal das Problem, aber da waren die Bits innerhalb eines Ports
> verdreht. Da ging das sehr gut und es war nichtmal maskieren nötig. Das
> müsstes du hier allerding machen wenn die anderen Bits der Ports noch
> anderweitig in Verwendung sind.

Die LUT müssen garnicht so groß sein, 2*16 Byte reichen und diese Lösung 
steht auch schon etwas weiter oben, ist aber langsamer als das GCC 
__builtin_avr_insert_bits bzw. die Bitstore/Load Geschichte...

: Bearbeitet durch User
von georg (Gast)


Lesenswert?

Tim T. schrieb:
> Und genau das finde ich ja komisch, warum sind die Bits nicht in einzeln
> beschreibbaren FF?

Weil du dir den falschen Prozessor ausgesucht hast - die meisten haben 
keine 1-Bit-Ports, sondern nur 8bit oder mehr. Ausnahme sind z.B. 
8051-Derivate. Wenn es dir zu viel Mühe ist oder du es nicht hinkriegst 
die Bits entsprechend zusammenzusetzen musst du eben den Prozessor 
wechseln.

Georg

von Tim T. (tim_taylor) Benutzerseite


Lesenswert?

georg schrieb:
> Tim T. schrieb:
>> Und genau das finde ich ja komisch, warum sind die Bits nicht in einzeln
>> beschreibbaren FF?
>
> Weil du dir den falschen Prozessor ausgesucht hast - die meisten haben
> keine 1-Bit-Ports, sondern nur 8bit oder mehr. Ausnahme sind z.B.
> 8051-Derivate. Wenn es dir zu viel Mühe ist oder du es nicht hinkriegst
> die Bits entsprechend zusammenzusetzen musst du eben den Prozessor
> wechseln.
>
> Georg

Oh Mann, immer wieder erstaunlich wie partiell manche Leute 
Informationen verarbeiten. Ist es echt so ungewöhnlich mal den ganzen 
verdammten Thread zu lesen bevor man seinen Senf dazu senftet?

von Markus (Gast)


Lesenswert?

Peter D. schrieb:
> Tim T. schrieb:
>> Sbi und cbi sind 2 Takt Instruktionen, komisch
>
> So komisch ist das nicht, es wird intern read-modify-write gemacht.
> Ein OUT ist dagegen nur ein write.

So einfach ist das nicht, denn sbi/cbi verhalten sich äußerlich bei 
mittlerweile nicht mehr ganz so neuen AVRs (~neuer als ATmega8) nicht 
immer wie RMW. Intern ist es evtl. RMW, aber das Verhalten passt nicht 
dazu.

Wer z.B. einen Ausgang togglen möchte, kann
1
PORTD ^= _BV(0);
nehmen und bekommt eine in/eor/out Sequenz, oder man nimmt
1
PIND = _BV(0);
und bekommt ein
1
sbi PIND, 0

Dadurch wird nur das PIND0 bit getoggelt. Würden sbi/cbi einen 
"normalen" RMW ausführen und andere bits in PIND wären 1, dann würden 
die ebenfalls getoggelt. Der Spezialfall hier ist also, dass die PINx 
Register in der Lesephase durch sbi/cbi immer 0 zurückgegeben. sbi/cbi 
selbst machen also evtl. tatsächlich RMW, aber es sieht nicht bei allen 
Registern so aus.

von Markus (Gast)


Lesenswert?

Markus schrieb:
>
1
PIND = _BV(0);

gemeint war natürlich
1
PIND |= _BV(0);

und es geht natürlich nur mit sbi, nicht cbi.

von Philipp L. (viech)


Lesenswert?

Ich danke euch.

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.