ATTiny2313 ISR-Stack-Nutzung

Ich verwende einen ATTiny2313 als seriellen Konzentrator. Es hat nur 128 Byte RAM. Ich glaube, dass mir während der ISR der RAM ausgeht. Meine Frage ist, wie viel RAM (Stack) ein ISR verwendet, um den Kontext (Register) zu speichern. Dh wenn ich ISRs verwende, wie viel werde ich von 128 Bytes auslassen. Gibt es eine Möglichkeit, einen Stapelüberlauf zu erkennen?

Antworten (1)

Nun, beim Überprüfen der ATTiny2313-Dokumentation auf Seite 15 heißt es:

Die Unterbrechungsausführungsantwort für alle freigegebenen AVR-Unterbrechungen beträgt mindestens vier Taktzyklen. Nach vier Taktzyklen wird die Programmvektoradresse für die eigentliche Unterbrechungsbehandlungsroutine ausgeführt. Während dieser Periode von vier Taktzyklen wird der Programmzähler auf den Stapel geschoben. Der Vektor ist normalerweise ein Sprung zur Unterbrechungsroutine, und dieser Sprung dauert drei Taktzyklen. Wenn während der Ausführung eines Mehrzyklusbefehls ein Interrupt auftritt, wird dieser Befehl abgeschlossen, bevor der Interrupt bedient wird. Wenn ein Interrupt auftritt, während sich die MCU im Schlafmodus befindet, wird die Reaktionszeit der Interrupt-Ausführung um vier Taktzyklen erhöht. Diese Erhöhung kommt zusätzlich zur Anlaufzeit aus dem gewählten Schlafmodus.

Eine Rückkehr von einer Unterbrechungsbehandlungsroutine dauert vier Taktzyklen. Während dieser vier Taktzyklen wird der Programmzähler (zwei Bytes) aus dem Stapel zurückgeholt, der Stapelzeiger wird um zwei erhöht und das I-Bit in SREG wird gesetzt.

Daher sehen Sie während eines Interrupts (dem PC) wirklich nur 2 Bytes auf dem Stapel; alles andere, was ein ISR auf den Stack legt, ist Sache des ISR selbst. Ich würde nicht erwarten, dass ein gut geschriebener Interrupt-Handler eine ganze Menge Stack-Speicherplatz benötigt.

Was den Stapelzeiger selbst betrifft, heißt es auf Seite 13:

Der Stack wird hauptsächlich zum Speichern temporärer Daten, zum Speichern lokaler Variablen und zum Speichern von Rücksprungadressen nach Interrupts und Unterprogrammaufrufen verwendet. Das Stapelzeigerregister zeigt immer auf die Spitze des Stapels. Beachten Sie, dass der Stack so implementiert ist, dass er von höheren Speicherorten zu niedrigeren Speicherorten wächst. Dies impliziert, dass ein Stapel-PUSH-Befehl den Stapelzeiger verringert.

Der Stapelzeiger zeigt auf den Daten-SRAM-Stapelbereich, wo sich die Subroutinen- und Interrupt-Stapel befinden. Dieser Stapelraum im Daten-SRAM muss vom Programm definiert werden, bevor Unterprogrammaufrufe ausgeführt oder Interrupts freigegeben werden. Der Stapelzeiger muss so eingestellt werden, dass er über 0x60 zeigt. Der Stack-Zeiger wird um eins dekrementiert, wenn Daten mit dem PUSH-Befehl auf den Stack geschoben werden, und er wird um zwei dekrementiert, wenn die Rückkehradresse mit einem Subroutinenaufruf oder Interrupt auf den Stack geschoben wird. Der Stapelzeiger wird um eins erhöht, wenn Daten mit der POP-Anweisung aus dem Stapel entnommen werden, und er wird um zwei erhöht, wenn Daten aus dem Stapel mit der Rückkehr von der Subroutine RET oder der Rückkehr von der Unterbrechung RETI entnommen werden.

Der AVR-Stapelzeiger ist als zwei 8-Bit-Register im E/A-Raum implementiert. Die Anzahl der tatsächlich verwendeten Bits ist implementierungsabhängig. Beachten Sie, dass der Datenraum in einigen Implementierungen der AVR-Architektur so klein ist, dass nur SPL benötigt wird. In diesem Fall ist das SPH-Register nicht vorhanden.

In Ihrem Fall ist meiner Meinung nach nur SPL vorhanden (128 Byte RAM = 7 Bit).

Abgesehen von der Hardware hängt es von Ihrem Framework ab, das für die meisten AVR-Teile GCC, GNU Binutils und avr-libc umfassen wird . Ein kurzer Blick in die avr-libc FAQ ergab zwei gute Fragen:

http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_reg_usage

