Armins AVR-Buch - Die_Stackwerte

⇑Die_Stackwerte


(armin77, 21.01.2013, 23:36:58)

Die Stackwerte (HWStack,SWStack,Framesize) in Bascom sind sehr wichtig.
Sie müssen in jedem Programm angegeben werden und dürfen nicht zu klein
dimensioniert werden.
Im Programm können sonst unerklärbare Fehlfunktionen auftreten.
Hier eine sehr gute Erklärung von Gerold Penz (www.halvar.at)


HWSTACK

Der Hardware-Stack ist der Bereich im Speicher, der dafür reserviert ist um Rücksprungadressen und, bei einem Interrupt, eine Sicherung der Register abzulegen.

Springst du mit GOSUB irgendwo hin, dann wird zuvor die Adresse gespeichert, zu der bei einem RETURN wieder zurück gekehrt werden soll. Wenn du innerhalb dieser Unterroutine in die du gesprungen bist, noch einmal GOSUB verwendest, dann wird diese zweite Rücksprungadresse ebenfalls auf den Hardware-Stack (=Stapel) abgelegt.

Bei einem RETURN wird dann von der zweiten Unterroutine in die erste Unterroutine zurück gesprungen. Dann wird die Adresse vom Stack gelöscht. Wird die erste Unterroutine beendet (RETURN), dann springt das Programm zur ersten Rücksprungadresse zurück. Auch diese Adresse wird wieder vom Hardware-Stack gelöscht.

Wie viel Speicher für diese Springerei benötigt wird, hängt also nicht von der Anzahl an GOSUBs oder CALLs ab, sondern davon, wie viele GOSUB oder CALLs ineinander verschachtelt sind. Für ein GOSUB in eine Unterroutine und noch ein GOSUB innerhalb dieser ersten Unterroutine und dann noch ein GOSUB innerhalb der zweiten Unterroutine brauchst du 3 x 2 Byte. Wenn du also deine Programme nicht extrem verschachtelst, dann brauchst du selten mehr als 10 Byte Hardware-Stack für diese Springerei.

Außer dieser Rücksprungadressen, legt Bascom auch die Werte der Register in diesen Stack, sobald ein Interrupt auftritt und der zugehörige Interrupt-Handler (ISR) angesprungen wird.

Bevor also die Unterprozedur aufgerufen wird, die immer dann automatisch ausgeführt wird, wenn ein Interrupt auftritt, werden die Register in den HWStack gesichert. Dafür werden 32 Byte benötigt. Wenn du also Interrupts aktivierst, dann brauchst du mindestens 32 Byte HWStack. Da man in einem Interrupt-Handler so wenig wie möglich macht und die Hauptarbeit normalerweise in der MainLoop verrichten lässt, ist es unüblich, innerhalb eines Interrupt-Handlers Unterprozeduren anzuspringen. Also wird selten mehr als 32 Byte HWStack benötigt.

Wenn du es aber doch machst, was ja kein Problem ist, dann musst du zu den benötigten 32 Byte noch die Byte für die Springerei dazuzählen.

Ich würde mal sagen, dass du beim HWSTACK großzügig bist, wenn du diesen am Anfang auf 40 Byte festlegst. Und falls du weißt, dass du nicht so viel brauchst, weil du z.B. keinen Interrupt aktiviert hast, kannst du später den HWSTACK entsprechend verkleinern.

$hwstack = 40

SWSTACK

So, und jetzt zum Software-Stack. Gleich wie beim Hardware-Stack gilt, dass nicht die Anzahl an Unterprozeduren (SUB) dafür ausschlaggebend ist wie groß der Software-Stack sein muss. Ausschlaggebend ist, wie verschachtelt die Aufrufe untereinander sind.

Wenn du zehn Unterprozeduren hast, aber immer nur eine dieser Unterprozeduren gleichzeitig aktiv wird, brauchst du nur so viel Software-Stack-Speicher wie ihn diese eine Unterprozedur benötigt. Wenn du innerhalb einer Unterprozedur eine andere Unterprozedur aufrufst, dann brauchst du im Software-Stack für beide Unterprozeduren Platz. Gleiches gilt auch für Funktionen.

Wie viel Platz ist das überhaupt?

