AVR-GCC. UART. Σειριακή επικοινωνία.

Τεκμηρίωση του ATmega8.

Το UART (Universal Asynchronous Receiver Transmitter).
Μέσω του UART οι AVR μπορούν με ευκολία να επικοινωνήσουν μέσω της σειριακής θήρας RS232 με έναν υπολογιστή ή με άλλες συσκευές.

Πιθανές εφαρμογές του UART είναι:

Μέσω για αποσφαλμάτωση.
Μέσω της θύρας RS232 σε έναν υπολογιστή. Αποσφαλμάτωση με την printf. Στην μεριά του υπολογιστή θα τρέχει κάποιο πρόγραμμα για σειριακή επικοινωνία. Τέτοια είναι το Hyper terminal, το hterm* (πολύ καλό) για Microsoft Windows, minicom, screen, gtkterm, hterm για Unix/Linux. Δεν μπορεί να πραγματοποιηθεί απ’ ευθείας σύνδεση του μικροελεγκτή με τον υπολογιστή λόγο των διαφορετικών επιπέδων τάσης. Για τον λόγο αυτό χρησιμοποιούνται μετατροπείς όπως το MAX232** που είναι αρκετά φτηνοί και εύκολοι στην χρήση. Υπολογιστές που δεν έχουν σειριακή θύρα μπορούν να συνδεθούν με έναν μετατροπέα*** usb σε σειριακή θύρα.

* http://www.der-hammer.info/terminal/index.htm

** http://www.ti.com/lit/ds/symlink/max232.pdf

*** USB to Serial

Διασύνδεση ανθρώπου-μηχανής.
Για παράδειγμα ένα μενού για εύκολη επικοινωνία, ή μέσω γραμμής εντολών.

Μεταφορά τιμών.
Για την αποθήκευση τιμών σε έναν data logger για παράδειγμα.

Σύνδεση με άλλες συσκευές.
Όπως modems, gps, κινητά τηλέφωνα, αισθητήρες, “έξυπνα” displays.

Διάφορους διαύλους (Field Bus).
Διαύλους βασισμένους στα RS422/RS485 (με την χρήση κατάλληλου μετατροπέα π.χ. MAX485). Για παράδειγμα DMX, MIDI, LIN*.

* http://en.wikipedia.org/wiki/RS-422
http://en.wikipedia.org/wiki/RS-485
http://en.wikipedia.org/wiki/Local_Interconnect_Network
http://en.wikipedia.org/wiki/MIDI
http://en.wikipedia.org/wiki/DMX512

Μερικοί AVRs έχουν περισσότερα από ένα ένα full-duplex (η συσκευή μπορεί να λάβει και να στείλει δεδομένα ταυτόχρονα δεδομένων) UART έτοιμα σε επίπεδο υλικού.
Νεότεροι AVRs (Atmega, Attiny) διαθέτουν ένα έως 4 USART. Η κύρια διαφορά του USART με το UART είναι ο εσωτερικός Buffer εισόδου/εξόδου FIFO καθώς και περισσότερες επιλογές για τον τρόπο λειτουργίας. Το μέγεθος του buffer ισούται με έναν χαρακτήρας.

Υλικό.

Το UART των AVR είναι βασισμένο σε επίπεδα τάσης TTL, 0 (λογικό 0) και 5 (λογικό 1) volts. Η διασύνδεση του RS232 είναι -3~-12 (λογικό 1) και +3~+12 (λογικό 0) volts. Για την ρύθμιση των επιπέδων υπάρχουν έτοιμα chips. Το ποιο γνωστό από αυτά είναι το MAX232 όπως είπαμε.

Μπορούμε να χρησιμοποιήσουμε κάποιο usb module που μπορεί να συνδεθεί απευθείας στον μικροελεγκτή μας. Ένα τέτοιο module (βασισμένο στο chip PL2303) στο Ebay στοιχίζει γύρω στα 5 €.

Πολλές φορές σφάλματα στην επικοινωνία με το UART οφείλονται σε λανθασμένες ρυθμίσεις του ρυθμού baud. Η ρύθμιση για συγκεκριμένο ρυθμό baud εξαρτάτε από την ταχύτητα χρονισμού του μικροελεγκτή. Κυρίως σε νέα κυκλώματα ή σε κυκλώματα που έχει πραγματοποιηθεί αλλαγή μικροελεγκτή. Πρέπει να είμαστε σίγουροι πως ο μικροελεγκτής λειτουργεί στην σωστή ταχύτητα και όχι για παράδειγμα σε κάποια εργοστασιακή ρύθμιση.

Οι καταχωρητές του UART.

Η πρόσβαση στο UART γίνεται μέσω τεσσάρων καταχωρητών. Τα USARTs των ATmega έχουν επιπρόσθετους καταχωρητές. Λεπτομέρειες υπάρχουν στα έγγραφα τεκμηρίωσης του κάθε μικροελεγκτή. Τα παρακάτω αφορούν του μικροελεγκτές ATmega8.

UCSRA.
USART Control and Status Register A. Ο καταχωρητής αυτός μας δείχνει το τι γίνεται στο USART.

