Forum: Mikrocontroller und Digitale Elektronik In Assembler auf C-Variablen zugreifen?


von Steffen Hausinger (Gast)


Lesenswert?

Hallo allerseits

Vorweg: ich programmiere mit WinAVR.

Es geht um folgendes: Ich habe einen zeitkritischen Teil in meinem
Programm, den ich mit einer Interruptroutine bedienen muss. WinAVR
sichert jedoch zu Beginn einer Interruptroutine sämtliche seiner
Arbeitsregister. Bis er damit fertig ist dauert es mir leider zu
lange.

Deshalb möchte ich die Interruptroutine in Assembler schreiben. Ich
muss in der Routine jedoch auf einige Variablen im C-Programm
zugreifen. Wie geht das? Geht das überhaupt?

Viele Grüsse,
Steffe

von Rufus T. Firefly (Gast)


Lesenswert?

Wenn die Variablen als globale Variablen deklariert sind, sind sie auf
Linkerebene sichtbar, die übliche Namenskonvention ist hier das
Voranstellen eines Unterstrichs.
Daher solltest Du aus Deinem Assemblercode heraus auf eine
beispielsweise Blafusel genannte Variable über den Namen _Blafusel
zugreifen können.

Ob das bei gccavr auch so aussieht, entzieht sich meiner Kenntnis; Du
kannst es a) ausprobieren und/oder b) in der Dokumentation/dem Tutorial
nachsehen ...

von crazy horse (Gast)


Lesenswert?

Alternativ: falls möglich, die automatische Sicherung ausschalten
#pragma savereg- (?)
sich dann das Listung anschauen und danach entscheiden, welche Register
gesichert werden müssen.

von Steffen Hausinger (Gast)


Lesenswert?

Hallo

Ich habe eben Deinen Tipp versucht, Rufus. Es hat leider nicht
geklappt. Dabei habe ich es mit

uint8_t i=12;
.
.
asm("tst _i"::);

versucht, alternativ mit und ohne Unterstrich sowie mit Deklaration als
"register". Es kommt die Fehlermeldung:

C:\WINDOWS\TEMP/ccNXKigb.s:424: Error: constant value required

Weißt Du woran das liegen könnte? Das Tutorial gibt nur etwas in Sachen
Parameterübergabe her :-(

@crazy horse: Guter Tipp! Allerdings verhaspel ich mich ja sofort,
sobald ich etwas in meiner Interruptroutine ändere und ich vergesse zu
schauen, ob das Einfluss auf die verwendeten Register hat... Aber
wenigstens hab ich jetzt schonmal eine Lösung :-)

von Michael (Gast)


Lesenswert?

Ohne es näher zu begründen, erscheint mir Dein Vorhaben etwas riskant.
Meine Empfehlung: deklariere die in Ass und C gemeinsam genutzten
Variablen 'public' im Ass-Modul und als 'extern' in C. Die Zugriffe
von C müssen bei abgeschaltetem Interrupt erfolgen (spezielles
Int-Enable kurzzeitig abschalten).
Anderfalls merkt das C-Programm nicht, wenn der Wert (insbesondere bei
int/long) während des Zugriffes verändert wird.

von Steffen Hausinger (Gast)


Lesenswert?

Hast Du oder jemand anderes vielleicht ein Codebeispiel für mich? Wie
gesagt, ich hab es doch schon mit globaler Variable in C probiert, aber
es ging nicht (Fehlermeldung)...

von Koopi (Gast)


Lesenswert?

; - SRAM Nutzung Asm----------------------------

.equ STARTADRESSE = 0x00B0

.equ BAdr_ErrorSendFrameBuffer   = STARTADRESSE + 0x0000;
.equ p_ErrorChecksumImSendBuffer = STARTADRESSE + 0x000F;
.equ BAdr_SendBuffer     = STARTADRESSE + 0x0010
usw.

; - SRAM Nutzung C ----------------------------
#define STARTADRESSE   0xB0 // muss direkt hinter Stack beginnen