Du brauchst für jede Variable, die an die Unterprozedur als Parameter übergeben wird, 2 Byte im SWStack. Hast du also eine Unterprozedur mit 2 Parametern, dann brauchst du dafür 4 Byte SWStack. Rufst du innerhalb dieser Unterprozedur eine andere Unterprozedur auf, die z.B. 3 Parameter erwartet, dann brauchst du zu den bereits verbrauchten 4 Byte noch (3 x 2) 6 Byte dazu. Das wären dann 10 Byte.

Jede innerhalb einer Unterprozedur mit dem Befehl LOCAL erstellte Variable braucht ebenfalls je 2 Byte im SWStack.

Nehmen wir also an, dass du im Normalfall bis zu 4 Parameter an eine Unterprozedur weitergibst und innerhalb der Prozeduren maximal 3 Variablen mit LOCAL definierst. Weiters nehmen wir mal an, dass du innerhalb einer solchen Unterprozedur maximal noch eine weitere Unterprozedur mit wiederum 4 Parametern aufrufst, die auch wieder bis zu 3 lokale Variablen hat, dann braust du für die Parameter der ersten Prozedur (4 x 2) 8 Byte. Und für die lokalen Variablen der ersten Prozedur (3 x 2) 6 Byte. Das sind für die erste Prozedur 14 Byte SWStack. Und die gleiche Menge kommt noch für den verschachtelten Aufruf der zweiten Unterprozedur dazu. Das wären dann 28 Byte die für den SWStack reserviert werden müsste.

Wenn man noch ein paar Reserverbytes dazu tut, dann bist du in solch einem Fall mit 32 Byte SWStack gar nicht mal so schlecht dran.

Wenn du also schon am Anfang deines Programmes den SWSTACK auf 32 stellst, dann sind solche (eher schon komplexe) Vorgänge recht gut abgedeckt. Wenn du später erkennst, dass du nicht so viel SWSTACK-Speicher benötigst, dann kannst du ihn ja immer noch verkleinern.

$swstack = 32

FRAME

Und weiter geht es mit dem FRAME. Im Frame werden Daten und keine Adressen gespeichert. Wenn an eine Unterprozedur ein Parameter übergeben wird, dann wird die Adresse dieses Parameters im SWSTACK gespeichert. Der Wert, also das was in dieser Variable drinnen steht, wird im FRAME gespeichert. Allerdings nur, wenn der Parameter mit dem Schlüsselwort BYVAL an die Prozedur übergeben wurde. Da es in den meisten fällen besser ist, Werte mit BYVAL zu übergeben, können wir davon ausgehen, dass der Wert der Variable im Frame gespeichert wird.

Nehmen wir wieder unsere Unterprozedur -- die mit den vier Parametern. Nehmen wir mal an, dass der erste und der zweite Parameter je eine BYTE-Variable sind. Der dritte Parameter ist eine WORD-Variable. Und der vierte Paramter eine STRING-Variable für 10 Zeichen. Weiters sind in der Unterprozedur 3 lokale BYTE-Variablen definiert.

Wird diese Unterprozedur aufgerufen, dann wird vorher in den HWSTACK die Rücksprungadresse abgelegt. Dann werden im SWSTACK die Adressen der Parameter und der lokalen Variablen abgelegt. -- jetzt kommts -- Und im FRAME werden die Werte der Parameter und der lokalen Variablen abgelegt. Wird die Unterprozedur wieder verlassen, dann werden die Daten im Frame wieder frei gegeben.

            2 x BYTE = 2 Byte (erster und zweiter Parameter)
            1 x WORD = 2 Byte (dritte Parameter)
            1 x STRING * 10 = 11 Byte (inkl. Abschlussbyte; vierter Parameter)
            3 x BYTE = 3 Byte (lokale Variablen)

Für diesen Aufruf werden im FRAME also 18 Byte benötigt.

Bascom braucht den FRAME aber auch für andere Sachen. Unter Anderem für die Umwandlung von Variablen in andere Datentypen. Du kannst also den FRAME recht groß dimensionieren. Nur wenn dir der übrige Speicher ausgeht, dann musst du den FRAME auf das Nötigste verkleinern.

Wenn du den FRAME großzügig mit 60 Byte dimensionierst, dann sollten die meisten Anwendungsfälle des ATmega8 abgedeckt sein. Wenn du weißt, dass du weniger brauchst, dann kannst du den FRAME ja kleiner machen.

$framesize = 60