Forum: Mikrocontroller und Digitale Elektronik IO-Port als Parameter in ASM-Unterprogramm


von André G. (andregrimm)


Lesenswert?

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é

von Spess53 (Gast)


Lesenswert?

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

von Steffen H. (avrsteffen)


Lesenswert?

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

von Steffen H. (avrsteffen)


Lesenswert?

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

von Spess53 (Gast)


Lesenswert?

Hi

>Ich weiß jetzt nicht genau ob es bei einem ATmega8515 geht,

Nein, geht mit diesem Oldtimer nicht.

mfG Spess

von Steffen H. (avrsteffen)


Lesenswert?

Ja, hab ich auch gerade gesehen. PINx ist da nur lesend zu verwenden.

von holger (Gast)


Lesenswert?

>Ich habe dazu schon einmal ein Makro geschrieben, dass auch super
>funktioniert.

Dann bleib dabei. Die Version mit Unterprogramm kostet
dich richtig satt Geschwindigkeit.

von Spess53 (Gast)


Lesenswert?

Hi

>.def targetPort = r18

Damit wird 'targetport' zur Adresse von r18, nicht zum Inhalt.

MfG Spess

von Spess53 (Gast)


Lesenswert?

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

von André G. (andregrimm)


Lesenswert?

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é

von Peter D. (peda)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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

von André G. (andregrimm)


Lesenswert?

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é

von André G. (andregrimm)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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?

von André G. (andregrimm)


Lesenswert?

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

von c-hater (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.