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

Nachdem im vorherigen Teil bereits ein Blink-Beispiel umgesetzt wurde, folgen in diesem weitere Beispiele, um die zentralen Hardwarefunktionen des ATmega328P in Assembler kennenzulernen und gezielt zu steuern.

LED fade

Das erste Projekt soll eine LED langsam aufhellen. Die Helligkeitssteuerung kann am ATmega328P über die PWM-Funktion vorgenommen werden.

PWM:

Diese Abkürzung steht für “Pulse Width Modulation”, was ins Deutsche mit “Pulsbreitenmodulation” übersetzt werden kann. Hierbei handelt es sich um ein Signal, das innerhalb einer festen Periode ein- bzw. ausgeschaltet wird. Das Verhältnis zwischen der eingeschalteten Zeit (High-Zustand) wird als Duty Cycle (Tastverhältnis) bezeichnet.

Um die Helligkeit der Leuchtdiode effektiv regeln zu können, empfiehlt sich die Verwendung genau dieses Effekts. Denn über das Tastverhältnis kann die Helligkeit direkt eingestellt werden, da dies der Dauer wie lange die LED eingeschaltet ist, entspricht.
Um die LED für unser Auge dunkler erscheinen zu lassen, muss nur noch die Wiederholung der Zyklen (Frequenz) erhöht werden, somit ist auch kein Flackern zu erkennen.


In der Arduino IDE wäre die PWM Steuerung ganz einfach mit dem analogWrite() Befehl möglich.
Da Sie in diesem Tutorial aber den internen Aufbau des Mikrocontrollers kennenlernen wollen, muss erneut ein Blick in das Datenblatt geworfen werden.

Programmablauf:


Wie Sie bereits aus dem letzten Teil wissen, wird der Wert im Timer Register mit der Zeit bis 255 erhöht. Mit jedem Timer sind zwei Spezielle Register verbunden, die sogenannten Output Compare Register (OCR). Wie aus dem Namen zu schließen ist, wird dessen Inhalt mit dem Timer Inhalt verglichen.
Der ATmega328P verfügt über 3 interne Timer, welche jeweils zwei Output Compare Register beinhalten. Diese OCR sind jeweils mit einem IO Pin verbunden, dem so genannten OC Pin, welcher den Zustand je nach Ergebnis des Vergleichs annehmen kann. Dieses Verhalten muss im Output Compare Flag (OCF) Bits eingestellt werden.

Befindet sich das Timer Register beim Wert 0, wird der Digitale IO Ausgang eingeschaltet (HIGH). Entspricht der Timerwert dem Wert des Output Compare Register (OCR), wird der Ausgang auf LOW ausgeschaltet.
Somit kann über das OCR der Duty Cycle festgelegt werden.

(Datenblatt S.75 S.84)

Die Frequenz des PWM-Signals kann durch den Prescaler festgelegt werden.

Beispiel:

Für das Beispiel wird Timer0 aus dem Blink Beispiel verwendet.

.include "m328Pdef.inc" ;1

.org 0x0000
rjmp main  ;2

main:
 
sbi DDRD, PD6 ;3

 
ldi r16, (1<<WGM01)|(1<<WGM00)|(1<<COM0A1)
 
out TCCR0A, r16 ;4

 
ldi r16, (1<<CS01)|(1<<CS00)
 
out TCCR0B, r16 ;5

 
ldi r16, 85
 
out OCR0A, r16 ;6

 
rjmp main

 

Zuerst werden wie gewohnt die Registerbezeichnungen aus der include Datei geladen (1). Im Anschluss wird an der Speicherstelle des RESET ein Sprung zum main Label ausgeführt (2).

Hier wird daraufhin der Pin PD6, an welchem auch der Output Compare Pin OC0A anliegt, durch das Data Direction Register in den Ausgangsmodus gesetzt (3).
Im Anschluss erfolgt die Konfiguration des Timers. In Register TCCR0A wird die PWM Funktionalität konfiguriert (4) und in TCCR0B, wie im letzten Teil, der Prescaler gesetzt (5).
In OCR0A kann nun der Duty Cycle eingestellt werden.

Das Programm kann hier heruntergeladen werden.

Ergebnis:

Abbildung 1: PWM Signal mit 33% Duty Cycle (Beispiel)

 

Abbildung 2: PWM Signal mit 50% Duty Cycle (OCR0A = 127)

 

Bitshifting

Bei diesem Beispiel wird ein neuer Befehl verwendet, welcher auf den ersten Blick kryptisch vorkommen könnte:
ldi r16, (1<<WGM01)|(1<<WGM00)|(1<<COM0A1)

 

