Die Idee ist, intelligente Sensoren und Aktoren durch Einsatz externerMicroprozessoren zu 
   realisieren. Die Kommunikation mit dem NXT soll per I²C stattfinden.
Meine Wahl fiel 
   auf den ATtiny45. Dieser Chip neben dem USI (
Universal
   
Serial 
Interface), 
   mit dem man recht einfach eine interrupt-gesteuerte I²C-Kommunikation implementieren kann. Neben 
   den für den I²C-Bus benötigten beiden Leitungen stehen vier  freie I/O-Leitungen, 
   die als digitale I/O-Kanäle oder als analoge Eingänge (ADC) betrieben werden 
   können, zur Verfügung. Zwei Timer, über die u.a. eine PWM-Steuerung eingerichtet werden 
   kann, runden die Möglichkeiten dieses Chips ab. Außerdem ist er in einer DIL-8-Version 
   erhältlich.         
DIL-8: 
     
   Damit ist er einerseits groß genug, dass man ihn mit hobbymäßigen Werkzeug anschließen 
   kann, andererseits klein genug, um in ein akzeptables Sensorghäuse zu passen.
Das
   
ATtiny45 
   Datenblatt kann bei Atmel abgerufen werden. Hier findet man auch bei den
   
Atmel Application 
   Notes unter 
"AVR312: Using the USI module as a I2C 
   slave" eine Dokumentation und auch Code, wie man mit dem USI-Modul einen I²C-Slave 
   realisieren kann. Der Beispiel-Code hat aber einen gravierenden Fehler (
Details 
   siehe hier).
Zu Testzwecken ist es am einfachsten, den I²C-Slave zunächst über 
   den PC anzusteuern. Dazu habe ich einen 
USB2I2C-Adapter 
   benutzt. Das gleiche Gerät kann man über eine
   
Vielzahl von Anbietern 
   beziehen. 
Vorbereitung des ATtiny:Der Adapter 
   arbeitet mit einer Taktfrequenz von 100kHz und beherrscht wohl kein
   
Clock-Stretching (oder
   
hier nachlesen). 
   Das Datenblatt zum Modul gibt hierzu keine Auskunft.
Damit der µC hier noch mitkommt, 
   muss er auf die höchstmögliche Frequenz gestellt werden. Dazu müssen die CKSEL fuses 
   auf ‘0001’ eingestellt werden. In PonyProg sieht das wie folgt aus:
   

Wenn der Chip später 
   an den NXT angeschlossen wird, kann man mit niedrigeren Frequenzen arbeiten. Der NXT bedient in der 
   Standardeinstellung den I²C-Bus mit einer Taktfrequenz von nur 9600 Hz. 
   
I²C-Slave-Modul (Download):
   Das Beispiel aus der Applikation Note habe ich so umgebaut, dass über vier Interface-Funktionen 
   betrieben werden kann. Im wesentlichen wurde der enthaltene Ringpuffer durch Callback-Funktionen 
   ersetzt. Außerdem habe ich einige Anweisungen umgestellt, damit das ganze etwas weniger zeitkritisch 
   wird.
   
	  
		 | I²C-Slave-Interface | 
	  
		 | void I2CSlave_Initialise(unsigned char I2CAddress) | Initialisiert die USI-Register für den I2C Modus | 
	  
		 | void I2CSlave_TransmissionStart(void) | Signalisiert, dass ein neuer Übertragungszyklus begonnen hat. Diese Funktion wird 
		 aufgerufen, wenn audem I²C-Bus die Startkonfiguration erkannt wurde.
 Wichtig: Die Funktion muss schnell 
		 zurückkehren!
 | 
	  
		 | unsigned char I2CSlave_DataRequest(void) | Liefert die Daten für das I2C-Modul. Diese Funktion wird aufgerufen, wenn das I2C-Modul 
		 aufgefordert wurde, Daten zu liefern (Read-Request des Masters).
 Wichtig: Die Funktion muss schnell 
		 zurückkehren!
 | 
	  
		 | void I2CSlave_DataReceived(unsigned char data) | Liefert Daten aus dem I2C-Modul. Diese Funktion wird aufgerufen, wenn das I2C-Modul 
		 Daten erhalten hat (Send-Request des Masters).
 Wichtig: Die Funktion muss schnell 
		 zurückkehren!
 | 
	  
		 | void I2CSlave_SetI2CAddress(unsigned char I2CAddress) | Legt eine neue I2C-Adresse fest. | 
   
   Anmerkungen:
   
	  - Die Initialisierung der Register muss vor der Freigabe der Interrupts erfolgen. Also erst
	  I2CSlave_Initialise() und danach erst
	  sei().
- Die Callback-Funktionen sind zeitkritisch. Mehr als die Daten zwischen zu speichern und ein 
	  Signal zu setzen ist nicht möglich. Wird dies nicht beachtet, gerät die Synchronisation 
	  mit dem I²C-Bus aus Takt.
- Das Modul akzeptiert sowohl die vorgesehene Bus-Adresse als auch die Adresse 0. Wenn man also 
	  die Adresse per Software änderbar im EEPROM ablegt , kann man bei unbekannter (fehlgeschlagener 
	  oder vergessener) Bus-Adresse über die Ansprache des Geräts mit der Adresse 0 wieder 
	  definierte Zustände herstellen. Natürlich sollte hierbei das zu konfigurierende Gerät 
	  allein angeschlossen sein.
