Hallo! Ich bin noch recht ungeübt was die Assemblerprogrammierung angeht und hoffe hier Hilfe für meine Fragestellung zu bekommen. Zielplattform ist ein ATMega8515L. Ich möchte in einem Unterprogramm "togglePin" die per Parameter übergebenen Pins am ebenfalls per Parameter übergebenen Port toggeln. Ich habe dazu schon einmal ein Makro geschrieben, dass auch super funktioniert. Für die Version mit Unterprogramm weiß ich allerdings nicht, wie ich den Port als Parameter übergeben soll. Hier das Makro: .Macro togglePin in r17, @0 //lese Zustand Zielport ein eor r17, @1 //Bits laut Bitmaske ändern out @0, r17 .Endmacro Der Aufruf erfolgt so: ldi r16, 0b00110101 togglePin PORTB, r16 Wie kann ich das in ein entsprechendes Unterprogramm bekommen? Ansatz: .def pinMask = r16 .def targetPort = r18 togglePin: in r17, targetPort //targetPort soll Parameter sein eor r17, pinMask //pinMask soll Parameter sein out targetPort, r17 ret Das mit dem Parameter targetPort funktioniert aber nicht. Kann ja auch gar nicht! Aber wie funktioniert es denn? Viele Grüße André
Hi >Das mit dem Parameter targetPort funktioniert aber nicht. Kann ja auch >gar nicht! Aber wie funktioniert es denn? Schon mal mit einem Portregister statt einem Register probiert. Noch ein Hinweis: 'in' sollte von PINx lesen und 'out' auf PORTx schreiben. MfG Spess
Sollte zum Beispiel so gehen:
1 | .def pinMask = r16 |
2 | .def targetPort = r18 |
3 | |
4 | |
5 | ldi targetPort, PORTB |
6 | ldi pinMask, 0b00000010 ; für PB1 |
7 | ;oder |
8 | ldi pinMask, 1<<PB7 | 1<<PB3 ; für PB7 und PB3 |
9 | rcall togglePin |
10 | |
11 | .. |
12 | .. |
13 | |
14 | tu irgendwas.. |
15 | |
16 | ; Unterprogramme |
17 | |
18 | togglePin: |
19 | in r17, targetPort //targetPort soll Parameter sein |
20 | eor r17, pinMask //pinMask soll Parameter sein |
21 | out targetPort, r17 |
22 | ret |
Zu PORTB wird ja in der AVRxxxxdef.inc eine Adresse hinterlegt, die du einfach bloß laden musst. Probier es doch einfach aus und kontrolliere es mit dem Simulator. Gruß Steffen
Ich weiß jetzt nicht genau ob es bei einem ATmega8515 geht, aber: PIN toggeln ganz einfach
1 | togglePin: |
2 | out PINx,BITx |
sollte auch toggeln..
Hi
>Ich weiß jetzt nicht genau ob es bei einem ATmega8515 geht,
Nein, geht mit diesem Oldtimer nicht.
mfG Spess
Ja, hab ich auch gerade gesehen. PINx ist da nur lesend zu verwenden.
>Ich habe dazu schon einmal ein Makro geschrieben, dass auch super >funktioniert. Dann bleib dabei. Die Version mit Unterprogramm kostet dich richtig satt Geschwindigkeit.
Hi
>.def targetPort = r18
Damit wird 'targetport' zur Adresse von r18, nicht zum Inhalt.
MfG Spess
Hi >Ich habe dazu schon einmal ein Makro geschrieben, dass auch super >funktioniert. Halte ich für ein Gerücht. Um zu funktionieren müsste es so
1 | .Macro togglePin |
2 | in r17, @0-2 //lese Zustand Zielport ein |
3 | eor r17, @1 //Bits laut Bitmaske ändern |
4 | out @0, r17 |
5 | .Endmacro |
MfG Spess
Hallo Steffen H., genau das funktioniert eben nicht: "error: invaild number" meldet der Assembler. Es ist augenscheinlich nicht möglich hier ein "Nicht-IO-Port-Register" anzugeben. Ich kann einer Konstante den PORTB zuweisen und diese dann benutzen. .equ targetPort = PORTB in r17, targetPort Das ist aber nicht wirklich hilfreich, da ich diese ja nicht im Verlauf des Programmes ändern kann. Auch mit .set funktioniert es nur bedingt, da nur der zuletzt im Programm gesetzte Wert verwendet wird. Ich habe also schon einiges probiert, aber noch keine Lösung gefunden. Ich weiß auch, dass PORTB auch nur eine Adresse ist. Wie bekomme ich diese Adresse aber als Parameter in mein Unterprogramm, so dass ich sie mit in und out verwenden kann? @Spess53: Ja das Macro ist schneller. Mir geht es aber um das Prinzip. Es könnte ja genau so gut ein viel umfangreicheres Unterprogramm sein und dann wäre ein Macro möglicherweise nicht mehr so günstig, wegen des höheren Speicherplatzbedarf. Im übrigen funktioniert das Macro wie beschrieben... Viele Grüße André
André Grimm schrieb: > Es könnte ja genau so gut ein viel umfangreicheres Unterprogramm sein > und dann wäre ein Macro möglicherweise nicht mehr so günstig, wegen des > höheren Speicherplatzbedarf. Nein, umgekehrt. Wenn Du es variabel haben willst, mußt Du mit LD/ST arbeiten, d.h. 16Bit-Pointer. Du mußt also vor jedem Aufruf 3 Register laden. Ist also teurer als 3 Befehle. Bei den neueren AVRs (ab 2003) ist es sogar nur ein Befehl: "SBI PINx, y".
André Grimm schrieb: > Ich habe also schon einiges probiert, aber noch keine Lösung gefunden. > Ich weiß auch, dass PORTB auch nur eine Adresse ist. Wie bekomme ich > diese Adresse aber als Parameter in mein Unterprogramm, so dass ich sie > mit in und out verwenden kann? Gar nicht. Die Adresse von der zu lesen bzw, auf die zu schreiben ist, ist bereits im In/Out Befehl enthalten. Da kannst sie nicht zur Laufzeit variabel machen. Wenn, dann müsste man die ganzen Portzugriffe über einen LD,ST abwickeln, wobei die Adresse von der zu lesen bzw. zu schreiben ist, über den Z-Pointer abgewickelt wird. Daher halte es mit holger: Bleib bei der Makro Version > @Spess53: Ja das Macro ist schneller. Mir geht es aber um das Prinzip. Das Prinzip ist, dass die I/O Register ebenfalls im Speicher auftauchen und du daher alle Operationen, die du mit dem SRAM machen kannst, auch mit den I/O Registern machen kannst. Und dann stehen dir alle Freiheiten offen, die du auch mit SRAM tun kannst, wie zb berechneter Zugriff über dern Z-Pointer. > Es könnte ja genau so gut ein viel umfangreicheres Unterprogramm sein > und dann wäre ein Macro möglicherweise nicht mehr so günstig, wegen des > höheren Speicherplatzbedarf. In üblichen Programmen ist das praktisch immer die günstigere Version. Wenn du es noch universeller haben willst, kommst du nicht mehr mit 3 Instruktionen über die Runden.
André Grimm schrieb: > Ich habe also schon einiges probiert, aber noch keine Lösung gefunden. > Ich weiß auch, dass PORTB auch nur eine Adresse ist. Wie bekomme ich > diese Adresse aber als Parameter in mein Unterprogramm, so dass ich sie > mit in und out verwenden kann? Wenn du die Frage so formulierst, ist die Antwort kurz und einfach: Garnicht. Und das ist auch ganz einfach zu begründen: Um Adressen übergeben zu können und später von diesen Adressen lesen zu können, wird "indirect" Adressierung benötigt. "In" und "out" gibt es aber nur mit "immediate" Addressierung, d.h. die Adresse steckt im Opcode und muß deshalb spätestens zur Assemblierzeit bekannt sein. Das alleine würde noch nicht das endgültige Aus bedeuten, das könnte man mit selbstmodifizierendem Code lösen. Blöderweise ist der AVR aber eine "Harvard"-Maschine, so daß auch dieser Ausweg entfällt. Also: geht absolut nicht. No way. Aber natürlich liegt das nur daran, daß du die falsche Frage gestellt hast. In Wirklichkeit bist du wahrscheinlich garnicht so scharf darauf, "in" bzw. "out" zu verwenden, du willst vielmehr eigentlich IO-Register lesen oder schreiben. Dafür wird man zwar, wenn immer möglich "in" bzw. "out" verwenden. Aber hier ist es halt nicht möglich. Also tut man das, was die Gesetze der Logik vorgeben: man nimmt einfach was anderes, um zu einer Lösung zu kommen. Dabei ist sehr hilfreich, daß die IO-Register (wie übrigens auch die CPU-Register) mit im SRAM-Adressraum liegen. Man kann also über ganz normale Lade- und Speicheroperationen ebenfalls darauf zugreifen. Das einzige, was man dazu wissen muß, ist das Mapping, also wo die einzelnen IO-Adressen im SRAM-Adreßraum zu finden sind. Das ist eine so wichtige Information, daß Atmel sogar so freundlich war, sie direkt in's Datenblatt zu schreiben (Beispiel aus dem zum M1284P): > When addressing I/O Registers as data space using > LD and ST instructions, 0x20 must be added to these addresses Und man sieht schon, da auch Atmel klar war, daß man die Sache am ehesten für das Problem der indirekten Adressierung verwenden würde. Denn bei lds/sts trifft das natürlich genauso zu, bloß könnte man dann ja gleich "in" bzw. "out" verwenden. Aber Vorsicht, bei den moderneren/größeren AVRs gibt es auch IO-Adressen, die von vornherein nicht per in/out erreichbar sind. Da hat Atmel blöderweise aber direkt die Zieladressen im data space deklariert, bei denen ist also nicht die $20 dazu zu addieren. Sinnvoller wäre gewesen, generell die Adresse im data space zu deklarieren und den Assembler bei der Codierung von in/out das Bit 5 daraus entfernen zu lassen. Also, die Lösung sieht so aus: .EQU IO_DATASPACEOFFSET=$20 ;->XL : IO-Adresse ; R17: Togglemaske ;<-XH : 0 togglebit: cpi XL,IO_DATASPACEOFFSET brcc memorymapped subi XL,-IO_DATASPACEOFFSET memorymapped: clr XH ld R16,X eor R16,R17 st X,R16 ret Aufruf etwa so: ldi XL,PORTA rcall togglebit
Hallo c-hater, super, vielen Dank! Genau nach einer solchen Lösung habe ich doch gesucht. Dass meine nicht funktionieren kann hatte ich ja selbst schon bemerkt. Nochmal vielen Dank an alle beteiligten für die sehr schnelle Hilfe! Viele Grüße André
Hallo Herr Buchegger, vielen Dank auch für ihre Antwort! Zu folgendem Teil hab ich dann aber noch eine Frage: Karl Heinz Buchegger schrieb: >> Es könnte ja genau so gut ein viel umfangreicheres Unterprogramm sein >> und dann wäre ein Macro möglicherweise nicht mehr so günstig, wegen des >> höheren Speicherplatzbedarf. > > In üblichen Programmen ist das praktisch immer die günstigere Version. > Welche? Meine bisherige Erkenntnis aus dem Studium diverser Literatur lautet: Macros, wenn es schnell gehen muß -> durch die Textersetzung durch den Assembler gibt es keine Sprünge. Unterprogramme, wenn der Platz sonst nicht reicht -> keine Textersetzung, Code ist nur einmal im Speicher. Viele Grüße André Grimm P.S.: Ich weiß, das ist ein anderes Thema ...
André Grimm schrieb: > Welche? direkt in/out zu verwenden. In den meisten Programmen sind die Ports und auch die Pins an denen etwas hängt, fix vergeben. > Macros, wenn es schnell gehen muß -> durch die Textersetzung durch den > Assembler gibt es keine Sprünge. > Unterprogramme, wenn der Platz sonst nicht reicht -> keine > Textersetzung, Code ist nur einmal im Speicher. Auch ein Funktionsaufruf kostet sowohl Zeit als auch Speicherplatz. Wieviele Toggler hast du denn in deinem Programm? Bei jedem Aufruf hast du ldi XL,PORTA rcall togglebit also 2 Befehle. Dem stehen gegenüber in r17, @0 //lese Zustand Zielport ein eor r17, @1 //Bits laut Bitmaske ändern out @0, r17 also 3 Befehle. D.h. pro Aufruf sparst du an der Aufrufstelle gerade mal 1 Befehl (1 Word) ein. Für das UPro brauchst du aber togglebit: cpi XL,IO_DATASPACEOFFSET brcc memorymapped subi XL,-IO_DATASPACEOFFSET memorymapped: clr XH ld R16,X eor R16,R17 st X,R16 ret 8 Befehle (also 8 Words). Erst mit 8 Stellen im Programm, an denen 1 Pin zu toggeln ist, hast du also platzmässig den Break-Even erreicht, an dem du bei jedem weiteren Toggle 1 Word gewinnst. Nicht zu vergessen, dass damit der X-Pointer erst mal in seiner Funktion gebunden wurde. Will man auch diese Beschränkung weghaben, dann sieht die Bilanz noch trister aus. Hast du in einem Programm soviele Stellen, an denen etwas getoggelt werden muss?
Hallo noch mal, auch für diese Antwort vielen Dank! Noch einmal die Klarstellung, es geht hier nicht wirklich um eine praktische Anwendung, sondern um ein Beispiel. Hier ist ihre Rechnung ein guter Anhaltspunkt, was alles zu beachten ist, wenn man eben vor der Frage steht MACRO oder Unterprogramm. Ich war beim Erlernen des AVR-Assemblers eben auf die Schwierigkeit gestoßen, den IO-PORT per Parameter an das UP zu übergeben. Da ich mir aber sicher war, dass es eine Lösung gibt, die mir allerdings nicht selbst in den Sinn kam, habe ich das hier erfragt und bin mit der Antwort nun auch sehr zufrieden. Also noch einmal vielen Dank an die Gemeinde (neudeutsch Community)! Viele Grüße André Grimm
Karl Heinz Buchegger schrieb: > 8 Befehle (also 8 Words). Da ist natürlich noch Optimierungspotential drinne, das war schließlich nur als Anschauungsbeispiel gedacht. Die Fallunterscheidung memorymapped oder nicht kann man natürlich problemlos auslagern und den Caller zur Entwurfszeit erledigen lassen. .EQU IO_DATASPACEOFFSET=$20 ;->XL : IO-Adresse ; R17: Togglemaske ;<-XH : 0 togglebit: clr XH ld R16,X eor R16,R17 st X,R16 ret Aufruf nunmehr etwa so: ldi XL,PORTA+IO_DATASPACEOFFSET rcall togglebit Oder so ldi XL,DIDR0 rcall togglebit Oder, unter Zuhilfenahme eines Macros, was einem die Arbeit abnimmt, diese Sache dauernd entscheiden zu müssen: #define IO_DATASPACEMAPPED(x) \ ( \ ( (x) < IO_DATASPACEOFFSET) ? \ (x + IO_DATASPACEOFFSET) : \ (x) \ ) Womit wir bei diesem Aufruftemplate ldi XL,IO_DATASPACEMAPPED(PORTA) rcall togglebit wären, aber drei deiner wertvollen Worte in der Subroutine gerettet haben und nebenbei noch auch noch drei Takte, was meist viel wichtiger ist... An der Registernutzung läßt sich hingegen nur wenig optimieren, zur indirekten Addressierung benötigt man nunmal ein Adreßregister, da beißt die Maus kein' Faden ab. allenfalls könnte man X komplett für diesen Kram freistellen und dann zwar kein Datenwort sparen, aber immerhin einen Takt bei jeden Aufruf der Routine.
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.