unsigned char BAdr_ErrorSendFrameBuffer[15] @ STARTADRESSE + 0x00;
unsigned char p_ErrorChecksumImSendBuffer   @ STARTADRESSE + 0x0F;
unsigned char SendBuffer[SEND_BUFFER_SIZE]  @ STARTADRESSE + 0x10;
usw.


Hallo,

ist in Codevision aber vielleicht hilft es

Koopi

von Steffen Hausinger (Gast)


Lesenswert?

Hallo Koopi

Was tust Du denn in diesem Programmteil? Ich lese daraus nur, dass Du
Äquivalenzen erstellst und globale Variablen deklarierst.

Wie greifst Du in Deinem Assemblerprogramm darauf zu?


Übrigens scheinen die "#pragma savereg" Befehle nicht zu
funktionieren :-( Gibt es bei WinAVR überhaupt #pragma? Kann mir sonst
jemand sagen, wie ich die Programmadresse einstellen kann, damit ich
manuell einen "rcall" setzen kann? Also quasi das Äquivalent zu
".org", nur in WinAVR?

Viele Grüsse,
Steffen

von Koopi (Gast)


Lesenswert?

Einfach nutzen ( bitte nicht den Sinn der Zeilen suchen ):

ldi YH, 1
ldi YL, BAdr_ErrorSendFrameBuffer
RecBufferCopy:
ld  R22, y+     ; aktuelles Byte aus ErrorSendFrameBuffer laden und
std y + 5, R22  ; aktuelles Byte wieder weiter hinten ablegen
cpi YL, BAdr_ErrorSendFrameBuffer + 16
brlo RecBufferCopy

for( i = 0; i < 15; ++i )
   BAdr_ErrorSendFrameBuffer[i+5] = BAdr_ErrorSendFrameBuffer[i];


Koopi

von Steffen Hausinger (Gast)


Lesenswert?

Passt!! Danke Koopi :-))

(1) Globale deklaration der Variablen
(2) Im Assemblerbefehl ist der Variablenname die Speicheradresse
(asm("lds r18,i") )

Jetzt fehlt mir zu meinem Glück nur noch eine Möglichkeit, bei SIGNAL
bzw. INTERRUPT die automatischen Sicherungen aller Arbeitsregister zu
unterdrücken. Oder eine Möglichkeit, manuell einen "rcall"-Befehl in
einen Interruptvektor schreiben zu können. Für letzteres muß ich
wissen, wie ich eine Programmadresse explizit auswählen kann (ähnlich
".org 0x0001" bei AVRStudio). Hast Du / ihr da auch noch einen Tipp?

Vielen Dank!
Steffen

von Rahul (Gast)


Lesenswert?

INTERRUPT sichert doch IMHO gar nicht die Register.
Deswegen unterscheidet man die beiden doch. Oder habe ich mich da mal
wieder verlesen?

von Steffen Hausinger (Gast)


Lesenswert?

INTERRUPT lässt weitere Interrupts zu, also fügt automatisch ein
"sei();" hinzu...

von Koopi (Gast)


Lesenswert?

Kann ich dir nichts zu sagen, denn "#pragma savereg-" funktioniert in
Codevision einwandfrei.
Sei vorsichtig mit ungesicherten Registern. Ich habe mit solchen
"Optimierungen" schon Tage verbracht. Der Absturz kommt nämlich an
einer Stelle, die nicht reproduzierbar ist.

Koopi

von Steffen Hausinger (Gast)


Lesenswert?

Ich habe noch zwei Fragen, aber zunächst kann man mein Problem wohl
folgendermaßen lösen:

Assembler-Datei erzeugen (Endung .S) und im Makefile inkludieren (siehe
Tutorial). In der Assemblerdatei den Interrupt-Bezeichner (hier
SIG_INTERRUPT0) global machen (=> .global SIG_INTERRUPT0). Anschließend
eine Marke mit diesem Namen erstellen (=> SIG_INTERRUPT0:) und dann dort
die Interruptroutine einfügen. WICHTIG: Der Name der Assemblerdatei darf
NICHT dem Namen einer anderen C-Datei entsprechen (Beispiel ow.c und
ow.S)!! (Das herauszufinden hat mich einen halben Tag gekostet und an
den Rand der Verzweiflung gebracht!!)