Testprogramme:
	  
		 | Einfaches 
		 "Single Byte" Programm | 
	  
		 | Ein über den I²C-Bus empfangenes Byte wird beim nächsten Lesevorgang zurückgesandt. | 
	  
		 | #include "I2CSlave.h"
 #include <interrupt.h>
 
 int main( void )
 {
		 //*****************************************************
 // Initialize I2C Module
 //*****************************************************
 unsigned char I2C_slaveAddress = 0x10; //Set TWI slave 
		 address
 I2CSlave_Initialise(I2C_slaveAddress);
 sei();
		 // Enable interrupts
 
 for(;;)
		 // This loop runs forever.
 {
 }
 } // main
 
 
 //*****************************************************
 // I2C Callbacks
 //*****************************************************
 unsigned 
		 char tmp = 0xFF; // Nicer than zero :-)
 
 unsigned char I2CSlave_DataRequest(void)
 { return tmp;
		 // Transmit saved byte
 }
 
 void I2CSlave_DataReceived(unsigned 
		 char data)
 { tmp = data; // Save received byte
 }
 
 void I2CSlave_TransmissionStart(void)
 { //Nothing 
		 to do
 }
 
 
 | 
   
   
   
	  
		 | Einfaches 
		 "Multi Byte" Programm | 
	  
		 | Über den I²C-Bus empfangene Bytes werden in einem Array gespeichert. Das Array 
		 wird bei jeder Übertragung komplett gefüllt. | 
	  
		 | #include "I2CSlave.h"
 #include <interrupt.h>
 
 int main( void )
 {
		 //*****************************************************
 // Initialize I2C Module
 //*****************************************************
 unsigned char I2C_slaveAddress = 0x10; //Set TWI slave 
		 address
 I2CSlave_Initialise(I2C_slaveAddress);
 sei();
		 // Enable interrupts
 
 for(;;)
		 // This loop runs forever.
 {
 }
 } // main
 
 
 //*****************************************************
 // I2C Callbacks
 //*****************************************************
 #define 
		 BUFFER_SIZE 4
 unsigned char buffer[BUFFER_SIZE];
 unsigned char ind=0;
 
 // Xmit data from buffer
 unsigned char I2CSlave_DataRequest(void)
 { unsigned char tmp = buffer[ind++];
 if (ind >= BUFFER_SIZE)
		 // No buffer overflow
 ind = BUFFER_SIZE -1;
 return tmp; // Rerturn 
		 data form buffer
 }
 
 // Save data in buffer
 void I2CSlave_DataReceived(unsigned char data)
 { buffer[ind++] = data;  
		 // Save received Byte
 if (ind >= BUFFER_SIZE)
		 // No buffer overflow
 ind = BUFFER_SIZE -1;}
 }
 
 void I2CSlave_TransmissionStart(void)
 { ind = 0;  
		 // Reset buffer index
 }
 
 
 | 
	  
		 | Anmerkungen Dies ist ein Testprogramm um die 
		 Funktionen zu demonstrieren und nicht für einen realen Einsatz geeignet. Die I²C-Verbindung 
		 kann jederzeit gestört werden. Dann hätte man unvollständige Daten im Puffer.
 Ebenso könnte während des Auslesens der Daten ein anderer Thread den Puffer modifizieren.
 
 Für den Praxisbetrieb darf nicht direkt in den Puffer geschrieben oder aus ihm gelesen 
		 werden. Die Daten müssen zwischengespeichert werden.
 
 
			Beim Empfang wird der Zwischenspeicher erst nach vollständiger Übertragung 
			umkopiert. Dies erfolgt sinnvollerweise in I2CSalve_DataReceived, wenn alle erwarteten Bytes 
			angekommen sind. Die temporären Daten werden verworfen, wenn der Empfang nicht vollständig 
			ist. Dies erfolgt entweder in I2CSlave_TransmissionStart() oder über einen Timeout-Mechanismus.Beim Senden müssen die Daten zunächst in einen Zwischenspeicher übernommen 
			werden. Die Speisung des I²C-Moduls erfolgt dann aus diesem Zwischenspeicher heraus. 
			Zum umkopieren ist wiederum I2CSlave_TransmissionStart() geeignet.Der Zugriff auf den Puffer beim Umkopieren muss über Prozess-Synchronisations-Mechanismen 
			(Mutex, Semaphore, etc.) geregelt werden.Wenn zu viele Operationen in den Callback-Funktionen durchgeführt werden, z.B. 
			die zu kopierenden Datenmengen zu groß werden, geht die Synchronisation mit dem I²C-Bus 
			verloren. Es ist also sinnvoll, nur die ersten notwendigsten Daten zu übertragen und 
			ein Signal zu setzen. Die restliche Übertragung der Daten kann dann über das Hauptprogramm 
			erfolgen, während das I²C-Modul bereits die ersten Daten überträgt oder 
			empfängt. | 
   
   Sourcecode 
   (Download)