Προγραμματισμός σε γλώσσα assembly

Εισαγωγή

Η CPU ενός μικροελεγκτή δέχεται εντολές και δεδομένα σε δυαδική μορφή δηλαδή αριθμούς αποτελούμενοι από 0 και 1. Το σύνολο των εντολών που γράφουμε εκφρασμένο σε δυαδικό ή δεκαεξαδικό κώδικα ονομάζεται γλώσσα μηχανής (machine code). Για να διευκολυνθούμε στην ανάπτυξη κώδικα μηχανής χρησιμοποιούμε τον assembler, ο οποίος είναι μια εφαρμογή που δέχεται μνημονικά ονόματα για τις εντολές χαμηλού επιπέδου και τις μετατρέπει σε κώδικά μηχανής.

Ένας assembler λέγεται low – level language διότι είναι σύμφωνος με τη λειτουργία της CPU και παράγει κώδικα συμβατό με τον κώδικα μηχανής.  Όμως το γράψιμο κώδικα στη γλώσσα assembly (όπως και στη γλώσσα μηχανής) θέλει μεγάλη προσπάθεια και προκαλεί μεγάλες δυσκολίες.

Για να γράφουμε εύκολα κώδικα αναπτύχθηκαν γλώσσες υψηλού επιπέδου (high – level language) στις οποίες ο προγραμματιστής δεν χρειάζεται να γράφει κώδικα βασισμένο στην εσωτερική δομή και λειτουργία της CPU. Ένας προγραμματιστής χρησιμοποιεί μια εφαρμογή ηλεκτρονικού υπολογιστή για να μετατρέπει τον κώδικα υψηλού επιπέδου σε γλώσσα μηχανής. Αυτή η εφαρμογή λέγεται compiler. Για την ανάπτυξη κώδικα έχουν δημιουργηθεί διάφοροι compilers, όπου σε αυτή τη σειρά μαθημάτων θα αναφερθούμε στον C compiler, ο οποίος περιέχεται στο πακέτο λογισμικού AVR Studio IDE

Δομή της γλώσσας Assembly

Ένας κώδικας σε γλώσσα assembly αποτελείται μεταξύ άλλων από σειρές εντολών assembly. Μια εντολή στη γλώσσα assembly αποτελείται από το μνημονικό  και προαιρετικά ακολουθούμενο από ένα ή δυο τελεστέους. Τα μνημονικά είναι οι εντολές προς την CPU ενώ οι τελεστέοι είναι τα δεδομένα που θα διαχειριστούν. Επίσης ένας κώδικας assembly περιέχει directives που δίνουν κατευθύνσεις στον assembler, όπως οι .ORG και .EQU

H directive .ORG προκαλεί τον assembler να ξεκινήσει να τοποθετεί στην μνήμη τον κώδικα σε συγκεκριμένη θέση της μνήμης ROM, ενώ η .EQU θέτει ένα όνομα σε μια σταθερά. Ένα παράδειγμα κώδικα assembly δίνεται παρακάτω:

           ; AVR Assembly Language Program To Add Some Data.
           ; store SUM in SRAM location 0x300
           .EQU  SUM = 0x300     ;SRAM loc $300 for SUM
           .ORG 00               ;start at address 0
           LDI R16, 0x25         ;R16 = 0x25
           LDI R17, $34          ;R17 = 0x34
           LDI R18, 0b00110001   ;R18 = 0x31
           ADD R16, R17          ;add R17 to R16
           ADD R16, R18          ;add R18 to R16
           LDI R17, 11           ;R17 = 0x0B
           ADD R16, R17          ;add R17 to R16
           STS SUM, R16          ;save the SUM in loc $300
HERE:      JMP HERE              ;stay here forever

        

Ο κώδικας ξεκινά με δυο directives. H .EQU που δίνει την τιμή 0x300 στο όνομα SUM και την .ORG 00 που ξεκινά να γράφει τον κώδικα μηχανής του προγράμματος μας από την θέση 00 της μνήμης ROM. Έπειτα ακολουθούν οι γραμμές εντολών όπου κάθε γραμμή αποτελείται από το μνημονικό και τους τελεστέους που δρα η εντολή.

