Forum: Mikrocontroller und Digitale Elektronik Macros ineinandergreifend


von Multi K. (multikulti)


Lesenswert?

Guten Tag,
ich habe mir zwei Makros gebastelt, die zum ausgeben von Zeichenketten 
über USART dienen sollen.

Im 1. Makro wird eine Routine aufgerufen die im 2. Makro steht. Hinter 
dem 1. Makro wird dann direkt per .db die Zeichenkette abgelegt. Damit 
das Programm weiß wo es nach der routine hinspringen soll, wird vorher 
noch die Sprungadresse in den z-pointer geladen.
1
;Syntax: string_out k
2
.MACRO   string_out
3
    ldi zh,HIGH(@0)        ; Rücksprungadresse in z-pointer laden
4
    ldi zl,LOW(@0)
5
    call string_out_routine    ; string_out_routine aufrufen. Dabei wird die Adresse des 1. datenbytes auf den Stack gelegt.
6
.ENDMACRO

Die Subroutine soll dann durchs 2. makro in den code eingesetzt werden. 
Im prinzip könnte man die routine auch so in den code schreiben, aber 
ich habe es als Makro gemacht, weil ich die routine dann beliebig 
parametrieren kann. D.h. ich kann mir aussuchen welche register 
verwendet werden sollen etc.
Im prinzip läd die routine nur die Adresse vom 1.Zeichen der 
Zeichenkette in den "String Pointer" Der String Pointer besteht nur aus 
2 Speicherzellen im RAM.
Danach aktiviert er das "USART Data register empty interrupt". Das 
interrupt wird immer ausgelöst wenn der USART für das nächste byte 
bereit ist.
1
;Syntax: string_out_set Rb,Rb
2
.MACRO string_out_set 
3
string_out_routine:    
4
  pop @0              ; Oberes Byte der Adresse des 1. Datenbytes des Strings vom Stack holen
5
  mov @1,@0            ; und mit 2 Multiplizieren ( mit sich selbst addieren)
6
  add @0,@1
7
  sts String_Pointer_HIGH_RAM,@0  ; Anschließend in den String Pointer ablegen
8
  pop @0
9
  mov @1,@0
10
  add @0,@1
11
  sts String_Pointer_LOW_RAM,@0
12
  sbi UCSRB,UDRIE          ; "USART Data register empty interrupt" Interrupt aktivieren (Interrupt wird sofort augelöst)
13
  ijmp              ; Ans ende der Zeichenkette bzw. zum nächsten Bbefehl springen
14
.ENDMACRO

Der Rest passiert dann in der Interrupt routine. Ich denke nich das die 
was mit dem Problem zu tun hat, aber ich schreibe sie einfach mal dabei:
1
USART_UDR:  push r16
2
      in r16,SREG
3
      push r16
4
      push ZH
5
      push ZL
6
      lds zh,String_Pointer_HIGH_RAM
7
      lds zl,String_Pointer_LOW_RAM
8
      nop
9
      nop
10
      lpm r16,z+
11
      cpi r16,0
12
      breq String_complete
13
      out UDR,r16
14
      sts String_Pointer_HIGH_RAM,zh
15
      sts String_Pointer_LOW_RAM,zl
16
      pop zl
17
      pop zh
18
      pop r16
19
      out SREG,r16
20
      pop r16
21
      reti
22
23
      String_complete:  pop zl
24
                pop zh
25
                pop r16
26
                out SREG,r16
27
                pop r16
28
                cbi UCSRB,UDRIE
29
                reti

Die Macros werden dann folgendermaßen eingesetzt:
1
string_out_set r16,r17
2
3
loop:
4
string_out r16,string_end
5
.db "Test !",0x0A,0x0D,0
6
string_end:

Das ganze hat ohne makros so funktioniert, aber wie es scheint, kann das 
1. makro nicht auf die routine im 2. makro zugreifen. Der compiler gibt 
folgende Fehlermeldung aus:
"C:\Dokumente und Einstellungen\Administrator\Eigene Dateien\AVR 
Projekte\UART_string_routine.asm(71): error: Undefined symbol: 
string_out_routine"

Er tut so als würde es die routine gar nicht geben. Ich denke ein Makro 
wird in den Quellcode einfach nur eingefügt, folglich müsste an der 
Stelle
1
 
2
string_out_set r16,r17

die routine stehen die mit dem makro "string_out" aufgerufen wird. Oder 
wird der code doch nicht einfach nur eingefügt?

Über ein bischen Hilfe währe ich sehr erfreut =)

mfg Steffen

von Grrr (Gast)


Lesenswert?

Ich rate mal, fdas Du vom avrasm v2 redest.

Dann setz das Label string_out_routine vor den macroaufruf.

