www.mikrocontroller.net

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


Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Rufus T. Firefly (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 ...

Autor: crazy horse (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 :-)

Autor: Michael (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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)...

Autor: Koopi (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Koopi (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Rahul (Gast)
Datum:

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

Autor: Steffen Hausinger (Gast)
Datum:

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

Autor: Koopi (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Peter Dannegger (peda)
Datum:
Angehängte Dateien:
  • I2C.C1 (698 Bytes, 99 Downloads)

Bewertung
0 lesenswert
nicht 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

Autor: thkais (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Werner B. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 ;-)

Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: thkais (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: thkais (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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-Tu...

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Steffen Hausinger (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.