AVR-GCC. Two Wire Interface (TWI). Aka I2C.

Το Two Wire Protocol, ή όπως είναι ευρέος γνωστό I2C, είναι ένα πρωτόκολλο που δημιούργησε η εταιρία Philips για την σύνδεση περιφερειακών συσκευών χαμηλής ταχύτητας. Το πρωτόκολλο αυτό χρησιμοποιεί δύο αμφίδρομες γραμμές επικοινωνίας που είναι “Pulled-Up” με αντιστάσεις. Την Serial Data Line (SDA) και την Serial Clock (SCL) . Στον δίαυλο I2C μπορούν να συνδεθούν μέχρι 128 συσκευές. Θα υπάρχει μία συσκευή που θα ελέγχει τις υπόλοιπες και ονομάζεται Master. οι υπόλοιπες ονομάζονται Slaves.

AVR TWI Master.

AVR TWI Slave.

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

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

Ο Master όταν θέλει να μιλήσει με κάποια περιφερειακή συσκευή ξεκινά μία κατάσταση START στον δίαυλο, “μιλά” με την συσκευή και στην συνέχεια τερματίζει την επικοινωνία με μία κατάσταση STOP. Στο διάστημα μεταξύ START και STOP ο δίαυλος “φαίνεται” κατειλημμένος. Παρακάτω βλέπουμε το block του TWI στους AVR.

Θα ασχοληθούμε με την περίπτωση που ο AVR θα έχει τον ρόλο του Master (που είναι και το ποιο πιθανόν να χρειαστεί κάποιος) στον δίαυλο.

TWBR (TWI Bit Register):

Ο καταχωρητής TWBR ελέγχει την συχνότητα του SCL όπως φαίνεται στον παρακάτω τύπο.

Η συχνότητα λειτουργία των περιφερικών είναι συνήθως 100 kHz. Λύνοντας την παραπάνω έχουμε:

Για να έχουμε μία σταθερή λειτουργία πρέπει η τιμή του TWBR να είναι μεγαλύτερη από 10.

TWCR (TWI Control Register):

TWINT (TWI Interrupt flag):
Το bit αυτό γίνεται ένα από το υλικό όταν ο δίαυλος επικοινωνίας έχει τελειώσει την εργασία του και περιμένει απάντηση από το πρόγραμμα. Αν είναι ενεργοποιημένα τα Interrups (I-bit στον SREG) και το TWIE στον TWCR είναι σε λογικό 1 θα προκληθεί ένα interrupt. To TWINT απενεργοποιείται γράφοντας λογικό 1 σε αυτό μέσω του προγράμματος. Το bit αυτό δεν απενεργοποιείται αυτόματα από το υλικό. Καθαρίζοντας το bit αυτό ξεκινά πάλι την λειτουργία του TWI. Οπότε οποιαδήποτε πράξη με τους καταχωρητές TWSR και TWDR πρέπει να ολοκληρωθεί πριν τον καθαρισμό.

TWEA ( TWI Enable Acknowledge Bit):
Το bit αυτό είναι υπεύθυνο για την δημιουργία του παλμού αναγνώρισης (acknowledge).

TWSTA (TWI START Condition Bit):
Το bit αυτό ενεργοποιείται από το πρόγραμμα για να γίνει η συσκευή Master. Εφόσον ο δίαυλος είναι ελεύθερος θα ξεκινήσει μία κατάσταση START. Αν ο δίαυλος είναι κατειλημμένος, περιμένει μέχρι την ανίχνευση μίας STOP κατάστασης και ξεκινά μία START. Το bit αυτό πρέπει να καθαρίζεται από το πρόγραμμα.

TWSTO (TWI STOP Condition Bit):
Το bit αυτό ενεργοποιείται από το πρόγραμμα για να ξεκινήσει μία κατάσταση STOP. Το bit αυτό καθαρίζεται αυτόματα όταν ολοκληρωθεί η κατάσταση STOP.

