Gast
#1568933
Ich arbeite mit GCC auf eine AtMega325P. Das toggeln mittels PIN geht bei dem Prozessor. Wie setzte ich das korrekt in C um? Muß ich das mit inline assembler abarbeiten?
|
Anzeige
|
GCC: Pin toggeln in einem Takt?
Gast
#1568933
Ich arbeite mit GCC auf eine AtMega325P. Das toggeln mittels PIN geht bei dem Prozessor. Wie setzte ich das korrekt in C um? Muß ich das mit inline assembler abarbeiten? zb. so:
Gast
#1568960
>PORTC ^= (1<<PC2); >PINC = 1 << PC2; Beides benötigt sichern nur 1 Takt? Nimm zweiteres. Ob der gcc soweit ist, dass er zweiteres als schnelleren Ersatz für ersteres erkennt, müsste man durch Studium des erzeugten Assembleroutputs rausfinden.
Gast
#1568967
>PINC = 1 << PC2;
Wenn ich mich recht erinnere setzt der Compiler das in "sbi" um.
Wie müsste denn das ganze mit inline assembler aussehen?
gf schrieb: >>PINC = 1 << PC2; > > Wenn ich mich recht erinnere setzt der Compiler das in "sbi" um. > Wie müsste denn das ganze mit inline assembler aussehen? Wozu willst du inline Assembler? sbi ist doch OK. Der springende Punkt ist ja nicht die Operation, sondern dass sie auf das PIN Register angewendet wird. Und das ist dann so definiert, dass eine Zuweisung eines 1-Bits an das Pin Register das entsprechende Bit im Port Register toggelt.
Gast
#1568972
gf schrieb:
> Beides benötigt sichern nur 1 Takt?
Nein, es sind 4 bzw. 2. In 1 Zyklus geht es mit:
auch wenn es unlogisch aussieht, aber s. Datenblatt.
Gast
#1568976
Hm, im Daten blatt steht halt SBI --> 2 Takte. Warum sollte das bei einem PIN Register nur einen Takt brauchen?
Gast
#1568977
Daten blatt = Datenblatt
Gast
#1568981
>PINC |= PC2;
Interessant. Was macht der Compiler da draus?
gf schrieb: >>PINC = 1 << PC2; > > Wenn ich mich recht erinnere setzt der Compiler das in "sbi" um. Das wäre dann ein Bug. SBI macht keine Zuweisnung, sondern ein ODER. Die erstes Ausführung kostet 2 Zyklen, da ein Register geladen wwerden muß. Peter
Gast
#1568984
> Hm, im Daten blatt steht halt SBI --> 2 Takte. Upps, stimmt. Dann geht es in einem Takt wohl nicht. Aber immerhin ist die |=-Methode die Platz sparendste, da sie nur 1 Befehl statt 2 oder 4 braucht. > Interessant. Was macht der Compiler da draus? Eben auch SBI :-/
Gast
#1568989
Wenn du Jörgs
in einer Schleife ausführst, ist die Chance groß, dass der Ladebefehl vor die Schleife gezogen wird, so dass für die Anweisung innerhalb der Schleife effektiv nur 1 Zyklus benötigt wird. Dasselbe gilt, wenn mehrere dieser Anweisungen hintereinander geschrieben werden.
Gast
#1568992
könnte so was gehen?
gf schrieb:
> könnte so was gehen?
Nein, die Syntax allein krankt hinten und vorn.
Sorry, lass die Finger vom Inline-Assembler, der ist nicht für dich
gemacht, und er ist zum Glück auch gar nicht notwendig. Wenn du
zwei Pins (und nicht eins, wie du eingangs schriebst) auf einmal
toggeln willst, dann schreibe doch bitte
oder wenn du die symbolische Schreibweise nicht magst
Schluss, aus, mehr brauchst du nicht (außer dem Einschalten der Compiler-Optimierung natürlich, aber wenn du die nicht einschaltest, stellt sich die Frage nach dem Zeitverhalten der Applikation gleich gar nicht). yalu's
ist allerdings nicht das, was du willst.
Gast
#1569039
>PINC = 3;
Das sind aber wieder 2 Takte oder ??
Was hast du eigentlich dauern mit deinen Takten? Wenn du eine Schwingung erzeugen willst, dann musst du das Ganze sowieso in eine Schleife packen und da wird dir die Schleife selbst wieder Takte dazubringen. Und höchst wahrscheinlich sogar mehr als das Port toggeln ausmacht. Ansonsten: Compilier es, schau dir das Assembler Listing an, hol dir das Datenblatt und zähl die Takte. gf schrieb: >>PINC = 3; > > Das sind aber wieder 2 Takte oder ?? Wie dir yalu schon schrieb, wenn du das in einer Schleife ausführst, wird das Laden des Registers außerhalb der Schleife erfolgen:
ergibt:
Die Frage ist natürlich, was du genau erreichen willst. Dafür müsstest du uns mehr von deinem Kontext erzählen. Selbst, wenn du endlos in einer Schleife ein Pin toggeln willst (wie hier gezeigt), brauchst du natürlich noch zwei Takte für eine Rücksprung in der Schleife. Eine limitierte Schleife kann man mit entsprechen- der Optimierung noch komplett entrollen lassen:
ergibt mit -O3
Natürlich hättest du das auch alles selbst probieren können... [Edith: da war Karl Heinz mal wieder schneller.]
Gast
#1569088
O.K. Erst mal zum Hintergund meiner Frage. Ich versuche mehrere MByte Daten per USB Bus an den Rechner zu übertragen. In einer Schleife wird nun ein externer AD-Wandler gestartet, dieser ausgelesen, die Umschaltung von analogen Quellen getätigt, die Daten in ein FIFO getaktet und noch einige mehr... Viele Aktionen bestehen dabei einfach daraus einen CLock für einen Baustein (z.B. ADC) zu erzeugen, was im allgemeinen mind. 4 Takte benötigt (SBI und CBI). Meine Intension ist die Taktzahl pro Schleifendurchgang zu minimieren. In der Schleife müssen 4 Pins an PortB z.T mehrfach getoggelt werden. Aber du willst nicht zufällig eine Standard-SPI in Software zimmern?
Gast
#1569118
Jörg Wunsch schrieb: > yalu's > PINC |= PC2; > ist allerdings nicht das, was du willst. Wieso nicht (abgesehen von der Zyklenzahl)? Falls du darauf hinaus wolltest, dass der vom Compiler generierte SBI-Befehl auch andere als das gewünschte Bit in PORTC toggelt: "Writing a logic one to PINxn toggles the value of PORTxn, independent on the value of DDRxn. Note that the SBI instruction can be used to toggle one single bit in a port." Deswegen schrieb ich oben, dass das die Anweisung zwar unlogisch aussieht, man aber ins Datenblatt schauen möge. Es soll AVRs geben, die zwar das Toggeln über PIN unterstützen, der SBI-Befehl aber mehr als ein Bit ändert, s. hier (nach "categories" suchen): http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=38612&start=0 Ich kenne allerdings keinen, auf den das zutrifft. yalu schrieb: > Jörg Wunsch schrieb: >> yalu's >> PINC |= PC2; >> ist allerdings nicht das, was du willst. > > Wieso nicht (abgesehen von der Zyklenzahl)? Weil du da in dieselbe Falle tappst, die auch beim Rücksetzen der Interrupt Flags besteht. Die von dir vorgeschlagene Anweisung versucht nicht ein einzelnes Bit zu schreiben. Du holst dir den momentanen Wert des Pinregisters, setzt noch 1 Bit dazu und schreibst das wieder auf das Pin Register. Abhängig vom tatsächlichen Zustand des Pin Registers vor der Operation, versuchst du daher unter Umständen mehrere Bits auf einmal auf 1 zu setzen und toggelst daher auch mehrere Bits. Genauer gesagt sollte PINC |= irgendwas; zur Folge haben, dass nebenbei alle Bits ausser "irgendwas" gelöscht werden. Das zumindest sagt die sich aus der Doku ergebende Logik. Weil alle Bits, die vorher gesetzt waren, als 1 wieder zurück geschrieben und damit gelöscht werden. Andererseits wird in der Doku nahegelegt, dass sbi PINC,0 nur Bit 0 toggelt und den Rest intakt lässt. Wenn das zutrifft, dann ist das eine Spezialbehandlung des SBI Befehls und das Ergebnis von PINC |= irgendwas; hängt nun davon ab, ob der Compiler SBI oder IN/ORI/OUT erzeugt. Der Compiler müsste deshalb eigentlich darauf verzichten, bei |= mit passendem Wert den Befehl SBI statt IN/ORI/OUT zu erzeugen. Das gefällt mit nicht wirklich.
Gast
#1569211
Kann mir mal bitte einer die korrekte Syntax zu obigen Zeilen korrigieren? Ich sehe hier die einzige chance einen "1-Takt toggle" zu realisieren. Danke...
Gast
#1569240
Karl heinz Buchegger schrieb: > Abhängig vom tatsächlichen Zustand des Pin Registers vor der > Operation, versuchst du daher unter Umständen mehrere Bits auf einmal > auf 1 zu setzen und toggelst daher auch mehrere Bits. Das war in früheren Zeiten auch meine Befürchtung bis ich den entsprechenden Abschnitt im Datenblatt gelesen habe (s. mein letzter Beitrag), wo explizit geschrieben steht, dass man den SBI-Befehl verwenden kann. Und da der Compiler ioreg|=1<<x in einen solchen umsetzt, funktioniert das auch. Der SBI-Befehl scheint in diesem Fall also keine gewöhnliche Read- Modify-Write-Sequenz auszuführen, sondern irgendetwas Spezielles zu tun. A. K. schrieb: > Der Compiler müsste deshalb eigentlich darauf verzichten, bei |= mit > passendem Wert den Befehl SBI statt IN/ORI/OUT zu erzeugen. Eigentlich ja. Auf der anderen Seite fände ich es ganz ok, wenn nichts geändert wird, weil man sonst tatsächlich Inline-Assembler bräuchte, um diesem Befehl zu erzwingen. Ich glaube auch nicht, dass dies den C-Standard verletzen würde: Es könnte ja sein (und vielleicht wird es auch tatsächlich so), dass die |=-Operation nicht ein komplettes Byte, sondern nur die zu verändernden Bits zurückschreibt. @gf: Bitte entschuldige das Offtopic gf schrieb:
>
> > > Kann mir mal bitte einer die korrekte Syntax zu obigen Zeilen > korrigieren? Ich sehe hier die einzige chance einen "1-Takt toggle" zu > realisieren. Und wie soll dieser 1-Takt toggle denn deiner Meinung nach funktionieren? Ob du jetzt den out machst, oder den Compiler den aus deinem C-Code generiert, spielt so gut wie keine Rolle, denn um die Zuweisung von 0x01 zu Wert kommst auch du nicht vorbei. Und ob DU jetzt im C-Code ein Register mit einem Wert befüllst, oder ob sich der Compiler das aus PINB = 0x01; generiert, ist Jacke wie Hose.
Gast
#1569256
> void funktion(void) > { > register unsigned char wert; register ist nur ein Hinweis, den der Compiler für gewöhnlich ignoriert. Der entscheidet selbst, welche Variablen er in Register steckt. > wert = 0x03; > asm volatile ("out PINB, wert"::) > } > > Kann mir mal bitte einer die korrekte Syntax zu obigen Zeilen > korrigieren? Ungefähr so:
Das garantiert dir aber nur, daß die asm-Zeile selbst in einem Takt ausgeführt wird. Der Compiler könnte trotzdem einen Ladebefehl direkt davor setzen, um wert aus dem Speicher zu lesen. Allerdings dürfte das ziemlich unwahrscheinlich sein.
Gast
#1569261
Karl heinz Buchegger schrieb: > Und ob DU jetzt im C-Code ein Register mit einem Wert befüllst, oder ob > sich der Compiler das aus > > PINB = 0x01; > > generiert, ist Jacke wie Hose. Nein !!! Aus deinem PINB = 1; macht der Compiler ein SBI. Damit komm ich nie auf eine Takt. Das geht nur mit OUT! gf schrieb: >> PINB = 0x01; >> >> generiert, ist Jacke wie Hose. > > Nein !!! > > Aus deinem PINB = 1; macht der Compiler ein SBI. Dann ist der Compiler fehlerhaft. Aus einem PINB = 0x01; KANN der Compiler keinen einzelnen sbi machen, weil die C-Anweisung offensichtlich den kompletten Port beeinflusst. Die Bits 7 bis 1 auf 0, Bit 0 auf 1. Das steht so im C-Code und ich wüsste jetzt nicht, wie der Compiler das mit cbi und sbi so umsetzen könnte, dass ein ordinärer out performancemässig nicht alles andere in die Tasche stecken würde. yalu schrieb: > weil man sonst tatsächlich Inline-Assembler bräuchte, um > diesem Befehl zu erzwingen. Und genau diesen Inline-Assembler benötigt man nun, um die SBI Operation zu erzwingen, weil bei IN/ORI/OUT etwas anderes herauskommt und man nur in Assembler sicher sein kann, dass wirklich SBI erzeugt wird. Anders ausgedrückt: Finger weg von PINx |= mask; Ist ohnehin selten sinnvoll, PINx = mask; tut es auch und ist eindeutig. > Ich glaube auch nicht, dass dies den > C-Standard verletzen würde: Da volatile uint8_t a; a |= b; per Definition identisch ist mit a = a | b; und auch mit volatile uint8_t one = 1; a = (a * one) | b; und spätestens darin a geschlossen gelesen und geschrieben wird, würde ich schon sagen, dass man hier quer zum Standard liegt. Aber da 8051 bekanntermassen als Standard gilt, wenn auch nicht für C, bewegt man sich so eben vom Standard "C" zum Standard "8051", denn dort hatte schon immer dieses Problem und scheinbar stört es dort niemanden.
Gast
#1569280
Karl heinz Buchegger schrieb: > KANN der Compiler keinen einzelnen sbi machen, weil die C-Anweisung > offensichtlich den kompletten Port beeinflusst. Die Bits 7 bis 1 auf 0, > Bit 0 auf 1. Das steht so im C-Code und ich wüsste jetzt nicht, wie der > Compiler das mit cbi und sbi so umsetzen könnte, dass ein ordinärer out > performancemässig alles andere in die Tasche stecken würde. Hm ... da geb ich dir recht. O.K. ich probier das ganze heute abend noch mal aus. @Rolf Danke, ich versuch mal ob ichs damit hin bekommme. Der Funktionsaufruf und der Funktionsprolog/-epilog fressen doch mehr Takte als 1 Takt. Also Funktion weglassen und stattdessen ein Makro benutzen. Generell: * http://www.nongnu.org/avr-libc/user-manual/inline_asm.html * http://www.rn-wissen.de/index.php/Inline-Assembler_in_avr-gcc Mein erster Schuss wäre sowas:
Testanwendung:
Unsicher bin ich mir, ob der Compiler so weit vorausschauend kompiliert, dass es weiss: "Im Makro brauche ich foo in einem höheren Register, deshalb legen ich es beim unsigned char foo = 1<<PB0; schon in einem solchen an". Habe keinen AVR-GCC zur hand um es kurzfristig zu testen. yalu schrieb: > Das war in früheren Zeiten auch meine Befürchtung bis ich den > entsprechenden Abschnitt im Datenblatt gelesen habe (s. mein letzter > Beitrag), wo explizit geschrieben steht, dass man den SBI-Befehl > verwenden kann. Da habe ich irgendwie permanent drüber gelesen. Ja, das stimmt natürlich, und damit funktioniert deine Befehlsfolge auch, allerdings für einen 2-Takte-Toggle, da SBI 2 Takte benötigt. Der einzige Vorteil des SBI gegenüber der Folge LDI/OUT ist dann, dass kein Hilfsregister benötigt wird, aber der wird dann wieder aufgehoben, wenn man das gleiche Pin mehrmals toggeln will, sodass das Hilfs- register mit gleichem Wert wieder verwendet werden kann. Ich habe testhalber folgendes:
auf einem STK600 programmiert, die drei LEDs blinken alle gleich. Alle drei Methoden funktionieren also. Hier ist der generierte Assemblercode:
(r20 und r21 werden natürlich vor der Schleife mit Werten geladen, die dann konstant bleiben.) Und was passiert bei PIND |= vier(); mit einer Funktion, die 8 zurückgibt, oder sonst irgendwas von dem der Compiler nicht weiss, dass es dem Wert 4 entspricht?
Gast
#1569394
Stefan B. schrieb: > Der Funktionsaufruf und der Funktionsprolog/-epilog fressen doch mehr > Takte als 1 Takt. Also Funktion weglassen und stattdessen ein Makro > benutzen. Beitrag "Re: GCC: Pin toggeln in einem Takt?" Hallo, also unter toggle verstand ich bisher immer eine Komplementierung....und die in 1 Taktzyklus ??? Gruß, Michael A. K. schrieb: > Und was passiert bei > PIND |= vier(); > mit einer Funktion, die 8 zurückgibt, oder sonst irgendwas von dem der > Compiler nicht weiss, dass es dem Wert 4 entspricht? Wenn der Compiler es nicht weiß, kann er nur ein komplettes read-modify-write implementieren, das ist doch sonnenklar. Die Optimierung kann nur zuschlagen, wenn sie erkennen kann, dass der Ausdruck zur Compilezeit konstant ist und nur ein einzelnes Bit gesetzt hat. Michael Appelt schrieb: > also unter toggle verstand ich bisher immer eine Komplementierung....und > die in 1 Taktzyklus ??? Ja, liest du dir denn wenigstens auch den geposteten Code durch? gf schrieb: > Stefan B. schrieb: >> Der Funktionsaufruf und der Funktionsprolog/-epilog fressen doch mehr >> Takte als 1 Takt. Also Funktion weglassen und stattdessen ein Makro >> benutzen. > > Beitrag "Re: GCC: Pin toggeln in einem Takt?" Deswegen verstehe ich ja nicht, warum du das mit der Funktion funktion() machen willst, wie du in in Beitrag "Re: GCC: Pin toggeln in einem Takt?" vorschlägst. Wenn du deine Funktion so
benutzt, brauchst du bestimmt mehr als einen Takt! Oder spekulierst du darauf, dass die funktion() auch ohne inline Schlüsselword vom Kompiler inline angelegt wird? Vergiss dabei die Takte für das Laden des Wertes in das Arbeitsregister nicht.
Gast
#1569423
A. K. schrieb: > Und was passiert bei > PIND |= vier(); Das geht dann ziemlich sicher schief. Du hast schon recht, wenn du schreibst: > Finger weg von PINx |= mask; Ist ohnehin selten sinnvoll, PINx = mask; > tut es auch und ist eindeutig. Aber es gibt hin und wieder Fälle, wo man gerne den SBI hätte, weil man damit ein Register und i.Allg. auch ein Programmspeicherwort einspart oder weil das Toggeln nichtunterbrechbar sein soll. Dass man dafür von gewissen Annahmen ausgehen muss, wie der Compiler den C-Code übersetzt, ist zwar nicht schön, aber in der Mikrocontrollerei nicht unüblich. Ohne solche Annahmen wären bspw. keine Zugriffe auf Doppelregister wie ADC mit nur einer C-Anweisung möglich, genauso wenig wie die _delay_x-Funk- tionen aus der AVR-Libc in ihrer aktuellen Form. Jörg Wunsch schrieb: > Wenn der Compiler es nicht weiß, kann er nur ein komplettes > read-modify-write implementieren, das ist doch sonnenklar. Das war ja der Sinn der Sache. Mich interessieren die LEDs.
Gast
#1569466
Stefan B. schrieb: > Deswegen verstehe ich ja nicht, warum du das mit der Funktion funktion() > machen willst, wie du in in Mir geht es doch gar nicht um die Funktion. Vergiss die einfach. Mir geht es einzig und allein darum: gf schrieb: >Meine Intension ist die Taktzahl pro Schleifendurchgang zu minimieren. yalu schrieb:
> oder weil das Toggeln nichtunterbrechbar sein soll.
Das ist es so auch:
PINx = mask;
Bleibt nur der Unterschied in der Länge und dem Register. Wer deshalb
auf SBI Wert legt, dem sei inline-assembler statt |= empfohlen, um
spätere Überrschungen zu vermeiden.
Dann braucht du bei 4 Pins bis zu 2^4=16 verschiedene Werte für wert. Die kannst du nicht alle lokal in Registern halten, so wie ich es mit dem Makro vor hatte. D.h. du musst in der Arbeitsschleife Werte in Register nachladen, um sie mit OUT verwenden zu können. Damit scheitert die 1-Takt Idee grundsätzlich. Zwei Takte pro Manipulation von 1 bis 4 Pins gleichzeitig mit LDI/OUT sind IMHO realistisch, wenn alle Pins am gleichen Port sind; mehr wenn die Pins zeitversetzt geschaltet werden. Irgendwo wird man sich das Optimum zwischen LDI/OUT und SBI/CBI auf den genauen Anwendungfall bzw. das gewünschte Timing maßschneidern müssen. Stefan B. schrieb: > Dann braucht du bei 4 Pins bis zu 2^4=16 verschiedene Werte für wert. Wenn es um mehr als ein Bit geht, klar. Aber damit musst du leben, oder eine andere Controllerfamilie verwenden. Beim Bitbanging sind die PIC24H ganz flott unterwegs. Propeller und XMOS bieten exaktes nebenläufiges Timing ohne Störung durch Interrupts. Jörg Wunsch schrieb:
> Ja, liest du dir denn wenigstens auch den geposteten Code durch?
Was meinst Du denn, wie ich das verlorene ^= gefunden habe ?
Oder wird das hier eine typische ingenieurmäßige Entwicklung an den
Anforderungen vorbei ?
Gruß,
Michael
Michael Appelt schrieb: > Jörg Wunsch schrieb: >> Ja, liest du dir denn wenigstens auch den geposteten Code durch? > > Was meinst Du denn, wie ich das verlorene ^= gefunden habe ? (Sorry, hier weiß ich nicht, wovon du gerade redest.) Warum glaubst du dann nicht, dass
tatsächlich nur einen Takt braucht und auch funktioniert? Nicht nur rein theoretisch, sondern ganz praktisch experimentell nachgewiesen -- OK, das Timing habe ich nicht nachgemessen, da habe ich mich aufs Atmel-Datenblatt verlessen, dass sie für ein OUT auch wirklich nur einen Takt brauchen. Hallo, aber es toggled nicht. Toggeln wäre XOR mit 1 Takt. Schau Dir die ersten Postings an, da war das noch korrekt, danach wurden bits gezielt auf 0 oder 1 gesetzt, was eben kein toggeln mehr ist. Das Thema an sich fand ich interessant, weil Lösungen mit 1 ASM-Befehl eben kein CLI/SEI drum herum brauchen, damit kein Interrupt dazwischen haut. Aber ich will Euren Plauderthread nicht länger stören ;-) Gruß, Michael Michael Appelt schrieb:
> aber es toggled nicht. Toggeln wäre XOR mit 1 Takt.
Ausgabe auf PINx toggelt auf neueren Devices sehr wohl. Mit OUT in einem
Takt, wenn die Maske bereits im Register steht.
A. K. schrieb: >> aber es toggled nicht. Toggeln wäre XOR mit 1 Takt. > > Ausgabe auf PINx toggelt auf neueren Devices sehr wohl. Zumal ich ja explizit dazu geschrieben habe, dass mein gepostetes Testprogramm funktioniert...
Gast
#1570495
Also ich habs gestern ausprobiert. "PINX = 0x01; PINX = 0x02; PINX = 0x04;" werden vom Compiler jeweils in ein "out p, Rd" umgewandelt. Die Register werden VOR der Schleife nur einmal geladen. Somit habe ich ein Toggeln in einem einzigen Takt erreicht. Das ganz funkioniert mit mind. 3 unabhängigen Bits die getoggelt werden sollen. Bei mehr Bits werden wohl irgendwann die verfügbaren Register knapp werden und das Laden findet dann wieder innerhalb der Schleife statt. Ich hab aber nicht ausprobiert wo die Grenze liegt. Jedenfalls hab ich eine Lösung :-) Vielen Dank für die rege Anteilnahme an der Diskussion. gf schrieb: > Bei mehr Bits werden wohl irgendwann die verfügbaren Register > knapp werden Sei froh, dass du nicht auf einem i386 arbeiten musst mit seinem chronischen Registermangel. :-) Antwort schreibenBitte melde dich an, um einen Beitrag zu schreiben. |
Anzeige
|