6. Επικοινωνία με Serial Monitor Αποστολή δεδομένων

Σε προηγούμενο άρθρο μάθαμε πως να στέλνουμε δεδομένα από το Arduino και να τα εμφανίζουμε στο παράθυρο Serial Monitor του υπολογιστή μας. Μπορούμε να κάνουμε και το αντίστροφο να στέλνουμε δεδομένα και εντολές από το Serial Monitor προς το Arduino.

Ο χρήστης πληκτρολογεί τα επιθυμητά δεδομένα στο πεδίο εισαγωγής του Serial Monitor και τα αποστέλλει στο Arduino, το οποίο τα λαμβάνει από τη σειριακή θύρα μέσω της εντολής Serial.read() ή Serial.readString(). Για να πραγματοποιηθεί σωστά η επικοινωνία, πρέπει να έχει οριστεί η ίδια ταχύτητα μετάδοσης δεδομένων (baud rate) τόσο στο πρόγραμμα του Arduino όσο και στο Serial Monitor.

Όταν θέλουμε να στείλουμε δεδομένα στο Arduino τα πληκτρολογούμε στη γραμμή εισαγωγής του Serial Monitor και πατάμε Enter. Μόλις πατήσουμε Enter μεταφέρονται σε ένα ενδιάμεσο buffer όπου το Arduino μπορεί να τα μεταχειριστεί και να τα διαβάσει με τις κατάλληλες συναρτήσεις, όπως παρουσιάζουμε στην συνέχεια.

Όταν πληκτρολογίσουμε δεδομένα στη γραμμή αποστολής και πατήσουμε Enter αυτά μεταφέρονται στο buffer. Αν μετα το Arduino εκτελέσει την συνάρτηση Serial.available(), αυτή επιστρέφει τον αριθμό των χαρακτήρων (bytes) που βρίσκονται διαθέσιμα για ανάγνωση στο buffer. Για παράδειγμα:

void loop(){
     if(Serial.available()>0){
     // υπάρχουν δεδομένα
     }
}

Η Serial.available() επιστρέφει πόσα bytes έχουν ήδη φτάσει στο buffer και περιμένουν να διαβαστούν.

Η συνάρτηση Serial.read() διαβάζει το επόμενο byte (χαρακτήρα) από το σειριακό buffer αν δεν έχει αδειάσει. Για παράδειγμα

void setup() {
     Serial.begin(9600);
}
void loop() {
     if(Serial.available()>0) {
     char c = Serial.read();
     Serial.print(“Received: “);
     Serial.println(c);
     }
}

Αν γράψουμε στο Serial Monitor τη λέξη “Yiannis” το Arduino θα απαντήσει:

Received: Y
Received: i
Received: a
Received: n
Received: n
Received: I
Received: s

Ένα άλλο παράδειγμα, στο οποίο έχουμε πολλά διαθέσιμα bytes, και για κάθε χαρακτήρα που λαμβάνεται αυτός να εμφανίζεται ξανά, ο αντίστοιχος κώδικας του εισάγεται ως εξής:

void setup() {
    Serial.begin(9600);
}
void loop() {
    if(Serial.available()>0) {
    char c = Serial.read();
    Serial.print(c);
    }
}

Έτσι όταν εισάγουμε το μήνυμα: “Hello from Arduino” στη γραμμή εισαγωγής και πατήσουμε Enter το Arduino απαντά στο παράθυρο του Serial Monitor ως εξής:

Hello from Arduino

Η συνάρτηση Serial.readString() διαβάζει το αλφαριθμητικό που έχουμε εισάγει στη γραμμή εισαγωγής (και έχουμε πατήσει Enter). Αυτή η συνάρτηση επιστρέφει ένα αντικείμενο τύπου String. Δειτε το ακόλουθο παράδειγμα:

void setup() {
    Serial.begin(9600);
}
void loop() {
    if(Serial.available()>0) {
    String message = Serial.readString();
    Serial.print(“Message: “);
    Serial.println(message);
    }
}

Αν γράψουμε στην γραμμή εισαγωγής: Hello from Arduino

Το Arduino θα εμφανίσει στο παράθυρο του Serial Monitor:

Message: Hello from Arduino

Μια πολύ χρήσιμη συνάρτηση είναι:

Serial.readStringUntil(‘c’); //όπου ‘c’ ένας οποιοσδήποτε χαρακτήρας

Αυτή η συνάρτηση διαβάζει χαρακτήρες μέχρι να συναντήσει ένα συγκεκριμένο χαρακτήρα (εκείνο που είναι το όρισμα της συνάρτησης). Αυτός ο χαρακτήρας μπορεί να είναι και ο χαρακτήρας αλλαγής γραμμής ‘\n’. Για παράδειγμα, έχουμε το ακόλουθο πρόγραμμα:

void setup() {
    Serial.begin(9600);
}
void loop() {
     if(Serial.available()>0) {
     String text = Serial.readStringUntil(‘ ‘);
     Serial.println(text);
}
}