TWWC (TWI Write Collision flag):
Το bit αυτό ενεργοποιείται όταν γίνεται προσπάθεια εγγραφής στον TWDR και το TWINT είναι 0. Το bit γίνεται μηδέν όταν γράφοντας στον TWDR με το TWINT να είναι 1.

TWEN (TWI Enable bit):
Όταν το bit αυτό γίνεται ένα ενεργοποιείται το TWI. Τότε το TWI αναλαμβάνει και τον έλεγχο των αντίστοιχων I/O, SDA και SCL. Όταν το bit αυτό γίνεται 0 τότε το TWI απενεργοποιείται ανεξάρτητα αν υπάρχει κάποια λειτουργία σε εξέλιξη.

TWIE (TWI Interrupt Enable):
Όταν το bit αυτό γράφεται σε λογικό 1 ενεργοποιούνται τα interrupt για τον TWI.

TWSR (TWI Status Register):

TWS7..3 (TWI Status):
Τα bit αυτά δείχνουν την κατάσταση που βρίσκεται το TWI. Περισσότερες λεπτομέριες στην τεκμηρίωση του ATmega8 σελίδα 176.

TWPS1..0 (TWI Prescaler):
Τα bit αυτά ελέγχουν τον prescaler για την συχνότητα του TWI.

TWDR (TWI Data Register):


Σε κατάσταση αποστολής ο καταχωρητής αυτός περιέχει το επόμενο byte που θα μεταδοθεί. Σε κατάσταση λήψης περιέχει το ληφθέν byte.

Ας δούμε ένα παράδειγμα επικοινωνίας ενός AVR με 2 chip PCF8574 της Texas Instruments μέσω TWI/I2C.

Μέχρι τώρα ο μικροελεγκτή μας λειτουργούσε σε συχνότητα 1 MHz. Για να μπορέσουμε να εκπληρώσουμε την ταχύτητα των 100 kHz του clock του TWI/I2C θα ανεβάσουμε την συχνότητα λειτουργίας στα 4 MHz. Αυτό μπορούμε να το κάνουμε προγραμματίζοντας τα κατάλληλα FUSE bits του AVR.

AVR FUSE Calculator.

Για τα 4 MHz οι τιμές των FUSE είναι lfuse=0xC3 και hfuse=0xD9. Αυτό μπορούμε να το κάνουμε συνήθως μέσω του IDE που χρησιμοποιούμε. Για παράδειγμα με το avrdude κάνουμε το εξής.

avrdude -P /dev/ttyUSB0 -pm8 -c stk500v2 -Ulfuse:w:0xc3:m -Uhfuse:w:0xd9:m

ACK.
NACK.

Το πρόγραμμα αποτελείτε από τρία αρχεία. Το main.c που περιέχει το κυρίως πρόγραμμα το twi.h που περιέχει τις δηλώσεις για τον έλεγχο του διαύλου και το twi.c που περιέχει το κυρίως πρόγραμμα για τον έλεγχο του διαύλου. Πρέπει να σημειωθεί πως υπάρχουν καλύτερες βιβλιοθήκες από την παρακάτω που προσφέρουν περισσότερες επιλογές. Μία τέτοια βιβλιοθήκη είναι αυτή του Peter Fleury.

Η βιβλιοθήκη του Peter Fleury.
Peter’s I2C lib

Τα αρχεία. twi.zip.

main.c

#include <avr/io.h>
#include <util/delay.h>
#include "twi.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 PCF8574_1 0x40
#define PCF8574_2 0x42

#define PF0 0
#define PF1 1
#define PF2 2
#define PF3 3
#define PF4 4
#define PF5 5
#define PF6 6
#define PF7 7




int main(void){

	uint8_t input = 0;

	uint8_t i=0,PCF=0xFF;


    i2c_init();

    i2c_start(PCF8574_2+WRITE);  // Εγγραφή στο 2ο PC8574
    i2c_write(0x00);             // Είσοδοι Pulled-Down.
    i2c_stop();                  // Κλείσιμο του διαύλου.


    while(1){


        i2c_start(PCF8574_2+READ); // Ανάγνωση από το 2ο PC8574
        input = i2c_read();        // και αποθήκευση στην input.
        i2c_stop();                // Κλείσιμο του διαύλου.


        if (input==1){
            i++;
        };

        if(i>7) {
            i=0;
            PCF=0xFF;
        };



        i2c_start(PCF8574_1+WRITE); // Εγγραφή στο 2ο PC8574
        PCF = ~(1<<i);
        i2c_write(PCF);
        i2c_stop();                 // Κλείσιμο του διαύλου.

        _delay_ms(100);
    };

    return 0;
}

