Σε προηγούμενη ενότητα είδαμε ότι ο AVR μπορεί να έχει το πολύ 8ΜΒ μνήμης προγράμματος και το πολύ 64ΚΒ μνήμης δεδομένων. Μπορούμε να χρησιμοποιήσουμε την μνήμη προγράμματος για την αποθήκευση σταθερών δεδομένων. Σε αυτή την ενότητα θα μάθουμε πώς να έχουμε πρόσβαση στα σταθερά δεδομένα που έχουν εκχωρηθεί στη μνήμη προγράμματος ROM. Πρώτα εξετάζουμε πώς να αποθηκεύσουμε σταθερά δεδομένα στη μνήμη ROM χρησιμοποιώντας την .DB (define byte) directive.
H directive .DB για εγγραφή σταθερών δεδομένων στην ROM
H .DB data directive χρησιμοποιείται για την διαμόρφωση της μνήμης προγράμματος ROM σε τμήματα τύπου byte και την εκχώρηση σταθερών τιμών. Όταν η .DB χρησιμοποιείται για την αποθήκευση σταθερών δεδομένων, οι αριθμοί μπορεί να είναι δεκαδικοί, δυαδικοί, δεκαεξαδικοί ή ASCII συμβολοσειρές. Η .DB directive χρησιμοποιείται συχνά για τον ορισμό ASCII strings.
Στο ακόλουθο παράδειγμα σημειώνεται ότι κάθε θέση της μνήμης προγράμματος ROM έχει πλάτος 2-bytes σε αντίθεση με την .DB directive που εκχωρεί δεδομένα πλάτους ενός byte. Εάν εκχωρούμε ένα σύνολο από bytes χρησιμοποιώντας την .DB directive, το πρώτο byte πηγαίνει στο χαμηλότερο byte της διεύθυνσης ROM, το δεύτερο byte πηγαίνει στο υψηλότερο byte της ίδιας διεύθυνσης ROM, το τρίτο byte πηγαίνει στο χαμηλότερο byte της επόμενης διεύθυνσης ROM κ.τ.λ.
Στην περίπτωση στην οποία εκχωρούμε ένα περιττό αριθμό από bytes στις θέσεις ROM χρησιμοποιώντας την .DB directive, o assembler αυτόματα κάνει τον αριθμό των δεσμευμένων θέσεων άρτιο τοποθετώντας 0 στο υψηλότερο byte της τελευταίας θέσης. Στο παράδειγμα θα πρέπει να χρησιμοποιήσουμε μονά εισαγωγικά (‘) για ένα μονό χαρακτήρα και διπλά εισαγωγικά (“) για μια συμβολοσειρά (string)
Παράδειγμα: Υποθέτουμε ότι έχουμε εκχωρήσει τα παρακάτω σταθερά δεδομένα στην μνήμη ROM σε ένα μικροελεγκτή AVR. Περιγράψτε τα περιεχόμενα κάθε θέσης μνήμης ROM αρχίζοντας από την τιμή $500
; MY DATA IN FLASH ROM
.ORG $500
DATA1: .DB 1, 8, 5, 3
DATA2: .DB 28 ; DECIMAL (1C in hex)
DATA3: .DB 0b00110101 ; BINARY (35 in hex)
DATA4: .DB 0x39 ; HEX
.ORG 0x510
DATA5: .DB 'Y' ; απλός ASCII χαρακτήρας
DATA6: .DB '2', '0', '0','5' ; αριθμοί ASCII
.ORG $516
DATA7: .DB "HELLO WORLD" ; ASCII string
Λύση: το DATA1 έχει τέσσερα bytes δεδομένων. Η directive “.ORG $500” προκαλεί τον assembler να βάλει το πρώτο byte του DATA1 στο χαμηλότερο byte της θέσης $500. Το δεύτερο byte του DATA1, το οποίο είναι το 8, πηγαίνει στο υψηλότερο byte της θέσης $500, το τρίτο byte πηγαίνει στο χαμηλότερο byte της θέσης $501 και το τέταρτο byte πηγαίνει στο υψηλότερο byte της θέσης $501.
To DATA2 θα εκχωρηθεί μετά το DATA1 στη θέση $502 της μνήμης. Επειδή το DATA2 έχει ένα byte και κάθε θέση μνήμης ROM έχει μέγεθος 2 bytes, o assembler βάζει 0 στο υψηλότερο byte της θέσης $502
Η γλώσσα AVR assembly επιτρέπει τη χρήση της directive .DW για να ορίσουμε τιμές μεγαλύτερες από 255 (0xFF) αλλά όχι μεγαλύτερες από 65535 (0xFFFF).
Παράδειγμα: Δώσε τα περιεχόμενα κάθε θέσης ROM ξεκινώντας από $600
.ORG $600
DATA1: .DW 0x1234, 0x1122
DATA2: .DW 28 ; DECIMAL (001C in hex)
DATA3: .DW 0x2239 ; HEX
Λύση: Το χαμηλότερο byte του 0x1234 το οποίο είναι το 0x34 πηγαίνει στο χαμηλότερο byte της θέσης $600 και το υψηλότερο byte 0x12 της τιμής πηγαίνει στο υψηλότερο byte της θέσης $600. Στη συνέχεια η τιμή 28 πηγαίνει στο χαμηλότερο byte της θέσης $601 ενώ στο υψηλότερο byte της ίδιας θέσης μπαίνει το 0. Ακολουθεί η τιμή 0x39 στο χαμηλότερο byte της θέσης $602 ενώ στο υψηλότερο byte μπαίνει η τιμή 0x22
Διαβάζοντας δεδομένα από τη μνήμη προγράμματος
Στο προηγούμενο παράδειγμα δείξαμε πώς να εκχωρούμε σταθερά δεδομένα στην μνήμη ROM. Τώρα χρειαζόμαστε ένα καταχωρητή που να δείχνει στα δεδομένα που θα ζητήσουμε από την μνήμη ROM. H AVR assembly χρησιμοποιεί τον καταχωρητή Ζ γι αυτό το σκοπό. Για αυτό το λόγο λέμε ότι χρησιμοποιούμε “έμμεση flash κατάσταση διευθυνσιοδότησης (register indirect flash addressing mode)”. Αυτή η κατάσταση διευθυνσιοδότησης χρησιμοποιείται συχνά για να έχουμε πρόσβαση σε δεδομένα που είναι εκχωρημένα στη μνήμη προγράμματος ROM.
Η εντολή LPM Rn, Z
Η εντολή LPM Rn, Z φορτώνει το byte που δείχνει ο καταχωρητής Ζ στον καταχωρητή γενικού σκοπού Rn. Όπως γνωρίζουμε κάθε θέση μνήμης προγράμματος έχει μέγεθος 2 bytes. Θα πρέπει να ορίσουμε εάν θέλουμε να διαβάσουμε το χαμηλότερο ή το υψηλότερο byte. Το λιγότερο σημαντικό bit (LSB) του καταχωρητή Ζ δείχνει εάν θέλουμε να διαβάσουμε το χαμηλότερο ή το υψηλότερο byte. Εάν LSB=0 τότε το χαμηλότερο byte θα διαβαστεί, διαφορετικά θα διαβαστεί το υψηλότερο byte. Τα υπόλοιπα bits του καταχωρητή Ζ (bit 1 εως bit 15) παριστάνουν την διεύθυνση της θέσης που θα διαβαστεί.
Παράδειγμα: Για να φορτώσουμε στον καταχωρητή R16 το υψηλότερο byte της θέσης $0002 (0b00000010) της μνήμης προγράμματος, θα πρέπει να φορτώσουμε τον καταχωρητή Ζ με την τιμή $0005 (0b00000101)
Λύση Μπορούμε να γράψουμε τον κώδικα χρησιμοποιώντας τις directives HIGH και LOW
LDI ZH, HIGH(0x0005) ; φόρτωσε τον ΖΗ με την τιμή 0x00
LDI ZL, LOW(0x0005) ; φόρτωσε τον ZL με την τιμή 0x05
LPM R16, Z ; φόρτωσε τον R16 με τα περιεχόμενα της θέσης που δείχνει ο Ζ
Για να διαβάσουμε το χαμηλότερο byte μιας θέσης, θα πρέπει να μετατοπίσουμε την διεύθυνση της θέσης ένα bit προς αριστερά. Για παράδειγμα, για να έχουμε πρόσβαση στο χαμηλότερο byte της διεύθυνσης μνήμης προγράμματος 0b00000010 θα πρέπει να φορτώσουμε στον Ζ την τιμή 0b00000100. Για να διαβάσουμε το υψηλότερο byte θα πρέπει να μετατοπίσουμε την διεύθυνση κατά μια θέση προς τα αριστερά και να θέσουμε το bit 0 σε 1.
Μπορούμε να μετατοπίσουμε την διεύθυνση χρησιμοποιώντας τον τελεστή <<. Για παράδειγμα, το ακόλουθο πρόγραμμα διαβάζει το χαμηλότερο byte της θέσης $100
LDI ZH, HIGH($100<<1) ; φορτώνει τον ZH με το υψηλότερο byte της διεύθυνσης
LDI ZL, LOW($100 <<1) ; φορτώνει τον ΖL με το χαμηλότερο byte της διεύθυνσης
LPM R16, Z
Εάν κάνουμε τη λογική πράξη OR ενός αριθμού με το 1, τότε το bit0 θα γίνει 1. Έτσι το ακόλουθο πρόγραμμα διαβάζει το υψηλότερο byte της θέσης $100
LDI ZH, HIGH(($100<<1)|1)
LDI ZL, LOW(($100<<1)|1)
LPM R16, Z ; φορτώνει τον R16 με τα περιεχόμενα της θέσης που δείχνει ο Ζ
Παράδειγμα: Υποθέτουμε ότι στη μνήμη προγράμματος ROM έχει καταχωρηθεί στη θέση $500 και πέρα το αλφαριθμητικό “WORLD PEACE.” Γράψε ένα πρόγραμμα που να στέλνει όλους τους χαρακτήρες στη PortB ένα byte κάθε φορά.
Λύση 1: χρήση μετρητή
.ORG $0000 ; Ξεκινώντας από τη θέση 0 της ROM
LDI R16, 11
LDI R20, 0xFF
OUT DDRB, R20 ; κάνε την PortΒ ως έξοδο.
LDI ZH, HIGH(MYDATA << 1) ; ZH = το υψηλότερο byte της διεύθυνσης ROM
LDI ZL, LOW(MYDATA << 1) ; ZL = το χαμηλότερο byte της διεύθυνσης ROM
L1: LPM R20, Z
OUT PORTB, R20 ; αποστολή στη PortB
INC ZL ; δείχνοντας στο επόμενο byte
DEC R16 ; μείωση του μετρητή
BRNE L1 ; επανάληψη όταν ο μετρητής δεν είναι 0
HERE: RJMP HERE
; δεδομένα που φορτώνονται στη μνήμη προγράμματος ROM ξεκινώντας από τη θέση $500
.ORG 0x500
MYDATA: .DB “WORLD PEACE”
Λύση2: χρήση του χαρακτήρα null στο τέλος του αλφαριθμητικού.
.ORG $0000 ; φόρτωσε τον κώδικα ξεκινώντας από τη θέση 0.
LDI R20, 0xFF
OUT DDRB, R20 ; κάνε την PortΒ ως έξοδο.
LDI ZH, HIGH(MYDATA << 1) ; ZH = το υψηλότερο byte της διεύθυνσης
LDI ZL, LOW(MYDATA << 1) ; ZL = το χαμηλότερο byte της διεύθυνσης
L1: LPM R20, Z
CPI R20, 0 ; σύγκρινε τον R20 με το 0
BREQ HERE ; διακλάδωση εάν είναι ίσο
OUT PORTB, R20 ; αποστολή στη PortB
INC ZL ; δείχνοντας στο επόμενο byte
RJMP L1 ; επανάληψη
HERE: RJMP HERE ; σταματώντας εδώ για πάντα
; δεδομένα που εκχωρούνται στη μνήμη προγράμματος ξεκινώντας από $500
.ORG 0x500
MYDATA: .DB “WORLD PEACE”,0 ; σημειώστε τον τελευταίο χαρακτήρα ως null
Επιλογή αυτόματης αύξησης του Ζ
Η εντολή LPM Rn, Z+
Χρησιμοποιώντας την εντολή “INC ZL” για να αυξήσουμε το δείκτη μπορεί να δημιουργήσει πρόβλημα όταν μια διεύθυνση σαν την $5FF αυξάνει. Το κρατούμενο δεν θα διαδοθεί στον ZH. O AVR μας δίνει την επιλογή με την εντολή LPM Rn, Z+ . Μετά που φορτώνεται ο Rn από την θέση ROM που δείχνει ο Ζ, ο Ζ αυξάνει κατά 1. Δες τα ακόλουθα παραδείγματα:
Παράδειγμα: Υποθέτουμε ότι στη μνήμη προγράμματος ROM έχει καταχωρηθεί στη θέση $500 και πέρα το αλφαριθμητικό “WORLD PEACE.” Γράψε ένα πρόγραμμα που να στέλνει όλους τους χαρακτήρες στη PortB ένα byte κάθε φορά.
Λύση
.ORG $0000 ; εγγραφή στη μνήμη προγράμματος από την θέση 0
LDI R20, 0xFF
OUT DDRB, R20 ; κάνε την PortB έξοδο
LDI ZH, HIGH(MYDATA << 1) ; ZH = το υψηλότερο byte της διεύθυνσης
LDI ZL, LOW(MYDATA << 1) ; ZL = το χαμηλότερο byte της διεύθυνσης
L1: LPM R20, Z+
CPI R20, 0 ; σύγκρινε τον R20 με το 0
BREQ HERE ; διακλάδωση όταν είναι ίσο
OUT PORTB, R20 ; αποστολή στη PortB
RJMP L1 ; επανέλαβε
HERE: RJMP HERE ; σταμάτησε εδώ
; δεδομένα που θα εκχωρηθούν στη μνήμη προγράματτος
.0x500
MYDATA: .DB “WORLD PEACE”, 0
Παράδειγμα: Υποθέστε ότι στη μνήμη ROM ξεκινώντας από τη θέση $100 υπάρχει το μήνυμα “The Promise of World Peace”. Γράψτε ένα πρόγραμμα που να φέρνει αυτό το μήνυμα στη CPU ένα byte κάθε φορά και να τοποθετήσει τα bytes στη μνήμη δεδομένων RAM ξεκινώντας από τη θέση $140
Λύση
.EQU RAM_BUF = 0x140
.ORG $0000 ; τοποθέτησε τον κώδικα ξεκινώντας από την θέση 0
LDI R20, 0xFF
OUT DDRB, R20 ; κάνε την PortΒ ως έξοδο.
LDI ZH, HIGH(MYDATA << 1) ; ZH = το υψηλότερο byte της διεύθυνσης
LDI ZL, LOW(MYDATA << 1) ; ZL = το χαμηλότερο byte της διεύθυνσης
LDI XH, HIGH(RAM_BUF) ; XH = $1 το υψηλότερο byte της διεύθυνσης RAM
LDI XL, LOW(RAM_BUF) ; XL = $40 το χαμηλότερο byte της διεύθυνσης RAM
L1: LPM R20, Z+
CPI R20, 0 ; σύγκρινε τον R20 με το 0
BREQ HERE ; διακλάδωση στο τέλος του string
ST X+, R20 ; φόρτωσε τον R20 στη μνήμη δεδομένων RAM και αύξησε τον Χ
RJMP L1 ; επανάληψη
HERE: RJMP HERE ; σταμάτησε εδώ και μείνε για πάντα