Welche Register werden vom C-Compiler verwendet?

  • Datentypen: char ist 8 Bit, int ist 16 Bit, long ist 32 Bit, long long ist 64 Bit, float und double sind 32 Bit (dies ist das einzige unterstützte Fließkommaformat), Zeiger sind 16 Bit (Funktionszeiger sind Wort Adressen, um die Adressierung von bis zu 128K Programmspeicherplatz zu ermöglichen). Es gibt eine Option -mint8 (siehe Optionen für den C-Compiler avr-gcc), um int 8 Bit zu machen, aber das wird von avr-libc nicht unterstützt und verstößt gegen C-Standards (int muss mindestens 16 Bit sein). Es kann in einer zukünftigen Version entfernt werden.

  • Rufbenutzte Register (r18-r27, r30-r31): Können von gcc für lokale Daten zugewiesen werden. Sie können sie frei in Assembler-Unterprogrammen verwenden. Das Aufrufen von C-Subroutinen kann jede von ihnen überfordern - der Aufrufer ist für das Speichern und Wiederherstellen verantwortlich.

  • Aufrufgespeicherte Register (r2-r17, r28-r29): Können von gcc für lokale Daten zugewiesen werden. Der Aufruf von C-Subroutinen lässt sie unverändert. Assembler-Subroutinen sind für das Sichern und Wiederherstellen dieser Register verantwortlich, wenn sie geändert werden. r29:r28 (Y-Zeiger) wird bei Bedarf als Rahmenzeiger (zeigt auf lokale Daten auf dem Stack) verwendet. Die Anforderung an den Aufgerufenen, den Inhalt dieser Register zu speichern/aufzubewahren, gilt sogar in Situationen, in denen der Compiler sie für die Übergabe von Argumenten zuweist.

  • Feste Register (r0, r1): Von gcc nie für lokale Daten zugewiesen, aber oft für feste Zwecke verwendet:

    r0 - temporäres Register, kann von jedem C-Code geschlagen werden (außer Interrupt-Handlern, die es speichern), kann verwendet werden, um sich für eine Weile etwas in einem Stück Assembler-Code zu merken

    r1 - wird in jedem C-Code immer als Null angenommen, kann verwendet werden, um sich für eine Weile etwas in einem Stück Assembler-Code zu merken, muss dann aber nach der Verwendung gelöscht werden (clr r1). Dies schließt jegliche Verwendung der [f]mul[s[u]]-Anweisungen ein, die ihr Ergebnis in r1:r0 zurückgeben. Interrupt-Handler speichern und löschen r1 beim Eintritt und stellen r1 beim Verlassen wieder her (falls es nicht Null war).

  • Konventionen für Funktionsaufrufe: Argumente - von links nach rechts zugewiesen, r25 bis r8. Alle Argumente sind so ausgerichtet, dass sie in geradzahligen Registern beginnen (Argumente mit ungerader Größe, einschließlich char, haben ein freies Register darüber). Dies ermöglicht eine bessere Nutzung der movw-Anweisung auf dem erweiterten Kern.

Wenn es zu viele sind, werden diejenigen, die nicht passen, auf dem Stapel weitergereicht.

Rückgabewerte: 8 Bit in r24 (nicht r25!), 16 Bit in r25:r24, bis zu 32 Bit in r22-r25, bis zu 64 Bit in r18-r25. 8-Bit-Rückgabewerte werden von der aufgerufenen Funktion durch Null/Vorzeichen auf 16 Bit erweitert (unsigned char ist effizienter als signed char – nur clr r25). Argumente für Funktionen mit variablen Argumentlisten (printf usw.) werden alle auf dem Stapel übergeben, und char wird zu int erweitert.

http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_ramoverlap

Wie erkennt man RAM-Speicher- und Variablenüberlappungsprobleme? Sie können einfach avr-nm auf Ihrer Ausgabedatei (ELF) ausführen. Führen Sie es mit der Option -n aus, und es sortiert die Symbole numerisch (standardmäßig sind sie alphabetisch sortiert).

Suchen Sie nach dem Symbol _end, das ist die erste Adresse im RAM, die nicht von einer Variablen zugewiesen wird. (avr-gcc fügt intern 0x800000 zu allen data/bss-Variablenadressen hinzu, also ignorieren Sie bitte diesen Offset.) Dann initialisiert der Laufzeit-Initialisierungscode den Stapelzeiger (standardmäßig) so, dass er auf die letzte verfügbare Adresse im (internen) SRAM zeigt . Somit ist der Bereich zwischen _end und dem Ende des SRAM das, was für den Stapel verfügbar ist. (Wenn Ihre Anwendung malloc() verwendet, was zB auch innerhalb von printf() passieren kann, befindet sich dort auch der Heap für dynamischen Speicher. Siehe Speicherbereiche und Malloc() verwenden.)

Die für Ihre Anwendung benötigte Stapelmenge lässt sich nicht so einfach ermitteln. Wenn Sie beispielsweise eine Funktion rekursiv aufrufen und vergessen, diese Rekursion zu unterbrechen, ist die erforderliche Stapelmenge unendlich. :-) Sie können sich den generierten Assembler-Code ansehen (avr-gcc ... -S), in jeder generierten Assembler-Datei befindet sich ein Kommentar, der Ihnen die Framegröße für jede generierte Funktion mitteilt. Das ist die Menge an Stack, die für diese Funktion benötigt wird, die müssen Sie für alle Funktionen zusammenzählen, bei denen Sie wissen, dass die Aufrufe verschachtelt werden könnten.

Ja - aber der avr gcc Compiler wird ein Register save/restore in die ISR programmieren, oder?
Vielleicht - sehen Sie, was ich über Register, Stack und RAM-Nutzung hinzugefügt habe. Wenn Sie wirklich besorgt sind, würde ich vorschlagen, die Assembly-Quelle (gcc -S foo.c) zu generieren und sie im Detail zu untersuchen.