von Michael U. (amiga)


Lesenswert?

Hallo,

sowas habe ich früher auf dem Z80 gemacht. :-))

Gibt es einen Grund, das 2. Macro überhaupt zu benutzen und nicht direkt 
als "Subroutine" abzulegen?

Kostet doch nur Flash, weil der Code ja bei jeder Benutzung angelegt 
wird.
Das Deine "Subroutine dann mit einem ijmp statt ret endet verwirrt doch 
nur die anderen... ;-))

Gruß aus Berlin
Michael

von Peter R. (gelb)


Lesenswert?

Labels in Makros genießen eine Sonderbehandlung. Sie haben zwar einen 
Namen (irgendwas muss man ja hinschreiben), aber man muss bedenken, dass 
Makros in der Regel mehrfach verwendet werden und bei Sprüngen innerhalb 
des Makros jedesmal eine andere Adresse benötigt wird.

Inerhalb von Makros kann man daher gleich mit relativen anonymen 
Adressen vom Typ PC+x arbeiten, das kommt der Sache am nächsten.

Feste Adressen in Makros, die von außerhalb angesprungen werden können, 
kann es daher nicht geben.

Grüße, Peter

von Multi K. (multikulti)


Lesenswert?

Michael U. schrieb:
> sowas habe ich früher auf dem Z80 gemacht. :-))
>
> Gibt es einen Grund, das 2. Macro überhaupt zu benutzen und nicht direkt
> als "Subroutine" abzulegen?
>
> Kostet doch nur Flash, weil der Code ja bei jeder Benutzung angelegt
> wird.
> Das Deine "Subroutine dann mit einem ijmp statt ret endet verwirrt doch
> nur die anderen... ;-))

Ja, ich wollte ein Makro haben, damit ich die subroutine in eine .inc 
packen kann. Es ist so gedacht das ich die routine dann bequem per makro 
in meinen quellcode einfügen kann. Ansonsten müsst ich die routine 
jedesmal manuell in den code einfügen und die register umschreiben. Das 
heißt auch das makro nur einmal in den quellcode eiingefügt wird, daher 
auch der Name "string_out_set".

Michael U. schrieb:
> Das Deine "Subroutine dann mit einem ijmp statt ret endet verwirrt doch
> nur die anderen... ;-))

die routine kann nicht mit ret enden, weil der der programmcounter dann 
auf den string zeigen würde. Der String wird ja direkt hinter den aufruf 
der subroutine angehängt. Ich habe da einen kleinen Trick angewendet. 
Beim call befehl wird die adresse der nachfolgenden Speicherzelle auf 
den stack geworfen. Beim ret wird die adresse dann wieder vom stack in 
den programmcounter geladen. Normalerweise steht dort dann der nächste 
befehl. Ich hab aber direkt nach dem call befehl den string abgelegt. 
D.h. es wird dann die adresse vom Stringanfang auf den stack gelegt. Die 
routine benutzt die Adresse dann. Damit die subroutine dann wieder 
zurück ins programm findet hab ich vorher den nächsten befehl in den 
z-pointer geladen, der dann per ijmp angesprungen wird.

Peter Roth schrieb:
> Labels in Makros genießen eine Sonderbehandlung. Sie haben zwar einen
> Namen (irgendwas muss man ja hinschreiben), aber man muss bedenken, dass
> Makros in der Regel mehrfach verwendet werden und bei Sprüngen innerhalb
> des Makros jedesmal eine andere Adresse benötigt wird.

Das ergibt natürlich Sinn. Da fällt mir jetzt auch keine elegantere 
methode ein als das Label "string_out_routine" vor das Makro zu setzen. 
Andererseits müsst ich die routine bei jedem programm das ich schreibe 
manuell einfügen. Das wollt ich mit dem makro ja eigentlich vermeiden.

von Peter D. (peda)


Lesenswert?

Steffen P. schrieb:
> D.h. ich kann mir aussuchen welche register
> verwendet werden sollen etc.

Damit wirst Du bald selber nicht mehr durchsehen und Schiffbruch 
erleiden.

Du mußt Dich einmal hinsetzen und Dir Registerkonventionen festlegen.
Z.B. A0..A3 als Arbeitsregister und Parameterübergabe, I0..I1 als 
Schleifenzähler, ZERO als Nullregister (R2).

Und diese müssen dann für das ganze Projekt auch eingehalten werden.
Genau dazu ist ja der .DEF Befehl gedacht, um das bequem an einer 
zentralen Stelle zu machen.

Register als Klartext (R0..R31) haben nirgends was im Quelltext 
verloren, da werden nur die definierten Namen verwendet.
Außnahmen sind nur Register, die festgelegt sind, z.B. R0,R1 für SPM- 
und MUL-Befehle oder die Pointerregister X,Y,Z.


