Forum: Compiler & IDEs Assembler - Funktionen in C einbinden


von Patrick (Gast)


Lesenswert?

Hallo,

ich möchte ein Assemblermodul als Funktion aus meinem C-Hauptprogramm
aufrufen können (kein InlineASM). Leider klappt das nicht.

bisher habe ich folgendes gemacht:
1. ASRC = port.S im makefile eingefügt.
2. ein kurzes Assembler Test-Prog soweit verändert:
;***********************************************************
;void port(unsigned char);

#include <avr/io.h>
#define temp1 = r16
#define temp2 = r17

    .global port
    .func port
port:
       ldi temp1, 0xFF       ;Port B = Ausgang
       out DDRC, temp1
loop:
    inc temp2
    out PORTC,temp2
    DLY:
       dec r4
       brne DLY
       dec r5
       brne DLY
rjmp loop
ret                   // end of function, return to main program
    .endfunc
;********************************************************************

Jetzt das Problem:
die Register defines (temp1) funktionieren nicht (Fehlermeldungen- bad
expression), wenn ich temp1, temp2 direkt mit den Registernamen
(r16,r17) ersetze, funktioniert das übersetzen, allerdings funktioniert
die port.S nicht.(Normalerweise müsste das Programm in der loop
(endlosschleife hängen bleiben)und den PORTC hochzählen).

Ich bin für jede Hilfe dankbar.

Gruß
Patrick

von Jörg Wunsch (Gast)


Lesenswert?

Nun, grundsätzlich erstmal ist Deine Vorgehensweise korrekt.

Aber:

> #define temp1 = r16

Das ist eine Präprozessoranweisung, in der gesagt wird, daß für jedes
Auftreten von temp1 "= r16" zu schreiben ist.  Ich glaube, das ist
nicht das, was Du wolltest. :-)

#define temp1 r16

ist es wohl eher.

Zweitens, bitte lies Dir zuerst die Registerzuordnungen für den
Compiler in der avr-libc FAQ durch.  r16 und r17 gehören nicht zu
den Registern, die Du blind modifizieren darfst.  Ich vermute, daß Du
hier irgendwo bei für den IAR geschriebenem Assemblercode gespickt
hast, die haben aber ein völlig anderes ABI.  Vergleichbar zu r16/r17
bei IAR wären r24/r25 beim GCC (erstes Argument bzw. Rückgabewert
einer Funktion).

Ferner, sieh Dir das Kapitel über die special function registers an.
Ganz so, wie Du das da schreibst, geht das nicht.  Als Hack könntest
Du

#define __SFR_OFFSET 0

benutzen, aber besser ist es, die umständliche Schreibweise (s.u.) zu
nehmen.

Außerdem solltest Du natürlich sicherstellen, daß der GCC keineswegs
r4 und r5 selbst irgendwo belegt, wenn Du die wie oben gezeigt
zweckentfremden willst.  Generell ist es aber oft keine gute Idee, dem
Compiler Register zu entziehen.

Abschließend, ich persönlich ein Fan der Unix-style local labels:

port:
  ldi  temp, 0xff
  out  _SFR_IO_ADDR(DDRC), temp1
1:  inc  temp2
  out  _SFR_IO_ADDR(PORTC), temp2
2:  dec  r4
  brne  2b
  dec  r5
  brne  2b
  rjmp  1b
  ret

Falls Du mit dem GDB debuggen willst, sind local labels praktisch
Pflicht (nach meiner Erfahrung), weil der GDB sonst beim ersten Label
in einer Funktion durcheinanderkommt.  Alternativ zu den rein
numerischen Labels kann man auch Labels benutzen, die mit ".L"
anfangen.  Die numerischen Labels haben den Vorteil, daß man sie
innerhalb einer Quelle wiederverwenden kann, es wird der jeweils
nächstgelegene Label ausgewählt beim Springen.  Habe ich auch irgendwo
in der Doku alles beschrieben...

von Patrick (Gast)


Lesenswert?

@ Jörg Wunsch
Erst mal vielen Dank für die schnelle Antwort.
Mit deiner Version funkioniert das Test-Prog.

Allerdings kann man anscheinend mit den local labels nur nach weiter
oben im Program verzweigen bzw. springen. Habe das Test-Prog auch mit
normalen Labels ausprobiert und es funktionierte.
Anscheinend lag das Problem an out PORTC,temp
erzetzt durch out _SFR_IO_ADDR(PORTC),temp. Das kann ich allerdings
nicht ganz nachvollziehen da in der io.h bzw. in der iom16.h
PORTC schon mit der adresse ersetzt wird: #define PORTC _SFR_IO8(0x15)

