Forum: Mikrocontroller und Digitale Elektronik AVR: Bit direkt in IO-Space kopieren


von ASM Superprofi (Gast)


Lesenswert?

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?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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:

: Bearbeitet durch User
von Karl M. (Gast)


Lesenswert?

Hallo,

so genau hast Du nicht geschrieben, was du willst.

Mit diesen Assembleranweisungen in kombination kommt man ans Ziel:

http://www.atmel.com/images/Atmel-0856-AVR-Instruction-Set-Manual.pdf

Schön wäre ein Beispiel deines Problem. Danke.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Um ein Bit aus einem Register in den IO-Bereich zu schreiben:
1
    SBRC Rn,  posA
2
    SBI  sfr, posB
3
    SBRS Rn,  posA
4
    CBI  sfr, posB

Und das ist auch atomar — zumindest falls Rn als normales Register 
verwendet wird.

von ASM Superprofi (Gast)


Lesenswert?

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.

von ASM Superprofi (Gast)


Lesenswert?

Johann L. schrieb:
> Johann L. schrieb:
>> Um ein Bit aus einem Register in den IO-Bereich zu schreiben:
>
>
1
>     SBRC Rn,  posA
2
>     SBI  sfr, posB
3
>     SBRS Rn,  posA
4
>     CBI  sfr, posB
5
>
>
> 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!

von ASM Superprofi (Gast)


Lesenswert?

Diese Methode verursacht leider einen starken Jitter. Schade....

von c-hater (Gast)


Lesenswert?

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.

von ASM Superprofi (Gast)


Lesenswert?

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?

von Rolf M. (rmagnus)


Lesenswert?

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.

von ASM Superprofi (Gast)


Lesenswert?

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 :/

von ASM Superprofi (Gast)


Lesenswert?

z.b.

bst r24,1
bld PORTB,serialpin

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Lothar (Gast)


Lesenswert?

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

von Rolf M. (rmagnus)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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.

von MaWin (Gast)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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

von Rolf M. (rmagnus)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch User
Beitrag #4985209 wurde vom Autor gelöscht.
von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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.

von ASM Superprofi (Gast)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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

von ASM Superprofi (Gast)


Lesenswert?

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.

von C-Amateur (Gast)


Lesenswert?

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.

von Thomas E. (picalic)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Thomas E. (picalic)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

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


Lesenswert?

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.

: Bearbeitet durch User
von Thomas E. (picalic)


Lesenswert?

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?

von (prx) A. K. (prx)


Lesenswert?

Thomas E. schrieb:
> daß man einen IO-Pin durch Schreiben auf's Port-Inputregister
> togglen kann.

Ebendies hatte ich oben präsentiert.

von Thomas E. (picalic)


Lesenswert?

A. K. schrieb:
> Ebendies hatte ich oben präsentiert.

Uuups - sorry, hatte ich überlesen!

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.