Peter

von Multi K. (multikulti)


Lesenswert?

Peter Dannegger schrieb:
> Du mußt Dich einmal hinsetzen und Dir Registerkonventionen festlegen.
> Z.B. A0..A3 als Arbeitsregister und Parameterübergabe, I0..I1 als
> Schleifenzähler, ZERO als Nullregister (R2).
>
> Und diese müssen dann für das ganze Projekt auch eingehalten werden.
> Genau dazu ist ja der .DEF Befehl gedacht, um das bequem an einer
> zentralen Stelle zu machen.

Nunja, ich möchte mir eine Laufzeitbibliothek anlegen. Die 
laufzeitbibliothek möchte ich ja allgemein für projekte nutzen, nicht 
nur für dieses. Ich müsste mich also immer an die genormten Register 
halten, wenn ich die Bibliothek benutze. Das wollte ich eigentlich 
vermeiden. So wie ich es jetzt gemacht habe muss ich ins makro nur die 
Arbeitsregister eingeben die fürs Makro verwendet werden sollen. So bin 
ich wesentlich flexibler beim verteilen von registern.
Außerdem gibts da noch ein ganz anderes problem: Wenn ich die subroutine 
nicht als makro sondern halt als normale subroutine in meine include 
datei packe, wird sie immer mit in den speicher geschrieben.


Aber zurück zum problem: Gibt es eine möglichkeit ein Label in einem 
Makro global zu machen? Kann der Assembler (avr Studio) das Label im 
Makro auslesen und mit der Adresse ein neues globales Label erstellen?

von Multi K. (multikulti)


Lesenswert?

Ja es geht, es ist einfacher als ich dachte^^
Die routine sieht jetzt so aus:
1
.MACRO string_out_set 
2
string_out_routine:
3
.equ string_out_routine_global = string_out_routine    
4
  pop @0              ; Oberes Byte der Adresse des 1. Datenbytes des Strings vom Stack holen
5
  mov @1,@0            ; und mit 2 Multiplizieren ( mit sich selbst addieren)
6
  add @0,@1
7
  sts String_Pointer_HIGH_RAM,@0  ; Anschließend in den String Pointer ablegen
8
  pop @0
9
  mov @1,@0
10
  add @0,@1
11
  sts String_Pointer_LOW_RAM,@0
12
  sbi UCSRB,UDRIE          ; "USART Data register empty interrupt" Interrupt aktivieren (Interrupt wird sofort augelöst)
13
  ijmp              ; Ans ende der Zeichenkette bzw. zum nächsten Bbefehl springen
14
.ENDMACRO

string_out_routine_global enthält jetzt die Adresse vom label 
string_out_routine. Jetzt kann ich von außerhalb auf das label im makro 
springen. Natürlich würds jetzt probleme geben wenn ich das makro 
mermals aufrufe, aber dazu ist es ja nicht gedacht.

So etwas wie smart linker gibt es beim assembler nicht oder?

von Peter D. (peda)


Lesenswert?

Steffen P. schrieb:
> Nunja, ich möchte mir eine Laufzeitbibliothek anlegen. Die
> laufzeitbibliothek möchte ich ja allgemein für projekte nutzen, nicht
> nur für dieses. Ich müsste mich also immer an die genormten Register
> halten, wenn ich die Bibliothek benutze.

Du verstehst nicht den Sinn dahinter.
Das Ausdenken von Regeln und sich daran halten ist kein Mumpitz, sondern 
soll Dir das Programmieren wesentlich erleichtern.
Man nimmt unnötige Flexibilität raus, um Übersicht zu gewinnen und damit 
weniger Fehler zu machen.

Wenn Du kein System hast, wie Du die Register verwendest, kannst Du nie 
größere Projekte bearbeiten. Du wirst Dich unweigerlich in Deinem 
Macro-Parameter-Wust verirren.

Das Vereinbaren von Konventionen ist nicht auf meinem Mist gewachsen, 
sondern jeder Compiler macht das so und fährt nicht schlecht dabei.


Das bedingte Assemblieren macht man üblicher Weise über Symbole:
1
.macro blabla
2
.equ WDTRIGGER = 1
3
.endmaco
4
5
.ifdef WDTRIGGER
6
; hier Code einfügen
7
.endif


Peter

von spess53 (Gast)


Lesenswert?

Hi

>Register als Klartext (R0..R31) haben nirgends was im Quelltext
>verloren, da werden nur die definierten Namen verwendet.

>Wenn Du kein System hast, wie Du die Register verwendest, kannst Du nie
>größere Projekte bearbeiten.

