Hallo.
Entweder steh ich auf dem Schlauch, oder dem AVR-Kern fehlt eine
wichtige grundsätzliche Funktion.
Ich habe keinen Weg gefunden, ein bestimmtes Bit aus einem Register in
den IO-Space zu kopieren. Kann das sein????
Es geht imho nur über Umwege und Sprungbefehle.
Das Problem kommt mir öfters unter, nämlich immer dann, wenn eine ISR
aufgerufen wird, während ich Bits in einem Zwischenregister (read modify
write) verarbeite.
Klar kann ich einfach cli sei machen, aber manchmal gibts halt ISRs, die
man nicht warten lassen kann und wenn man dann noch nebenbei eine
Routine abarbeitet, deren Taktbudget knapp bemessen ist, wirds
kompliziert.
Ich frag deshalb einfach mal in die Runde: Gibts da nicht vielleicht
doch einen Trick?
Nimm LAS, LAC oder LAT (XMega).
Und wenn's im lower IO ist, gehen SBI und CBI, wobei die Bitposition
spätestens zur Link-Zeit bekannt sei muss.
Um ein Bit aus einem Register in den IO-Bereich zu schreiben:
Wenn ich das Byte in r16 seriell ausgeben will, geht es z.B. so
in r24,PORTB
bst r16,0
bld r24,serialpin
out PORTB,r24
bst r16,1
bld r24,serialpin
out PORTB,r24
bst r16,2
bld r24,serialpin
out PORTB,r24
bst r16,3
bld r24,serialpin
out PORTB,r24
bst r16,4
bld r24,serialpin
out PORTB,r24
bst r16,5
bld r24,serialpin
out PORTB,r24
bst r16,6
bld r24,serialpin
out PORTB,r24
bst r16,7
bld r24,serialpin
out PORTB,r24
(Ist jetzt nur mal schnell als Beispiel hingeschrieben)
Wenn mir da jetzt eine ISR zwischenfunkt und den Port ändert,
überschreibe ich hier spätestens beim nächsten out die ISR-Änderung.
Entweder arbeite ich mit cli sei oder ich muss mit sprungbefehlen und
sbi und cbi arbeiten, was arg auf die Performance geht.
>> Und das ist auch atomar — zumindest falls Rn als normales Register> verwendet wird.
Das sieht gut aus und ist nur ein Takt mehr pro Bit. Das probiere ich
mal aus. Danke!
ASM Superprofi schrieb:> Diese Methode verursacht leider einen starken Jitter. Schade....
Logisch, zwei Takte.
Dein Problem ist, dass du dein Problem zu isoliert siehst. Gerade in
Assembler ist es nötig (und möglich!), das Gesamtwerk zu optimieren.
Wenn man nämlich eine sehr zeitkritische Aufgabe hat, kann man einfach
mal ein Register speziell dafür reservieren. Das geht, weil man selbst
bei recht komplexen Werken nur selten wirklich alle Register braucht, da
der AVR einfach recht viele davon besitzt. Und selbst wenn es knapp mit
Registern wird, spart ein reserviertes an hochfrequenter Stelle oft weit
mehr Takte, als das Ausweichen auf SRAM an weniger frequenten Stellen
kostet.
Hat man also so ein exclusives Register, kann man es als sog.
Schattenregister für ein SFR benutzen. Sprich: alle Ausgaben auf das SFR
gehen nicht direkt auf das SFR, sondern auf das Schattenregister und nur
dieses wird tatsächlich auf das SFR ausgegeben. Damit löst sich
automatisch das Problem der Atomität und man kann z.B. sowas machen:
.DEF PORTASHADOW=R2
bst ctrlreg,ctrlbit
bld PORTASHADOW,outbit
out PORTA,PORTASHADOW
Das ist ein Takt weniger als in dem Konstrukt mit sbrc/s und s/cbi und
vor allem passiert die tatsächliche Ausgabe immer (relativ) im gleichen
Takt.
Und dann ist da noch ein wichtiger Punkt, über den du nachdenken
solltest: wie kommt eigentlich das Bit ursprünglich in das Register,
welches ich hier symbolisch ctrlreg genannt habe? Ist es wirklich nötig,
dieses in genau diesem Register an genau der Position von ctrlbit zu
speichern? Täte es nicht auch das outbit in PORTASHADOW genauso? Wenn
ja, entfallen nämlich nochmal zwei Takte und es bleibt einzig:
out PORTA,PORTASHADOW
Effizenter geht es dann wirklich nicht mehr...
Zu solchen Lösungen kommt man nur, wenn man das Gesamtproblem
betrachtet. Genau diese Möglichkeit ist es, die man einem Compiler
voraus hat und nur so kann man eine nennenswert höhere Effizienz
erreichen als dieser.
Der Nachteil ist natürlich, dass hochoptimierter Code selten bis nie
wiederverwendbar ist, weil er ja eben gerade aus der Optimierung des
Gesamtproblems entstanden ist. Anderes Problem -> sehr wahrscheinlich
anderes Optimierungsergebnis.
c-hater schrieb:> Zu solchen Lösungen kommt man nur, wenn man das Gesamtproblem> betrachtet.
Gutes Stichwort. Hast du den Thread überhaupt ganz gelesen, damit meine
ich nicht nur die letzten zwei Postings?
ASM Superprofi schrieb:> Wenn mir da jetzt eine ISR zwischenfunkt und den Port ändert,> überschreibe ich hier spätestens beim nächsten out die ISR-Änderung.
Wenn du nicht während der gesamten Ausgabe die Interrupts sperrst,
zerhaut's dir doch eh das Timing. Musst du also sowieso machen.
Rolf M. schrieb:> Wenn du nicht während der gesamten Ausgabe die Interrupts sperrst,> zerhaut's dir doch eh das Timing. Musst du also sowieso machen.
Richtig. Wenn man in der blöden Situation ist, dass zwei Dinge
zeitkritisch sind, hat man verloren bzw. muss sich eine andere Lösung
überlegen.
Aber hier gehts darum dass eine ISR zeitkritisch ist, die nicht warten
kann und eine parallel laufende nicht-zeitkritische Routine, die mittels
read-modify-write IO-Ports bearbeitet.
Bisher funktioniert nur die Lösung von Johann (einigermassen, wenn man
mal vom starken Jitter absieht.)
Man bräuchte sowas wie bst direkt in den IO-Space. Schade, dass das
nicht geht :/
ASM Superprofi schrieb:> Diese Methode verursacht leider einen starken Jitter. Schade....
Den hättest du dann aber doch auch, wenn es durch eine einzige
Instruktion machbar wäre: Je nachdem, ob eine (lange) ISR vor oder nach
der Instruktion getriggert wird, wird die Instruktion eben später oder
oder früher ausgeführt.
ASM Superprofi schrieb:> Ich habe keinen Weg gefunden, ein bestimmtes Bit aus einem Register in> den IO-Space zu kopieren
Selbst auf einem 8051 (oder ARM mit bit-addressierbarem IO-Space) geht
das nur mit zwei ASM-Befehlen: bit-extract und store-bit
ASM Superprofi schrieb:> Aber hier gehts darum dass eine ISR zeitkritisch ist, die nicht warten> kann und eine parallel laufende nicht-zeitkritische Routine, die mittels> read-modify-write IO-Ports bearbeitet.
Dann verstehe ich diese Aussage nicht:
ASM Superprofi schrieb:> Diese Methode verursacht leider einen starken Jitter. Schade....
Entweder ist deine serielle Ausgabe zeitkritisch, dann darf sie nicht
unterbrochen werden, oder sie ist es nicht. Dann kann dir der Jitter
aber egal sein.
Wobei ich mir eh nicht vorstellen kann, wie deine serielle Ausgabe nicht
zeitkritisch sein kann. Da es ja offenbar keinen Clock gibt, handelt es
sich wohl um eine asynchrone Übertragung. Dann darf die aber nicht
mitten im Byte plötzlich mal stehen bleiben, um eine ISR abzuarbeiten.
> Man bräuchte sowas wie bst direkt in den IO-Space. Schade, dass das> nicht geht :/
Besser wäre, einen µC mit einem USI zu verwenden, das sowas einfach
nebenher in Hardware macht.
ASM Superprofi schrieb:> Gutes Stichwort. Hast du den Thread überhaupt ganz gelesen, damit meine> ich nicht nur die letzten zwei Postings?
Ja.
Gegenfrage: Hast du meinen Vorschlag überhaupt verstanden? Ich
beantworte es mir mal selber, nein, hast du definitiv nicht.
Johann L. schrieb:> Und das ist auch atomar
Nein. Es kann zum beliebigen Zeitpunkt ein Interrupt reinkommen und das
Register oder den IO-Bereich verändern.
ASM Superprofi schrieb:> Aber hier gehts darum dass eine ISR zeitkritisch ist, die nicht warten> kann und eine parallel laufende nicht-zeitkritische Routine, die mittels> read-modify-write IO-Ports bearbeitet.
Ja, genau für dieses Szenario stellt mein Vorschlag die bestmögliche
Lösung dar...
MaWin schrieb:> Johann L. schrieb:>> Und das ist auch atomar>> Nein. Es kann zum beliebigen Zeitpunkt ein Interrupt reinkommen und das> Register oder den IO-Bereich verändern.
Nein, zwischen Lesen und Schreiben des I/O-Ports kann kein Interrupt
reinkommen, da SBI/CBI beides innerhalb einer Instruktion tun, anders
als oben mit IN und OUT.
MaWin schrieb:> Johann L. schrieb:>> Und das ist auch atomar>> Nein. Es kann zum beliebigen Zeitpunkt ein Interrupt reinkommen und das> Register oder den IO-Bereich verändern.
Das ist aber immer der Fall. Zur Laufzeit sieht die Sequenz nämlich so
aus (Rn.posA = 0):
1
SBRC Rn, posA
2
SBRS Rn, posA
3
CBI sfr, posB
oder so (Rn.posA = 1):
1
SBRC Rn, posA
2
SBI sfr, posB
3
SBRS Rn, posA
Es gibt also nur 1 Instruktion, die das SFR ändert. Natürlich kann vor
oder nach dem SBI / CBI eine ISR laufen, aber wäre auch dann der Fall,
wenn man den Block in CLI + SEI einrahmt.
Falls Rn also ein normales (nicht asynchron verändertes) Register ist,
verhältsich die Sequenz so wie eine atomare Sequenz.
Anders ausgedrückt: Die Sequenz in CLI + SEI zu klammern bringt nix
außer Vergrößerung der IRQ-Latenz.
Falls die Sequenz Teil einer längeren Befehlsabfolge mit mehreren
solchen I/O-Zugriggen ist, kann das evtl. anders aussehen. Aber wenn
eine solche Sequenz nicht ein monolithischer Block ist und z.B: 8 Bits
einzeln und atomar behandelt (z.B. CLI + IN + OUT + SEI) und zwischen
solchen Teilsequenzen ISRs laufen, dann hat man auch dann einen Jitter.
Dieser Jitter ist dann aber eine eigene Baustelle.
Ohne low/high vs high/low Jitter und bezogen auf alle anderen Bits im
Zielregister atomar:
;in=regA,bitA, out=portB,bitB
bst regA, bitA
bld r16, bitB
in r17, portB
eor r16, r17
and r16, 1<<bitB
out pinB, r16
Geht nicht bei der ATmega8/16/32 Generation, muss neuer sein.
A. K. schrieb:> Geht nicht bei der ATmega8/16/32 Generation, muss neuer sein.
Wieso sollte das da nicht gehen? Geht natürlich auch dort.
Ist aber trotzdem deutlich schlechter als meine Lösung...
c-hater schrieb:>> Geht nicht bei der ATmega8/16/32 Generation, muss neuer sein.>> Wieso sollte das da nicht gehen? Geht natürlich auch dort.
Nö. Bei denen bewirkt ein
out pinB, ...
exakt garnichts.
c-hater schrieb:> A. K. schrieb:>>> Geht nicht bei der ATmega8/16/32 Generation, muss neuer sein.>> Wieso sollte das da nicht gehen? Geht natürlich auch dort.
Ah, ja. PINx. Alles klar. Das macht diese Variante noch schlechter.
c-hater schrieb:> ASM Superprofi schrieb:>>> Gutes Stichwort. Hast du den Thread überhaupt ganz gelesen, damit meine>> ich nicht nur die letzten zwei Postings?>> Ja.>> Gegenfrage: Hast du meinen Vorschlag überhaupt verstanden? Ich> beantworte es mir mal selber, nein, hast du definitiv nicht.
Ich habe verstanden, was du geschrieben hast. Du auch? Ich sage mal
nein.
Wenn du mit Schattenregister nämlich meinst, dass die ISR den selben zum
verändern nimmt, hättest du das dazu schreiben müssen. Nur so macht
deine Aussage irgendwie wenigstens halbwegs Sinn. Vom Problem der
schlechten Portabilität (alle Programmierer müssen den selben
Schattenregister verwenden, keiner darf direkt den Port ändern) wollen
wir mal gar nicht erst reden.
Johanns Methode ist bisher die beste.
ASM Superprofi schrieb:> Wenn du mit Schattenregister nämlich meinst, dass die ISR den selben zum> verändern nimmt, hättest du das dazu schreiben müssen.
Das habe ich. EXCLUSIV RESEVIERTES REGISTER. Deutlicher kann man kaum
darauf hinweisen.
Ich bin sogar noch extra darauf eingegangen, warum das sehr oft die
effizenteste Lösung ist...
> Nur so macht> deine Aussage irgendwie wenigstens halbwegs Sinn.
Nein. Das ist direkt und ganz unmittelbar der Sinn. Dein Problem war
nur, dass du ihn nicht verstanden hast...
> Vom Problem der> schlechten Portabilität (alle Programmierer müssen den selben> Schattenregister verwenden, keiner darf direkt den Port ändern) wollen> wir mal gar nicht erst reden.
Auch dieses Problem habe ich im letzten Absatz meines Postings
hinreichend gewürdigt. Wobei hier das Problem eigentlich garkein so
großes ist, man kann ja sehr leicht konfigurierbar machen, welches
konkrete Register das Schattenregister sein soll. Genau dafür gibt's ja
diese .def-Zeile im Code...
> Johanns Methode ist bisher die beste.
Nein. Maximal ist sie unter deinem arg beschränkten Blickwinkel die
beste. Technisch die beste ist sicher meine Lösung. Und zwar sowohl
unter dem Aspekt der Gesamtperformance als auch unter dem Aspekt des
minimalen Jitter.
Mein Gott, ich nenne mich zwar nicht "ASM-Superprofi", aber bin im
Gegensatz zu dir wirklich einer. Zumindest auf der AVR8-Plattform.
Einfach deshalb, weil ich das seit über zehn Jahren intensiv mache und
sogar meine Brötchen damit verdiene. Zumindest zu einem gewissen
Prozentsatz...
Dein Ton gefällt mir nicht. Er ist um es deutlich zu sagen unter aller
Sau. Ich gehe deshalb auf deine Auswürfe nicht weiter ein.
Beruhige dich erstmal, dann reden wir vielleicht weiter.
Der c-hater hat selten mal meine Meinung, aber wer sich als Super-Profi
bezeichnet, der sollte die Fragen dieses Threads nicht stellen müssen.
Wenn man Anforderungen hat, die die HW nicht erfüllt, dann hat man sie
Falsch gewählt. Wenn ich ein Byte jitterfrei morsen muß, dann benutz ich
halt eine UART oder zumindest ein Schieberegister wie die USI, das mit
festen Takt schiebt. Steve Wozniac hat mit Software Diskettencontroller
gebaut, aber der war nach dem Maßstab hier kein SuperProfi, sondern ein
Gott.
C-Amateur schrieb:> Steve Wozniac hat mit Software Diskettencontroller> gebaut, aber der war nach dem Maßstab hier kein SuperProfi, sondern ein> Gott.
Der hat eben einen vernünftig designeden Microprozessor zur Verfügung
gehabt, den 6502 - nicht so einen absolut registerorientierten
AVR-Schrott ;D
(duck und weg...)
Thomas E. schrieb:> Der hat eben einen vernünftig designeden Microprozessor zur Verfügung> gehabt, den 6502 - nicht so einen absolut registerorientierten> AVR-Schrott ;D
Also bitte, wenn schon dann TIs 9900. Der I/O-Space dieses 16-Bitters
war 1 Bit breit. Da hat sich diese Problematik völlig in Luft aufgelöst.
Wenn ein I/O Register nur 13 Bits brauchte, dann hatte es auch nur 13
Bits und jedes davon war einzeln, atomar und nebenwirkungsfrei
ansprechbar.
A. K. schrieb:> Also bitte, wenn schon dann TIs 9900.
Mit dem hatte ich mich nie näher beschäftigt - zu viele Register! (Bei
mehr, als einem Arbeitregister, verliere ich beim Programmieren in
Assembler immer die Übersicht, deshalb benutze ich ja auch PICs...)
Thomas E. schrieb:>> Also bitte, wenn schon dann TIs 9900.>> Mit dem hatte ich mich nie näher beschäftigt - zu viele Register!
Also bitte! Weniger geht kaum, nur 3 echte Register: Program Counter,
Statusregister und Workspace Pointer. Das ist weniger als bei 6502.
> mehr, als einem Arbeitregister, verliere ich beim Programmieren in> Assembler immer die Übersicht, deshalb benutze ich ja auch PICs...)
Laut Microchip hat der olle 16F84 insgesamt 68 General Purpose Register.
Die liegen zwar im RAM, aber da liegen die schlappen 16 Workspace
Register vom 9900 auch.
ASM Superprofi schrieb:> Wenn ich das Byte in r16 seriell ausgeben will, geht es z.B. so> in r24,PORTB> bst r16,0> bld r24,serialpin> out PORTB,r24> ...>> Wenn mir da jetzt eine ISR zwischenfunkt und den Port ändert,> überschreibe ich hier spätestens beim nächsten out die ISR-Änderung.> Entweder arbeite ich mit cli sei oder ich muss mit sprungbefehlen und> sbi und cbi arbeiten, was arg auf die Performance geht.
Was soll das heissen "arg auf die Performance geht" ?
So wie du das oben geschrieben hast, dauert es 3 Takte pro bit.
Für 8 bits sind das ganze 24 Takte.
Und du kannst für diese 24 Takte die ISR nicht sperren ?
Das ist (für mich) absoluter Unsinn.
> Bisher funktioniert nur die Lösung von Johann (einigermassen, wenn man> mal vom starken Jitter absieht.)
Das ist keine Lösung, das war nur ein Vorschlag. Die einzige Lösung ist
die mit cli/sei.
Ohne cli/sei kann es immer Jitter geben, selbst wenn du direkt die
bits setzen könntest, da die ISR jederzeit zuschlagen kann.
P.S.
Wenn es aber wirklich atomar und unabhängig/ungestört von der ISR
gehen sollte, hätte ich es ganz einfach mit SPI gemacht.
serialpin auf PB.3 setzen und gut ist es.
Bei SPI2X = 0 / SPI Clock Vorteiler = 2 oder
SPI2X = 1 / SPI Clock Vorteiler = 2 haut das immer hin,
nur ist es halt 1 Takt schneller oder langsamer als dein Beispiel.
A. K. schrieb:> Laut Microchip hat der olle 16F84 insgesamt 68 General Purpose Register.
Ja, stimmt schon - die PICs konnte ich überhaupt erst von da ab ohne
Kopfschmerzen programmieren, nachdem ich mir als Eselsbrücke überlegt
hatte, das man sich die "General Purpose Register" statt als
Arbeitsregister auch einfach mal als RAM-Speicher vorstellen kann...
Zum eigentlichen Problem unseres TO: bin ja kein AVR-Kenner (zu viele
Register - s.o.), habe da aber früher schon mal ins Datenblatt geschaut,
um zu prüfen, ob ein AVR vielleicht doch zu irgendwas sinnvollem zu
gebrauchen ist (für anspruchslose Aufgaben, evtl. als Slave-Prozessor
für einen PIC10F202?) - da ist mir irgendwas im Gedächtnis hängen
geblieben, daß man einen IO-Pin durch Schreiben auf's Port-Inputregister
togglen kann. Damit sollte sich doch eine atomare und völlig jitterfreie
Bit-Ausgabe machen lassen?