So sieht das ganze nun bei mir aus:

---Datei owasm.S---

#include <avr/io.h>     // SIEHE UNTEN!!!

.global SIG_INTERRUPT0

SIG_INTERRUPT0:
...Quellcode...

.end

---Datei owasm.S---


Nun zu meinem neuen Problem: Er kennt, trotz Einbindung von <avr/io.h>
die Standardbezeichner (bsp. SREG, MCUCR,...) nicht. Einzige Ausnahme:
PORTB erkennt. Er gibt mir die Fehlermeldung "Error: number must be
less than 64" aus! Schreibe ich die IO-Adresse manuell hin,
funktioniert es allerdings. Was soll das?!

Nächste Ungereimtheit: Wenn ich das Include von <avr/io> weglasse, dann
bindet er mit einem Mal meinen gesamten Assemblercode nicht mehr ein!
Wie kann das sein?! Muss ich den Code in einer C-Datei mit "#include
<owasm.S>" einfügen? Im Tutorial steht davon nichts!

Ich bin hier wirklich auf Eure Hilfe angewiesen, ich komme absolut
nicht mehr weiter! Ich habe schon so viel Zeit investiert, bin zwar
schon ein bißchen schlauer geworden, aber ich mag gar nicht daran
denken dass einer von Euch vielleicht sagt: "Das hätte ich Dir gleich
sagen können!".

Ich würde mich wirklich sehr freuen, wenn ihr mir weiterhelfen könnt!!

Grüsse,
Steffen

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

Ich mußte auch mal Assembler verwenden, da es ja im AVR keine
Interruptprioritäten gibt, aber ein Timerinerrupt sehr schnell
ausgeführt werden mußte.

Um das zu erreichen, mußte ich den I2C-Interrupt sperren ohne aber das
Interruptflag zu löschen und dann "sei".

Ich habe dazu inline Assembler benutzt, dann sind alle C-Variablen
uneingeschränkt zugreifbar.

Die Syntax ist in der Doku beschrieben, ist aber etwas
gewöhnungsbedürftig.


In der Regel sind die Register R2..R11 vom Compiler unbenutzt und
können somit für eigene Assemblersachen benutzt werden (spart
tonnenweise PUSH und POP), sollte man aber nach dem Compilieren
überprüfen.

Ich habe mir also einen nackten I2C-Interrupt gebaut, der dies in
Assembler tut und dann den eigentlichen Handler aufruft. Dieser ist
dann als Signal definiert und sichert somit selber alle seine benutzten
Register.


Die Anordnung der I2C-Bits ist ja wirklich die maximal ungünstigste:
Hätte man das TWIE in den unteren IO-Bereich und getrennt vom TWINT
untergebracht, wäre der ganze Schrunz durch ein simples CBI erledigt
gewesen.

Anbei mal der Code als Beispiel.


Peter

von thkais (Gast)


Lesenswert?

Probiers mal mit _SFR_IO_ADDR

z.B. out _SFR_IO_ADDR(PORTB),temp
     in temp,_SFR_IO_ADDR(PINB)

Ich versuche mich auch gerade im Austausch von Daten zwischen Assembler
und C.
Zwar leuchtet mir das obige Beispiel ein, aber geht das nicht auch
einfacher, oder muß ich grundsätzlich den Speicher der globalen
Variablen selbst bestimmen?

von Werner B. (Gast)


Lesenswert?

Das "retten" der Register unterdrücken ...

z.B.
void SIG_OUTPUT_COMPARE( void ) _attribute_ ( ( signal, naked ) );
void SIG_OUTPUT_COMPARE( void )
{
  asm volatile ( "nop" );
  asm volatile ( "reti" );
}

P.S.  Das ist alles nur geklaut ;-)

von Steffen Hausinger (Gast)


Lesenswert?

Erstmal vielen Dank Euch dreien! Ihr habt mich eine ganzes Stück
weitergebracht!

