Die erste Version eines USI-Slave-Bibliothek recht einfach aufgebaut. Die neue Version v2 bietet einen deutlich höheren Komfort.


Download der Quellen


Dateien

Das Modul UsiTwiSlave stellt ein Framework zur Bereitstellung einer interruptgetriebenen TWI-Slave-Schnittstelle (I²C) auf AVR-Prozessoren mit einer USI zur Verfügung. Es besteht aus vier Dateien (zu Konventionen siehe auch: Bibliothek: Kein zweites Mal!):

UsiTwiSlave.c Source-File: Die Datei muss in das Applikations-Projekt (Projektmappen-Explorer) eingebunden werden. Dies erfolgt am besten als Link. Die zugehörige "Build-Action" bei den Datei-Eigenschaften im Projektmappen-Explorer muss auf "Compile" stehen. Dies erfolgt i.d.R. automatisch, wenn eine Datei mit der Extension ".c" eingebunden wird.
UsiTwiSlave.h Header-File: Die Datei muss per #include "UsiTwiSlave.h" in das Applikations-Projekt eingebunden werden. Es empfiehlt auch diese Datei als Link in das Dateiverzeichnis einzubinden. So kann sie einfach angezeigt werden.
UsiTwiSlave.cfg.h Configuration-File: Die Datei enthält applikationsspezifische Einstellungen. Diese Datei ist im Projektverzeichnis zur Verfügung zu stellen. Sie wird per #include "UsiTwiSlave.cfg.h" sowohl in "UsiTwiSlave.h" als auch in "UsiTwiSlave.c" eingebunden.
UsiTwiSlave.tmp.h Configuration-Template: Vorlagendatei für "UsiTwiSlave.cfg.h". Ins Projektverzeichnis kopieren, umbenennen und anpassen.

Neben diesen Dateien ist -wie üblich- GeneralDefinitions.h bereit zu stellen. Dort werden einige der benötigten Präprozessor-Makros definiert.


Betriebs-Modi

Es stehen drei Modi zur Auswahl:

Betriebs-Modi
STANDARD Es gibt keine Unterstützung durch die Bibliothek. Zu sämtlichen relevanten Ereignissen werden Callback-Funktionen aufgerufen, die von der Applikation implementiert werden müssen.
SIMPLE Es wird jeweils nur ein einzelnes Byte zur Verfügung gestellt. Lese- und Schreibzugriffe des Masters werden vollkommen über interne Funktionen bedient und können durch zusätzliche Callback-Funktionen überwacht werden.
REGISTER Die Daten werden in einem strukturierten und adressierbaren I²C-Register zur Verfügung gestellt bzw. dort abgelegt.  Lese- und Schreibzugriffe des Masters werden vollkommen über interne Funktionen bedient und können durch zusätzliche Callback-Funktionen überwacht werden.

Der Modus wird über die Präprozessor-Konstante TWI_OPERATION_MODE eingestellt. Mögliche Werte sind TWI_MODE_STANDARD, TWI_MODE_SIMPLE oder TWI_MODE_REGISTER. Die Definition von TWI_OPERATION_MODE und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird überprüft.

Über weiter Präprozessor-Kostanten können die verschiedenen Modi konfiguriert werden (s.u.).

Betriebsmodus STANDARD

Es werden keine Automatismen implementiert. Die Callback-Funktionen

uint8_t TwiDataRequest() zur Bereitstellung von Daten (s.u.)
void TwiDataReceived(uint8_t data) zum Empfang von Daten (s.u.)
void TwiTransmissionStart(TwiTransferType rw) zur Steuerung (s.u.)

müssen von der Applikation implementiert werden. Die Implementierung von TwiDataRequest ist obligatorisch. TwiDataReceived und TwiTransmissionStart sind einstellbar.

Betriebsmodus SIMPLE

In diesem Modus wird jeweils nur ein einzelnes Byte übertragen. Dieses Byte wird in der Variablen TwiData zur Verfügung gestellt bzw. dort abgelegt. Die Funktion TwiDataRequest() zur Bereitstellung von Daten wird intern implementiert und muss NICHT durch die Applikation implementiert werden. Sie übergibt jeweils den aktuellen Inhalt von TwiData.

void TwiTransmissionStart (TwiTransferType rw) kann von der Applikation implementiert werden. Die Notwendigkeit TwiTransmissionStart() zu implementieren kann über die Präprozessor-Konstante TWI_TRANSMISSION_START_MSG abgeschaltet werden (s.u.).

Das Systemverhalten beim Datenempfang wird über die Präprozessor-Konstante TWI_RECEIVE_MODE gesteuert:

TWI_RECEIVE_MODE
NONE Eingehende Daten werden ignoriert.
TWI_SIMPLE Eingehende Bytes werden in der Variablen TwiData abgelegt.
TWI_FLAG Eingehende Bytes werden in der Variablen TwiData abgelegt. Zusätzlich wird die Variable TwiDataReceivedFlag deklariert. Diese wird auf true gesetzt, wenn ein Byte empfangen wurde. Die Applikation kann diese Variable bei Bedarf löschen (asynchroner Betrieb).
TWI_CALLBACK Die von der Applikation zu implementierende Callback-Funktion void TwiDataReceived(uint8_t data) wird aufgerufen (synchroner Betrieb). TwiData wird NICHT befüllt.
TWI_CB_INLINE Wie TWI_CALLBACK. Die Callback-Funktion wird als ALWAYS_INLINE void TwiDataReceived(uint8_t data) in UsiTwiSlave.cfg.h zur Verfügung gestellt.

Die Definition von TWI_RECEIVE_MODE und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird überprüft.

Betriebs-Modus REGISTER

In diesem Modus werden die Daten in einem strukturierten und adressierbaren I²C-Register zur Verfügung gestellt bzw. dort abgelegt. Jede Schreiboperation des Maters beginnt in diesem Betriebsmodus zunächst mit der Übertragung des Registerindex (ein Byte). Jedes weitere Byte wird dann an der vom Index angegeben Stelle abgelegt und der Index inkrementiert.

Das Lesen erfolgt ebenfalls an der vom Index angegeben Stelle. Durch das Schreiben eines einzelnen Bytes (= Registerindex) vor dem Lesebefehl kann diese Stelle verschoben werden.

Die Struktur des I²C-Registers wird über den Typ I2CRegister_t festgelegt. Dieser Typ muss in UsiTwiSlave.cfg.h definiert werden.

Am sinnvollsten wird er als structure hinterlegt. Es sind aber auch andere Darstellungen möglich. Intern wird auf das Register stets mit dem Typ-Konverter ((uint8_t *)&I2CRegister)[] zugegriffen.
typedef struct
{ ...
} I2CRegister_t;

Die Verwaltung des Index (RegisterIndex) zum Zugriff auf das I²C-Register erfolgt intern. Wird der Index durch den Master auf einen Wert gesetzt, der größer als die Größe des Registers ist, wird der Index auf das letzte Byte des Registers gesetzt. Ähnliches gilt beim Inkrementieren des Index. Würde er zu Zugriffen außerhalb das Register-Speicherraums führen, wird das Inkrementieren unterdrückt. Sicherheitshalber fügt man an das Ende des Registers ein zusätzliches Byte ein, dass in solchen Situationen ohne weitere Seiteneffekte beschrieben werden kann.

Die Funktion TwiDataRequest() zur Bereitstellung von Daten wird intern implementiert und muss NICHT durch die Applikation implementiert werden. Sie übergibt jeweils den durch den Registerindex gekennzeichneten Inhalt des I²C-Registers. void TwiTransmissionStart(TwiTransferType rw) kann von der Applikation implementiert werden. Die Notwendigkeit TwiTransmissionStart() zu implementieren kann über TWI_TRANSMISSION_START_MSG abgeschaltet werden (s.u.).

Das Systemverhalten beim Datenempfang wird über die Präprozessor-Konstante TWI_RECEIVE_MODE gesteuert:

TWI_RECEIVE_MODE
NONE Eingehende Daten werden ignoriert.
TWI_SIMPLE Eingehende Bytes werden an der passenden Stelle im I²C-Register abgelegt.
TWI_FLAG Eingehende Bytes werden an der passenden Stelle im I²C-Register abgelegt. Zusätzlich wird die Variable TwiDataReceivedFlag deklariert. Diese wird auf true gesetzt, wenn ein Byte empfangen wurde. Die Applikation kann diese Variable bei Bedarf löschen.
TWI_CALLBACK Die von der Applikation zu implementierende Callback-Funktion void TwiDataReceived(uint8_t index, uint8_t data) wird aufgerufen. Das I²C-Register wird NICHT beschrieben.
TWI_CB_INLINE Wie TWI_CALLBACK. Die Callback-Funktion wird als ALWAYS_INLINE void TwiDataReceived(uint8_t index, uint8_t data) in UsiTwiSlave.cfg.h zur Verfügung gestellt.

Die Definition von TWI_RECEIVE_MODE und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird überprüft.

Über TWI_REGISTER_INDEX_REGISTER kann festgelegt werden, ob und welches Register zur Aufnahme der internen Variablen RegisterIndex genutzt werden kann. Mögliche Werte sind NONE und 2..17. Ist die Konstante mit NONE belegt, wird die Variable im RAM abgelegt. Die Definition von TWI_REGISTER_INDEX_REGISTER und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird überprüft.