Meine Assember-Funktionen sind sehr umfangreich, so dass ich fast alle
verfügbaren Register verwenden musste. Jetzte meine Frage: Ist es
möglich das ich die benutzten Register zu Begin der ASM-Funktion auf
den Stack schreibe (PUSH) und am ende wieder rausschreibe (POP). Könnte
ich so alle Register benutzen ohne den Compiler wichtige Register zu
überschreiben?

Gruß
Patrick

von Andreas S. (andreas) (Admin) Benutzerseite


Lesenswert?

Ja, du musst nur aufpassen dass keine Interruptroutine dazwischenfunkt
und dir die Register kaputt macht.

von Jörg Wunsch (Gast)


Lesenswert?

> Allerdings kann man anscheinend mit den local labels nur nach weiter
> oben im Program verzweigen bzw. springen.

Nein, der angehängte Suffix entscheidet: `b' sucht sich den nächsten
Label mit dieser Nummer rückwärts, `f' sucht vorwärts.  Sowas geht
also (nur als sinnloses Beispiel -- ich habe nicht viel Ahnung von
AVR-Assembler, so daß ich mir das ein bißchen abquäle jetzt):

  ldi  r16, 5
  clr  r17
  ldi  r26, lo8(XXX)
  ldi  r27, hi8(XXX)
1:  ld  r18, X+
  eor  r17, r18
  tst  r17
  breq  1f
  dec  r16
  brne  1b
1:  ret

Das "breq 1f" springt an den Ausgang, das "brne 1b" springt zurück
an
den Schleifenanfang.

> Das kann ich allerdings nicht ganz nachvollziehen da in der io.h
> bzw. in der iom16.h PORTC schon mit der adresse ersetzt wird:
> #define PORTC _SFR_IO8(0x15)

Wenn Du Dir jetzt noch die Definition von _SFR_IO8() ansiehst, kannst
Du es vielleicht eher nachvollziehen. :-)

Diese Definitionen sind so gezimmert, daß Du halt aus C die direkte
Zuweisungsnotation benutzen kannst, die Atmel auch in den
Datenblättern propagiert:

  PORTC = 42;
  tmp = PINC;

Dabei wird erstmal generisch über MMIO-Adressen gearbeitet (da einzig
diese auf allen IO-Registern verfügbar sind).  Der Optimierer
konvertiert dann die MMIO-Zugriffe in direkte Port-IO, wenn das
möglich ist (sprich, wenn die Registeradresse klein genug ist).

> Könnte ich so alle Register benutzen ohne den Compiler wichtige
> Register zu überschreiben?

Andreas hat Dir ja schon geantwortet.  Schau Dir inbesondere in der
(avr-libc-) FAQ die Registernutzungsrichtlinien genau an, damit Du
weißt, was Du retten mußt und was nicht.

von Patrick (Gast)


Lesenswert?

Die Assembler Funktionen enthalten Interruptroutinen. Welche Register
müssen dann noch gesichert werden? Das Flag Register SREG ?

von Jörg Wunsch (Gast)


Lesenswert?

Ja, natürlich.  Praktisch alles.  Ist ja auch logisch, ein Interrupt
kann schließlich an beliebiger Stelle ausgeführt werden und darf keine
Spuren hinterlassen.

Laß einfach den Compiler mal eine ISR übersetzen und guck Dir an, was
er daraus macht.

Allerdings sind viele PUSH/POPs in einer ISR doch auch recht
zeitaufwendig, da fragt man sich nach dem Sinn, warum Du eine ISR in
Assembler schreiben willst...

von Patrick (Gast)


Lesenswert?

Vielen Dank für eure Antworten.
@Jörg Wunsch: Die Assembler-Funktionen existieren bereits und
funktionieren, deshalb will ich Sie weiterverwenden.

Jetzt habe ich aber noch ein weiteres Problem:

1. Die Adressangabe für die Interrupthandler funktioniert nicht mehr
   z.B: .org 0x26
         rjmp timer0_compare

   In C geht es ja mit der Funktion:
   INTERRUPT(SIG_OUTPUT_COMPARE0 ){...}

2. Das GICR Register lässt sich leider nicht so beschreiben wie
   die anderen Register (fehlermeldung: constant value required )
   z.B: out _SFR_IO_ADDR(GICR), temp1
   komischerweise kommt keine Fehlermeldung wenn ich nur das INT0
   nehme. (Ich schreibe dann ein Byte in ein Bit )
   out _SFR_IO_ADDR(INT0), temp1