Mit dem Bitshifting Befehl 1<<6 würde beispielsweise das Bit 1 um 6 Stellen nach links geschoben werden, was zum Ergebnis 00100000 führen würde. Im obigen Beispiel wird durch die Bezeichnungen wie ‘WGM01’ die Position des Bits im Register dargestellt.
Somit wird durch ‘1<<WGM01’ eine 1 im Bit WGM01 gesetzt. Diese Positionen sind in der Include Datei gespeichert.

Da aber noch andere Bits gesetzt werden sollen, ist das ‘logische Oder’ notwendig. Dadurch wird bei allen drei Bits ein Vergleich durchgeführt und somit das Register mit allen gesetzten Bits generiert.

Beispiel:
00100000 | 00010000 | 10000000  Ergebnis: 10110000

 

Abschluss

Zum Schluss soll das Programm die LED langsam aufleuchten lassen. Das heißt, der Duty Cycle muss stets erhöht werden.
Um die Geschwindigkeit zu regeln, wird die Delay Funktion aus dem Blink Programm des vorherigen Teils verwendet.

 

 

Hardware:

Für das Beispiel benötigen Sie folgende Komponenten:
ATmega328 Mikrocontroller (Varianten: USB-C oder USB-B)

LED (LED Sortiment)

Widerstand 220𝝮 (Widerstands Sortiment)

 

Schließen Sie eine LED wie folgt an den Mikrocontroller an:

Abbildung 3: Schaltplan

 

Software:

Kompilieren Sie folgendes Programm und laden Sie die .hex Datei auf den Mikrocontroller.
Hier können Sie das Programm herunterladen.

 

Makros

Da die Wartefunktion in nahezu jedem Beispiel verwendet wird, soll diese nun so abgeändert werden, dass sie wie die delay() Funktion in der Arduino IDE jederzeit ganz einfach, mit der Wartezeit als Parameter, aufgerufen werden kann.
Hierfür bietet die Assembler Sprache das sogenannte Makro. Damit kann ein Programmteil mit mehreren Parametern erstellt werden.
Die neue Wartefunktion soll über einen Parameter verfügen, über welchen die Wartezeit in Millisekunden angegeben werden kann.

Da der Zahlenraum in den klassischen 8-bit Registern sehr eingeschränkt ist, wird nun ein 16-Bit Register erstellt. Die Theorie hierzu wurde bereits im ersten Teil besprochen.

 

Makro

Die Definition eines Makros ist relativ einfach:
.macro Name

 

Der Name des Makros wird im Anschluss wiederverwendet, um dieses im Programm aufzurufen. Hinter dem Namen können Parameter gesetzt werden, eine Verwendung mehrerer Parameter ist natürlich auch möglich.

 

Der Inhalt der Parameter nach dem Aufruf kann mit @ und der Nummer aufgerufen werden.

Zum Beispiel:
ldi r16, @0

 

Zum Schluss muss der Makro Abschnitt noch mit:

.endmacro

 

beendet werden.

Programm

Um ein effizientes Programm zu erhalten, wird dieses in zwei Teile aufgeteilt. Der erste initialisiert den Timer, indem die entsprechenden Bits in die Register gesetzt werden.

init_timer0:
   
ldi temp, (1 << CS01) | (1 << CS00) ; Prescaler 64
   
out TCCR0B, temp

   
ldi temp, (1 << TOIE0)
   
sts TIMSK0, temp

   
sei
   
ret

 

Beim zweiten Teil handelt es sich um das tatsächliche Makro, welches mit der Wartezeit in Millisekunden als Parameter aufgerufen wird:

.macro delay
   
   
push r24
   
push r25

   
ldi r24, low(@0)
   
ldi r25, high(@0)

   
clr overflowsL
   
clr overflowsH

  wait_loop:
   
cp overflowsL, r24
   
cpc overflowsH, r25
   
brne wait_loop

   
pop r25
   
pop r24
   
.endmacro

Das Makro nutzt den RAM zur temporären Speicherung der Anzahl an Overflows, die vergehen müssen, bis die gewünschte Wartezeit erreicht ist. Der Ablauf entspricht dem Programm aus Teil 2.

Durch den Prescaler ist die Dauer eines Overflows auf etwa eine Millisekunde eingestellt. Da die tatsächliche Dauer jedoch etwa 1,024 ms beträgt, kann es bei längeren Delay-Zeiten zu geringen Ungenauigkeiten kommen.

 

Das Makro kann im Programm, wie folgt aufgerufen werden:
delay 1000

 

Das komplette Blink Beispiel, unter Verwendung des delay Makros, können Sie hier herunterladen.

 

Fazit

Im nächsten Teil werden Sie neben weiteren Beispielen unter anderem auch den Inline Assembler der Arduino IDE kennenlernen, mit welchem es möglich ist, einzelne Codeabschnitte in den klassischen Arduino C Code einzubinden.

 

Viel Spaß beim Nachbauen :)

Projekte für fortgeschrittene

Deja un comentario

Todos los comentarios son moderados antes de ser publicados