RXC (UART Receive Complete):
Αυτό το bit γίνεται ένα (1) όταν κάποιος χαρακτήρας μεταφερθεί από τον καταχωρητή ολίσθησης (shift register) λήψης στον καταχωρητή λήψης δεδομένων. Η ανάγνωση του χαρακτήρα από τον καταχωρητή δεδομένων πρέπει να γίνει όσο το δυνατόν ποιο γρήγορα. Αν αυτό δεν συμβεί μέχρι την πλήρη λήψη του επόμενου χαρακτήρα θα δημιουργηθεί σφάλμα υπερχείλισης. Με την ανάγνωση του καταχωρητή δεδομένων το bit αυτό γίνεται μηδέν (0).

TXC (UART Transmit Complete):
Αυτό το bit γίνεται ένα (1) όταν ο καταχωρητής ολίσθησης μετάδοσης έχει μεταδώσει τον χαρακτήρα που είχε και δεν εκκρεμεί άλλος χαρακτήρας προς μετάδοση στον καταχωρητή δεδομένων μετάδοσης. Αυτό σημαίνει πως έχουν πλήρη ολοκλήρωση της επικοινωνίας. Αυτό το bit είναι σημαντικό για μεταδόσεις half-duplex, όταν το πρόγραμμα πρέπει να σταματήσει την εκπομπή για να αρχίσει η λήψη. Το bit αυτό γίνεται μηδέν (0) όταν καλείται το interrupt ολοκλήρωσης της μετάδοσης, διαφορετικά μπορούμε να το κάνουμε μηδέν γράφοντας ένα (1) στην θέση του.

UDRE (UART Data Register Empty):
Αυτό το bit δείχνει κατά πόσο είναι έτοιμος ο buffer μετάδοσης να δεχτεί τον προς μετάδοση χαρακτήρα. Αυτό το bit γίνεται ένα (1) εάν ο buffer μετάδοσης είναι άδειος. Είναι μηδέν (0) εφόσον υπάρχει κάποιος χαρακτήρας στον καταχωρητή δεδομένων μετάδοσης και δεν έχει περάσει στον καταχωρητή ολίσθησης μετάδοσης. Για λόγους συμβατότητας με νεότερους μικροελεγκτές η Atmel συνιστά να κάνουμε το bit αυτό μηδέν (0) κατά την χρήση του UCSRA.

FE (Framing Error):
Το bit αυτό είνα ένα (1) όταν το UART ανιχνεύσει ένα σφάλμα πλαισίωσης σε κάποιον χαρακτήρα. Όταν για παράδειγμα το “stop bit” ενός χαρακτήρα είναι μηδέν (0). Το bit αυτό γίνεται αυτόματα μηδέν (0) όταν το “stop bit” του χαρακτήρα που λήφθηκε είναι ένα (1).

DOR (Data Over Run):
Αυτό το bit γίνεται ένα (1) όταν το πρόγραμμά μας δεν είναι σε θέση να λάβει τον χαρακτήρα που περιμένει στον καταχωρητή δεδομένων λήψης πριν την πλήρη λήψη του επόμενου χαρακτήρα. Ο επόμενος χαρακτήρας χάνεται. Το bit αυτό γίνεται αυτόματα μηδέν (0) όταν ο ληφθέντας χαρακτήρας μπορεί να μεταφερθεί στον καταχωρητή δεδομένων λήψης.

PE (Parity Error):
Αυτό το bit γίνεται ένα (1) όταν στον χαρακτήρα στον buffer δεδομένων λήψης ανιχνευτεί κάποιο λάθος ισοτιμίας. Το bit αυτό γίνεται μηδέν όταν ο χαρακτήρας μεταφερθεί στον καταχωρητή δεδομένων λήψης.

U2X (Double the transmission speed):
Αυτό το bit χρησιμοποιείται μόνο στην ασύγχρονη λειτουργία. Σε σύγχρονη λειτουργία είναι μηδέν (0). Αν το bit είναι ένα τότε ο διαιρέτης του ρυθμού baud από 16 γίνεται 8 διπλασιάζοντας τον ρυθμό μετάδοσης.

MPCM (Multi Processor Communication Mode):
Αυτό το bit όταν είναι ένα (1) ενεργοποιείτε η λειτουργία Multi-Processor. Στην λειτουργία αυτή κάθε εισερχόμενο πλαίσιο που δεν περιέχει πληροφορίες διεύθυνσης θα αγνοείτε.

UCSRΒ.

USART Control and Status Register Β. Σε αυτόν το καταχωρητή ορίζουμε πως θέλουμε να χρησιμοποιήσουμε το UART.

RXCIE (RX Complete Interrupt Enable):
Αν αυτό το bit είναι ένα (1), ενεργοποιείται ένα interrupt RX Complete (ολοκλήρωση λήψης) όταν το UART λάβει έναν χαρακτήρα. Φυσικά πρέπει να είναι ενεργοποιημένα τα interrupts.

TXCIE (TX Complete Interrupt Enable):
Αν αυτό το bit είναι ένα (1), ενεργοποιείται ένα interrupt ΤX Complete (ολοκλήρωση αποστολής) όταν το UART έχει στείλει έναν χαρακτήρα. Φυσικά πρέπει να είναι ενεργοποιημένα τα interrupts.