Das ist mir doch etwas zu hanebüchen. 'defs' sind vielleicht in 
kleineren Programme hilfreich. Bei grösseren Programmen ist spätestens 
beim dritten Unterprogramm der gewählte Bezeichner total irreführend 
oder man verbringt seine Zeit mit Registerdefinieren und Redefinieren 
statt mit Programmieren. Total tödlich und als einziges verwerflich ist 
die gemischte Verwendung von Registerdefinitionen und Registernamen. 
Das führt unweigerlich zum Chaos.

MfG Spess

von Peter D. (peda)


Lesenswert?

spess53 schrieb:
> Das ist mir doch etwas zu hanebüchen. 'defs' sind vielleicht in
> kleineren Programme hilfreich. Bei grösseren Programmen ist spätestens
> beim dritten Unterprogramm der gewählte Bezeichner total irreführend
> oder man verbringt seine Zeit mit Registerdefinieren und Redefinieren
> statt mit Programmieren.

Hä?

Die Register werden nur einmal gobal definiert und dann natürlich in 
sämtlichen Funktionen beibehalten.
Sonst klappt ja die Parameterübergabe nicht mehr, wenn da jede Funktion 
ihr eigenes Süppchen kocht.
Z.B. A0 ist das low-Byte des ersten Parameters bzw. ersten Returnwertes 
usw.
Hier mal ein Beispiel:

http://www.mikrocontroller.net/attachment/16260/UDIV32.ASM


Peter

von spess53 (Gast)


Lesenswert?

Hi

>Z.B. A0 ist das low-Byte des ersten Parameters bzw. ersten Returnwertes
>usw.

Wenn ich, nur mal zum Beispiel, den oder die Eingangswerte eines 
Unterprogramms nicht verändern will, weil ich die noch brauche, haut 
dieses System schon per Definition nicht nicht mehr hin. Oder ich muss 
zusätzlichen Overhead, wie diese MOV-Orgien, die ich aus 
disassemblierten C-Code kenne, erzeugen.
Anderer Fall: Dieses 'temp'-Gedödel. Diese Register sollten universell 
sein, also >=r16. Nach Abzug von X,Y,Z bleiben davon 10 Register übrig, 
die man aber auch für die Parameter haben möchte. Kann sehr schnell eng 
werden. Also wieder entweder inkonsequent oder Byteschubsen.

Dieses Prinzip lässt sich mit etwas Diszilin auch ohne DEFs 
verwirklichen. Und man ist bleibt flexibel.
Von mir aus kann das jeder halten wie er will, aber ein absolutes Muss 
ist es keinesfalls.

MfG spess

von Peter D. (peda)


Lesenswert?

spess53 schrieb:
> Wenn ich, nur mal zum Beispiel, den oder die Eingangswerte eines
> Unterprogramms nicht verändern will, weil ich die noch brauche, haut
> dieses System schon per Definition nicht nicht mehr hin. Oder ich muss
> zusätzlichen Overhead, wie diese MOV-Orgien, die ich aus
> disassemblierten C-Code kenne, erzeugen.

Du brauchst in jedem Fall "MOV-Orgien" (2* MOVW bei 32 Bit ist für mich 
aber noch lange keine Orgie).

Der AVR hat ja keine 3 Operanden Befehle (a = b + c), es kann also kein 
anderes Ergebnisregister angegeben werden.
Der ursprüngliche Wert wird zerstört, Ausnahme ist nur der 
Comparebefehl, da er kein Ergebnis speichert.

Wenn also mit einem Wert gerechnet werde soll und der gleiche Wert noch 
benötigt wird, dann mußt Du eine Kopie anlegen!

Ich kann mir auch nicht vorstellen, daß es besser ist, mehrere 
Rechenfunktionen für verschiedene Registerkombinationen im Code 
anzulegen. Die paar MOVW sind dagegen nur Pinats.


Peter

von spess53 (Gast)


Lesenswert?

Hi

>Der AVR hat ja keine 3 Operanden Befehle (a = b + c), es kann also kein
>anderes Ergebnisregister angegeben werden.

Es ist aber wohl ein Unterschied (r16 ist vorgegeben) wenn ich schreibe:

....
ldi r17,5
add r16,r17

oder

....
ldi r17,5
add r17,r16

oder?

>Ich kann mir auch nicht vorstellen, daß es besser ist, mehrere
>Rechenfunktionen für verschiedene Registerkombinationen im Code
>anzulegen.

Da hast du schon recht. Allerdings kein Argument für 'DEFs'. Ob ich in 
einem Unterprogramm r16...r23 oder A0...A7 herumwuseln habe macht für 
mich keinen Unterschied. Wenn ich allerdings gerade nicht weiß ob X1 
gerade r15 oder r16 ist, dann schon.

MfG Spess

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.