Über TWI_STATE_REGISTER kann festgelegt werden, ob und welches Register zur Aufnahme der internen Variablen TransmissionState genutzt werden kann. Mögliche Werte sind NONE und 2..17. Ist die Konstante mit NONE belegt, wird die Variable im RAM abgelegt. Die Definition von TWI_STATE_REGISTER und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird überprüft.

In den Empfangs-Modi TWI_SIMPLE und TWI_FLAG kann über die Präprozessor-Konstante TWI_WRITE_PERMIT ein schreibgeschützter Bereich zu Beginn des Registers festgelegt werden. Schreiboperationen mit einem Index kleiner als TWI_WRITE_PERMIT werden unterdrückt. Wird kein schreibgeschützter Bereich benötigt, wird die Konstante mit 0 belegt.  Die Definition von TWI_WRITE_PERMIT und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird überprüft.


API

Die Einbindung in die Applikation (API) erfolgt prinzipiell über drei Callback-Funktionen (Ereignisse), die je nach Betriebsmodus durch die Applikation bereit gestellt werden müssen oder durch interne Funktionen behandelt werden:

API
TwiTransmissionStart() Callback: Der Beginn einer Datenübertragung wird gemeldet.
TwiDataReceived() Callback: Ein Byte wurde empfangen und kann bearbeitet werden (Master ⇒ Slave).
TwiDataRequest() Callback: Ein Byte wird zur Übertragung angefordert (Slave ⇒ Master).

Das Problem bei Callback-Funktion ist, dass der Compiler nun nicht mehr genau weiß, welche Register in den Callback-Funktion von der Applikation manipuliert werden. Deshalb muss er vorsichtshalber alle möglicherweise betroffen Register (etwa zehn Stück) beim Eintritt in die ISR auf den Stack sichern. Dies benötigt zum einem viel Zeit (jeder 'push-' und jeder 'pop'-Befehl benötigt zwei Prozessor-Takte) und zum anderen ggf. unnötigen Platz auf dem Stack. Insbesondere der zusätzliche Zeitbedarf ist kritisch, wenn der I²C-Master bei hohen Übertragungsraten klein Clock-Stretching beherrscht.

 Hier ist es sinnvoll, entweder zu einem der folgenden Modi zu wechseln oder die betroffenen Callback-Funktionen als 'static inline' in "UsiTwiSlave.cfg.h" zu hinterlegen. Z.B.
ALWAYS_INLINE void TwiTransmissionStart(TwiTransferType rw)
{ // Code für diese Funktion
}
Wie dies genau zu konfigurieren ist, ist bei den einzelnen Funktionen beschrieben.

Ereignis TwiTransmissionStart

Die zugehörige Callback-Funktion ist wie folgt deklariert:
   void TwiTransmissionStart(TwiTransferType rw)

TwiTransferType kann folgenden Ausprägungen annehmen:

TwiTransferType
TwiRead Der I2C-Master beabsichtigt Daten über die reguläre Geräteadresse vom Slave abzufragen (Slave ⇒ Master).
TwiWrite Der I2C-Master beabsichtigt Daten über die reguläre Geräteadresse an den Slave zu senden (Master ⇒ Slave).
BroadcastRead Der I2C-Master beabsichtigt Daten über die Broadcast-Adresse vom Slave abzufragen (Slave ⇒ Master).
BroadcastWrite Der I2C-Master beabsichtigt Daten über die Broadcast-Adresse an den Slave zu senden (Master ⇒ Slave).

Die Notwendigkeit TwiTransmissionStart() zu implementieren kann über die Präprozessor-Konstante TWI_TRANSMISSION_START_MSG abgeschaltet werden. Mögliche Ausprägungen sind TRUE, INLINE und FALSE.

TWI_TRANSMISSION_START_MSG
TRUE Die Applikation muss die Funktion void TwiTransmissionStart(TwiTransferType rw) implementieren.
INLINE Die Funktion ist als ALWAYS_INLINE void TwiTransmissionStart(TwiTransferType rw){...} in UsiTwiSlave.cfg.h zu hinterlegen.
FALSE Die Funktion wird nicht aufgerufen und muss nicht implementiert werden.

Die Definition von TWI_TRANSMISSION_START_MSG und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird überprüft.

Ereignis TwiDataReceived

Dieses Ereignis wird über die Präprozessor-Konstante TWI_RECEIVE_MODE gesteuert. Die zugehörige Callback-Funktion ist abhängig vom Betriebsmodus wie folgt deklariert:
   void TwiDataReceived(uint8_t data) (Betriebsmodus STANDARD oder SIMPLE)