UDRIE (UART Data Register Empty Interrupt Enable):
Αν αυτό το bit είναι ένα (1), ενεργοποιείται ένα interrupt όταν το UART είναι έτοιμο να δεχτεί έναν νέο χαρακτήρα προς αποστολή. Φυσικά πρέπει να είναι ενεργοποιημένα τα interrupts.

RXEN (Receiver Enable):
Μόνο όταν ενεργοποιείται (λογικό 1) το bit αυτό ενεργοποιείτε ο δέκτης του UART. Εφόσον δεν έχει ενεργοποιηθεί το bit αυτό το αντίστοιχο pin δρα σαν κανονικό I/O pin του μικροελεγκτή.

TXEN (Transmitter Enable):
Μόνο όταν ενεργοποιείται (λογικό 1) το bit αυτό ενεργοποιείτε ο πομπός του UART. Εφόσον δεν έχει ενεργοποιηθεί το bit αυτό το αντίστοιχο pin δρα σαν κανονικό I/O pin του μικροελεγκτή.

UCSZ2 (Character Size):
Το bit αυτό σε συνδυασμό με το UCSZ1 bit στον UCSRC θέτει των αριθμό των bib δεδομένων (μέγεθος χαρακτήρα) σε ένα πλαίσιο λήψης ή μετάδοσης.

RXB8 (Receive Data Bit 8):
Το bit αυτό είναι το ένατο (9) bit των δεδομένων που λαμβάνονται εάν αυτά έχουν μήκος εννέα (9) bit.

TXB8 (Transmit Data Bit 8):
Το bit αυτό είναι το ένατο (9) bit των δεδομένων προς αποστολή εάν αυτά έχουν μήκος εννέα (9) bit.

UCSRC
USART Control and Status Register C.

URSEL (USART Register SELect):
Με αυτό το bit επιλέγουμε αν θα γράψουμε στον UCSRC ή στον UBRRH. Όταν διαβάζουμε από τον UCSRC τότε το bit αυτό διαβάζεται σαν ένα. Για να γράψουμε στον UCSRC πρέπει να γράψουμε ένα (1) στο bit αυτό.

UΜSEL (USART Μode SELect):
Με αυτό το bit διαλέγουμε σύγχρονη ή ασύγχρονη λειτουργία. Κάνοντας το bit ένα (1) διαλέγουμε σύγχρονη μετάδοση.

UPM1:0 (USART Parity Mode):
Τα bit αυτά καθορίζουν την ισοτιμία.

USBS (USART Stop Bit Select):
Το bit αυτό καθορίζει τον αριθμό των stop bit σε κάθε μεταδιδόμενο πλαίσιο.

UCSZ1:0 (USART Character Size):
Τα bit αυτά σε συνδυασμό με το bit UCSZ2 στον UCSRB καθορίζουν τα bit δεδομένων του πλαισίου.

UCPOL (USART Clock POLarity):
Το bit αυτό χρησιμοποιείται μόνο στον σύγχρονο τρόπο λειτουργίας. Δεν αναλύσουμε περαιτέρω την λειτουργία του. Σε κάθε περίπτωση υπάρχουν λεπτομέρειες στην τεκμηρίωση του κάθε μικροελεγκτή.

UDR (UART Data Register):
Τα δεδομένα μεταδίδονται μεταξύ της CPU και του UART. Από την στιγμή που το UART είναι full-duplex πρόκειται για δύο φυσικούς καταχωρητές που μοιράζονται την ίδια διεύθυνση. Ανάλογα με το αν γίνεται εγγραφή ή ανάγνωση το UART επιλέγει αυτόματα την σωστή διεύθυνση για τον UDR.

UBRR (UART Baud Rate Register ):
Σε αυτόν τον καταχωρητή ορίζουμε το πόσο γρήγορα θέλουμε να επικοινωνεί το UART. Η τιμή που πρέπει να πάρει ο UBRR καθορίζεται από τον παρακάτω τύπο.

Ο ρυθμός baud μπορεί να είναι πάνω από 115200 αναλόγως με τον μικροελεγκτή και την συχνότητα λειτουργίας. Δείτε σχετικά στην τεκμηρίωση του κάθε μικροελεγκτή.

Αρχικοποίηση του UART.
Θέλουμε τώρα να στείλουμε δεδομένα με το UART σε κάποια σειριακή θύρα. Το πρώτο πράγμα που πρέπει να κάνουμε είναι να αρχικοποιήσουμε το UART. Ανάλογα με την λειτουργία που θέλουμε ρυθμίζουμε κατάλληλα τους UART Control Registers.

Αρχικά θέλουμε μόνο να στείλουμε για δοκιμή χωρίς interrupts, η αρχικοποίηση είναι αρκετά εύκολη, αφού το μόνο που έχουμε να κάνουμε είναι να θέσουμε το Transmitter Enable bit (ΤΧΕΝ) στον UCSRB και να διαλέξουμε μέγεθος πακέτου, ισοτιμία και stop bits στον UCSRC.

UCSRB |= (1<<TXEN);  //Ενεργοποίηση του UART TX
UCSRC = (1<<URSEL)|(1 << UCSZ1)|(1 << UCSZ0); //Ασύγχρονη 8N1

