Forum: Mikrocontroller und Digitale Elektronik TWI Interrupt manuell setzen


von AVR (Gast)


Lesenswert?

Hi,

ich versuche mich gerade an der Implementierung einer TWI (I2C) Master 
Bibliothek, welche Interrupt gesteuert ablaufen soll. Konkret stehe ich 
jetzt vor folgendem Problem:

Mittels einer Funktion "twi_master_start(uint8_t address)" habe ich eine 
START Condition erzeugt. Nun soll über "twi_master_transmit(unsigned 
char message, message_length)" eine bestimmte Menge an Bytes verschickt 
werden.

Da es wie gesagt Interrupt basiert ablaufen soll, suche ich nun eine 
Möglichkeit wie ich in der Funktion "twi_master_transmit()" den 
Interrupt mit dem Vektor "TWI_vect" ausführen kann. Dort würde ich dann 
gerne über den entsprechenden Puffer iterieren und die Nachrichten auf 
den Bus legen. Leider scheint es nur die folgenden Möglichkeiten zu 
geben einen solchen Interrupt auszulösen:

> The TWINT Flag is set in the following situations:
> • After the TWI has transmitted a START/REPEATED START condition
> • After the TWI has transmitted SLA+R/W
> • After the TWI has transmitted an address byte
> • After the TWI has lost arbitration
> • After the TWI has been addressed by own slave address or general call
> • After the TWI has received a data byte
> • After a STOP or REPEATED START has been received while still addressed
> as a Slave
> • When a bus error has occurred due to an illegal START or STOP condition

Die START Condition habe ich aber schon mittels "twi_master_start()" 
erzeugt und ggf. befindet sich die dafür notwendige Adresse nicht mehr 
im Puffer, sodass ich nicht nochmals eine erzeugen kann. Was gäbe es 
sonst noch für Möglichkeiten, bzw. wie würdet ihr das Ganze angehen?

Ich habe schon einige Implementierungen einer "TWI Master" Bibliothek 
gesehen, allerdings sind die meisten nicht Interrupt basiert. Atmel's 
Application Note 315 enthält eine Interruptgesteuerte 
Beispielimplementierung, dort wird aber die start() und transmit() 
Funktion "zusammengewürfelt", sodass ich am Anfang des Puffers immer die 
entsprechende Adresse befinden muss. Das gefällt mir nicht sonderlich 
gut.

Habt ihr brauchbare Vorschläge?

Vielen Dank!

von Klaus (Gast)


Lesenswert?

AVR schrieb im Beitrag #2885329:
> start() und transmit()
> Funktion "zusammengewürfelt", sodass ich am Anfang des Puffers immer die
> entsprechende Adresse befinden muss.

Für die I2C Hardware (im Master) ist das Adressbyte einfach nur das 
erste Byte, also Daten, die auf den Bus sollen. Sie kann dir nicht 
helfen, zwischen I2C Adresse, möglichem Adresspointer im Slave und Daten 
zu unterscheiden. Bau eine kleine State-Maschine in den Interrupt 
Handler, die immer das passende schickt.

MfG Klaus

von AVR (Gast)


Lesenswert?

Hm, ich bin mir nicht sicher, ob wir hier nicht aneinander 
vorbeisprechen.

Ich will im Wesentlichen folgenden Ablauf ermöglichen:

twi_master_start(0xD8);
twi_master_transmit(message, 2);
twi_master_stop();

Dabei soll das Ganze Interrupt gesteuert ablaufen, d.h. insbesondere das 
Verschicken der Nachricht, welche hier 2 Byte lang ist. Andernfalls 
müsste ich ja das entsprechende Flag pollen und warten. Das ist bei 100 
KHz SCL Takt und 16 MHz Mikrocontrollertakt doch eine ganze Menge an 
Zyklen, die hier verstreichen.

Wie meinst du das jetzt mit der Statemachine? So ganz klar ist mir das 
noch nicht ...

von AVR (Gast)


Lesenswert?

Was mir unklar ist (bzw. was meine ursprüngliche Frage ist): Wie bewege 
ich den AVR dazu die TWI ISR auszuführen ohne eine START bzw. STOP 
Condition loszuschicken? Ich habe ja idealerweise zu diesem Zeitpunkt 
die Adresse bereits auf den Bus gelegt und kenne diese gar nicht mehr. 
In der ISR würde ich jetzt den entsprechenden Puffer auf den Bus legen. 
Wie aber führe ich die ISR aus ;)?

von TestX .. (xaos)


Lesenswert?

lass doch in der ISR ne statemachine mitlaufen..dann weisst du was 
gerade los ist..

von Harald M. (mare_crisium)


Lesenswert?

AVR,