bzw.
   void TwiDataReceived(uint8_t index, uint8_t data) (Betriebsmodus REGISTER).

TWI_RECEIVE_MODE
NONE Eingehende Daten werden ignoriert. Die Funktion TwiDataReceived wird nicht aufgerufen und muss nicht implementiert werden.
TWI_AUTO Eingehende Daten werden automatisch behandelt. Die Funktion TwiDataReceived wird nicht aufgerufen und muss nicht implementiert werden. Diese Funktionalität steht im Betriebsmodus STANDARD nicht zur Verfügung!
TWI_FLAG Eingehende Daten werden automatisch behandelt. Die Funktion wird nicht aufgerufen und muss nicht implementiert werden. Nachdem ein Dateneingang verzeichnet wurde, wird die Variable TwiDataReceivedFlag auf true gesetzt. Die Applikation setzt diese Variable nach der Verarbeitung der eingegangen Daten wieder auf false zurück, um einen erneuten Dateneingang erkennen zu können. Diese Funktionalität steht im Betriebsmodus STANDARD nicht zur Verfügung!
TWI_CALLBACK Die Applikation implementiert die Funktion void TwiDataReceived(..).
TWI_CB_INLINE Die Funktion ALWAYS_INLINE void TwiDataReceived(...) wird in UsiTwiSlave.cfg.h bereit gestellt.

Ereignis TwiDataRequest

uint8_t TwiDataRequest() steht nur im Betriebsmodus STANDARD zur Verfügung. Die Callback-Funktion kann entweder als Standard-Funktion von der Applikation implementiert oder als Inline-Funktion in der Datei UsiTwiSlave.cfg.h bereit gestellt werden. Die Präprozessor-Konstante TWI_DATA_REQUEST_MODE ist entsprechend entweder auf STANDARD oder auf INLINE zu setzen. Die Definition von TWI_DATA_REQUEST_MODE und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird überprüft.


Geräte-Adresse

Das Modul besitzt zwei Adress-Modi. Über die Konstanten TWI_FIXED_TWI_ADDRESS kann diese Konfiguriert werden:

Adress-Modi
VARIABLE Die Präprozessor-Konstante TWI_FIXED_TWI_ADDRESS hat die Ausprägung NONE. Die Geräte-Adresse wird bei der Modul-Initialisierung über void TwiInitialize(uint8_t TwiAddress) festgelegt und kann zu einem späteren Zeitpunkt über void TwiSetAddress(uint8_t TwiAddress) geändert werden.
FIXED Die Geräte-Adresse wird über die Präprozessor-Konstante TWI_FIXED_TWI_ADDRESS vorgegeben und beim Kompilieren fest eingestellt. Die Initialisierung erfolgt über void TwiInitialize (void) deklariert. TwiSetAddress() steht nicht zur Verfügung. Mögliche Werte für TWI_FIXED_TWI_ADDRESS sind 1..127.

Die Definition von TWI_FIXED_TWI_ADDRESS und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird überprüft.

In beiden Adress-Modi kann über TWI_ACCEPT_BROADCAST_MSG festgelegt werden, ob zusätzlich zu der oben festgelegten Adresse auch die I²C-Broadcast-Adresse 0 akzeptiert wird. Mögliche Werte sind TRUE oder FALSE. Die Definition von TWI_ACCEPT_BROADCAST_MSG und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird überprüft.


Ergänzungen

Interrupt-Flag

Bei zeitkritischen Routinen (z.B. das Zählen von Schleifendurchläufen) ist es interessant zu wissen, ob zwischendurch ein Interrupt aufgetreten ist. In diesem Fall dauert der Schleifendurchlauf natürlich länger. Über die Präprozessor-Konstante USI_INT_MESSAGE kann festgelegt werden, dass bei jedem USI-Interrupt die Variable InteruptOccured mit true belegt wird. InteruptOccured wird in GeneralDefinitions.h definiert. Mögliche Werte sind TRUE oder FALSE. Die Definition von USI_INT_MESSAGE und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird überprüft.

TWI-Status

Intern wird das I²C-Interface über eine State-Machine realisiert. Über die Präprozessor-Konstante USI_OVF_TWI_STATE_REGISTER kann festgelegt werden, wo der Zustand gespeichert wird.  Mögliche Werte sind NONE und 2..17. Ist die Konstante mit NONE belegt, wird die Variable USI_TWI_Overflow_State im RAM abgelegt, ansonsten wird das angegebene Register genutzt. Die Definition von TWI_STATE_REGISTER und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird überprüft.