Η αρχικοποίηση του UART τελείωσε. Το μόνο που μένει είναι να κάνουμε είναι να ρυθμίσουμε τον ρυθμό Baud. Η ρύθμιση γίνεται μέσω των 8-bit καταχωρητών UBRRL και UBRRH που αποτελούν τον καταχωρητή 16-bit UBRR. Ο υπολογισμός της τιμής του UBRR γίνεται κατά την διάρκεια της μεταγλώττισης και έτσι δεν καταναλώνει μνήμη ή επεξεργαστική ισχύ. Ωστόσο η τιμή λαμβάνεται σαν ακέραιος, τυχόν δεκαδικά ψηφία απλά “κόβονται”. Δεν γίνεται καμία στρογγυλοποίηση. Έτσι η εξίσωση για τον ρυθμό Baud που είδαμε παραπάνω δεν δουλεύει πάντοτε σωστά για όλες τις συχνότητες. Για τον λόγο αυτό χρησιμοποιούμε ένα μικρό κόλπο. Είναι καλύτερα κατά την διαίρεση ακεραίων να γίνεται στρογγυλοποίηση στην μεγαλύτερη ακέραια τιμή και όχι στην μικρότερη. Για να το πετύχουμε αυτό προσθέτουμε την μισή τιμή του παρανομαστή στον αριθμητή (π.χ. το (a/(b*16))-1 γίνεται ((a + b*8)/(b*16))-1 ). Η παραπάνω συνάρτηση θα γίνει:

Στο πρόγραμμά μας η τιμή UBBR_VAL μας βοηθά στην στρογγυλοποίηση.

/*
   Αρχικοποίηση του UART:
   Υπολογισμός της τιμής του UBRR σε σχέση με
   την συχνότητα και τον επιθυμητό ρυθμό Baud
*/
#ifndef F_CPU
/*
   Σε νεότερες εκδόσεις του avr-gcc η τιμή της συχνότητας F_CPU
   μπορεί να καθοριστεί σε κάποιο Makefile, κατευθείαν στις
   παραμέτρους του μεταγλωττιστή. Για τον λόγο αυτό κάνουμε τον
   έλεγχο με ένα “#ifndef .... #endif”. Αν ορίσουμε εδώ την F_CPU
   αλλά και στις παραμέτρους του μεταγλωττιστή ο μεταγλωττιστής
   μας ενημερώνει με ένα “Warning”.
   Το “κόλπο” αυτό μπορεί να μας διευκολύνει σε πολλές περιπτώσεις.
   Όταν το πρόγραμμα μας ανιχνεύσει πως η F_CPU δεν έχει οριστεί
   στις παραμέτρους του μεταγλωττιστή την ορίζει και μας ενημερώνει
   με ένα “Warning”. */

#warning "H F_CPU den exei oristei 8a parei thn timh 4000000"
#define F_CPU 4000000UL  //Συχνότητα σε Hz – Χρησιμοποιήστε τύπο unsigned long

#endif

#define BAUD 9600UL //Ρυθμός Baud

//Υπολογισμοί
#define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1) //”Έξυπνη” στρογγυλοποίηση
#define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1)))   //Πραγματικός Ρυθμός Baud
#define BAUD_ERROR ((BAUD_REAL*1000)/BAUD) //Σφάλμα ανά 1000 μέρη.

#if ((BAUD_ERROR1010))
  #error To sfalma ston ry8mo Baud einai megalytero toy 1%, mataiosh!
#endif

Σημείωση. Όλες οι παραπάνω ρυθμίσεις αφορούν τον μικροελεγκτή ATmega8. Για άλλους μικροελεγκτές οι ρυθμίσεις ενδέχεται να διαφέρουν. Σε κάθε περίπτωση συμβουλευτείτε την τεκμηρίωση του μικροελεγκτή.

Η avr-libc διαθέτει μακροεντολές για τον υπολογισμό της τιμής του UBBR από την F_CPU και τον επιθυμητό ρυθμό Baud. Οι μακροεντολές βρίσκονται στο αρχείο κεφαλίδας util/setbaud.h *. Το αρχείο αυτό πρέπει να τοποθετείτε στο πρόγραμμα μας μετά την δήλωση της F_CPU και του ρυθμού Baud. Η τιμή για τον UBRR δημιουργείτε με τις UBRRH_VALUE και UBRRL_VALUE. Επίσης γίνεται και έλεγχος αν έχουμε λειτουργία U2X μέσω τις μακροεντολής USE_2X. Το παρακάτω παράδειγμα κάνει χρήση του setbaud.h.

#include <avr/io.h>

#define F_CPU 1000000 //Πιθανόν ορισμένο απο παράμετρο του μεταγλωττιστή
#define BAUD 9600
#include <util/setbaud.h>

void uart_init(void)
{
   UBRRH = UBRRH_VALUE;
   UBRRL = UBRRL_VALUE;

#if USE_2X
   /* Απαιτείτε λειτουργία σε U2X */
   UCSRA |= (1 << U2X);
#else
   /* Η λειτουργία U2X δεν είναι απαραίτητη */
   UCSRA &= ~(1 << U2X);
#endif

  UCSRB |= (1<<TXEN);   //Ενεργοποίηση του UART TX

  UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);  //Ασύγχρονη 8N1