Μπορούμε να γράφουμε σχόλια μιας γραμμής εφόσον τοποθετήσουμε τον χαρακτήρα του ελληνικού ερωτηματικού μπροστά στην γραμμή σχολίων. Προσέξτε την ετικέτα “HERE” ακολουθούμενη από τον χαρακτήρα “:” στην τελευταία γραμμή. Εδώ χρησιμοποιείται από την εντολή “JMP” δημιουργώντας ένα ατέρμονο βρόγχο.

Assembling an AVR program

Μετά που παρουσιάσαμε την δομή ενός κώδικα Assembly τίθεται το ερώτημα πως δημιουργείται, μεταφράζεται και είναι έτοιμο για χρήση; Τα βήματα για την δημιουργία ενός εκτελέσιμου αρχείου assembly είναι τα ακόλουθα:

1] Πρώτα χρησιμοποιούμε ένα επεξεργαστή κειμένου για να εισάγουμε τον κώδικα. Στην περίπτωση AVR μικροελεγκτών χρησιμοποιούμε την εφαρμογή Atmel Studio IDE, το οποίο περιέχει ένα επεξεργαστή κειμένου, assembler, simulator και άλλα, όλα μαζί σε ένα πακέτο. Μπορείς να γράψεις τον κώδικα και σε άλλους επεξεργαστές κειμένου όπως το Notepad των Windows τέτοια που να παράγουν ASCII αρχεία. Το όνομα του αρχείου πηγαίου κώδικα (το αρχείο κώδικα που γράφουμε) θα πρέπει να έχει προέκταση το “asm”. Ένα πηγαίο αρχείο με προέκταση “asm” χρησιμοποιείται από ένα assembler στο επόμενο βήμα.

2] Το “asm” αρχείο που περιέχει τον πηγαίο κώδικα τροφοδοτεί τον AVR assembler. Ο assembler παράγει το object αρχείο, το hex αρχείο, ένα eeprom αρχείο, ένα list αρχείο και ένα map αρχείο. Το object αρχείο έχει προέκταση “obj”, το hex αρχείο έχει προέκταση “hex”, το list αρχείο έχει προέκταση “lss”, το map αρχείο έχει προέκταση “map” και το eeprom αρχείο έχει προέκταση “eep”.

Έπειτα από ένα επιτυχημένο link το hex αρχείο είναι έτοιμο να φορτωθεί στην μνήμη ROM του μικροελεγκτή. Επίσης μπορούμε να γράψουμε το eeprom αρχείο στην μνήμη EEPROM του AVR.

Το αρχείο asm λέγεται και πηγαίος κώδικας και θα πρέπει να έχει “asm” προέκταση και μπορεί να δημιουργηθεί από ένα επεξεργαστή κειμένου όπως το Notepad των Windows. Πολλοί assemblers διαθέτουν επεξεργαστή κειμένου. Ο assembler μετατρέπει το asm πηγαίο κώδικα σε κώδικα μηχανής και παρέχει το obj (object) αρχείο το οποίο έχει προέκταση “obj”.

Για να μπορέσει να μεταφράσει ο assembler το πηγαίο αρχείο θα πρέπει να μην έχει λάθη. Το Atmel Studio IDE μας παρέχει μηνύματα λαθών για να διορθώσουμε τον κώδικα.

Τα αρχεία “lss” και “map

Το αρχείο map μας δείχνει τις ετικέτες που ορίζονται στο κώδικα μαζί με τις τιμές τους. Μελέτησε την ακόλουθη εικόνα:

Το αρχείο lss (list) είναι πολύ χρήσιμο στον προγραμματιστή. Δείχνει το δυαδικό και τον πηγαίο κώδικα και δείχνει ποιες εντολές χρησιμοποιούνται στον πηγαίο κώδικα και το πόσο της μνήμης που ο κώδικας χρησιμοποιεί.