Αυτός ο κώδικας διαβάζει χαρακτήρες από την γραμμή εισαγωγής μέχρι να συναντήσει το χαρακτήρα του διαστήματος. Έτσι αν πληκτρολογήσουμε το αλφαριθμητικό “Hello from Arduino” εμφανίζει στο παράθυρο του Serial Monitor το ακόλουθο αποτέλεσμα:

Hello
from
Arduino

Μπορούμε να δημιουργήσουμε ένα μικρό σύστημα εντολών, όπως φαίνεται στο ακόλουθο παράδειγμα κώδικα:

void setup() {  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(9600);
  delay(2000);
  Serial.println("Arduino Command Interface");
  Serial.println("Commands:");
  Serial.println("LED ON");
  Serial.println("LED OFF");}void loop() {  if (Serial.available()) {
    String command = Serial.readStringUntil('\n');
    command.trim();
    if (command == "LED ON") {
      digitalWrite(LED_BUILTIN, HIGH);
      Serial.println("LED ON");
    } else if (command == "LED OFF") {
      digitalWrite(LED_BUILTIN, LOW);
      Serial.println("LED OFF");
    } else {
      Serial.println("Unknown command");
    }
  }}

Τώρα το Serial Monitor λειτουργεί σαν ένα απλό τερματικό ελέγχου.

Η Serial.flush( ) είναι μια από τις συναρτήσεις του Arduino που συχνά προκαλεί σύγχυση, κυρίως επειδή η σημασία της έχει αλλάξει σε σχέση με τις παλιές εκδόσεις του Arduino API.

Η σημερινή Serial.flush( ) χρησιμοποιείται για να περιμένει μέχρι να ολοκληρωθεί η αποστολή δεδομένων στο buffer για το Arduino. Αυτό είναι ιδιαίτερα σημαντικό όταν το πρόγραμμα πρέπει να είναι βέβαιο ότι κάποια δεδομένα έχουν πράγματι σταλεί πριν συνεχίσει στην επόμενη εντολή.

Έτσι η συνάρτηση Serial.println(“Hello”); μπορεί να ολοκληρωθεί πολύ γρήγορα από την πλευρά του προγράμματος, ενώ η πραγματική μετάδοση των bytes συνεχίζεται στο παρασκήνιο. Αυτό επιτρέπει στο πρόγραμμα να συνεχίσει την εκτέλεση του χωρίς να περιμένει απαραίτητα την ολοκλήρωση κάθε byte. Παράδειγμα:

Serial.println(“Hello”);
Serial.flush( );
digitalWrite(LED_BUILTIN, HIGH);

Εδώ το πρόγραμμα δεν προχωρεί στην digitalWrite(LED_BUILTIN, HIGH) μέχρι να ολοκληρωθεί η αποστολή δεδομένων με την Serial.println( ).

Όταν πληκτρολογούμε κάτι στη γραμμή αποστολής του Serial Monitor και πατήσουμε Enter, μπορούμε να έχουμε διαφορετικές επιλογές για το line ending. Συναντάμε επιλογές όπως:

No line ending
Newline
Carriage return
Both NL & CR

Με την επιλογή Newline στο αποστελλόμενο αλφαριθμητικό προστίθεται στο τέλος ο χαρακτήρα διαφυγής ‘\n’ αλλαγής γραμμής. Με την επιλογή Carriage return προστίθεται στο τέλος ο χαρακτήρας διαφυγής ‘\r’ carriage return. Με την επιλογή Both NR & CR προστίθενται στο τέλος και οι δυο αυτοί χαρακτήρες διαφυγής. Με την επιλογή No line ending δεν προστίθενται κανένας χαρακτήρας διαφυγής στο τέλος του αλφαριθμητικού.

Η συνάρτηση Serial.end( ) τερματίζει τη σειριακή επικοινωνία του Arduino με το Serial Monitor του υπολογιστή μας. Για παράδειγμα:

Serial.begin(9600);
Serial.println(“Starting . . . “);
Serial.end( );

Μετά το Serial.end( ) δεν πρέπει να θεωρούμε ότι η σειριακή επικοινωνία λειτουργεί κανονικά μέχρι να γίνει ξανά Serial.begin(9600);

Στις εφαρμογές Arduino, η σειριακή επικοινωνία αποτελεί έναν εύκολο τρόπο ανταλλαγής δεδομένων μεταξύ του υπολογιστή και της πλακέτας. Μέσω του Serial Monitor του Arduino IDE μπορούμε να στέλνουμε αριθμητικές τιμές στο Arduino και να τις επεξεργαζόμαστε μέσα στο πρόγραμμα που τρέχει σ’ αυτό. Για την ανάγνωση ακέραιων και δεκαδικών αριθμών μπορούν να χρησιμοποιηθούν οι συναρτήσεις Serial.parseInt() και Serial.parseFloat().