* Avr-libc setbaud.h.

** Υπολογισμός Baud: http://www.wormfood.net/avrbaudcalc.php

Αποστολή με το UART.

Αποστολή ενός απλού χαρακτήρα.
Για να στείλουμε έναν χαρακτήρα με το UART πρέπει απλά να γράψουμε τον χαρακτήρα αυτόν στον UDR (Uart Data Register). Πριν από αυτό πρέπει να σιγουρευτούμε πως το UARt είναι σε θέσει να στείλει τον χαρακτήρα. Αυτό γίνεται με την εξέταση του bit UDRE στον UCSRA.

//Για τον ATmega8. Για άλλους μικροελεγκτές δείτε στην τεκμηρίωση.
    while ((UCSRA & (1<<UDRE))== 0)  //Αναμονή μέχρι να είναι δυνατή η αποστολή
    {
    };

    UDR = 'x';   /* Εγγραφή του χαρακτήρα */

Αποστολή αλφαριθμητικού (String).
Η διαδικασία αποστολής αλφαριθμητικών υλοποιείτε με δύο συναρτήσεις. Την uart_puts που δίνει το αλφαριθμητικό ανά ένα χαρακτήρα στην uart_putc που είναι υπεύθυνη για την αποστολή του χαρακτήρα που λαμβάνει. Η uart_puts είναι ανεξάρτητη του μικροελεγκτή που χρησιμοποιούμε. H uart_putc εξαρτάται από τον μικροελεγκτή αφού χρησιμοποιεί καταχωρητές του UART.

/* ATmega8 */
int uart_putc(unsigned char c)
{
    while ((UCSRA & (1<<UDRE))== 0)  //Αναμονή μέχρι να είναι δυνατή η αποστολή
    {
    }

    UDR = c;                      /* Αποστολή του χαρακτήρα */
    return 0;
}

/* H uart_puts είναι ανεξάρτητη του μικροελεγκτή */
void uart_puts (char *s)
{
    while (*s) {        /* Όσο το *s!='\0' δεν είναι το τέλος του αλφαριθμητικού ('\0')" */
        uart_putc(*s);
        s++;
    };
}

Αποστολή περιεχομένου μεταβλητών.
Πριν την αποστολή του περιεχομένου μιας μεταβλητής πρέπει πρώτα να μετατραπεί σε έναν χαρακτήρα ASCII, που είναι αναγνωρίσιμος από τον άνθρωπο. Για ένα μόνο ψηφίο η μετατροπή είναι πολύ εύκολη. Απλά προσθέτουμε την ASCII τιμή του “0” στην μεταβλητή.

#include <avr/io.h>
#include <stdlib.h>

// uart_putc, uart_init κτλ
// Δείτε παραπάνω

int main (void)
{
   // Αποστολή των 0123456789
   char c;
   int i;
   uart_init();

   for (i=0; i<=9; ++i) {
      c = i + '0';
      uart_putc( c );
   }

   while (1) {
   }

   return 0;

}

Αν απαιτείτε η αποστολή περισσότερων του ενός χαρακτήρα τότε γίνεται χρήση συναρτήσεων που μετατρέπουν αριθμούς σε αλφαριθμητικά (String). Η avr-libc διαθέτει για την μετατροπή 16 bit (int16_t) ακεραίων σε αλφαριθμητικά την συνάρτηση itoa (intiger to ascii) *. Η μεταβλητή που θα δεχτεί την έξοδο της itoa πρέπει να έχει τον απαραίτητο χώρο ώστε να χωρέσει το σύμβολο τέλους του αλφαριθμητικού (”) καθώς και το πρόσημο του αριθμού.

#include <avr/io.h>
#include

//...

// uart_putc, uart_init κτλ
// Δείτε παραπάνω

int main(void)
{
   char s[7];
   int16_t i = -12345;

   uart_init();

   itoa( i, s, 10 ); // 10 για δεκαδικό σύστημα
   uart_puts( s );

   // Ίδιο με παραπώνω. Η itoa επιστέφει την διεύθυνση του αλφαριθμητικού.
   uart_puts( itoa( i, s, 10 ) );

   while (1) {

   }

   return 0;
}

Για μη προσιμασμένους ακεραίους των 16 bit υπάρχει η utoa (unsigned int to ascii) *. Οι συναρτήσεις για 32 bit (int32_t, uint32_t) είναι η ltoa και η ultoa *.
Η μετατροπή μεταβλητών κινητής υποδιαστολή, float σε αλφαριθμητικό υπάρχουν οι συναρτήσεις dtostre και dtostrf *. Η dtostre χρησιμοποιεί επιστημονική μορφοποίηση.

#include <avr/io.h>
#include

// uart_putc, uart_init κτλ
// Δείτε παραπάνω

/* Από την τεκμηρίωση της avr-libc:
char* dtostrf(
  double __val,
  char   __width,
  char   __prec,
  char * __s
)
*/

int main(void)
{

   char s[8];
   float f = -12.345;

   uart_init();

   dtostrf( f, 6, 3, s );
   uart_puts( s );
   // Ποιο σύντομα: uart_puts( dtostrf( f, 7, 3, s ) );

   while (1) {

   }
   return 0;
}

