Hallo Leute, ich habe einen Chip, den ich digital auslese. Man gibt ihm einen Takt und er gibt 18bit aus. Ob high oder low lese ich am PORTpin DO1 von PORTC ab. (es ist ein AS5045 chip, magnetic rotary encoder) Ich habe definiert: uint32_t data, aux; for (dIndex=11; dIndex>=0; dIndex--) {//readout position data aux = (PINC & (1<<DO1))>>DO1; // erzeugt ne 1 oder 0 als 32bitinteger je nach DO1-Pinstatus data |= aux<<dIndex; // 1 or 0 pushed left by dIndex positions ... } es funktioniert so wie ich will, sprich die Logik stimmt. Dies ist meine "optimalste" Variante. Vielleicht geht es ja noch besser. Wer kann mir sagen wie ich dies 2 Zeilen moeglichst hardwarefreundlich umgestalte, sprich in moeglichst wenigen Prozessortakten abarbeite. Laut Disassembler von AVR Studio entstehen fuer die beiden Zeilen innerhalb der Schleife satte 29 Assemblerbefehle. vielleicht hat jemand eine ganz andere Idee, wie man das verbessern kann. Anderes Prinzip, etc... danke schonmal, mg, Johannes
Ohne Garantie dass es stimmt (weil nicht getestet): for (dIndex=11; dIndex>=0; dIndex--) {//readout position data if(PINC & (1<<DO1)) data|=1; data<<=1; } kleine Frage noch: data ist hier doch maximal 11 Bit lang (dIndex = 11), warum nimmst du dann keine 16 Bit Variable?? Oder überseh ich da jetzt was? mfg
Hallo Johannes Vielleicht so, wenn ich richtig verstanden habe.. unsigned long Read_AS5045(void) { unsigned long Data = 0; PinClk = 0; for (char i=...; i>= 0; i--) // wie viele Bits..11, 18, ..? { PinClk = 1; // setze Takt Data = Data | PinData; //lies Daten PinClk = 0; // reset Takt Data = Data << 1; // Daten nach links schieben } return Data; } Ich weiss auch nicht wie effizient Dein Compiler die Anweisungen übersetzt. Es sind aber lauter elementare Befehle Beste Grüsse Geri
.. jetzt ist mir Michael noch zuvor gekommen...:) Die Lösung sieht aber auch etwas anders aus. Falls nur 11 Bit zum Auslesen sind, dann reicht eine 16-Bit-Variable. Je nach Hardware müsste man vor dem Setzen von PinClk die Daten zuerst auslesen. Kenne das Verhalten Deiner Hardware aber nicht. Je nach Compiler müsste man bei Data = Data| (unsigned long) PinData; eine Typenkonvertierung vornehmen. Die Abfrage, wie sie Michael gemacht hat, würde ich in diesem Fall nicht machen, weil man vergleichen und dann u.U. springen muss. Das ist nicht notwendig Beste Grüsse Geri
Danke Geri, danke Michael! meine 11 ist ein Fehler fuer meine Frage. Es haette 17 heissen sollen. Die 11 ruehrt daher, dass ich versucht hatte die insgesamt 18bit in zwei 16bit Variable zu lesen. Beim Schiften mussen dann jeweils nur 2 Register geschoben werden, nicht 4. Dabei kommt man schon auf eine deutliche Zeitverbesserung, obwohl der C Code doppelt so lang ist, zwei for-Schleifen benoetigt werden... Geri: deine Loesung sieht nicht schlecht aus. PinData muesste ich nur mit PINC2 ersetzen (in meinem speziellen Fall). Ich probiers mal aus. Michael: in C ganz schoen kurz. Mal sehen was mein AVR-GCC draus macht. dank, mg, Johannes
Hallo Johannes ..bitte gerne. Lass uns das Ergebenis bitte wissen Die binären operationen lassen sich selbstverständlich auch abkürzen, wie Michael schön aufgezeigt hat. Beste Grüsse Geri
Hallo, habe jetzt nach dem (Geri-)Muster "Data = Data | PinData; //lies Daten" und der mit (Michael-)Sptimierung :) folgende Zeile versucht: data |= PINC2<<dIndex; Aber es funktioniert nicht. Was fuer einen Variablentyp hat PINC2? Ich denke, ohne eine zusaetzaliche Operation in der ich die 0x0001 erzeuge, die ich verschieb und mit data veroder gehts doch nicht. Auch im AVR-GCC Tutorial wird PINC2 nicht einfach abgefragt. Dort wird auch ein if verwendet und dementsprechend agiert. Vielleicht ist Michaels Variante doch simpler. Johannes
.. wie gesagt, ich kenne Deinen Compiler nicht. Entweder es ist eine Typenkonvertierung notwendig PinData hat den Datentyp Bit Data |= (unsigned long) PinData; // auf diese Weise könnte es evtl. gehen. Bzgl. Schieben: Je nach verwendeter Hardware ist das Schieben nach mehreren Bits nicht gereade effizient. Nicht jede CPU kann das. Das oben aufgezeigte Prinzip stimmt aber. Beste Grüsse Geri
Hallo, also ich bin ein wenig "enttauscht" ... Ich dachte mein uC ist besser... Folgender Befehl fuer eine 16bit Variable data |=1; benoetigt 9 TaktZyklen. data<<=1 benoetigt gar 10. Tja, so ideal wie an der Uni in einer Vorlesung ist die Welt eben nicht. Mit der Methode von Michael redizier ich die Performance meines Codes von 25 Taktzyklen auf 17. Immerhin einiges gewonnen. Wenn man noch mehr ausquetschen will muss man warscheinlich alles selbst in Assembler machen... Die Variante mit PINC2 hab ich nicht ganz durchgechecked. Was ist PINC2 fuer ein Variablentyp. Klar ist, er wird um dIndex nach links geschoben. dIndex aendert sich bei jedem Durchlauf, der uC muss in Abhaengigkeit davon mehr oder weniger schieben, das heisst er muss testen und dann auch springen. Es tauchen RJMP Befehle auf. Insofern stimmt es nicht, dass es nur elementare Befehle sind. Jedenfalls vielen Dank Jungs! Gruss aus HongKong, Johannes
Hi Geri, das Prinzip ist natuerlich richtig. Ob mein ATMega16 effiziente HArdware ist beim schieben um mehrere bits auf einmal weis ich nicht. Jedenfalls muss man bei Michaels Methode immer genau eine Stelle schieben. Das besser. Johannes
Hallo Johannes "Die Variante mit PINC2 hab ich nicht ganz durchgechecked. Was ist PINC2 fuer ein Variablentyp. Klar ist, er wird um dIndex nach links geschoben. dIndex aendert sich bei jedem Durchlauf, der uC muss in Abhaengigkeit davon mehr oder weniger schieben, das heisst er muss testen und dann auch springen. Es tauchen RJMP Befehle auf. Insofern stimmt es nicht, dass es nur elementare Befehle sind. " PinClk = 1; // setze Takt Data = Data | PinData; //lies Daten PinClk = 0; // reset Takt Data = Data << 1; // Daten um 1 nach links schieben PINClk ist vom Datentyp Bit. Er wird nicht um dIndex nach links geschoben! Es wir immer um 1 bit geschoben. dIndex kommt im Code ja gar nicht vor:) Beste Grüsse Geri
"... in der Eile des Gefechts, sieht er nicht nach links und rechts! ..." da hab ich ja einiges durcheinandergebracht. Sorry fuer die Defaimierung. " data |= PINC2; " bringt bei mir in der Simulation im AVR Studio nicht das gewuenschte Ergebnis. Ob ich das PINC2 an oder ausklicke es wird immer eine 2 addiert(0b00000010) und dann geschoben. Wo ist der Datentyp Bit definiert? hab ich noch nie gehoert? hab immer nur mit 8bit Vars gearbeitet soweit ich mir dessen bewusst bin. mg, Johannes
Hallo Johannes Datentyp bit wird nicht von allen Compiler unterstützt. Aber z.B. von Keil (C167 etc...) oder Sourceboost (PIC). In Deinem Fall steckt hinter PinC2 eine unsigned char Variable und s.w. wird ein 8-bit-Port hier gleichzeitig eingelesen. in diesem Fall ist das Demaskieren mit anschliessnder Und-Verknüpfung notwndig - so wie Michael aufgezeigt hat " if(PINC & (1<<DO1)) " oder alternativ legst du Deinen Datenpin hardware-seitig auf Pin 0 und schreibst: Data = Data | (unsigned long) (DataPin & 0x01); Dann kannst du dir immer noch die Abfrage sparen. Was nun effizienter ist, musst du mal prüfen:) Dieser Weg ist dann halt ein wenige unflexibler, weil der Eingangspin am niedrigst signifikaten Bit liegen muss. Beste Grüsse Geri
Data = Data | (unsigned long) (DataPin & 0x01) Abfrage von bit 0 Data = Data | (unsigned long) (DataPin & 0x80) Abfrage von bit 7 Unflexibel ist dieser Weg doch nicht. Mit der Verundung kann man jedes Bit rausfischen. MW
Hallo Michael In Johannes seinem Fall schon, weil er das untereste Bit laufend einschieben muss. Wenn das bitt höhe signifikat ist, dann muss man es zuerst nach rechts schieben. Das erfordert wieder etwas Zeit. Beste Grüsse Geri
Wenn du den AVR-Gcc verwendest ist PINC2 eine konstante zahl. Im Prinzip steht das nur für (1<<2). Du könntest stattdessen auch PIND2 schreiben, die Nummerierung ist unabhängig vom Port. Desahlb musst du um den konkreten Wert herauszufidnen PORTC & PINC2 schreiben. Versuch bei meinem Code mal das Data|=1 duch ein Data++ zu ersetzen. Viell ist das noch ein bisschen schneller. Der Grund warum der Compiler so viel Assembler erzeugt ist einfach der, dass AVRs (die Mega und At90 ... Serie) intern alles mit 8 Bit Variablen rechnen. Da du aber 16 bzw 32 Bit Variablen benutzt gibt das einen relativ großen Overhead. Wenn dich das "if" stört, versuchs mal damit:-) for (dIndex=11; dIndex>=0; dIndex--) {//readout position data data |= ( (PINC & (1<<DO1)) >> D01 ); data<<=1; } mfg
Man kann auch ohne Schieben auskommen. Angenommen, man möchte im Ergebnis Bit 0 setzen, wenn in der Quelle Bit 5 gesetzt ist: Data |= (Quelle & 0x20) ? 1 : 0; (oder für die Bitzählfreunde unter uns) Data |= (Quelle & (1 << 5)) ? 1 : 0; Wobei man sich natürlich schon die Effizienz des erzeugten Codes anschauen sollte - wenn der Prozessor beim Schieben Äonen benötigt, dann sehe ich Chancen für den ?-Operator. Mit dem gleichen Konstrukt lassen sich auch beliebige andere Kombinationen ohne Unterschiede in der Ausführungszeit hinbekommen: Setzen von Bit 6 im Ergebnis bei gesetztem Bit 3 in der Quelle: Data |= (Quelle & 0x08) ? 0x40 : 0; resp. Data |= (Quelle & (1 << 3)) ? (1 << 6) : 0; Habe hier gerade keinen Compiler verfügbar, vielleicht mag ja mal wer so eine Zeile übersetzen und die benötigten Takte vergleichen mit der klassischen Schiebevariante.
Hallo Rufus @Rufus: "Data |= (Quelle & 0x20) ? 1 : 0;" Die Verwendung des Bedingungsoperator "?" ist programmtechnisch schön weil man unabhängig von der Pinbelegung wird, wenn man 0x20 als Konstante definiert. Der Code wird auch schön kurz. Im asm-code wird dann aber glaube ich wieder ein jump stehen. Bin aber auch auf dei Performance gespannt... Beste Grüsse Geri
Die Variante von Michael ist mit Abstand die effizienteste, da ein Bit im Register zu setzen nur ein Befehl ist und somit der Eingangspin direkt mit einer SBIC-Instruktion gelesen werden kann. Ein Fehler ist aber drin, man muß zuerst schieben und dann verodern, sonst ist das Ergebnis immer * 2. Und irgendwo muß man auch noch den Taktpin togglen, je nach Flanke, nach der der Datenpin gültig ist. Allgemein sind direkte if ohne else-Zweig optimal (spart mindestens einen Sprung ein). Der ?: Operator wird vom AVR-GCC sehr schlecht optimiert, sieht also nur schön kurz aus, isses aber nich. Ein Mensch würde den "x |= 0" Zweig komplett wegoptimieren, der GCC führt in brav aus. Peter
Ich frage mich aber, was es für einen Sinn macht den Code solange umzustellen, bis er a) nicht mehr lesbar ist b) vom Compiler in Version x.y.z toll optimiert wird c) vom Compiler in Version y.z.x total anders verstanden wird Warum dann nicht lieber gleich in Assembler? Wozu überhaupt eine Hochsprache benutzen? In Wirklichkeit bescheißt man sich doch nur selber. Wer soll das ganze Geraffel denn nachher wirklich verstehen? Ich persönlich würde immer den sauberen C-Code bevorzugen. Sauberer Code optimiert meiner Meinung nach auch am besten. Wenn der Compiler das nicht sauber genug hinbekommt, dann sollte man den Teil lieber gleich in Assembler codieren. Mein Tip: der bessere C-Code ist der, den Mensch auch sofort verstehen kann. mfg, Stefan.
"Ich frage mich aber, was es für einen Sinn macht den Code solange umzustellen, bis er a) nicht mehr lesbar ist b) vom Compiler in Version x.y.z toll optimiert wird c) vom Compiler in Version y.z.x total anders verstanden wird " Das würde ich auch nicht wollen, deshalb ja mein Tip mit Michaels Code. Der ist alles zugleich, kurz, gut zu verstehen und gut zu compilieren. Nur weil man einen Code das erste mal hinschreibt, muß er noch lange nicht besser verstehbar sein. In der Regel ist aber kürzerer Code auch besser verstehbar. Software-SPI ist ne Sache, die alle Nase lang vorkommt, da lohnt es sich schon, den Code besser zu formulieren. Peter
@Peter: Das Optimierungsverhalten von gcc ließe sich also durch leichtes Umstellen meines Ansatzes erhalten. Statt Data |= (Quelle & 0x20) ? 1 : 0; nunmehr if (Quelle & 0x20) Data |= 1; Natürlich ist auch hier der generierte Code interessant, den ich mangels akutem Zugriff auf einen Compiler wiederholt anderen zur Untersuchung überlassen muss.
Noch eine Meinung dazu: Ich schreibe bei einem Zugriff auf einen speziellen Chip doch immer gerne eine passende Assembler-Routine in eine eigene Datei. Pro: -> modular = wiederverwertbar -> das geht mit dem GCC sehr schön -> gleichzeitig Interrupts sperren, kannst Du besser oszilloskopieren -> Timing absolut nachvollziehbar -> brauchst Du nicht rätselzuraten über Compileroptimierungen -> ist zwar nicht so schön portabel, aber man ist sowieso so nahe an der Hardware, dass es nun mal sehr speziell ist.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.