@Peter: Leider funktioniert Dein Code bei mir nicht. Du benutzt
"SIGNALN" statt "SIGNAL", richtig? Wird dadurch das Sichern der
Register umgangen? In der Routine rufst Du dann eine mit
"__attribute__ ((signal))" deklarierte Funktion auf. Ist das eine zur
Interruptroutine umfunktionierte Routine? Sichert er hier die Register
und beendet den Code mit "reti"?
Bei mir springt er bei der Ausführung gleich wieder auf Reset. Er
scheint die Routine also nicht als Interrupt zu erkennen und gibt mir
Fehler wie "function declaration isn't a prototype" oder "return
type defaults to `int'" :-( Hast Du eine Idee, wo mein Problem
liegt?

@thkais: Stimmt, mit _SFR_IO_ADDR funktioniert es! In der <ioxxxx.h>
werden die Standardbezeichner ja mit _SFR_IO definiert. Wo ist denn
hier der Unterschied? Warum funktioniert es mit C-Code, aber nicht mit
Assembler? Und, wichtiger, muss ich jetzt jedesmal _SFR_IO_ADDR
verwenden oder gibt es eine Möglichkeit einfach nur die Bezeichner zu
nehmen?

@Werner: Genau diese Funktionalität habe ich ursprünglich gesucht :-)
Die Interruptroutine ist dann zwar tatsächlich "naked", also auch
ohne "reti", aber es ist ja kein Problem das manuell hinten
einzusetzen! Toll!

Vielen Dank nochmal!
Steffen

von thkais (Gast)


Lesenswert?

So genau kenne ich mich in C auch nicht aus, IMHO liegt es daran, daß
die SFR - Adressen nicht mit den RAM-Adressen übereinstimmen.
Man könnte sich etwas Schreibarbeit mit einigen define - Statements
ersparen.
Inzwischen habe ich auch herausgefunden, wie man bequem und einfach auf
eine globale Variable von C zugreift, z.B. um eine globale C-Variable in
einer Assembler Interrupt-Routine zu verändern.

Im C-Code die globale Definition:

volatile uint8_t zaehler;
.
.
.


Im Assembler-Code:

.extern zaehler
temp = 16

.global SIG_8INTERRUPT0
SIG_INTERRUPT0:

    push temp
    in temp,_SFR_IO_ADDR(SREG)
    push temp

    in temp,_SFR_IO_ADDR(PINB)
    sts zaehler,temp

    pop temp
    out _SFR_IO_ADDR(SREG)
    pop temp
    reti

von thkais (Gast)


Lesenswert?

Ich war mal so frei, meine Erkenntnisse in das GCC-Tutorial zu
schreiben. Wäre nett, wenn ein C-Kenner sich das Ganze mal anschaut -
hoffentlich habe ich keinen Mist verzapft ;)

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Globale_Variablen_f.FCr_Datenaustausch

von Peter D. (peda)


Lesenswert?

"@Peter: Leider funktioniert Dein Code bei mir nicht."


Stimmt, da fehlte noch was:


#define asmv asm volatile                       // inline assembler
                                                // naked interrupt
#define SIGNALN(signame)                                 \
void signame (void) _attribute_ ((naked));             \
void signame (void)



Peter

von Steffen Hausinger (Gast)


Lesenswert?

Ach jetzt kapier ich Deinen Code erst, Peter! Der Backslash "\" ist
ein Zeilenumbruch (kannte ich vorher nicht) und Du definierst den
Prototyp und anschließend gleich den Funktionskopf in einem! Das
Prinzip ist also auch so, wie Werner es später vorgeschlagen hat. Alles
klar :-)

@thkais: Das mit den Variablen hatte ich mit Hilfe von Koopi gestern
auch noch rausgefunden (siehe Posting um 22:01). Allerdings ohne das
Schlüsselwort ".extern", das hätte sicherlich auch nochmal Probleme
gegeben.

Also nochmal vielen vielen Dank Euch allen! Ich glaube, ab jetzt komme
ich alleine weiter :-)

Viele Grüsse,
Steffen

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.