Gruß
Patrick

von Jörg Wunsch (Gast)


Lesenswert?

> z.B: .org 0x26

Vergiß .org.  Das hat man schon zu Z80-Zeiten unter CP/M nicht mehr
benutzt. ;-)

Im Ernst, für einen Prozeß, bei dem Du für verschiebliche
(`relocatable') Objektdateien assemblierst, die dann erst vom Linker
an den endgültigen Platz sortiert werden, hat die Angabe einer
bestimmten Speicherstelle in der Quelldatei keinen Sinn.  Diese Angabe
müßtest Du dem Linker ja übergeben.

> In C geht es ja mit der Funktion:
> INTERRUPT(SIG_OUTPUT_COMPARE0 ){...}

Im Assembler genauso.

#include <avr/io.h>

.global SIG_OUTPUT_COMPARE0
SIG_OUTPUT_COMPARE0:
  ...
  reti

> Das GICR Register lässt sich leider nicht so beschreiben wie die
> anderen Register (fehlermeldung: constant value required )

Hmm, welcher Prozessor?  Das tut für mich...

> komischerweise kommt keine Fehlermeldung wenn ich nur das INT0
> nehme. (Ich schreibe dann ein Byte in ein Bit )

Nein, machst Du nicht, sondern Du schreibst einfach nur Unsinn. :-) Im
Assembler bist Du für Deinen Unfug halt selst zuständig...  Du benutzt
dann eine Bitnummer als Registeradresse und schreibst Dein temp1 auf
dieses Register.  Allerdings läßt mir der Assembler diesen Versuch mit

foo.S:3: Error: number must be less than 64

nicht durchgehen.

von Patrick (Gast)


Lesenswert?

Vielen Dank für deine Hilfe. Die Assemblerfunktion läuft einwandfrei.
Jetzt hab ich aber noch eine Frage:
Ist es möglich die Einsprunglabel für die Interupts nur lokal anzulegen
(nicht .global SIG_OUTPUT_COMPARE0), da diese sowieso nur in der
Funktion selbst benutzt werden. Somit könnte man doch wieder neue
IR-Routinen für die gleichen Interrupt in anderen Funktionen schreiben
(natürlich ebenfalls lokal).
Mein Versuch .global SIG_OUTPUT_COMPARE0 mit
.local SIG_OUTPUT_COMPARE0 zu ändern schlug leider fehl, da er dann
irgendwo ins Nirvana springt und ein Neustart des Programmes erfolgt.
MfG Patrick

von Jörg Wunsch (Gast)


Lesenswert?

Nein, das geht nicht, da die Interruptvektortabelle vom Linker
zusammengebaut wird.  Wenn Du die Labels nicht global hast, setzt der
Linker stattdessen die als `weak' markierten gleichnamigen Funktionen
aus dem C-Startup-Code dafür ein.

Was für einen Sinn soll das haben, sie lokal zu benutzen?  Das
verstehe ich einfach nicht.  Interrupteinsprungpunkte sind doch per
definitionem global, da sie ausschließlich von der Hardware
angesprungen werden, nicht von anderen Funktionen aus.

von jp Soroka (Gast)


Lesenswert?

-----------------------------

Diplomand SOROKA JP
HTW des Saarlandes
06898-692-5831
jp.soroka@web.de
--------------------

Nun möchte ich eine Ladeelektronik für ein Li-Ion 3S3P Batterypack
entwickeln.
Bei meiner Suche habe ich bereits eine interessante Application Note
gefunden :

www.atmel.com/dyn/resources/prod_documents/doc1659.pdf

Nun bin ich momentan mit der ganze Problematik um zu überlegen. Es gibt
ein Controller, der mit hochsprachige Quellcode programmiert ist. Ich
habe bereits das eine oder das andere Programm runtergeladen, aber ich
glaube das nicht alle Dateien datei ware (.c / .h Dateien in C).

Die Frage die ich mir stellt :

1. Gibt es Leute die mir emailmässig helfen können ?
2. Wo kann ich Evaluation Karte kaufen ?

jp.soroka@web.de

-------------------------------------
AOL :    Xcider21
MSN :    seuldepuispeu@hotmail.com
-------------------------------------

von Jörg Wunsch (Gast)


Lesenswert?

Bitte nicht also Followup auf jeden Thread hier!  Mach dafür einen
eigenen Thread auf, genau einen.

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.