Nachdem im ersten Teil die Grundlagen der Assemblersprache geklärt wurden, werden in diesem Teil Standard Testprogramme umgesetzt und der Ablauf genau erläutert.
Um die Tutorials einfach umzusetzen, wird ein Mikrocontroller mit ATmega328P benötigt. Hier empfiehlt sich ein AZ-ATmega328-Board mit USB-C Anschluss.
Assemblersprache
Zu Beginn sollten Sie die wichtigsten Befehle und die Struktur der Assemblersprache kennenlernen.
Befehle
Dies ist eine Übersicht über die wichtigsten Atmel AVR Assembler Befehle:
Daten & Register Operatoren |
|
ldi reg, data |
Lädt die angegebene Zahl in das Register |
mov reg1, reg2 |
Kopiert den Inhalt von Register2 in Register1 |
in reg, IOreg |
Lädt das Byte eines IO Registers in ein Register |
out IOreg, reg |
Lädt das Byte eines Registers in das IO Register |
clr reg |
Setzt alle Bits des Registers auf ‘0’ |
ser reg |
Setzt alle Bits des Registers auf ‘1’ |
sbi reg, bitPos |
Setzt das Bit der angegebenen Position im Register auf den Wert ‘1’ |
cbi reg, bitPos |
Setzt das Bit der angegebenen Position im Register auf den Wert ‘0’ |
inc reg |
Erhöht im Register den Zahlenwert des Inhalts um 1 (DEC) |
dec reg |
Verringert im Register den Zahlenwert des Inhalts um 1 (DEC) |
add reg1, reg2 |
Addiert Register2 zu Register1 |
sub reg1, reg2 |
Subtrahiert Register2 von Register1 |
|
|
Vergleichsoperatoren |
|
cpi reg, data |
Vergleicht ein Register mit einem Konstantwert und Speichert das Ergebnis im Zeroflag des Status Registers |
cp reg, reg |
Vergleicht zwei Register und Speichert das Ergebnis im Zeroflag des Status Registers |
brne label |
Falls die Zeroflag des Status Registers, durch einen Vergleich, auf falsch (‘0’) gesetzt ist wird das angegebene Label ausgeführt |
breq label |
Falls die Zeroflag des Status Registers, durch einen Vergleich, auf wahr (‘1’) gesetzt ist wird das angegebene Label ausgeführt |
|
|
Programmstruktur Operatoren |
|
rcall label |
Aufrufen eines Labels mit der Möglichkeit an diese Stelle zurückzukehren |
ret |
Kann aufgerufen werden um zur gespeicherten Stelle des Program Counters (PC) nach einem call zurückspringen |
rjmp label |
Sprung zu einem Label |
rcall, rjmp vs. call, jmp |
Das Präfix r steht für relativ. Der grobe Unterschied besteht darin, dass relative Sprünge bzw. Aufrufe zwar weniger Speicher verbrauchen, aber nur eine begrenzte Reichweite im Speicher haben. Die call/jmp Anweisung hingegen hat Zugriff auf den gesamten Speicher. |
|
Eine vollständige Übersicht der Befehle finden Sie zum Beispiel unter folgendem Wikipedia Artikel: Atmel AVR instruction Set.
Anweisungen an den Assembler
.equ
Hiermit kann ein konstanter Wert im Programm deklariert werden.
Vergleichbar zu const byte in der Arduino IDE.
.def
Mit diesem Befehl kann ein Name für ein Register definiert werden.
Vergleichbar zu #define in der Arduino IDE
.include
Dies wird benutzt, um externe Dateien in den Code einzubinden.
Vergleichbar zu #include in der Arduino IDE.
.org
Diese Anweisung ist speziell in AVR Assembler, um den Code an bestimmten Stellen im Flashspeicher zu positionieren.
Dies wird zum Beispiel am Anfang eines jeden Assembler Programms verwendet, um nach dem Reset (0x0000) mit dem Programm zu beginnen beziehungsweise zum RESET-Label zu springen.
Label
In der Befehlsübersicht wurden Labels bereits öfter genannt. Dabei handelt es sich um Abschnitte im Code, zu denen mit Hilfe der Programmstruktur Operatoren gesprungen werden können.
Diese werden im Code wie folgt erstellt:
labelname:
Befehl ; Kommentar
Befehl ; Kommentar
Befehl ; Kommentar
Register
Im ATmega328P gibt es einige Peripherie Register, zum Beispiel für die GPIO Ports, die Schnittstellen wie SPI oder UART, Interrupts oder Timer. Auf diese wird in den Beispielen genauer eingegangen.
Ein wichtiges Register um die Funktionsweise der Vergleiche zu verstehen ist das Statusregister SREG. Dieser besteht aus mehreren Bits, hier auch Flag genannt, die bei Operatoren, wie zum Beispiel das Zero Flag, beim Vergleich verwendet werden.
Eine detaillierte Übersicht des Statusregisters finden Sie im Datenblatt des ATmega328P auf Seite 11.
Beispiele
Um das angeeignete theoretische Wissen in der Praxis anzuwenden, folgt nun das bekannte Blink Programm, dass Sie wahrscheinlich auch als erstes Programm in der Arduino IDE auf Ihren UNO geladen haben.
LED Blink
Dieses Programm soll eine LED aufblinken lassen.
Das assembler Programm soll folgenden Ablauf befolgen:
digital Pin auf 5V (HIGH) setzen
Eine Sekunde warten
digital Pin auf 0V (LOW) setzen
Eine Sekunde warten
Ein und Ausschalten der IO Pins:

Abbildung 1: Vollständiges Pinout Quelle
Um einen IO Pin des Mikrocontroller ansteuern zu können, muss zuerst mit Hilfe des Pinouts die IO Bezeichnung des Chip ermittelt werden.
Für das Blink Beispiel empfiehlt sich die Onboard LED ‘L’, welche mit D13 verbunden ist. Dem Pinout entsprechend ist dies PB5 des ATmega328P. Aus dieser Bezeichnung kann ermittelt werden, dass es sich um das 5. Registerbit des IO-Registers Port B handelt.
Mit dem sbi und cbi Befehl kann dieser Port ganz einfach angesteuert werden:
sbi PORTB, 5 ; Anschalten
Bevor der IO angesteuert werden kann, muss noch die Pin- Funktion festgelegt werden. Hierfür gibt es das DDR Register (DataDirectionRegister).
Mit folgendem Befehl kann der IO in den Ausgangsmodus gestellt werden:
sbi DDRB, 5
Warten
In der Assemblersprache gibt es keine direkte Wartefunktion, wie das delay() in der Arduino IDE.
Ein möglicher Ansatz wäre, den Programmfluss durch Befehle, welche einen Prozessortakt-Zyklus verbrauchen, zu blockieren und währenddessen den Inhalt eines Registers entsprechend zu erhöhen. Da der Mikrocontroller über einen Timer verfügt, ist es eleganter, diesen zu verwenden.
Der Timer erhöht seinen Inhalt bei jedem Taktimpuls, da der UNO einen 16 MHz Oszillator verwendet, wären dies 16.000.000 Erhöhungen in der Sekunde. Um diese zu verringern, gibt es den so genannten Prescaler, welcher über das Register TCCR0B festgelegt werden kann. Wird hier der maximale Prescaler (f/1024) gesetzt, entspricht dies 15.625 Takten in der Sekunde.
ldi r16, 0b00000101
out TCCR0B, r16
siehe Datenblatt S.87
Der Timer0 umfasst lediglich eine Länge von 8 Bit, was einem maximalen Zahlenwert von 255 entspricht. Dadurch kommt es bereits bei Takt 256 zum sogenannten ‘Overflow’ des Timers. Bei diesem Overflow wird der Wert auf null gesetzt und ein Interrupt ausgelöst, welcher auf die Speicheradresse 0x0020 zeigt (Datenblatt S.49).
Von dieser Speicheradresse kann daraufhin mit dem .org Befehl in einen Code Abschnitt gesprungen werden, in welchem diese Anzahl der Overflows gespeichert werden soll.
Bei einer Wartezeit von einer Sekunde beträgt diese Overflow-Anzahl etwa 61.
.org 0x0020
rjmp TIMER0OVF
delay:
clr overflows
sec_count:
cpi overflows,61
brne sec_count
ret
TIMER0OVF:
inc overflows
reti
Im finalen Programm muss noch die Interrupt Funktion aktiviert werden. Dies kann einfach zu Beginn durch den Befehl
ldi r16, 0b00000001
sts TIMSK0, r16
sei ; globale interrupts (SREG)
ausgeführt werden.
Finaler Code
Zum Schluss werden die Code Abschnitte noch zusammengesetzt und es ergibt sich das finale Blink Programm.
Fügen Sie diese Zeilen in eine Datei Blink.asm ein.
Alternativ kann die Datei auch hier heruntergeladen werden.
.include "./m328Pdef.inc" |
Zum Schluss laden Sie die kompilierte .hex Datei auf den UNO. Das Vorgehen finden Sie im ersten Teil der Blog-Reihe.
In diesem Blogbeitrag wurden die Grundlagen der Assembler Programmierung vertieft und zuletzt im Blink Beispiel angewandt.
Im folgenden Teil werden noch weitere Beispiele umgesetzt.
Viel Spass beim Nachbauen :)
2 Kommentare
Bastian Brumbi
Hallo Bernd Buffen,
Sie haben recht cp ist für den Vergleich zweier Register, wogegen cpi ein Register mit einem Konstanten Wert vergleicht.
In der aktuellen Version ist dies nun korrigiert.
Vielen Dank für den Hinweis.
Grüße,
Bastian
Bernd
Hallo,
ist die Beschreibung von CP und CPI im Artikel nicht vertauscht ?
-———
Vergleichsoperatoren:
cp reg, data – Vergleicht ein Register mit einem Konstantwert und Speichert das Ergebnis im Zeroflag des Status Registers
cpi reg, reg -Vergleicht zwei Register und Speichert das Ergebnis im Zeroflag des Status Registers
Gruß
Bernd Buffen