* http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html

Λήψη με το UART.

Λήψη ενός απλού χαρακτήρα.

Για να γίνει η λήψη ενός χαρακτήρα πρέπει να ενεργοποιηθεί το τμήμα του δέκτη του UART μέσου του RXEN bit στον καταχωρητή UCSRB. Στην ποιο απλή περίπτωση περιμένει απλά ένα σήμα και μετά κοιτά την διαθεσιμότητα του καταχωρητή UDR που κρατά τον χαρακτήρα.

#include <inttypes.h>
#include <avr/io.h>

/* Αρχικοποίηση ρυθμού Baud */
/* .... */
/* Ισχύ ότι και στην αποστολή */

void uart_init(void)
{
    UBRRH = UBRR_VAL >> 8;
    UBRRL = UBRR_VAL & 0xFF;

    UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);  // Ασύγχρονη 8N1
    UCSRB |= (1<<RXEN);                        // Ενεργοποίηση του RX
}

/* Λήψη χαρακτήρα */
uint8_t uart_getc(void)
{
    while ((UCSRA & (1<<RXC)) == 0){}  // Αναμονή μέχρι την λήψη
                                       // του χαρακτήρα

    return UDR;                        // Επιστροφή του χαρακτήρα
                                       // από τον UDR
}

Λήψη αλφαριθμητικών (string).
Το πρώτο πράγμα που πρέπει να κάνουμε για την λήψη αλφαριθμητικών είναι το κριτήριο με το οποίο ο μικροελεγκτής θα αντιλαμβάνεται το τέλος ενός αλφαριθμητικού. Πολύ συχνά χρησιμοποιείτε το “Return” (πλήκτρο Enter) για τον καθορισμό του τέλους του αλφαριθμητικού. Ο χρήστης έχει εισάγει κάτι και πατά το Enter, όπως στην γραμμή εντολών.
Δεν υπάρχει κάποιος περιορισμός ως προς τον χαρακτήρα που θα χρησιμοποιηθεί. Πρέπει όμως να είναι σίγουρο πως ο συγκεκριμένος χαρακτήρας τερματισμού του αλφαριθμητικού δεν θα μπερδευτεί με κάποιον χαρακτήρα που υπάρχει στο κείμενο του αλφαριθμητικού. Αν για παράδειγμα το κείμενο δεν περιέχει το σύμβολο “ ‘ ” μπορούμε να χρησιμοποιούμε αυτό σαν τέλος του αλφαριθμητικού.
Πολύ συχνά γίνεται η υπόθεση πως ένα αλφαριθμητικό είναι σαν την γραμμή ενός κειμένου και τερματίζεται με τον χαρακτήρα νέας γραμμής “\n”.
Η διαδικασία λήψης είναι: Λήψη και συλλογή των χαρακτήρων σε έναν πίνακα (buffer) μέχρι την εμφάνιση του χαρακτήρα τερματισμού ή το γέμισμα του πίνακα. Στην συνέχεια τοποθετείται στο τέλος ο χαρακτήρας “” που αποτελεί το τέλος αλφαριθμητικού στην C.

/* Λήψη χαρακτήρα */
uint8_t uart_getc(void)
{
    // Αναμονή για εμφάνιση χαρακτήρα
    while (!(UCSRA & (1<<RXC))){}

    return UDR; // Επιστροφή του χαρακτήρα από τον UDR
}

void uart_gets( char* Buffer, uint8_t MaxLen )
{
  uint8_t NextChar;
  uint8_t StringLen = 0;

  // Αναμονή για λήψη του επόμενου χαρακτήρα
  NextChar = uart_getc();

  // Λήψη μέχρι:
  // * την εμφάνιση του χαρακτήρα τερματισμού
  // * το γέμισμα του πίνακα
  while( NextChar != '\n' && StringLen < MaxLen - 1 ) {
    *Buffer++ = NextChar;
    StringLen++;
    NextChar = uart_getc();
  }

  *Buffer = '\0';   // Εισαγωγή του συμβόλου τερματισμού '\0' στην C
}

Πρέπει να σιγουρέψετε πως ο πίνακας θα έχει αρκετό χώρο.

char Line[40];  // Αλφαριθμητικό μεγέθους 39 χαρακτήρων

uart_gets( Line, sizeof( Line ) );

Με την χρήση της sizeof δεν επιστρέφουμε τον αριθμό χαρακτήρων αλλά το μέγεθος σε byte. Στην συγκεκριμένη περίπτωση ένας χαρακτήρας είναι ίσος με ένα byte. Αν για κάποιο λόγο θέλουμε κάποιον άλλον τύπο δεδομένων πρέπει να “διορθώσουμε” το μέγεθος του πίνακα με τον παρακάτω τρόπο.

int Line[40];  // Πίνακας με τύπο ακέραιο

uart_gets (Line,sizeof(Line)/sizeof(Line[0]));

UART με Interrupts.

Λήψη (RX).
Για να προκληθεί ένα interrupt στον atmega8 κατά την λήψη ενός χαρακτήρα πρέπει να ενεργοποιηθεί το bit RXCIE στον καταχωρητή UCSRB.

