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
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 ...
Alternativ: falls möglich, die automatische Sicherung ausschalten #pragma savereg- (?) sich dann das Listung anschauen und danach entscheiden, welche Register gesichert werden müssen.
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 :-)
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.
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)...
; - 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
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
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
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
INTERRUPT sichert doch IMHO gar nicht die Register. Deswegen unterscheidet man die beiden doch. Oder habe ich mich da mal wieder verlesen?
INTERRUPT lässt weitere Interrupts zu, also fügt automatisch ein "sei();" hinzu...
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
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
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
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?
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 ;-)
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
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
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
"@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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.