In der ATTiny2313-Dokumentation auf Seite 15 heißt es:
Die Interrupt-Ausführungsantwort für alle aktivierten AVR-Interrupts beträgt mindestens vier Taktzyklen. Nach vier Taktzyklen wird die Programmvektoradresse für die eigentliche Interrupt-Handhabungsroutine ausgeführt. Während dieser vier Taktzyklen wird der Programmzähler auf den Stapel geschoben. Der Vektor ist normalerweise ein Sprung zur Interruptroutine, 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 Ruhemodus befindet, wird die Antwortzeit für die Interruptausführung um vier Taktzyklen erhöht. Diese Erhöhung erfolgt zusätzlich zur Startzeit aus dem ausgewählten Schlafmodus.
Eine Rückkehr von einer Interrupt-Handhabungsroutine dauert vier Taktzyklen. Während dieser vier Taktzyklen wird der Programmzähler (zwei Bytes) vom Stapel zurückgeschoben, der Stapelzeiger um zwei erhöht und das I-Bit in SREG gesetzt.
Sie sehen also während eines Interrupts (des PCs) wirklich nur 2 Bytes auf dem Stapel. Alles andere, was ein ISR auf den Stapel legt, liegt beim ISR selbst. Ich würde nicht erwarten, dass ein gut geschriebener Interrupt-Handler viel Stapelspeicher benötigt.
Auf dem Stapelzeiger selbst 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 Oberseite des Stapels. Beachten Sie, dass der Stapel so implementiert wird, dass er von höheren Speicherplätzen zu niedrigeren Speicherplätzen wächst. Dies impliziert, dass ein Stack PUSH-Befehl den Stack Pointer verringert.
Der Stapelzeiger zeigt auf den Daten-SRAM-Stapelbereich, in dem sich die Subroutinen- und Interrupt-Stapel befinden. Dieser Stapelbereich im Daten-SRAM muss vom Programm definiert werden, bevor Unterprogrammaufrufe ausgeführt oder Interrupts aktiviert werden. Der Stapelzeiger muss auf einen Punkt über 0x60 eingestellt sein. Der Stapelzeiger wird um eins dekrementiert, wenn Daten mit dem PUSH-Befehl auf den Stapel übertragen werden, und um zwei, wenn die Rücksprungadresse mit einem Unterprogrammaufruf oder einer Unterbrechung auf den Stapel übertragen wird. Der Stapelzeiger wird um eins erhöht, wenn Daten mit dem POP-Befehl aus dem Stapel gepoppt werden, und er wird um zwei erhöht, wenn Daten mit der Rückkehr vom Unterprogramm RET oder vom Interrupt RETI aus dem Stapel gepoppt werden.
Der AVR-Stapelzeiger ist als zwei 8-Bit-Register im E / A-Bereich 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).
Über die Hardware hinaus hängt es von Ihrem Framework ab, an dem für die meisten AVR-Teile GCC, GNU Binutils und avr-libc beteiligt sein werden . Ein kurzer Blick auf die FAQ zu avr-libc 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 ist 64 Bit, float und double sind 32 Bit (dies ist das einzige unterstützte Gleitkommaformat), Zeiger sind 16 Bit (Funktionszeiger sind Wörter Adressen, um die Adressierung von bis zu 128 KB Programmspeicherplatz zu ermöglichen). Es gibt eine Option -mint8 (siehe Optionen für den C-Compiler avr-gcc), um int 8 Bit zu erstellen. Diese Option wird jedoch von avr-libc nicht unterstützt und verstößt gegen C-Standards (int muss mindestens 16 Bit betragen). Es kann in einer zukünftigen Version entfernt werden.
Anrufverwendete Register (r18-r27, r30-r31): Kann von gcc für lokale Daten zugewiesen werden. Sie können sie in Assembler-Subroutinen frei verwenden. Das Aufrufen von C-Unterprogrammen kann jedes von ihnen blockieren - der Anrufer ist für das Speichern und Wiederherstellen verantwortlich.
Anrufgespeicherte Register (r2-r17, r28-r29): Kann von gcc für lokale Daten zugewiesen werden. Durch Aufrufen von C-Unterprogrammen bleiben diese unverändert. Assembler-Subroutinen sind für das Speichern und Wiederherstellen dieser Register verantwortlich, falls diese geändert werden. r29: r28 (Y-Zeiger) wird bei Bedarf als Rahmenzeiger verwendet (zeigt auf lokale Daten auf dem Stapel). Die Anforderung, dass der Angerufene den Inhalt dieser Register speichern / beibehalten muss, gilt auch in Situationen, in denen der Compiler sie für die Übergabe von Argumenten zuweist.
Feste Register (r0, r1): Nie von gcc für lokale Daten zugewiesen, sondern häufig für feste Zwecke verwendet:
r0 - temporäres Register, das von jedem C-Code (mit Ausnahme von Interrupt-Handlern, die es speichern) überlastet werden kann, kann verwendet werden, um sich eine Weile innerhalb eines Assembler-Codes etwas zu merken
r1 - wird in jedem C-Code als immer Null angenommen, kann verwendet werden, um sich eine Weile innerhalb eines Assembler-Codes an etwas zu erinnern, muss dann aber nach der Verwendung gelöscht werden (clr r1). Dies schließt jede Verwendung der Anweisungen [f] mul [s [u]] ein, die ihr Ergebnis in r1: r0 zurückgeben. Interrupt-Handler speichern und löschen r1 beim Eintritt und stellen r1 beim Beenden wieder her (falls es nicht Null war).
Funktionsaufrufkonventionen: Argumente - von links nach rechts zugewiesen, r25 bis r8. Alle Argumente sind so ausgerichtet, dass sie in geraden Registern beginnen (Argumente ungerader Größe, einschließlich char, haben ein freies Register darüber). Dies ermöglicht eine bessere Verwendung des movw-Befehls auf dem erweiterten Kern.
Wenn zu viele, werden diejenigen, die nicht passen, auf den Stapel übergeben.
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 auf Null / Vorzeichen auf 16 Bit erweitert (vorzeichenloses Zeichen ist effizienter als vorzeichenbehaftetes Zeichen - nur clr r25). Argumente für Funktionen mit variablen Argumentlisten (printf usw.) werden alle auf dem Stapel übergeben, und char wird auf int erweitert.
http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_ramoverlap
Wie erkennt man RAM-Speicher und variable Überlappungsprobleme?
Sie können einfach avr-nm für Ihre Ausgabedatei (ELF) ausführen. Führen Sie es mit der Option -n aus, und die Symbole werden numerisch sortiert (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 Daten- / BSS-Variablenadressen hinzu. Ignorieren Sie diesen Offset.) Anschließend initialisiert der Laufzeitinitialisierungscode den Stapelzeiger (standardmäßig) so, dass er auf die letzte verfügbare Adresse im (internen) SRAM verweist . Somit ist der Bereich zwischen _end und dem Ende des SRAM für den Stapel verfügbar. (Wenn Ihre Anwendung malloc () verwendet, was z. B. auch in printf () vorkommen kann, befindet sich dort auch der Heap für den dynamischen Speicher. Siehe Speicherbereiche und Verwenden von malloc ().)
Die für Ihre Anwendung erforderliche Stapelmenge kann nicht so einfach ermittelt werden. 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 (avr-gcc ... -S) ansehen. In jeder generierten Assembler-Datei gibt es einen Kommentar, der Ihnen die Frame-Größe für jede generierte Funktion angibt. Das ist die Menge an Stapel, die für diese Funktion benötigt wird. Sie müssen diese für alle Funktionen addieren, bei denen Sie wissen, dass die Aufrufe verschachtelt sein könnten.