(Preise Update) |
Uwegw (Diskussion | Beiträge) (überarbeitete Version) |
||
Zeile 1: | Zeile 1: | ||
Programm für einen AVR mit [[TWI]] (Hardware-[[I2C]])-Schnittstelle als Slave. | Programm für einen AVR mit [[TWI]] (Hardware-[[I2C]])-Schnittstelle als Slave. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
Manchmal stellt sich die Aufgabe, mehrere AVRs per [[I2C]] zu vernetzen. Ein Beispiel ist die Erweiterung eines bestehenden Systems um einen leistungsstärkeren Controller. Dies ist etwa beim [[Asuro]] oder [[Yeti]] denkbar, um z.B. mehr Ein/Ausgänge oder Speicherplatz zu bekommen. | Manchmal stellt sich die Aufgabe, mehrere AVRs per [[I2C]] zu vernetzen. Ein Beispiel ist die Erweiterung eines bestehenden Systems um einen leistungsstärkeren Controller. Dies ist etwa beim [[Asuro]] oder [[Yeti]] denkbar, um z.B. mehr Ein/Ausgänge oder Speicherplatz zu bekommen. | ||
− | Man kann aber beim Bau eines Roboters auch von Anfang an auf ein System von mehreren vernetzten Controllern setzen. So kann man verschiedene Aufgabe, die weitgehend unabhängig voneinader gleichzeitg erledigt werden müssen, besser verteilen. Denkbar ist z.B. ein Controller für die Motorsteuerung, einer für die Sensorik, einer für Ein-und Ausgabe(wie LCD und Bedientaster),... , und ein zentraler Controller, der die Richtung vorgibt und alle anderen Controller mit Befehlen versorgt. Ein konkretes Beispiel ist der Roboterbausatz Nibo (siehe: [http://www.nicai-systems.de/nibo.html]) von nicai-systems, der einen Atmel ATmega128 als Hauptcontroller und zwei Atmel ATtiny44 als Controller für die Motorsteuerung und die IR-Sensorik einsetzt. Die Kommunikation läuft mit 400 kHz über den I2C-Bus mit dem ATmega128 als Master. | + | Man kann aber beim Bau eines Roboters auch von Anfang an auf ein System von mehreren vernetzten Controllern setzen. So kann man verschiedene Aufgabe, die weitgehend unabhängig voneinader gleichzeitg erledigt werden müssen, besser verteilen. Denkbar ist z.B. ein Controller für die Motorsteuerung, einer für die Sensorik, einer für Ein-und Ausgabe (wie LCD und Bedientaster),... , und ein zentraler Controller, der die Richtung vorgibt und alle anderen Controller mit Befehlen versorgt. Ein konkretes Beispiel ist der Roboterbausatz Nibo (siehe: [http://www.nicai-systems.de/nibo.html]) von nicai-systems, der einen Atmel ATmega128 als Hauptcontroller und zwei Atmel ATtiny44 als Controller für die Motorsteuerung und die IR-Sensorik einsetzt. Die Kommunikation läuft mit 400 kHz über den I2C-Bus mit dem ATmega128 als Master. |
− | Schließlich kann ein entsprechend programmierter AVR auch als Ersatz für handelsübliche I2C-Bauteile dienen. Der kleinste AVR mit Hardware-I2C, der ATmega48, ist mit 1,30€ (Reichelt) billiger als viele normale I2C-ICs. | + | Schließlich kann ein entsprechend programmierter AVR auch als Ersatz für handelsübliche I2C-Bauteile dienen. Der kleinste AVR mit Hardware-I2C, der ATmega48, ist mit 1,30€ (Reichelt) billiger als viele normale I2C-ICs. Er kann bei entsprechender Programmierung z.B. die Aufgaben von zwei PCF8574 (8bit-Portexpander, 1,25€) und einem PCF8591 (4fach AD-Wandler, 2,35€) übernehmen und außerdem noch als I2C-EEPROM mit 256 Bytes dienen. |
Das folgende Programm (twislave.c) steuert das TWI (Hardware-I2C)-Interface eines AVRs als Slave an. Es müsste auf allen AVRs der Mega-Reihe funktionieren, die über eine TWI-Schnittstelle verfügen. | Das folgende Programm (twislave.c) steuert das TWI (Hardware-I2C)-Interface eines AVRs als Slave an. Es müsste auf allen AVRs der Mega-Reihe funktionieren, die über eine TWI-Schnittstelle verfügen. | ||
Zeile 37: | Zeile 12: | ||
+ | |||
+ | == Master == | ||
Ein Codeschnipsel für den Master. Es wird die [http://homepage.hispeed.ch/peterfleury/avr-software.html#libs I2C-Master-Bibliothek von Peter Fleury] verwendet. | Ein Codeschnipsel für den Master. Es wird die [http://homepage.hispeed.ch/peterfleury/avr-software.html#libs I2C-Master-Bibliothek von Peter Fleury] verwendet. | ||
Es wird geprüft, ob der Slave bereit ist, dann werden die ersten drei Bytes aus dem txbuffer des Slaves gelesen und in byte0..2 abgespeichert. | Es wird geprüft, ob der Slave bereit ist, dann werden die ersten drei Bytes aus dem txbuffer des Slaves gelesen und in byte0..2 abgespeichert. | ||
Zeile 58: | Zeile 35: | ||
</pre> | </pre> | ||
− | Die twislave.c für den Slave. Stand: | + | == Slave sourcecode == |
+ | Die twislave.c für den Slave. Stand: 19.2.2010 | ||
<pre> | <pre> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
+ | #include <util/twi.h> //enthält z.B. die Bezeichnungen für die Statuscodes in TWSR | ||
+ | #include <avr/interrupt.h> //dient zur Behandlung der Interrupts | ||
+ | #include <stdint.h> //definiert den Datentyp uint8_t | ||
+ | #include "twislave.h" | ||
//%%%%%%%% Globale Variablen, die vom Hauptprogramm genutzt werden %%%%%%%% | //%%%%%%%% Globale Variablen, die vom Hauptprogramm genutzt werden %%%%%%%% | ||
+ | /*Der Buffer, in dem die Daten gespeichert werden. | ||
+ | Aus Sicht des Masters läuft der Zugrif auf den Buffer genau wie bei einem I2C-EEPROm ab. | ||
+ | Für den Slave ist es eine globale Variable | ||
+ | */ | ||
+ | volatile uint8_t i2cdata[i2c_buffer_size+1]; | ||
− | |||
− | |||
− | |||
− | |||
− | + | volatile uint8_t buffer_adr; //"Adressregister" für den Buffer | |
− | volatile uint8_t | + | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
/*Initaliserung des TWI-Inteface. Muss zu Beginn aufgerufen werden, sowie bei einem Wechsel der Slave Adresse | /*Initaliserung des TWI-Inteface. Muss zu Beginn aufgerufen werden, sowie bei einem Wechsel der Slave Adresse | ||
− | Parameter | + | Parameter adr: gewünschte Slave-Adresse |
*/ | */ | ||
− | void init_twi_slave (uint8_t adr) | + | void init_twi_slave(uint8_t adr) |
{ | { | ||
TWAR= adr; //Adresse setzen | TWAR= adr; //Adresse setzen | ||
Zeile 169: | Zeile 73: | ||
//ACK nach empfangenen Daten senden/ ACK nach gesendeten Daten erwarten | //ACK nach empfangenen Daten senden/ ACK nach gesendeten Daten erwarten | ||
#define TWCR_ACK TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(0<<TWWC); | #define TWCR_ACK TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(0<<TWWC); | ||
+ | |||
//NACK nach empfangenen Daten senden/ NACK nach gesendeten Daten erwarten | //NACK nach empfangenen Daten senden/ NACK nach gesendeten Daten erwarten | ||
#define TWCR_NACK TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(0<<TWWC); | #define TWCR_NACK TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(0<<TWWC); | ||
+ | |||
//switched to the non adressed slave mode... | //switched to the non adressed slave mode... | ||
#define TWCR_RESET TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(0<<TWWC); | #define TWCR_RESET TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(0<<TWWC); | ||
− | |||
//Die Bitmuster für TWCR_ACK und TWCR_RESET sind gleich. Dies ist kein Fehler und dient nur der Übersicht! | //Die Bitmuster für TWCR_ACK und TWCR_RESET sind gleich. Dies ist kein Fehler und dient nur der Übersicht! | ||
− | |||
/*ISR, die bei einem Ereignis auf dem Bus ausgelöst wird. Im Register TWSR befindet sich dann | /*ISR, die bei einem Ereignis auf dem Bus ausgelöst wird. Im Register TWSR befindet sich dann | ||
Zeile 188: | Zeile 92: | ||
{ | { | ||
− | case TW_SR_SLA_ACK: // 0x60 Slave Receiver, wurde adressiert | + | // Slave Receiver |
− | TWCR_ACK; // nächstes Datenbyte empfangen, ACK danach | + | |
+ | case TW_SR_SLA_ACK: // 0x60 Slave Receiver, Slave wurde adressiert | ||
+ | TWCR_ACK; // nächstes Datenbyte empfangen, ACK danach senden | ||
buffer_adr=0xFF; //Bufferposition ist undefiniert | buffer_adr=0xFF; //Bufferposition ist undefiniert | ||
break; | break; | ||
− | case TW_SR_DATA_ACK: // 0x80 Slave Receiver, | + | case TW_SR_DATA_ACK: // 0x80 Slave Receiver, ein Datenbyte wurde empfangen |
data=TWDR; //Empfangene Daten auslesen | data=TWDR; //Empfangene Daten auslesen | ||
if (buffer_adr == 0xFF) //erster Zugriff, Bufferposition setzen | if (buffer_adr == 0xFF) //erster Zugriff, Bufferposition setzen | ||
{ | { | ||
− | |||
//Kontrolle ob gewünschte Adresse im erlaubten bereich | //Kontrolle ob gewünschte Adresse im erlaubten bereich | ||
− | if(data< | + | if(data<i2c_buffer_size) |
{ | { | ||
buffer_adr= data; //Bufferposition wie adressiert setzen | buffer_adr= data; //Bufferposition wie adressiert setzen | ||
Zeile 205: | Zeile 110: | ||
else | else | ||
{ | { | ||
− | + | buffer_adr=0; //Adresse auf Null setzen. Ist das sinnvoll? TO DO! | |
} | } | ||
TWCR_ACK; // nächstes Datenbyte empfangen, ACK danach, um nächstes Byte anzufordern | TWCR_ACK; // nächstes Datenbyte empfangen, ACK danach, um nächstes Byte anzufordern | ||
} | } | ||
− | else //weiterer Zugriff, Daten empfangen | + | else //weiterer Zugriff, nachdem die Position im Buffer gesetzt wurde. NUn die Daten empfangen und speichern |
{ | { | ||
− | + | ||
− | + | if(buffer_adr<i2c_buffer_size) | |
− | if(buffer_adr< | + | |
{ | { | ||
− | + | if(!I2C_reg_Schreibschutz[buffer_adr]) //Wenn Position nicht schreibgeschützt... | |
− | + | i2cdata[buffer_adr]=data; //...dann Daten in Buffer schreibe | |
− | + | ||
− | + | ||
− | + | ||
} | } | ||
+ | buffer_adr++; //Buffer-Adresse weiterzählen für nächsten Schreibzugriff | ||
+ | TWCR_ACK; | ||
} | } | ||
break; | break; | ||
− | case TW_ST_SLA_ACK: // | + | |
− | case TW_ST_DATA_ACK: //0xB8 Slave Transmitter, | + | //Slave transmitter |
+ | |||
+ | case TW_ST_SLA_ACK: //0xA8 Slave wurde im Lesemodus adressiert und hat ein ACK zurückgegeben. | ||
+ | //Hier steht kein break! Es wird also der folgende Code ebenfalls ausgeführt! | ||
+ | |||
+ | case TW_ST_DATA_ACK: //0xB8 Slave Transmitter, Daten wurden angefordert | ||
if (buffer_adr == 0xFF) //zuvor keine Leseadresse angegeben! | if (buffer_adr == 0xFF) //zuvor keine Leseadresse angegeben! | ||
Zeile 231: | Zeile 141: | ||
buffer_adr=0; | buffer_adr=0; | ||
} | } | ||
− | + | ||
− | + | if(buffer_adr<i2c_buffer_size) | |
− | if(buffer_adr< | + | |
{ | { | ||
− | + | TWDR = i2cdata[buffer_adr]; //Datenbyte senden | |
+ | buffer_adr++; //bufferadresse für nächstes Byte weiterzählen | ||
} | } | ||
else | else | ||
{ | { | ||
− | + | TWDR=0; //Kein Daten mehr im Buffer | |
} | } | ||
+ | TWCR_ACK; | ||
break; | break; | ||
− | case TW_ST_DATA_NACK: //0xC0 Keine Daten mehr gefordert | + | case TW_ST_DATA_NACK: // 0xC0 Keine Daten mehr gefordert |
− | case TW_SR_DATA_NACK: //0x88 | + | case TW_SR_DATA_NACK: // 0x88 |
− | case TW_ST_LAST_DATA: //0xC8 Last data byte in TWDR has been transmitted (TWEA = “0”); ACK has been received | + | case TW_ST_LAST_DATA: // 0xC8 Last data byte in TWDR has been transmitted (TWEA = “0”); ACK has been received |
− | case TW_SR_STOP: // 0xA0 STOP empfangen | + | case TW_SR_STOP: // 0xA0 STOP empfangen |
default: | default: | ||
− | TWCR_RESET; | + | TWCR_RESET; |
break; | break; | ||
− | |||
} //end.switch (TW_STATUS) | } //end.switch (TW_STATUS) | ||
} //end.ISR(TWI_vect) | } //end.ISR(TWI_vect) | ||
− | |||
− | |||
////Ende von twislave.c//// | ////Ende von twislave.c//// | ||
− | |||
</pre> | </pre> | ||
− | + | == Slave header == | |
− | + | Die zugehörige Headerdatei twislave.h für den Slave. Stand: 19.2.2010 | |
<pre> | <pre> | ||
− | |||
− | |||
− | |||
− | |||
− | + | #ifndef _TWISLAVE_H | |
+ | #define _TWISLAVE_H | ||
− | + | #include <util/twi.h> //enthaelt z.B. die Bezeichnungen fuer die Statuscodes in TWSR | |
− | + | #include <avr/interrupt.h> //dient zur behandlung der Interrupts | |
− | + | #include <stdint.h> //definiert den Datentyp uint8_t | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | /** | |
− | + | * @defgroup twislave TWI-Slave | |
+ | * @code #include "twislave.h" @endcode | ||
+ | * | ||
+ | * @brief Betrieb eines AVRs mit Hardware-TWI-Schnittstelle als Slave. | ||
+ | * Zu Beginn muss init_twi_slave mit der gewuenschten Slave-Adresse als | ||
+ | * Parameter aufgerufen werden. | ||
+ | * | ||
+ | * Der Datenaustausch mit dem Master erfolgt ueber den Buffer i2cdata, | ||
+ | * auf den von Master und Slave zugegriffen werden kann. | ||
+ | * Dies ist fuer den Slave eine globale Variable (Array aus uint8_t). | ||
+ | |||
+ | * Der Zugriff durch den Master erfolgt aehnlich wie bei einem | ||
+ | * normalen I2C-EEPROM. | ||
+ | * Man sendet zunaechst die Bufferposition, an die man schreiben will, | ||
+ | * und dann die Daten. | ||
+ | * Die Bufferposition wird automatisch hochgezaehlt, sodass man mehrere | ||
+ | * Datenbytes hintereinander schreiben kann, ohne jedesmal die | ||
+ | * Bufferadresse zu schreiben. | ||
+ | * | ||
+ | * Um vom Master aus zu lesen, uebertraegt man zunaechst in einem | ||
+ | * Schreibzugriff die gewuenschte Bufferposition und liest dann nach | ||
+ | * einem repeated start die Daten aus. Die Bufferposition wird | ||
+ | * automatisch hochgezaehlt, sodass man mehrere Datenbytes | ||
+ | * hintereinander lesen kann, ohne jedesmal die Bufferposition zu | ||
+ | * schreiben. | ||
+ | * | ||
+ | * Abgefangene Fehlbedienung durch den Master: | ||
+ | * - Lesen ueber die Grenze des txbuffers hinaus | ||
+ | * - Schreiben ueber die Grenzen des rxbuffers hinaus | ||
+ | * - Angabe einer ungueltigen Schreib/Lese-Adresse | ||
+ | * - Lesezugriff, ohne vorher Leseadresse geschrieben zu haben | ||
+ | * | ||
+ | * @author uwegw | ||
+ | */ | ||
+ | |||
+ | /*@{*/ | ||
+ | |||
+ | //%%%%%%%% von Benutzer konfigurierbare Einstellungen %%%%%%%% | ||
+ | /**@brief Groesse des Buffers in Byte (2..254) */ | ||
+ | #define i2c_buffer_size I2C_REG_ANZAHL | ||
+ | |||
+ | |||
+ | //%%%%%%%% Globale Variablen, die vom Hauptprogramm genutzt werden %%%%%%%% | ||
+ | /**@brief Der Buffer, in dem die Daten gespeichert werden. | ||
+ | * Aus Sicht des Masters laeuft der Zugrif auf den Buffer genau wie | ||
+ | * bei einem I2C-EEPROm ab. | ||
+ | * Fuer den Slave ist es eine globale Variable | ||
*/ | */ | ||
+ | volatile uint8_t i2cdata[i2c_buffer_size+1]; | ||
− | # | + | /**@brief Initaliserung des TWI-Inteface. Muss zu Beginn aufgerufen werden, sowie bei einem Wechsel der Slave Adresse |
+ | * @param adr gewuenschte Slave-Adresse */ | ||
+ | void init_twi_slave(uint8_t adr); | ||
+ | |||
+ | //%%%%%%%% ab hier sind normalerweise keine weiteren Aenderungen erforderlich! %%%%%%%%// | ||
+ | //____________________________________________________________________________________// | ||
+ | |||
+ | |||
+ | //Bei zu alten AVR-GCC-Versionen werden die Interrupts anders genutzt, daher in diesem Fall mit Fehlermeldung abbrechen | ||
+ | #if (__GNUC__ * 100 + __GNUC_MINOR__) < 304 | ||
+ | #error "This library requires AVR-GCC 3.4.5 or later, update to newer AVR-GCC compiler !" | ||
+ | #endif | ||
+ | |||
+ | //Schutz vor unsinnigen Buffergroessen | ||
+ | #if (i2c_buffer_size > 254) | ||
+ | #error Buffer zu gross gewaehlt! Maximal 254 Bytes erlaubt. | ||
+ | #endif | ||
+ | |||
+ | #if (i2c_buffer_size < 2) | ||
+ | #error Buffer muss mindestens zwei Byte gross sein! | ||
+ | #endif | ||
+ | |||
+ | /*@}*/ | ||
+ | |||
+ | #endif //#ifdef _TWISLAVE_H | ||
+ | ////Ende von twislave.h//// | ||
+ | |||
+ | </pre> | ||
+ | |||
+ | |||
+ | == Slave Testprogramm == | ||
+ | Ein Testprogramm für den Slave. | ||
+ | <pre> | ||
+ | /* | ||
+ | Testprogramm für die twislave.c | ||
+ | Der Buffer wird mit Werten gefüllt. Dann wird er fortlaufend über die serielle Schnittstelle ausgegeben. | ||
+ | */ | ||
+ | |||
+ | |||
+ | #include "twislave.h" | ||
#include "uart.c" | #include "uart.c" | ||
Zeile 319: | Zeile 292: | ||
init_twi_slave(SLAVE_ADRESSE); | init_twi_slave(SLAVE_ADRESSE); | ||
− | // | + | //i2cdatamit Werten füllen, die der Master auslesen und ändern kann |
for(uint8_t i=0;i<buffer_size;i++) | for(uint8_t i=0;i<buffer_size;i++) | ||
{ | { | ||
− | + | i2cdata[i]=10+i; | |
} | } | ||
Zeile 335: | Zeile 308: | ||
while(1) | while(1) | ||
{ | { | ||
− | uart_puts(" | + | uart_puts("i2cdata:\r\n"); |
for(uint8_t i=0;i<buffer_size;i++) | for(uint8_t i=0;i<buffer_size;i++) | ||
{ | { | ||
− | uart_puti( | + | uart_puti(i2cdata[i]); |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
uart_puts("\r\n"); | uart_puts("\r\n"); | ||
} | } | ||
Zeile 353: | Zeile 319: | ||
} //end.main | } //end.main | ||
</pre> | </pre> | ||
− | |||
− | |||
[[Kategorie:Microcontroller]] | [[Kategorie:Microcontroller]] | ||
[[Kategorie:Quellcode C]] | [[Kategorie:Quellcode C]] |
Version vom 19. Februar 2010, 18:31 Uhr
Programm für einen AVR mit TWI (Hardware-I2C)-Schnittstelle als Slave. Manchmal stellt sich die Aufgabe, mehrere AVRs per I2C zu vernetzen. Ein Beispiel ist die Erweiterung eines bestehenden Systems um einen leistungsstärkeren Controller. Dies ist etwa beim Asuro oder Yeti denkbar, um z.B. mehr Ein/Ausgänge oder Speicherplatz zu bekommen.
Man kann aber beim Bau eines Roboters auch von Anfang an auf ein System von mehreren vernetzten Controllern setzen. So kann man verschiedene Aufgabe, die weitgehend unabhängig voneinader gleichzeitg erledigt werden müssen, besser verteilen. Denkbar ist z.B. ein Controller für die Motorsteuerung, einer für die Sensorik, einer für Ein-und Ausgabe (wie LCD und Bedientaster),... , und ein zentraler Controller, der die Richtung vorgibt und alle anderen Controller mit Befehlen versorgt. Ein konkretes Beispiel ist der Roboterbausatz Nibo (siehe: [1]) von nicai-systems, der einen Atmel ATmega128 als Hauptcontroller und zwei Atmel ATtiny44 als Controller für die Motorsteuerung und die IR-Sensorik einsetzt. Die Kommunikation läuft mit 400 kHz über den I2C-Bus mit dem ATmega128 als Master.
Schließlich kann ein entsprechend programmierter AVR auch als Ersatz für handelsübliche I2C-Bauteile dienen. Der kleinste AVR mit Hardware-I2C, der ATmega48, ist mit 1,30€ (Reichelt) billiger als viele normale I2C-ICs. Er kann bei entsprechender Programmierung z.B. die Aufgaben von zwei PCF8574 (8bit-Portexpander, 1,25€) und einem PCF8591 (4fach AD-Wandler, 2,35€) übernehmen und außerdem noch als I2C-EEPROM mit 256 Bytes dienen.
Das folgende Programm (twislave.c) steuert das TWI (Hardware-I2C)-Interface eines AVRs als Slave an. Es müsste auf allen AVRs der Mega-Reihe funktionieren, die über eine TWI-Schnittstelle verfügen.
Das System ist als eine Art Dualport-RAM konzipiert, Master und Slave teilen sich also einen Speicherbereich und können darüber Daten austauschen.
Inhaltsverzeichnis
Master
Ein Codeschnipsel für den Master. Es wird die I2C-Master-Bibliothek von Peter Fleury verwendet. Es wird geprüft, ob der Slave bereit ist, dann werden die ersten drei Bytes aus dem txbuffer des Slaves gelesen und in byte0..2 abgespeichert.
#include "i2cmaster.c" //I2C-Master-Routinen von Peter Fleury verwenden (siehe http://homepage.hispeed.ch/peterfleury/avr-software.html#libs) #define SLAVE_ADRESSE 0x50 uint8_t byte0; uint8_t byte1; uint8_t byte2; if(!(i2c_start(SLAVE_ADRESSE+I2C_WRITE))) //Slave bereit zum lesen? { i2c_write(0x00); //Buffer Startadresse zum Auslesen i2c_rep_start(SLAVE_ADRESSE+I2C_READ); //Lesen beginnen byte0= i2c_readAck(); byte1= i2c_readAck(); byte2= i2c_readNak(); //letztes Byte lesen, darum kein ACK i2c_stop(); }
Slave sourcecode
Die twislave.c für den Slave. Stand: 19.2.2010
#include <util/twi.h> //enthält z.B. die Bezeichnungen für die Statuscodes in TWSR #include <avr/interrupt.h> //dient zur Behandlung der Interrupts #include <stdint.h> //definiert den Datentyp uint8_t #include "twislave.h" //%%%%%%%% Globale Variablen, die vom Hauptprogramm genutzt werden %%%%%%%% /*Der Buffer, in dem die Daten gespeichert werden. Aus Sicht des Masters läuft der Zugrif auf den Buffer genau wie bei einem I2C-EEPROm ab. Für den Slave ist es eine globale Variable */ volatile uint8_t i2cdata[i2c_buffer_size+1]; volatile uint8_t buffer_adr; //"Adressregister" für den Buffer /*Initaliserung des TWI-Inteface. Muss zu Beginn aufgerufen werden, sowie bei einem Wechsel der Slave Adresse Parameter adr: gewünschte Slave-Adresse */ void init_twi_slave(uint8_t adr) { TWAR= adr; //Adresse setzen TWCR &= ~(1<<TWSTA)|(1<<TWSTO); TWCR|= (1<<TWEA) | (1<<TWEN)|(1<<TWIE); buffer_adr=0xFF; sei(); } //Je nach Statuscode in TWSR müssen verschiedene Bitmuster in TWCR geschreiben werden(siehe Tabellen im Datenblatt!). //Makros für die verwendeten Bitmuster: //ACK nach empfangenen Daten senden/ ACK nach gesendeten Daten erwarten #define TWCR_ACK TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(0<<TWWC); //NACK nach empfangenen Daten senden/ NACK nach gesendeten Daten erwarten #define TWCR_NACK TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(0<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(0<<TWWC); //switched to the non adressed slave mode... #define TWCR_RESET TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA)|(0<<TWSTA)|(0<<TWSTO)|(0<<TWWC); //Die Bitmuster für TWCR_ACK und TWCR_RESET sind gleich. Dies ist kein Fehler und dient nur der Übersicht! /*ISR, die bei einem Ereignis auf dem Bus ausgelöst wird. Im Register TWSR befindet sich dann ein Statuscode, anhand dessen die Situation festgestellt werden kann. */ ISR (TWI_vect) { uint8_t data=0; switch (TW_STATUS) //TWI-Statusregister prüfen und nötige Aktion bestimmen { // Slave Receiver case TW_SR_SLA_ACK: // 0x60 Slave Receiver, Slave wurde adressiert TWCR_ACK; // nächstes Datenbyte empfangen, ACK danach senden buffer_adr=0xFF; //Bufferposition ist undefiniert break; case TW_SR_DATA_ACK: // 0x80 Slave Receiver, ein Datenbyte wurde empfangen data=TWDR; //Empfangene Daten auslesen if (buffer_adr == 0xFF) //erster Zugriff, Bufferposition setzen { //Kontrolle ob gewünschte Adresse im erlaubten bereich if(data<i2c_buffer_size) { buffer_adr= data; //Bufferposition wie adressiert setzen } else { buffer_adr=0; //Adresse auf Null setzen. Ist das sinnvoll? TO DO! } TWCR_ACK; // nächstes Datenbyte empfangen, ACK danach, um nächstes Byte anzufordern } else //weiterer Zugriff, nachdem die Position im Buffer gesetzt wurde. NUn die Daten empfangen und speichern { if(buffer_adr<i2c_buffer_size) { if(!I2C_reg_Schreibschutz[buffer_adr]) //Wenn Position nicht schreibgeschützt... i2cdata[buffer_adr]=data; //...dann Daten in Buffer schreibe } buffer_adr++; //Buffer-Adresse weiterzählen für nächsten Schreibzugriff TWCR_ACK; } break; //Slave transmitter case TW_ST_SLA_ACK: //0xA8 Slave wurde im Lesemodus adressiert und hat ein ACK zurückgegeben. //Hier steht kein break! Es wird also der folgende Code ebenfalls ausgeführt! case TW_ST_DATA_ACK: //0xB8 Slave Transmitter, Daten wurden angefordert if (buffer_adr == 0xFF) //zuvor keine Leseadresse angegeben! { buffer_adr=0; } if(buffer_adr<i2c_buffer_size) { TWDR = i2cdata[buffer_adr]; //Datenbyte senden buffer_adr++; //bufferadresse für nächstes Byte weiterzählen } else { TWDR=0; //Kein Daten mehr im Buffer } TWCR_ACK; break; case TW_ST_DATA_NACK: // 0xC0 Keine Daten mehr gefordert case TW_SR_DATA_NACK: // 0x88 case TW_ST_LAST_DATA: // 0xC8 Last data byte in TWDR has been transmitted (TWEA = “0”); ACK has been received case TW_SR_STOP: // 0xA0 STOP empfangen default: TWCR_RESET; break; } //end.switch (TW_STATUS) } //end.ISR(TWI_vect) ////Ende von twislave.c////
Slave header
Die zugehörige Headerdatei twislave.h für den Slave. Stand: 19.2.2010
#ifndef _TWISLAVE_H #define _TWISLAVE_H #include <util/twi.h> //enthaelt z.B. die Bezeichnungen fuer die Statuscodes in TWSR #include <avr/interrupt.h> //dient zur behandlung der Interrupts #include <stdint.h> //definiert den Datentyp uint8_t /** * @defgroup twislave TWI-Slave * @code #include "twislave.h" @endcode * * @brief Betrieb eines AVRs mit Hardware-TWI-Schnittstelle als Slave. * Zu Beginn muss init_twi_slave mit der gewuenschten Slave-Adresse als * Parameter aufgerufen werden. * * Der Datenaustausch mit dem Master erfolgt ueber den Buffer i2cdata, * auf den von Master und Slave zugegriffen werden kann. * Dies ist fuer den Slave eine globale Variable (Array aus uint8_t). * Der Zugriff durch den Master erfolgt aehnlich wie bei einem * normalen I2C-EEPROM. * Man sendet zunaechst die Bufferposition, an die man schreiben will, * und dann die Daten. * Die Bufferposition wird automatisch hochgezaehlt, sodass man mehrere * Datenbytes hintereinander schreiben kann, ohne jedesmal die * Bufferadresse zu schreiben. * * Um vom Master aus zu lesen, uebertraegt man zunaechst in einem * Schreibzugriff die gewuenschte Bufferposition und liest dann nach * einem repeated start die Daten aus. Die Bufferposition wird * automatisch hochgezaehlt, sodass man mehrere Datenbytes * hintereinander lesen kann, ohne jedesmal die Bufferposition zu * schreiben. * * Abgefangene Fehlbedienung durch den Master: * - Lesen ueber die Grenze des txbuffers hinaus * - Schreiben ueber die Grenzen des rxbuffers hinaus * - Angabe einer ungueltigen Schreib/Lese-Adresse * - Lesezugriff, ohne vorher Leseadresse geschrieben zu haben * * @author uwegw */ /*@{*/ //%%%%%%%% von Benutzer konfigurierbare Einstellungen %%%%%%%% /**@brief Groesse des Buffers in Byte (2..254) */ #define i2c_buffer_size I2C_REG_ANZAHL //%%%%%%%% Globale Variablen, die vom Hauptprogramm genutzt werden %%%%%%%% /**@brief Der Buffer, in dem die Daten gespeichert werden. * Aus Sicht des Masters laeuft der Zugrif auf den Buffer genau wie * bei einem I2C-EEPROm ab. * Fuer den Slave ist es eine globale Variable */ volatile uint8_t i2cdata[i2c_buffer_size+1]; /**@brief Initaliserung des TWI-Inteface. Muss zu Beginn aufgerufen werden, sowie bei einem Wechsel der Slave Adresse * @param adr gewuenschte Slave-Adresse */ void init_twi_slave(uint8_t adr); //%%%%%%%% ab hier sind normalerweise keine weiteren Aenderungen erforderlich! %%%%%%%%// //____________________________________________________________________________________// //Bei zu alten AVR-GCC-Versionen werden die Interrupts anders genutzt, daher in diesem Fall mit Fehlermeldung abbrechen #if (__GNUC__ * 100 + __GNUC_MINOR__) < 304 #error "This library requires AVR-GCC 3.4.5 or later, update to newer AVR-GCC compiler !" #endif //Schutz vor unsinnigen Buffergroessen #if (i2c_buffer_size > 254) #error Buffer zu gross gewaehlt! Maximal 254 Bytes erlaubt. #endif #if (i2c_buffer_size < 2) #error Buffer muss mindestens zwei Byte gross sein! #endif /*@}*/ #endif //#ifdef _TWISLAVE_H ////Ende von twislave.h////
Slave Testprogramm
Ein Testprogramm für den Slave.
/* Testprogramm für die twislave.c Der Buffer wird mit Werten gefüllt. Dann wird er fortlaufend über die serielle Schnittstelle ausgegeben. */ #include "twislave.h" #include "uart.c" #define BAUD 9600 //Baudrate #define SLAVE_ADRESSE 0x50 //Die Slave-Adresse //Funktion für Warteschaleifen //Parameter: Wartezeit in Milisekunden #include <util/delay.h> void warte (int loop) //loop: wartezeit in ms { int i; for(i=0;i<loop;i++) _delay_ms(1); } int main (void) { //TWI als Slave mit Adresse slaveadr starten init_twi_slave(SLAVE_ADRESSE); //i2cdatamit Werten füllen, die der Master auslesen und ändern kann for(uint8_t i=0;i<buffer_size;i++) { i2cdata[i]=10+i; } //Serielle Schnittstelle aktivieren uart_init((UART_BAUD_SELECT((BAUD),F_CPU))); uart_puts("I2C-Test\r\n"); uart_puts("Teste I2C-Slave mit Adresse "); uart_puti(SLAVE_ADRESSE); uart_puts("\r\n"); uart_puts("\r\n"); //Leerzeile //in einer Endlosschleife den Inhalt der Buffer ausgeben while(1) { uart_puts("i2cdata:\r\n"); for(uint8_t i=0;i<buffer_size;i++) { uart_puti(i2cdata[i]); uart_puts("\r\n"); } uart_puts("\r\n");//leerzeile warte(500); } //end.while } //end.main