twi.h

#ifndef _TWI_H
#define _TWI_H   1


#include <avr/io.h>


#define READ    1

#define WRITE   0

// Αρχικοποίηση του TWI/I2C. Clock 100 kHz.
extern void i2c_init(void);

// Τερματισμός του TWI/I2C.
extern void i2c_stop(void);

// Εκκίνηση του TWI/I2C.
extern uint8_t i2c_start(unsigned char addr);

// Εγγραφή στον δίαυλο.
extern uint8_t i2c_write(unsigned char data);

// Ανάγνωση από τον δίαυλο.
extern uint8_t i2c_read(void);


#endif

twi.c

#include <avr/io.h>
#include <compat/twi.h>
#include "twi.h"


// Συχνότητα του TWI/I2C clock σε Hz.
#define SCL_CLOCK  100000L


// Αρχικοποίηση του TWI/I2C. Clock 100 kHz.
void i2c_init(void){

  TWSR = 0;                         // Prescaler 0, άρα το 4^TWRS=1.

  TWBR = ((F_CPU/SCL_CLOCK)-16)/2;  // Το θέλουμε μεγαλύτερο από 10.
                                    // Είναι 12 οπότε ΟΚ.
}


// Εκκίνηση του TWI/I2C.
uint8_t i2c_start(uint8_t address){

    uint8_t   twst;

	// Ενεργοποίηση του TWI/I2C.
	TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);

	// Αναμονή μέχρι να τελειώσει η μετάδοση.
	while(!(TWCR & (1<<TWINT)));

	// Έλεγχος TWI Status Register. Μάσκα στα bit του prescaler.
	twst = TW_STATUS & 0b11111000;
	if ((twst != TW_START) && (twst != TW_REP_START)) return 1;

	// Αποστολή της διεύθυνσης της συσκευής.
	TWDR = address;
	TWCR = (1<<TWINT) | (1<<TWEN);

	// Ανανομή μέχρι την ολοκλήρωση της αποστολής και λήψη των ACK/NACK.
	while(!(TWCR & (1<<TWINT)));

	// Έλεγχος TWI Status Register. Μάσκα στα bit του prescaler.
	twst = TW_STATUS & 0xF8;
	if ( (twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK) ) return 1;

	return 0;

}


// Τερματισμός του TWI/I2C.
void i2c_stop(void){

    // Αποστολή κατάστασης STOP.
	TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);

	// Αναμονή μέχρι την εκτέλεση της κατάστασης STOP
	// και την απελευθέρωση του διαύλου.
	while(TWCR & (1<<TWSTO));

}


// Εγγραφή στον δίαυλο.
uint8_t i2c_write(uint8_t data){

    uint8_t   twst;

	// Αποστολή δεδομένων στην διεύθυνση.
	TWDR = data;
	TWCR = (1<<TWINT) | (1<<TWEN);

	// Αναμονή μέχρι την ολοκλήρωση της αποστολής.
	while(!(TWCR & (1<<TWINT)));

	// Έλεγχος TWI Status Register. Μάσκα στα bit του prescaler.
	twst = TW_STATUS & 0xF8;
	if( twst != TW_MT_DATA_ACK) return 1;
	return 0;

}

// Ανάγνωση από τον δίαυλο.
uint8_t i2c_read(void){

	TWCR = (1<<TWINT) | (1<<TWEN);
	while(!(TWCR & (1<<TWINT)));

    return TWDR;

}

Ο δίαυλος TWI/I2C του δεύτεροτ PCF8574 όπως φαίνεται στον παλμογράφο.

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