Programmieren für Fortgeschrittene - Assembler - Teil 2 - AZ-Delivery

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"

.def temp = r16       ; Reg für temporäres Zwischenspeichern
.def overflows = r17  ; Anzahl an Overflows

.org 0x0000
rjmp RESET

.org 0x0020
rjmp TIMER0OVF


RESET:
  
ldi temp,  0b00000101  ; Prescaler
  
out TCCR0B, temp

  
ldi temp, 0b00000001   ; Interrupt
  
sts TIMSK0, temp

  
sei            ; enable global interrupts

  
clr temp
  
out TCNT0, temp        ; initialize the Timer/Counter to 0

  
sbi DDRB, 5            ; set PD4 to output


blink:
  
sbi PORTB, 5    ; EIN
  
rcall delay
  
cbi PORTB, 5    ; AUS
  
rcall delay

  
rjmp blink
 
delay:
  
clr overflows
   sec_count:
    
cpi overflows,30
  
brne sec_count
  
ret                    ;>30 return = 0,5sek

TIMER0OVF:
  
inc overflows
  
reti

 

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 :)

Projekte für fortgeschrittene

2 Kommentare

Bastian Brumbi

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

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

Kommentar hinterlassen

Alle Kommentare werden von einem Moderator vor der Veröffentlichung überprüft

Empfohlene Blogbeiträge

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery