Monday, March 5, 2012

Arduino, προγραμματίζοντας με λίγη assembly

Γενικά έχουμε πει ότι το arduino προγραμματίζεται σε C/C++. Παρ' όλα αυτά μπορούμε να χρησιμοποιήσουμε και στοιχεία assembly όπως θα κάναμε εάν θέλαμε να προγραμματίσουμε έναν avr μικροελεγκτή χωρίς τον bootloader του arduino και δίχως τις κατάλληλες βιβλιοθήκες avr-lib της C. Ας δούμε λοιπόν μερικά παραδείγματα στα οποία θα χρησιμοποιήσουμε λίγη assembly στον ορισμό και στην χρήση των ακροδεκτών του arduino.

Παράδειγμα 1:



Σε αυτό το παράδειγμα θα θέσουμε ένα led σε διακοπτόμενη λειτουργία χρησιμοποιώντας απ' τη μια την κλασσική σύνταξη και απ'την άλλη δουλεύοντας με τα ports του arduino. Απ' τη μία έχουμε το κλασσικό παράδειγμα http://arduino.cc/en/Tutorial/Blink με τον παρακάτω κώδικα:


void setup() {
// initialize the digital pin as an output.
// Pin 13 has an LED connected on most Arduino boards:
pinMode(13, OUTPUT);
}
void loop() {
digitalWrite(13, HIGH); // set the LED on
delay(1000);  // wait for a second
digitalWrite(13, LOW);  // set the LED off
delay(1000);  // wait for a second
}

και απ ́ την άλλη μπορούμε να το κάνουμε ως εξής:

1. Βρίσκουμε σε ποιό port του arduino μας αντιστοιχεί ο ακροδέκτης 13.
Ψάχνουμε το pin mapping της υλοποίησης που χρησιμοποιούμε, για το mega 2560 είναι: http://arduino.cc/en/Hacking/PinMapping2560.

Βλέπουμε ότι το 13 αντιστοιχεί στο port B και στο 7 ( PB7). Κάθε port αποτελείται από 3 καταχωρητές. Έχουμε και λέμε:

– DDRB (Data Direction Register): ορίζει εάν ο ακροδέκτης είναι εισόδου ή εξόδου. Ένα bit για
κάθε ακροδέκτη(1x8=1byte).
– PORTB: περιέχει την κατάσταση των ακροδεκτών που είναι ρυθμισμένοι για έξοδο. Αλλά
χρησιμοποιείται και για να ενεργοποιήσουμε τις pull-up αντιστάσεις*.
– PINB: περιέχει την κατάσταση των ακροδεκτών που είναι ρυθμισμένοι για είσοδο.

*Όταν ορίζουμε την κατάσταση ενός ακροδέκτη σαν είσοδο, τότε αυτή “παίζει” μεταξύ 0-5V ή Low-High. Για να σταθεροποιήσουμε την κατάστασή τους, ενεργοποιούμε τις pull-up και επομένως η κατάστασή τους είναι High, μέχρι να αλλάξει (από έναν διακόπτη πχ).

Ας δούμε λοιπόν το παράδειγμα με λίγη assembly:

void setup() {
DDRB = B10000000; // PB7, 7th bit = 1 for output
}
void loop() {
PORTB = B10000000; // set the LED on
delay(1000);  // wait for a second
PORTB = B00000000;
// set the LED off
delay(1000);  // wait for a second
}



Παράδειγμα 2:
Άλλο ένα παράδειγμα για να δούμε και τη χρήση και του καταχωρητή PINB.

#define mask B01000000 // mask is used to get the values of specific bits
void setup() {
DDRB = B10000000; // PB7, 7th bit = 1 for output
//PB6, 6th bit = 0 for input
PORTB = B01000000; //pull-up resistor for input
}
void loop() {
if (!(PINB&mask)) // 6th bit is used as variable
PORTB = B11000000; // set the LED on for
else
PORTB = B01000000;  // set the LED off
delay(150);  // wait for a second
}

Η μάσκα χρησιμοποιείται για να ελέγξουμε το bit-διακόπτη μόνον. Αφού ορίσουμε κατάλληλα τις εισόδους-εξόδους, βάζουμε και την αντίσταση pull-up. Αυτό σημαίνει ότι ο διακόπτης είναι στο High και με το πάτημα ή τη γείωση γενικότερα μεταβαίνει στο Low.

Στη συνθήκη if (!(PINB&mask)) ελέγχουμε με τη βοήθεια της μάσκας μόνο το 6ο bit. Για να έχουμε την αντιστοιχία πατημένο - led=high, ελεύθερο-led = low, πρέπει να πάρουμε την άρνηση της συνθήκης. Έτσι όταν πατάμε τον διακόπτη έχουμε NOT( 0 AND 1) = 1, άρα θέτουμε το led on.

H καθυστέρηση εισάγεται για να εξαλειφθεί ο θόρυβος μέχρι να καθοριστεί η κατάσταση του led.

Παράδειγμα 3 – λίγη assembly ακόμα:

schematic


Σε αυτό το παράδειγμα θα χρησιμοποιήσουμε 8 leds για να κάνουμε πράξεις μεταξύ bit. Τα 4 leds θα
είναι έξοδος (PORTB) και τα άλλα 4 (PORTL) θα είναι η μεταβλητή είσοδος για να δούμε τις πράξεις.


#define mask B00001000 // mask is used to get the values of specific bits
void setup() {
DDRE = B00001000;  //switch pin 5
PORTE = B00001000;
DDRL = B00000000;  //variable bits
PORTL = B11110000;  //pins 42-45,42=higher bit
DDRB = B11110000;  // PB7-4 pins 13-10, 13=higher for output
PORTB = B00000000; //initiation
//Serial.begin(9600);
}
void loop() {
PORTB = B00000000; //cause XOR always change we initiate PORTB
//in order to watch the same resault
if (PINE&mask)  // 4th bit of E port is used as variable
{
PORTB = (PORTB ^ (~PINL)); // set the LED on
delay(150);  //Serial.print((~PINL&B11110000), DEC);
}
else
PORTB = B10000000;  // set the LED off */
delay(150);  // wait for a second
}

Αρχικά ορίζουμε εισόδους-εξόδους. Επειδή όταν ορίζουμε έναν ακροδέκτη σαν έξοδο αυτός πάλλεται από 0-5V καλό είναι πάντα να ενεργοποιούμε τις pull-up resistors, και αν χρειάζεται να πέρνουμε το συμπλήρωμά του (~). Έτσι έχουμε PORTL = B11110000 και PORTE = B00001000.
Άρα στη συνθήκη if (PINE&mask) ελέγχουμε ότι δεν είναι γειωμένος.

Έπειτα στην πράξη XOR (1 για διαφορετικά, 0 όμοια) επειδή όταν τα led-μεταβλητές ανάβουν αμυδρά είμαστε στην κατάσταση LOW, μιας και οι αντίστοιχοι ακροδέκτες είναι στην ουσία συνδεδεμένοι στη γείωση – μέσω των led – χρησιμοποιούμε το συμπλήρωμα για να υπάρχει αντιστοιχία high-1, low-0.
Δηλαδή η πλήρης απουσία φωτεινότητας σημαίνει ότι δεν έχουμε πτώση τάσης κάπου αλλού και άρα οι ακροδέκτες διαβάζουν 5V (=high). Επομένως μπορούμε με αυτό τον τρόπο να συγκρίνουμε τα αποτελέσματα τις πράξεις απ' ευθείας. Ο λόγος που υπάρχει το else είναι απλά για λόγους ελέγχου, κατά την υλοποίηση.

Όσο για τον μηδενισμό του PORTB στην αρχή της επανάληψης, αυτό γίνεται για να διατηρείται σταθερό το αποτέλεσμα που πέρνουμε από την πράξη. Αλλιώς θα άλλαζε διαρκώς.
Μέσα στην if θα μπορούσαμε να βάλουμε κι άλλες συνθήκες και διακόπτες κάνονταςδιάφορες πράξεις ανάλογα με το πάτημα του κουμπιού.

Τα παραπάνω παραδείγματα δεν είναι προφανώς εξολοκλήρου γραμμένα σε assembly, αλλά δανειστήκαμε κάποιους καταχωρητές κατά βάση για μαθησιακούς σκοπούς. Ένω θα μπορούσαν να χρησιμεύσουν και για λόγους βελτιστοποίησης.

No comments:

Post a Comment