Atmel liefert für die TWI-Schnittstelle alles was Du brauchst 
mundgerecht: Das Wichtigste ist das TWSR-Register. Bei jedem Interrupt 
findest Du dort ein Byte, das Dir anzeigt, was seit dem vorhergehenden 
Interrupt passiert ist (Du musst den Inhalt des TWSR nur durch 8 
dividieren).

Das Programm, das beim TWI-Interrupt aufgerufen wird, muss also als 
Erstes das TWSR lesen und dann anhand des Wertes entscheiden, welche 
Aktion als Nächstes ausgeführt werden soll. Sowas macht man am Besten 
mit einer Tabelle, bei der Wert des TWSR (dividiert durch 8) als Index 
dient. Das ist es, was die anderen Antworter meinen, wenn sie von "state 
machine" (Zustandsautomat) schreiben.

Z.B.: Wenn in der Betriebsart Master-Transmitter die START-Bedingung 
erfolgreich abgesetzt wurde, enthält das TWSR beim Interrupt den Wert 
8*0x01=0x08. Dann weisst Du, dass jetzt die TWI-Adresse des Sklaven 
gesendet werden muss. Beim nächsten Interrupt bekommst Du dann 
8*0x03=0x18 zurück, wenn der Sklave mit ACK geantwortet hat. Wenn er NAK 
gesagt hat, bekommst Du 8*0x04=0x20 zurück. Je nachdem musst Du dann die 
nächste Aktion programmieren. Es geht wirklich wie am Schnürchen. Den 
ganzen Ablauf beim Master-Transmitter findest Du in der Doku, zum 
ATmega8 z.B., in Tabelle "Figure 87" auf Seite 187. Für die anderen 
Betriebsarten gibt es eigene Tabellen.

Ciao,

mare_crisium

von Peter D. (peda)


Lesenswert?

AVR schrieb im Beitrag #2885329:
> Die START Condition habe ich aber schon mittels "twi_master_start()"
> erzeugt und ggf. befindet sich die dafür notwendige Adresse nicht mehr
> im Puffer

Dann hast Du es falsch programmiert.
Im Main setzt Du nur das Startbit und der Interrupt macht den ganzen 
Rest inclusive Stop.
Dazu muß er natürlich einen Puffer übergeben kriegen. Und dieser Puffer 
enthält die Slaveadresse und alle Datenbytes.
Bzw. beim Lesen nur die Slaveadresse und der Interrupt füllt die 
Datenbytes.

AVR schrieb im Beitrag #2885367:
> Ich will im Wesentlichen folgenden Ablauf ermöglichen:
>
> twi_master_start(0xD8);
> twi_master_transmit(message, 2);
> twi_master_stop();

Das ist der Ablauf ohne Interrupt und somit Quatsch.

Mit Interrupt:
- erzeuge Puffer, schreibe Länge, Adresse und Daten hinein
- übergib den Zeiger auf den Puffer dem Interrupt
- setze Startbit
Und der Interrupt setzt irgendwann ein Ready-Flag, daß alles erledigt 
ist.


Peter

von katastrophenheinz (Gast)


Lesenswert?

Hallo AVR,

vielleicht hilft dir das hier weiter:
Atmels AppNote für Nutzung des TWI-Moduls als Master

Dokument: http://www.atmel.com/Images/doc2564.pdf
Beispielcode: http://www.atmel.com/Images/AVR315.zip

von Harald M. (mare_crisium)


Lesenswert?

AVR,

nur eins nicht vergessen: Am Ende der Prozedur, die beim TWI-Interrupt 
aufgerufen wird, MUSS(!!!) das Bit TWINT im Register TWCR immer auf 
"Eins" gesetzt werden. Andernfalls bleibt der Interrupt inaktiv.

Ciao,

mare_crisium

