Assembler directives
Ενώ οι εντολές λένε τι η CPU θα κάνει οι directives (αλλιώς ψευδοεντολές) δίνουν κατευθύνσεις στον assembler. Για παράδειγμα οι εντολές LDI και ADD είναι εντολές στην CPU ενώ οι .EQU , .DEVICE και .ORG είναι directives στον assembler. Στις επόμενες ενότητες παρουσιάζουμε τις πιο συνηθισμένες directives του AVR μικροελεγκτή και πως χρησιμοποιούνται. Οι directives μας βοηθούν να αναπτύξουμε τον κώδικα ευκολότερα και να τον κάνουμε ευανάγνωστο.
.EQU
Αυτή χρησιμοποιείται για να δηλώσουμε μια σταθερή τιμή ή σταθερή διεύθυνση. Η χρήση της .EQU δεν δεσμεύει χώρο στη μνήμη για αποθήκευση δεδομένων, απλά διασυνδέει ένα σταθερό αριθμό με μια ετικέτα δεδομένων ή διευθύνσεων, έτσι ώστε όταν η ετικέτα εμφανίζεται στον κώδικα η σταθερά που αντιπροσωπεύει αντικαθιστά την ετικέτα. Στο ακόλουθο παράδειγμα χρησιμοποιείται η .EQU για την σταθερά COUNT και στη συνέχεια χρησιμοποιείται για τη φόρτωση του καταχωρητή R21
.EQU COUNT = 0x25
…
LDI R21, COUNT ; R21 = 0x25
Όταν εκτελείται η παραπάνω εντολή “LDI R21, COUNT” ο καταχωρητής R21 θα φορτωθεί με την τιμή 0x25. Με την .EQU directive μπορούμε να τροποποιήσουμε μαζικά αριθμητικές τιμές που αντιπροσωπεύονται από κοινή ετικέτα.
Όπως αναφέραμε σε προηγούμενη ενότητα, μπορούμε να χρησιμοποιούμε τα ονόματα των Ι/Ο καταχωρητών αντί τις διευθύνσεις τους (π.χ. μπορούμε να γράψουμε “OUT PORTB, R20” αντί “OUT 0x05, R20). Αυτό γίνεται με την βοήθεια της .EQU directive. Με την συμπερίληψη του κατάλληλου αρχείου τα Ι/Ο ονόματα καταχωρητών ορίζονται με τις διευθύνσεις τους χρησιμοποιώντας την .EQU directive.
.SET
Αυτή η directive χρησιμοποιείται για να ορίσουμε σταθερές τιμές ή σταθερές διευθύνσεις. Οι directives .SET και .EQU είναι πανομοιότυπες που διαφέρουν στο γεγονός ότι η τιμή που εκχωρείται στην .SET directive μπορεί να εκχωρηθεί ξανά αργότερα.
Χρησιμοποιώντας την .EQU για εκχώρηση σταθερών τιμών
Για την εξάσκηση της .EQU μελέτησε το ακόλουθο παράδειγμα:
; in hexadecimal
.EQU DATA1 = 0X39 ; one way to define hex value
.EQU DATA2 = $39 ; another way to define hex value
; in binary
.EQU DATA3 = 0b00110101 ; binary (35 in hex)
; in decimal
.EQU DATA4 = 39 ; decimal numbers (27 in hex)
; in ASCII
.EQU DATA5 = ‘2’ ; ASCII characters
Χρησιμοποιώντας την .EQU για εκχώρηση SFR διευθύνσεων
Η .EQU directive χρησιμοποιείται ευρέως για εκχώρηση SFR διευθύνσεων. Μελέτησε τον ακόλουθο κώδικα:
.EQU COUNTER = 0x00 ; counter value 00
.EQU PORTB = 0x05 ; SFR Port B address
LDI R16, COUNTER ; R16 = 0x00
OUT PORTB, R16 ; Port B (loc 0x05) now has 00 too
Χρησιμοποιώντας την . EQU για την εκχώρηση RAM διευθύνσεων
Μια άλλη συνηθισμένη χρήση της .EQU είναι η εκχώρηση διευθύνσεων της εσωτερικής μνήμης SRAM. Μελέτησε ένα άλλο παράδειγμα κώδικα, για να καταλάβεις περισσότερα:
.EQU SUM = 0x120 ; assign RAM loc to SUM
LDI R20, 5 ; load R20 with 5
LDI R21, 2 ; load R21 with 2
ADD R20, R21 ; R20 = R20 + R21
ADD R20, R21 ; R20 = R20 + R21
STS SUM, R20 ; store the result in loc 0x120
Η χρήση της .EQU directive είναι ιδιαίτερα βολική, όταν μια διεύθυνση χρειάζεται να αλλάξει στην περίπτωση που θέλουμε να χρησιμοποιήσουμε ένα άλλο AVR τσιπ στην εργασία μας.
Με την .EQU directive είναι ευκολότερο να αναφερόμαστε σε ένα όνομα παρά σε ένα αριθμό όταν προσπελαύνουμε θέσεις διευθύνσεων της RAM.
.ORG
Η .ORG directive χρησιμοποιείται για να ορίσουμε την αρχή των διευθύνσεων. Μπορεί να χρησιμοποιηθεί και για τα δυο κώδικα και δεδομένα.
.INCLUDE
H .INCLUDE directive προκαλεί τον AVR assembler να προσθέσει τα περιεχόμενα ενός αρχείου στον κώδικα που γράφουμε. Για παράδειγμα η επόμενη γραμμή κώδικα θα συμπεριλάβει ένα αρχείο με όνομα “myfile.inc”
.INCLUDE myfile.inc
Στο Atmel Studio 7 όταν επιλέγεις ένα τσιπ το IDE αυτόματα προσθέτει τους ορισμούς στον κώδικα που γράφεις και δεν χρειάζεται να συμπεριλάβεις κάποιο αρχείο.
Ορίζοντας αριθμητικές και λογικές εκφράσεις με σταθερές τιμές
Σε προηγούμενη ενότητα ορίσαμε σταθερές τιμές χρησιμοποιώντας την οδηγία .EQU προς τον assembler. Το Atmel Studio IDE υποστηρίζει αριθμητικές πράξεις μεταξύ των αριθμητικών τιμών. Για παράδειγμα, στο επόμενο απόσπασμα κώδικα, ο καταχωρητής R24 φορτώνεται με την τιμή 29, η οποία είναι το αποτέλεσμα της αριθμητικής έκφρασης “(( ALFA – BETA ) * 2 )+9”
.EQU ALFA = 50
.EQU BETA = 40
LDI R23, ALFA ; R23 = ALFA = 50
LDI R24, ((ALFA - BETA) * 2) +9 ; R24 = ((50 – 40) *2) +9 = 29
To Atmel Studio IDE υποστηρίζει λογικές πράξεις μεταξύ των αριθμητικών εκφράσεων. Για παράδειγμα στο ακόλουθο πρόγραμμα ο καταχωρητής R21 φορτώνεται με την τιμή 0x14
.EQU C1 = 0x50
.EQU C2 = 0x10
.EQU C3 = 0x04
LDI R21, (C1 & C2)|C3 ; R21 = (0x10 & 0x50)|0x04 = 0x10|0x04 = 0x14
Επίσης χρησιμοποιούνται οι τελεστές μετατόπισης οι οποίοι μετατοπίζουν αριστερά ή δεξιά μια σταθερή τιμή. Για παράδειγμα, η επόμενη εντολή
LDI R16, 0b00000111 << 1 ; R16 = 0b00001110
φορτώνει στον καταχωρητή R20 με την τιμή 0b00001110. Μια από τις χρήσεις του τελεστή μετατόπισης είναι η αρχικοποίηση των καταχωρητών. Οι τελεστές μετατόπισης είναι οι εξής:
<< αριστερή μετατόπιση
>> δεξιά μετατόπιση
Παράδειγμα:
LDI R20, 0b101 << 2 ; R20 = 0b10100
μετατοπίζει το αριστερό όρισμα προς τα αριστερά κατά θέσεις ίσες με το δεξιό όρισμα.
LDI R20, 0b100 >> 1 ; R20 = 0b010
μετατοπίζει το αριστερό όρισμα προς τα δεξιά κατά θέσεις ίσες με το δεξιό όρισμα.
Παράδειγμα
Υποθέτουμε ότι θέλουμε να θέσουμε τις σημαίες Z και C του καταχωρητή κατάστασης SREG σε 1 και τις υπόλοιπες σε 0.
Σύμφωνα με την εικόνα του Status Register φορτώνουμε την τιμή 0b00000011 στον καταχωρητή SREG:
LDI R20, 0b00000011 ; Z=1, C=1
OUT SREG, R20
Σε αυτό το παράδειγμα υπολογίσαμε τον αριθμό 0b00000011 σύμφωνα με την εικόνα του καταχωρητή κατάστασης.
Υποθέτουμε ότι γράφουμε ένα πρόγραμμα και θέλουμε να κάνουμε την ίδια εργασία. Θα πρέπει να κοιτάξουμε στο datasheet για δούμε τη δομή του καταχωρητή SREG. Για να γίνει η εργασία πιο εύκολη, τα ονόματα των bits του καταχωρητή κατάστασης ορίζονται στο header file για κάθε μικροελεγκτή τύπου AVR. Για παράδειγμα, στο M328DEF.INC υπάρχουν οι ακόλουθες γραμμές κώδικα.
...
; SREG - Status Register
.equ SREG_C = 0 ; carry flag
.equ SREG_Z = 1 ; zero flag
.equ SREG_N = 2 ; negative flag
.equ SREG_V = 3 ; 2's complement overflow flag
.equ SREG_S = 4 ; sign bit
.equ SREG_H = 5 ; half carry flag
.equ SREG_T = 6 ; bit copy storage
.equ SREG_I = 7 ; global interrupt enable
Έτσι μπορούμε να χρησιμοποιήσουμε τα ονόματα των bits αντί να θυμόμαστε την δομή του καταχωρητή κατάστασης ή να τα βρίσκουμε στο datasheet. Για παράδειγμα το ακόλουθο απόσπασμα κώδικα θέτει τη σημαία Ζ του SREG σε ένα και μηδενίζει τα υπόλοιπα bits
LDI R16, 1<<SREG_Z ; R16 = 1<<1 = 0b00000010
OUT SREG, R16 ; SREG = 0b00000010
Στο ακόλουθο απόσπασμα κώδικα, οι σημαίες V και S του SREG τίθενται σε 1:
LDI R16, (1<< SREG_V) | (1<<SREG_S) ; R16 = 0b1000 | 0b10000 = 0b11000
OUT SREG, R16 ; SREG = 0b00011000 (V = S = 1 και οι υπόλοιπες σημαίες 0)
Σημείωση: όταν ο assembler μετατρέπει ένα πρόγραμμα σε γλώσσα μηχανής, πρώτα αντικαθιστά τις τιμές στα ονόματα που ορίζονται από τις directives .EQU Έτσι η χρήση directives δεν επηρεάζει τις επιδώσεις του κώδικα, αλλά κάνει τον κώδικα πιο αναγνώσιμο.
Παράδειγμα
Γράψτε κώδικα που να θέτει τα πιν PB2 και PB4 της PORTB σε 1 και να μηδενίζει τα υπόλοιπα
(α) χωρίς την χρήση directives και
(β) χρησιμοποιώντας directives
(α)
LDI R20, 0x14 ; R20 = 0x14
OUT PORTB, R20 ; PORTB = R20
για να κάνουμε τον κώδικα περισσότερο αναγνώσιμο, μπορούμε να γράψουμε τον αριθμό στο δυαδικό σύστημα, επομένως:
LDI R20, 0b00010100 ; R20 = 0x14
OUT PORTB, R20 ; PORTB = R20
(β)
LDI = R20, (1<<4) | (1<<2) ; R20 = (0b10000 | 0b00100) = 0b10100
OUT PORTB, R20
Όπως έχουμε αναφέρει σε άλλη ενότητα, τα ονόματα των bits των καταχωρητών εισόδου – εξόδου ορίζονται στο header file για κάθε μικροελεγκτή AVR. Εδώ τα ΡΒ2 και ΡΒ4 ορίζονται να είναι ίσα με τους αριθμούς πιν 2 και 4 αντίστοιχα, για τον μικροελεγκτή ATmega328P. Επομένως μπορούμε να γράψουμε τον κώδικα ως εξής:
LDI R20, (1<<PB4) | (1<<PB2)
OUT PORTB, R20
Το header file M328DEF.INC περιέχει τους ορισμούς των PB2, PB4 κ.τ.λ. σαν directives. Αυτό το αρχείο συμπεριλαμβάνεται στην αρχή του προγράμματος μας. Έτσι ο assembler μεταφράζει την εντολή: LDI R20, (1<<PB4) | (1<<PB2) κατά το γνωστό τρόπο αφού γνωρίζει τις τιμές PB2 και PB4.
Παράδειγμα
Στο ακόλουθο απόσπασμα κώδικα:
.equ C1 = 2
.equ C2 = 3
LDI R20, C1|(1<<C2) ; R20 = 2|(1<<3) = 0b00000010 | 0b00001000 = 0b00001010
Οι δυο πρώτες γραμμές είναι assembler directives που θέτει στις σταθερές C1 και C2 τις τιμές 2 και 3 αντίστοιχα. Στην συνέχεια υπολογίζεται η τιμή C1 | (1<<C2) και ο assembler αντικαθιστά την τιμή στην εντολή LDI. Επομένως η LDI R20, C1|(1<<C2) μεταφράζεται σε LDI R20, 0b00001010 και έπειτα ο assembler μετατρέπει αυτή την εντολή σε γλώσσα μηχανής.
Οι συναρτήσεις HIGH() και LOW()
Οι συναρτήσεις HIGH() και LOW() δίνουν αντίστοιχα το υψηλότερο και το χαμηλότερο byte μιας 16-bit τιμής. Για παράδειγμα, στο ακόλουθο απόσπασμα κώδικα οι τιμές 0x55 και 0x44 φορτώνονται στους καταχωρητές R16 και R17 αντίστοιχα.
LDI R16, LOW(0x4455) ; R16 = 0x55
LDI R17, HIGH(0x4455) ; R17 = 0x44
Σε άλλη ενότητα χρησιμοποιούμε τις ακόλουθες εντολές ώστε ο δείκτης σωρού να δείχνει στην τελευταία θέση της μνήμης:
LDI R16, HIGH(RAMEND) ; R16 = 0x08 (for ATmega328)
OUT SPH, R16 ; SPH = the high byte of address
LDI R16, LOW(RAMEND); R16 = 0xFF
OUT SPL, R16 ; SPL = the low byte of address
Αυτές οι εντολές δουλεύουν ως εξής: Στο header file του AVR η σταθερά RAMEND ορίζεται σαν η διεύθυνση της τελευταίας θέσης της μνήμης. Για παράδειγμα στο M328DEF.INC υπάρχει η ακόλουθη γραμμή:
.equ RAMEND = 0x08FF
Έτσι οι συναρτήσεις HIGH() και LOW() διαχωρίζουν την σταθερά RAMEND σε δυο bytes $08 και $FF τα οποία στη συνέχεια εκχωρούνται στους καταχωρητές SPH και SPL αντίστοιχα.