Η Serial.parseInt( ) χρησιμοποιείται για την ανάγνωση ενός ακέραιου αριθμού που έχει εισαχθεί στη γραμμή αποστολής. Η συνάρτηση διαβάζει τους χαρακτήρες που έχουν σταλεί από το Serial Monitor και μετατρέπει την αριθμητική ακολουθία σε ακέραιο αριθμό. Για παράδειγμα:

void setup(){
   Serial.begin(9600);
}
void loop() {
   if (Serial.available() > 0) {
      int number = Serial.parseInt();
      Serial.print("Ο αριθμός που έστειλες είναι: ");
      Serial.println(number);
    }
}

Αν στο Serial Monitor πληκτρολογήσουμε:
25

το Arduino θα αποθηκεύσει την τιμή 25 στη μεταβλητή number και θα εμφανίσει:
Ο αριθμός που έστειλες είναι: 25

Η parseInt( ) μπορεί επίσης να διαβάσει αρνητικούς αριθμούς, όπως -10.

Η Serial.Float( ) λειτουργεί με παρόμοιο τρόπο, αλλά χρησιμοποιείται για την ανάγνωση δεκαδικών αριθμών. Η συνάρτηση μετατρέπει τα δεδομένα που έχουν εισαχθεί στη γραμμή αποστολής του Serial Monitor σε αριθμό κινητής υποδιαστολής (float). Παράδειγμα:

void setup() {
    Serial.begin(9600);
}
void loop() {
    if (Serial.available() > 0) {
       float number = Serial.parseFloat();
       Serial.print("Ο δεκαδικός αριθμός είναι: ");
       Serial.println(number);
     }
}

Αν στείλουμε από το Serial Monitor:
12.75

το Arduino θα αποθηκεύσει την τιμή 12.75 στη μεταβλητή number και θα εμφανίσει:
Ο δεκαδικός αριθμός είναι: 12.75

Διαφορά μεταξύ parseInt( ) και parseFloat( ). Η βασική διαφορά των δύο συναρτήσεων είναι ο τύπος του αριθμού που διαβάζουν:

Επομένως, όταν θέλουμε να εισάγουμε ένα αριθμό χωρίς δεκαδικό μέρος, χρησιμοποιούμε την parseInt( ), ενώ όταν χρειαζόμαστε αριθμό με δεκαδικά ψηφία χρησιμοποιούμε την parseFloat( ).

Η συνάρτηση Serial.setTimeout() χρησιμοποιείται για να καθορίσει το χρονικό διάστημα που θα περιμένουν ορισμένες συναρτήσεις της σειριακής επικοινωνίας, όπως οι Serial.parseInt() και Serial.parseFloat(), μέχρι να λάβουν τα απαραίτητα δεδομένα. Ο χρόνος δίνεται σε milliseconds (ms). Για παράδειγμα, η εντολή Serial.setTimeout(5000); ορίζει χρόνο αναμονής 5 δευτερολέπτων. Ακολουθεί ένα παράδειγμα με parseInt( ):

void setup() {
    Serial.begin(9600);
    Serial.setTimeout(3000);
    Serial.println("Δώσε έναν ακέραιο αριθμό:");
}
void loop() {
    if (Serial.available() > 0) {
    int number = Serial.parseInt();
    Serial.print("Έδωσες: ");
    Serial.println(number);
    }
}

Στο παραπάνω παράδειγμα, η Serial.setTimeout(3000) ορίζει χρονικό όριο 3000 ms, δηλαδή 3 δευτερόλεπτα. Όταν εκτελείται η Serial.parseInt( ), το Arduino μπορεί να περιμένει μέχρι αυτό το χρονικό διάστημα για να ολοκληρωθεί η ανάγνωση των δεδομένων. Ακολουθεί ένα παράδειγμα με parseFloat( ):

void setup() {
    Serial.begin(9600);
    Serial.setTimeout(3000);
    Serial.println("Πληκτρολόγησε έναν δεκαδικό αριθμό:");
}
void loop() {
   if (Serial.available() > 0) {
   float temperature = Serial.parseFloat();
   Serial.print("Η τιμή είναι: ");
   Serial.println(temperature);
   }
}

Εδώ η parseFloat( ) θα χρησιμοποιήσει το timeout των 3 δευτερολέπτων.

Συμπέρασμα: οι Serial.parseInt( ) και Serial.parseFloat( ) χρησιμοποιούνται για την ανάγνωση αριθμητικών δεδομένων από την έξοδο του Serial Monitor, ενώ η Serial.setTimeout( ) καθορίζει το χρονικό όριο που αναμένει το Arduino για την ανάγνωση αυτών των δεδομένων. Έτσι, η setTimeout( ) αποτελεί συμπληρωματική ρύθμιση των συναρτήσεων parseInt( ) και parseFloat( ) και βοηθά στον καλύτερο έλεγχο της σειριακής επικοινωνίας.