Αν τα αρχεία μπορούν να γίνουν προσβάσιμα με ένα επεξεργαστή κειμένου όπως το Notepad μπορούν να εμφανιστούν στην οθόνη του υπολογιστή ή να σταλούν σε ένα εκτυπωτή.

The program counter and program ROM space in the AVR

Ο σημαντικότερος καταχωρητής σε ένα AVR μικροελεγκτή είναι ο PC (program counter). O program counter χρησιμοποιείται από τη CPU για να δείξει την διεύθυνση της επόμενης εντολής που θα εκτελεστεί. Καθώς η CPU αντλεί τον κώδικα της εντολής από την μνήμη προγράμματος ROM o program counter αυξάνεται αυτόματα για να δείξει στην επόμενη εντολή. Όσα σε περισσότερα bits σε πλάτος είναι ο program counter τόσο σε μεγαλύτερο πλάτος μνήμης ROM μπορεί να προσπελαύνει. Για παράδειγμα ένας 14-bit program counter μπορεί να προσπελαύνει το μέγιστο 16Κ  (214 = 16Κ) μέγεθος μνήμης.

Σε ένα AVR μικροελεγκτή κάθε θέση μνήμης Flash έχει πλάτος 2bytes. Για παράδειγμα σε ένα ATmega328 του οποίου η μνήμη Flash είναι 32Κbytes η μνήμη Flash οργανώνεται σαν 16Κ Χ 16bits και ο program counter είναι 14bits στο πλάτος (214 = 16Κ θέσεις μνήμης) Ο ATmega64 έχει ένα 15-bit program counter οπότε η Flash έχει 32Κ θέσεις (215 = 32 Κ) στις οποίες κάθε θέση περιέχει 2bytes (32Κ Χ 2bytes = 64Kbytes). Στην περίπτωση ενός 16bit program counter  το μέγιστο της περιοχής κώδικα είναι 64Κ  (216 = 64Κ) η οποία καταλαμβάνει την περιοχή διευθύνσεων 0000 – $FFFF.

Θα πρέπει να σημειώσουμε ότι η πρώτη θέση στην μνήμη ROM σε ένα AVR μικροελεγκτή έχει διεύθυνση 000000 και η τελευταία διεύθυνση της μνήμης κώδικα ROM δεν είναι ίδια για όλα τα μέλη της οικογένειας που ορίζεται από το μέγεθος της μνήμης ROM που περικλείει ο μικροελεγκτής. Το μέλος ATmega8 έχει 8Κ πάνω στο τσιπ μνήμης ROM. Αυτή η 8Κ μνήμη ROM οργανώνεται σαν 4Κ Χ 2 bytes και έχει διευθύνσεις από 00000 έως $00FFF. Επομένως η πρώτη θέση της  μνήμης Flash έχει διεύθυνση 00000 και η τελευταία θέση έχει διεύθυνση $00FFF.

Από πού ξεκινά να εκτελεί ο AVR μόλις τροφοδοτηθεί με τάση;

Ένα ερώτημα που πρέπει να απαντήσουμε για ένα μικροελεγκτή είναι το εξής: Από ποια διεύθυνση ξεκινά να εκτελεί εντολές μόλις τροφοδοτηθεί με τάση (δηλαδή να εφαρμόσουμε τάση VCC στο πιν RESET). Ό AVR στην αρχή που τροφοδοτηθεί με τάση ο PC (program counter)  έχει την τιμή 00000. Αυτό σημαίνει ότι η πρώτη εντολή που θα εκτελέσει βρίσκεται στην θέση με διεύθυνση 00000. Για αυτό το λόγο σε ένα AVR σύστημα η πρώτη εντολή πρέπει να γραφεί στην διεύθυνση 00000 της ROM διότι από εκεί ξεκινά η εκτέλεση των εντολών. Στον AVR assembler, αυτό το επιτυγχάνουμε με την έκφραση .ORG στον πηγαίο κώδικα όπως έχουμε αναφέρει σε προηγούμενη ενότητα.