AVR-Assembler-Makros zur Programmierung von Microcontrollern

Seitenübersicht



 www.schramm-software.de

Es muss nicht immer C, BASCOM oder eine andere 'Hochsprache' sein - AVR-Microcontroller lassen sich gut in ihrer Assemblersprache programmieren. Insbesondere kleinere Aufgaben, in denen überwiegend 'nah an der Hardware' gearbeitet werden, also vor allem Eingänge gelesen, Ausgänge gesetzt, Timer programmiert werden usw., lassen sich gut in dieser Weise angehen.

In Assembler zu programmieren, bedeutet nicht, dass man immer wieder die nahezu gleichen Befehlsfolgen tippen muss, weil der Prozessor bestimmte direkte Adressierungen nicht erlaubt, und schließt auch nicht aus, Programme übersichtlich z. B. durch Einrückungen zu strukturieren. Das ist alles nur eine Frage der Programmiertechnik und des Einsatzes geeigneter Makros! Nebenher nehmen Makros einem stupide Rechen- und Nachschlagearbeit ab (ist diese Schleife kurz genug für ein bedingtes Branchkommando, kann dieser Port über OUT angesprochen werden? ...) und verbessern die Portabilität des Programms von einem AVR-µC-Typ auf einen anderen.

Im Folgenden stellen wir Ihnen/euch einige Tipps und Makros vor, die den Spaß bei der Assemblerprogrammierung deutlich erhöhen können. Die Makros lassen sich in dieser Form im AVR-Studio einsetzen (kostenloser Download bei www.atmel.com); für andere Entwicklungsumgebungen sind evtl. Anpassungen erforderlich.


Download: Sämtliche hier vorgestellten Makros in einer Include-Datei (sowie weitere elementare Makros, die von einigen Programmen in dieser Tipps-Rubrik benötigt werden)


Makro: 16bit-Registerpaar mit Direktwert füllen

.macro ldi_hl ;(reg, 16bit-Zahl)
  ldi @0h,high(@1)
  ldi @0l,low(@1)
.endmacro

Ein sehr einfaches Makro, das man aber häufig einsetzen wird, sobald man 16bit-Zahlenwerte benötigt, auch als Adressen / Pointer. Das Makro vereinfacht das Laden von 16bit-Konstanten in die Registerpaare X, Y und Z. Einsatzbeispiele:

  ldi_hl z,muster*2 ;Adr. muster im Flash-Speicher -> ZH:ZL
  ldi_hl y,datum    ;Adr. datum im SRAM-Speicher -> YH:YL

Zu beachten ist - das gilt jedoch unabhängig vom Einsatz dieses Makros -, dass die Adressen im Flash-Speicher standardmäßig wortweise gezählt werden, so wie program counter und stack pointer sie benötigen. Um über ein als Adresspointer eingesetztes Registerpaar, im Beispiel Z, auf Daten zuzugreifen, die im Flash abgelegt sind, muss die Wort-Adresse verdoppelt werden, da der Controller in diesem Fall eine Byte-Adresse erwartet - daher die Multiplikation mit zwei im ersten Beispiel. Das SRAM hingegen wird immer byteweise adressiert, und die Verdoppelung entfällt.

Man muss sich nicht auf X, Y, Z als Registerpaare beschränken. Prinzipiell lassen sich beliebige Register ab R16 in diesem Makro verwenden, wenn man sie zuvor mit geeigneten symbolischen Namen versehen hat, etwa in dieser Weise:

.DEF AL = r18
.DEF AH = r19
.DEF WL = r24
.DEF WH = r25

Auf diese Weise stehen uns die Registerpaare A und W zur Verfügung, die aber für den Microcontroller keine besondere Bedeutung haben, also nicht in gleicher Weise wie X, Y, Z eingesetzt werden können. Allerdings sind die Wort-Befehle ADIW und SBIW auch für das Registerpaar R25:R24 geeignet, könnten nun also auch mit W verwendet werden.


Makro: Kontrollregister füllen

Häufig muss man eine 8bit-Konstante in ein I/O-Register (Port) schreiben. Das geht nur indirekt, indem man die Konstante zunächst in einem Arbeitsregister ablegt. Hier hilft das folgende Makro, das allerdings den Inhalt von Register 16 überschreibt. Abhängig von der Lage des Ports im I/O-Adressraum des eingesetzten Controllers wird entweder das (kürzere und schnellere) OUT-Kommando oder der STS-Befehl eingesetzt.

