$hwstack, $swstack, $framesize

Der ATmega8 hat 8 kByte FLASH-Speicher (8192 Byte). In diesen Flash wird das Programm gespeichert. Dieses Programm bleibt im µC auch wenn man den Strom vom µC nimmt.

Der ATmega8 hat 1 kByte SRAM (1024 Byte). In diesem SRAM finden die Werte der Programmvariablen und auch der HWSTACK, der SWSTACK und der FRAME seinen Platz. Dieser Speicher verliert die Werte wenn kein Strom mehr am µC anliegt.

Der ATmega8 hat 512 Byte EEPROM. Das ist ein nichtflüchtiger Speicher, der beschrieben werden kann. Das ist vergleichbar mit einer SD-Karte, wie man sie aus den digitalen Fotoaparaten kennt. In diesen EEPROM kannst du Einstellungen ablegen, die nach dem Aussschalten gespeichert bleiben sollen.

Die Größe des Programmes siehst du, nach dem Kompilieren des Programmes, wenn du im Menü „Programmieren“ auf den Menüeitrag „Ergebnis anzeigen“ klickst.

Die Größe des Programmes zeigen die Zeilen ROMIMAGE und FLASH USED an.

SRAM

Bascom teilt den SRAM in mehrere Speicherbereiche ein. Einen Teil nimmt der Hardware-Stack ein. Einen anderen Teil dieses SRAM wird zum Software-Stack und ein anderer Teil wird unter dem Namen „Frame“ reserviert. Diese Speicherbereiche nutzt Bascom direkt um Rücksprungadressen, Registersicherungen, lokale Variablen und Parameterübergaben an Prozeduren und Funktionen abzuspeichern.

Der Rest des SRAM wird für globale Variablen verwendet.

$hwstack, $swstack und $framesize sollten im Programm angegeben werden. Einfach, damit man den Überblick über den Speicherverbrauch behält und damit das Programm, falls man es weitergibt auch bei Anderen läuft. Man kann ja nie wissen, welche Werte der Andere in seiner IDE eingestellt hat.

Dieser Beitrag wird jetzt ein wenig komplizierter. Wenn du nicht ganz folgen kannst, dann ist das kein Problem. Ganz unten steht die Empfehlung für die Größe der Speicherbereiche. Zum Glück hat der ATmega8 genug SRAM-Speicher. So kann man als Anfänger schon mal je 100 Byte für die Stack-Speicher und den Frame reservieren, ohne groß darüber nachdenken zu müssen. Werden die Programme komplizierter, oder hat man weniger SRAM zur Verfügung, dann muss man sich über die Speicheraufteilung Gedanken machen. Dann solltest du diesen Beitrag noch einmal durchlesen und die Einstellungen, deinem Programm entsprechend, anpassen.

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

Vorschläge

ATmega8/ATmega16

Der ATmega8 hat insgesamt 1024 Byte SRAM zur Verfügung. Von diesem SRAM werden die Bytes für den HWSTACK, den SWSTACK und den FRAME abgezogen.

$hwstack = 40
$swstack = 32
$framesize = 60

Dann bleiben für die Variablen des Hauptprogrammes noch 892 Byte übrig. Man kann also noch gut nach oben korrigieren, falls mehr HWSTACK, SWSTACK oder FRAME benötigt wird, oder wenn man auf „Nummer Sicher“ gehen möchte.

$hwstack = 100
$swstack = 100
$framesize = 100

Auch mit diesen Werten sollte alles so lange gut laufen, so lange das Programm nicht mehr als 700 Byte für globale Variablen braucht.

ATtiny13

Der ATTiny13 hat insgesamt nur 64 Byte SRAM. Da muss man schon aufpassen, keine zu großen Werte für die Stacks und den Frame zu verwenden. Wer den ATtiny13 verwendet, muss auf jeden Fall genau wissen, wieviel Speicher für was verwendet wird.

Ich habe z.B. ein Programm auf dem ATtiny13 laufen, das diese Einstellungen verwendet:

$hwstack = 32
$swstack = 5
$framesize = 20

Es bleiben also gerade noch 7 Byte für das restliche Programm übrig.

Wer einen kleinen µC braucht, aber zu wenig SRAM hat, der sollte bei den ATtiny-Controllern auf den ATtiny45 setzen. Der ATtiny45 hat 256 Byte SRAM.

Je kleiner der SRAM, desto mehr muss man bei der Vergabe der Speicherbereiche aufpassen. Ab dem ATmega8 ist das nicht mehr so ein großes Problem, da man mehr Speicher zur Verfügung hat.