zu deutsch: "Die Schlange lauert im Grase" oder "trau schau, wem?"

 

Der avr-gcc bietet die Möglichkeit, Variablen in Registern abzulegen. Dies vereinfacht zum einen den Datenaustausch mit Assemblerprogrammen und zum anderen benötigt der Zugriff auf ein Register nur einen Taktzyklus und zwei Byte Programmcode. Der Zugriff auf das SRAM erfordert zwei Zyklen und vier Byte Programmcode.

Eine Variable, deren Wert in einem Register abgelegt werden soll, wird durch Angabe der Speicherklasse "register" wie folgt deklariert:
  register uint8_t Reg3 asm(„r3“);

Es ist allerdings Obacht geboten, damit man dem Compiler nicht ins Handwerk pfuscht. Die Benutzung der Register R2-R17 ist recht unkritisch. Wenn der Compiler diese benutzt, sorgt er dafür, dass sie wieder restauriert werden (siehe avr-gcc Register_Layout und Avr-gcc/Interna). Nichtsdestotrotz sollte man sich den vom Compiler generierten Assemblercode (.lss-Datei) intensiv anschauen, insbesondere dann, wenn das Programm nicht das tut, was es eigentlich sollte.

Es gibt zwei besondere Fallstricke, auf die man achten muss. Zum einem ist die Angabe eines Registers als Speicherort für eine Variable nur ein Vorschlag, an den sich der Compiler nicht unbedingt halten muss.

Das zweite Problem entsteht, wenn auf das Register in mehreren Modulen (.c-Dateien) zugegriffen werden muss. Hier schlägt die Optimierungsfunktion des Compilers unbarmherzig zu. Der Compiler erkennt den modulübergreifenden Zugriff nicht, und optimiert Anweisungen mit Zugriffen auf das Register weg. Dies gilt sowohl für lesende als auch für schreibende Zugriffe auf das Register. Auch bei im SRAM abgelegten Variablen würde er die Zugriffe wegoptimieren. Bei diesen „regulären“ Variablen hat man jedoch die Möglichkeit, die Variable als „volatile“ zu deklarieren. Hierdurch weiß der Compiler, dass diese Variable ggf. an anderer Stelle geändert wurde.

Den Compiler kann man jedoch überreden, Registerzugriffe verlässlich durchzuführen. Man definiert einfach zwei Funktionen mit passendem Assembler-Code, die den Registerinhalt für C verfügbar machen; je eine zum Lesen und eine zum Schreiben. Die Assembler-Anweisungen lassen sich als "volatile" deklarieren. Dies verhindert, dass der Compiler sie wegoptimiert.

Die beiden Funktionen deklariert man als „inline“ und versieht zusätzlich mit dem Attribut „always inline“, da die „inline“-Deklaration ebenfalls nur als Empfehlung für den Compiler gilt, dieses Attribut aber eine inline-Kompilierung erzwingt. Das ganze sieht dann so aus:



static inline __attribute__((always_inline)) void SetReg3(uint8_t tmp)
{ asm volatile ("mov r3, %0" : : "r" (tmp): "r3");}

static inline __attribute__((always_inline)) uint8_t GetReg3(void)
{ uint8_t tmp; asm volatile ("mov %0, r3": "=r" (tmp)); return tmp;}

Die Funktionen werden kompiliert zu

mov r3, r24

und

mov r24, r3

Zusätzlich sollte man dem Compiler mitteilen, dass man das r3-Register benutzt hat.

register uint8_t Reg3 asm("r3");

Die Variablen- und Funktion-Deklarationen bindet man dann per .h-Datei in alle betroffen Module ein.

 

Nicht verschweigen möchte ich folgenden Hinweis aus Galileo Computing. Man sollte schon genau überlegen, ob man die Speicherklasse "register" verwendet.

Das Schlüsselwort register wird heute eigentlich überhaupt nicht mehr benutzt. Der Bezugsrahmen ist derselbe wie bei auto und lokalen Variablen. Durch Voransetzen des Schlüsselworts register weisen Sie den Compiler an, eine Variable so lange wie möglich im Prozessorregister (CPU-Register) zu halten, um dann blitzschnell darauf zugreifen zu können. Denn Prozessorregister arbeiten wesentlich schneller als Arbeitsspeicher. Allgemein wird vom Gebrauch von register abgeraten, denn ob und welche Variable der Compiler in dem schnellen Prozessorregister hält, entscheidet er letztlich selbst. Somit ist das Schlüsselwort eigentlich nicht erforderlich.