.macro out_i ;(Port, 8bit-Zahl) - zerstört Inhalt von R16
  ldi r16,@1
 .if @0 < 64
  out @0,r16
 .else
  sts @0,r16
 .endif
.endmacro

Anwendungsbeispiele:

  out_i SPL,low(RAMEND)  ;Stackpointer-Low-Byte setzen
  out_i PORTB,0b00111111 ; LEDs an PB0 bis PB5 einschalten

Makros für Zählschleifen

Immer wieder benötigt man Schleifen in Programmen, im einfachsten Fall in Form einer Zählschleife. In höheren Programmiersprachen gibt es hierzu das Kontrollkommando FOR. Im Assemblerprogramm muss man die Schleifenkontrolle etwas umständlich mit mehreren Befehlen realisieren. Auch hier lässt sich mit Hilfe von Makros Tipparbeit einsparen und wird zugleich das Programm leichter durchschaubar.

.macro for ;(reg, anzahl bzw. startwert, label); für reg ab R16
  ldi @0,@1
  .set @2 = pc
.endmacro

.macro next_down ;(reg, label)
  dec @0
.if pc-@1 < 64
  brne @1
.else
  breq pc+2
  rjmp @1
.endif
.endmacro

.macro next_up ;(reg, endwert, label)
  inc @0
  cpi @0,@1
.if pc-@1 < 64
  brne @2
.else
  breq pc+2
  rjmp @2
.endif
.endmacro

.macro loop ;(label, reg) - Register muss vorbelegt sein
  .set @0 = pc
.endmacro

Eine Schleife beginnt immer mit FOR. Hierdurch wird ein Startwert in ein Register eingetragen und eine Rücksprungmarke definiert (falls der Startwert bereits im Register steht, statt dessen das Makro LOOP verwenden). Das Schleifenende wird durch NEXT_DOWN oder NEXT_UP markiert, je nachdem, ob herunter- oder heraufgezählt werden soll. Man muss sich für ein beliebiges Label und ein Zählregister entscheiden und diese Informationen gleichlautend in den zusammengehörigen Schleifenkontroll-Makros verwenden. Die folgenden Beispiele verdeutlichen die Anwendung:

  out_i PORTB,0b100 ; alle LEDs bis auf die an PB2 aus
  ldi r18,0b110 ; 1-Bits schalten um bei OUT
  for r21,0,khz_loop ; 0 entspricht 256 Durchläufen
    for r19,20,khz_wt ; Warteschleife für knapp 1/2 ms
    next_down r19,khz_wt
    out PINB,r18 ; PB2 und PB1 umschalten
  next_down r21,khz_loop ; 256 Durchläufe
...
  for r21,10,bsplp ; r21 soll von 10 bis 50 laufen
    ...
  next_up r21,50,bsplp

In den Beispielen ist der 'Schleifeninhalt' eingerückt. Das entspricht der in höheren Programmiersprachen üblichen Gepflogenheit, die hilft, die Programmstruktur zu verdeutlichen. Ferner sieht man, dass sich die Schleifen verschachteln lassen.

In dieser Form sind die Schleifen auf 8bit-Zähler ausgelegt und somit auf den Zahlenbereich von 0 bis 255 beschränkt, erlauben also maximal 256 Durchläufe. Wenn das nicht genügt, kann man das Prinzip durch die Verwendung mehrerer Register natürlich auch auf größere Zahlenbereiche ausweiten.

Selbstverständlich kann man anstatt der R-Angaben auch symbolische Registernamen verwenden.

Übrigens prüfen die Assembler-IF-Kommandos, ob die Schleifenlänge gering genug ist für einen direkten bedingten Branch an den Schleifenanfang oder ob eine Kombination aus gegenteiligem Branch und Jump erforderlich ist - also eine Art von Codeoptimierung.


Download: Sämtliche hier vogestellten Makros in einer Include-Datei


Mehr Informationen zum Thema?

Weitere interessante Makros findet man bspw. im mikrocontroller.net.


Zurück zur Übersicht