// Εδώ, μακροεντολές για τον ρυθμό Baud κτλ

void uart_init(void)
{
  UBRRH = UBRR_VAL >> 8;
  UBRRL = UBRR_VAL & 0xFF;
  UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); // Ασύγχρονη 8N1
  UCSRB |= (1<<RXEN)|(1<<TXEN)|(1<<RXCIE);  // Ενεργοποίηση RX,TX
                                            // και interrupt
}

Φυσικά πρέπει να ενεργοποιηθούν τα interrupts (Global Interrupt Enable) με την εντολή sei(). Τα σχετικά με τα interrupt περιλαμβάνονται στο αρχείο κεφαλίδας “avr/interrupt.h”.

Το interrupt ενεργοποιείτε με κάθε επιτυχή λήψη ενός χαρακτήρα. Επιπρόσθετα χρειάζεται και η ρουτίνα ISR().

#define UART_MAXSTRLEN 20

volatile uint8_t uart_str_complete = 0; // 1: Επιτυχής λήψη
                                        // αλφαριθμητικού
volatile uint8_t uart_str_count = 0;
volatile char uart_string[UART_MAXSTRLEN + 1] = "";
ISR(USART_RXC_vect)
{
  unsigned char nextChar;

  // Ανάγνωση χαρακτηρων από τον buffer.
  nextChar = UDR;

  //απόρριψη αν ο uart_string είναι σε χρήση
  if( uart_str_complete == 0 ) {

    // Τα δεδομένα τοποθετούνται στον uart_string.
    // Αν δεν είναι χαρακτήρας
    // τερματισμού του αλφαριθμητικού ή τέλος του
    // πίνακα σημαίνει πως αυτός
    // χρησιμοποιείται.
    if( nextChar != '\n' &&
        nextChar != '\r' &&
        uart_str_count < UART_MAXSTRLEN - 1 ) {
      uart_string[uart_str_count] = nextChar;
      uart_str_count++;
    }
    else {
      uart_string[uart_str_count] = '\0';
      uart_str_count = 0;
      uart_str_complete = 1;
    }
  }
}

Πως λειτουργεί: Ολοκληρώθηκε η λήψη ενός αλφαριθμητικού οπότε έγινε λήψη του χαρακτήρα τερματισμού “\n ή \r” ή φτάσαμε το μέγιστο UART_MAXSTRLEN. Η καθολική μεταβλητή uart_str_complete γίνεται 1. Αφού επεξεργαστούμε και κάνουμε ότι θέλουμε με το αλφαριθμητικό πρέπει να βάλουμε την καθολική μεταβλητή uart_str_complete σε λογικό 0. Έτσι ένα νέο αλφαριθμητικό μπορεί να περάσει στον buffer.

Ένα λειτουργικό παράδειγμα χρήσης UART:
Στον παρακάτω κώδικα στέλνουμε μέσω του υπολογιστή έναν χαρακτήρα ASCII. Ο μικροελεγκτής τον λαμβάνει, στέλνει πρώτα έναν χαρακτήρα αλλαγής σειράς (\n) και μετά στέλνει τον επόμενο (c+1) χαρακτήρα ASCII από αυτό που έλαβε.

#include <avr/io.h>

/*Η παρακάτω δήλωση ελέγχει αν στον compiler έχουμε βάλει την παράμετρο F_CPU.
  Αν όχι, την περνά. */

#ifndef F_CPU
    #warning "H F_CPU den exei oristei 8a parei thn timh 8000000"
    #define F_CPU 1000000UL  // Use unsigned long (UL)
#endif

#define BAUD 4800UL //Ρυθμός Baud

// Υπολογισμοί.
#define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1) // ”Έξυπνη” στρογγυλοποίηση
#define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1)))   // Πραγματικός Ρυθμός Baud
#define BAUD_ERROR ((BAUD_REAL*1000)/BAUD)    // Σφάλμα ανά 1000 μέρη.

#if ((BAUD_ERROR<990) || (BAUD_ERROR>1010))
  #error To sfalma ston ry8mo Baud einai megalytero toy 1%, mataiosh!
#endif

void uart_init(void);
uint8_t uart_getc(void);
uint8_t uart_putc(unsigned char);

int main(void){

    uint8_t c;

    uart_init();

    while(1){

        // Λήψη ενός χαρακτήρα.
        c = uart_getc();

        // Αποστολή του χαρακτήρα νέας σειράς.
        uart_putc('\n');

        // Αποστολή του χαρακτήρα c+1.
        uart_putc(c+1);

    };

    return 0;
}

/* Λήψη χαρακτήρα */
uint8_t uart_getc(void){

    // Αναμονή μέχρι την λήψη του χαρακτήρα
    while ((UCSRA & (1<<RXC)) == 0){}

    return UDR; // Επιστροφή του χαρακτήρα από τον UDR
}

uint8_t uart_putc(unsigned char c){

    //Αναμονή μέχρι να είναι δυνατή η αποστολή
    while ((UCSRA & (1<<UDRE))== 0) {};

    UDR = c;  /* Αποστολή του χαρακτήρα */
    return 0;
}