von AVR (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Dazu muß er natürlich einen Puffer übergeben kriegen. Und dieser Puffer
> enthält die Slaveadresse und alle Datenbytes.
Und genau dieser Ansatz gefällt mir nicht sonderlich, weil ich hier 
Daten und Adresse im Puffer "vermische". Lässt sich das denn nicht 
eleganter lösen? Vorallem gebe ich bei diesem Ansatz ja immer den Bus 
komplett frei, weil ich im letzten Schritt ja immer eine STOP Condition 
verschicke.

Vielen Dank!

von Oliver S. (oliverso)


Lesenswert?

AVR schrieb im Beitrag #2885707:
> weil ich hier
> Daten und Adresse im Puffer "vermische".

Dann schreib halt deine ISR so, daß der zwei Puffer übergeben werden, 
einer mit Daten, einer mit Adresse. Alles kann, nichts muß, your 
choice...

Oliver

von xfr (Gast)


Lesenswert?

Wenn Du den Code der ISR sowohl von den Bibliotheksfunktionen als auch 
von der ISR aus aufrufen willst, könntest Du ihn in eine eigene Funktion 
packen. Ist aber imo keine gute Idee, da es ineffizient ist und den 
Programmfluss undurchsichtig und fehleranfällig macht.

Wenn Du Dein Start/Transmit/Stop-Interface unbedingt willst, könntest in 
der transmit()-Funktion prüfen, ob die Start-Condition schon fertig 
gesendet wurde. Wenn ja, sendest Du das erste Byte von der 
transmit()-Funktion aus und alle folgenden in der ISR. Wenn nein, wird 
das erste Byte von der ISR aus versendet (nachdem die Start-Condition 
raus ist).

Die sinnvollste Variante ist aber imo, den kompletten Transmit mit nur 
einem Funktionsaufruf zu starten. Wenn Du darüber entscheiden können 
willst, ob Start/Stop-Conditions gesendet werden sollen, könntest Du ja 
noch ein entsprechendes Flag vorsehen. Also etwas so:
1
#define TWI_NONE  0
2
#define TWI_START 1
3
#define TWI_STOP  2
4
twi_transmit(uint8_t addr, const uint8_t* data, uint8_t len, uint8_t flags);

Eine Aufruf wäre dann z.B.
1
twi_transmit(0xD8, message, 2, TWI_START | TWI_STOP);

Oder Du machst das gleiche mit Deinen drei Funktionen. Nur dass 
twi_start() und twi_stop() nichts tatsächlich senden, sondern nur die 
Flags bzw. die Adresse zwischenspeichern, die dann im 
twi_transmit()-Befehl in einem Rutsch zusammen mit den Daten gesendet 
werden.

von Karl H. (kbuchegg)


Lesenswert?

AVR schrieb im Beitrag #2885707:

> Und genau dieser Ansatz gefällt mir nicht sonderlich, weil ich hier
> Daten und Adresse im Puffer "vermische".

Niemand hindert dich daran, dir eine 'Message' Struktur zu bauen, in der 
du dir die Dinge so ablegst, wie dir das besser gefällt.

Das Grundprinzip ist immer noch das gleiche: Eine Funktion stellt alle 
Informationen für die ISR zusammen und bereit, die die ISR zum Arbeiten 
braucht.
Ob das jetzt nur ein einziger BUffer ist, oder ob das eine Struktur ist, 
in der die die Dinge schön in einzelne Member aufdröselst - deine 
Entscheidung und hängt nur von dem handwerklichen Programmiergeschick 
ab.

> Vorallem gebe ich bei diesem Ansatz ja immer den Bus
> komplett frei, weil ich im letzten Schritt ja immer eine STOP
> Condition verschicke.

Wenn du in deiner Message Struktur einen Member hast, der aussagt: für 
diese Übertragung keine Stop-Condition erzeugen, dann kann sich die ISR 
danach richten und eben keine Stop-Condition erzeugen.


Das Prinzip ist:
Das Arbeitspferd ist die ISR.
Was immer die an Informationen braucht um so zu arbeiten wie du das 
haben willst, das braucht sie an Informationen. Also muss diese Info 
irgendwer bereitstellen. Zb. eine Funktion, die die Daten entgegen nimmt 
(ev. mit Zusatzinfo), und so aufbereitet, dass die ISR damit arbeiten 
kann.


Du bist der Programmierer, der Künstler! Du entscheidest, wie das 
konkret und im Detail funktionieren soll.

von Peter D. (peda)


Lesenswert?

AVR schrieb im Beitrag #2885707:
> Und genau dieser Ansatz gefällt mir nicht sonderlich, weil ich hier
> Daten und Adresse im Puffer "vermische".

Dann schreibste eben den Puffer als Struct (Länge, Adresse, Daten) und 
diese als Union mit nem Byte-Array.

Der CPU ist es völlig wurscht, welcher Variable Du welchen Namen gibst 
und an welcher RAM-Adresse sie landet.


AVR schrieb im Beitrag #2885707:
> Vorallem gebe ich bei diesem Ansatz ja immer den Bus
> komplett frei, weil ich im letzten Schritt ja immer eine STOP Condition
> verschicke.

Vermutlich stört Dich das fürs EEPROM lesen.
Dann einfach 2 Längenbyte übergeben, Schreiblänge und Leselänge und der 
Interrupthandler kann automatisch das Repeat-Start einfügen.


Peter

von AVR (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Dann schreibste eben den Puffer als Struct (Länge, Adresse, Daten) und
> diese als Union mit nem Byte-Array.
Irgendwie komme ich immer noch nicht ganz mit. Wofür brauche ich denn 
die Union? Im Struct würde dann doch schon alles stehen? Bzw. wie "groß" 
wäre das Byte-Array dann überhaupt?

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.