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!
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
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 ...
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 ;)?
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
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
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
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!
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
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:
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.
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.
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
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?