void uart_init(void){

  UBRRH = UBRR_VAL >> 8;
  UBRRL = UBRR_VAL & 0xFF;

  UCSRB |= (1<<RXEN)|(1<<TXEN);  //Ενεργοποίηση του UART TX,RX
  UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);  //Ασύγχρονη 8N1
}

Έτοιμες βιβλιοθήκες.
Για την χρήση σειριακής επικοινωνίας υπάρχουν πολλές έτοιμε αξιόλογες βιβλιοθήκες. Μία πολύ καλή και αρκετά δοκιμασμένη βιβλιοθήκη με την χρήση interrupts είναι η παρακάτω.
http://beaststwo.org/avr-uart/index.shtml
http://beaststwo.org/avr-uart/Updated%20_UART_Library.zip
Αποτελείται από τα αρχεία uart.c, uart.h.

Ένα παράδειγμα με την εν λόγο βιβλιοθήκη.

#include <avr/io.h>
#include <avr/interrupt.h>
#include "uart.h"

/*Η παρακάτω δήλωση ελέγχει αν στον compiler έχουμε βάλει την παράμετρο F_CPU.
  Αν όχι, την περνά. */

#ifndef F_CPU
    #warning "H F_CPU den exei oristei 8a parei thn timh 8000000"
    #define F_CPU 1000000UL  // Use unsigned long (UL)
#endif

#define BAUD 4800UL //Ρυθμός Baud

// Υπολογισμοί.
#define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1) // ”Έξυπνη” στρογγυλοποίηση
#define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1)))   // Πραγματικός Ρυθμός Baud
#define BAUD_ERROR ((BAUD_REAL*1000)/BAUD)    // Σφάλμα ανά 1000 μέρη.

#if ((BAUD_ERROR1010))
  #error To sfalma ston ry8mo Baud einai megalytero toy 1%, mataiosh!
#endif

int main(void){

    // Αρχικοποίηση του UART.
    uart_init(UART_BAUD_SELECT(BAUD,F_CPU));

    sei(); // Ενεργοποίηση των interrupt;

    uart_puts("\n\n");

    uart_puts("###########################\n");
    uart_puts("#                         #\n");
    uart_puts("#   This is a UART test   #\n");
    uart_puts("#                         #\n");
    uart_puts("###########################\n");

    while(1){

    };

    return 0;
}

Αν συνδιάσουμε την παραπάνω βιβλιοθήκη με τις συναρτήσεις του αρχείο κεφαλίδας stdio.h του AVR-GCC έχουμε ένα πολύ αποτελεσματικό τρόπο για την μεταφορά δεδομένων, σε έναν υπολογιστή για παράδειγμα, που βοηθά πολύ στην αποσφαλμάτωση δεδομένων.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include "uart.h"

/*Η παρακάτω δήλωση ελέγχει αν στον compiler έχουμε βάλει την παράμετρο F_CPU.
  Αν όχι, την περνά. */

#ifndef F_CPU
    #warning "H F_CPU den exei oristei 8a parei thn timh 8000000"
    #define F_CPU 1000000UL  // Use unsigned long (UL)
#endif

#define BAUD 4800UL //Ρυθμός Baud

// Υπολογισμοί.
#define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1) // ”Έξυπνη” στρογγυλοποίηση
#define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1)))   // Πραγματικός Ρυθμός Baud
#define BAUD_ERROR ((BAUD_REAL*1000)/BAUD)    // Σφάλμα ανά 1000 μέρη.

#if ((BAUD_ERROR<990) || (BAUD_ERROR>1010))
  #error To sfalma ston ry8mo Baud einai megalytero toy 1%, mataiosh!
#endif

//adjust UARTsendChar() function for stream
static int UARTsendstream(char c, FILE *stream);
//----set output stream to UART----
static FILE uart_str = FDEV_SETUP_STREAM(UARTsendstream, NULL, _FDEV_SETUP_WRITE);

static int UARTsendstream(char c , FILE *stream){
    uart_putc(c);
    return 0;
}

int main(void){

    // Δοκιμαστική μεταβλητή.
    uint8_t test_var = 11;

    // Αρχικοποίηση του UART.
    uart_init(UART_BAUD_SELECT(BAUD,F_CPU));

    sei(); // Ενεργοποίηση των interrupt;

    stdout = &uart_str;                           // Stream initialization.
    uart_init( UART_BAUD_SELECT(BAUD,F_CPU) );    // UART initialization.

    printf("\n\n");

    printf("###########################\n");
    printf("#                         #\n");
    printf("#   This is a UART test   #\n");
    printf("#                         #\n");
    printf("###########################\n");

    printf("\nH metavlhth test_var exei timh: %d\n",test_var);

    test_var++;

    printf("\nH metavlhth test_var exei timh: %d\n",test_var);

    while(1){

    };

    return 0;
}

avrlibc stdio.h.

Παρακάτω βλέπουμε την μετάδοση των ASCII χαρακτήρων a και b σε έναν παλμογράφο.

a: 97 Δεκαδικό, 0x61 Δεκαεξαδικό, 0b01100001 Δυαδικό.
b: 98 Δεκαδικό, 0x62 Δεκαεξαδικό, 0b01100010 Δυαδικό.

Advertisements
This entry was posted in AVR, Electronics and tagged , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s