Hallo!
Ich bin gerade dabei, mir eine interruptgesteuerte I2C-Lib zu stricken.
Ich sitze daran auch schon einige Wochen und komme einfach nicht weiter.
Momentan teste ich alles auf einem ATmega2560.
Mit der Library von Peter Fleury funktionert das Ganze einwandfrei, also
kann es schon mal nicht an der Verdrahtung liegen.
Im Anhang finden sich meine Quelldateien. Kurze Erklärung:
Mit i2c_initMaster() wird die Schnittstelle mit dem AVR als Master
initialisiert, übergeben wird die SCL-Clock in kHz. Mit i2c_send()
sollen Daten zum Senden bzw. ein Datenpuffer zum Reinlesen
bereitgestellt werden.
1
/* Initialize I2C interface as master. Requires global interrupts to be enabled.
2
* uint16_t freq: bit rate in kHz
3
*
4
*/
5
voidi2c_initMaster(uint16_tfreqInKHz);
6
7
/* Puts an address and pointer to a data buffer into a transmit FIFO. If the
8
* I2C interface is in idle, a transmission is initiated.
Die zweite ISR(TWI_vect) ist für Debugging-Zwecke gedacht, die den Wert
von TWSR auf Ports rausschreibt bzw. über den UART an den PC sendet.
Testweise hab ich mal ein Progrmm geschrieben, das mit der Debug-ISR
arbeitet. Nachdem die ISR abgearbeitet wurde, stehen an PORTA 0xB3 und
an PORTC 0x20. Über den UART wurden 7 Leerzeichen (?) und ein Rufzeichen
gesendet.
Grüße
Hab das ZIP nicht aufgemacht.
Aber dein i2c_send interface sieht nicht besonders geeignet für
Interrupt-Betrieb aus. Das muss doch blockieren bis alles gesendet ist,
warum dann Interrupt-Betrieb?
Oder willst du eine große Queue haben und schreibst so selten rein, dass
sie nicht überläuft?
Sebastian schrieb:> Oder willst du eine große Queue haben und schreibst so selten rein, dass> sie nicht überläuft?
Genau. Im Interrupt werden dann nur die Daten aus diesem Puffer
abgearbeitet.
restfet schrieb:> Genau. Im Interrupt werden dann nur die Daten aus diesem Puffer> abgearbeitet.
Und was passiert wenn der leer ist? Du startest dann genau welche neue
TWI-Operation mit dem Löschen des TWINT am Ende der TWI ISR?
Also TWI Interrupt nach letzter Operation abschalten, TWINT nicht
löschen und im i2c_send den Interrupt wieder anschalten und TWINT
löschen.
Also hier eine genauere Erklärung nur mal vom Schreib- (Transmitter)
Vorgang:
Mit i2c_send() werden eine Slave-Adresse, Datenbytes und deren Anzahl in
einen Puffer kopiert. Wenn das I2C gerade arbeitet, passiert nichts
weiter. Wenn das I2C aber grade inaktiv ist, wird eine Startbedingung
ausgelöst. Danach sollte ja ein Interrupt auftreten.
Im Interrupt ist eine State-Machine mittels switch-case implementiert.
Der aktuelle Status entspricht dabei (TWSR & 0xF8).
Der sollte im ersten Moment anzeigen, dass eine Startbedingung
aufgetreten ist. Im ersten case-Zweig wird dann die Slave-Adresse und
das R/W-Bit (in dem Fall W) in TWDR geladen. Dieser Zweig wird auch
ausgeführt, wenn eine Repeated-Start-Bedingung auftritt. Danach wird
noch TWINT gelöscht (auf 1 gesetzt) und die ISR verlassen.
Nachdem vom Slave ein ACK zurückkommt, tritt der nächste Interrupt auf.
In dem case-Zweig wird ein Index auf 0 gesetzt. Danach wird gleich in
den nächsten case-Zweig gesprungen, wo überprüft wird, ob sich noch
Daten im aktuell bearbeiteten Puffer befinden (Index < Anzahl
Datenbytes). Wenn das der Fall ist, wird das nächste Datenbyte in TWDR
geladen und der Index erhöht. Danach wird wieder TWINT gelöscht und die
ISR verlassen.
Das wird so lange wiederholt, wie sich Datenbytes im aktuellen Puffer
befinden.
Sobald das letzte Datenbyte übermittelt wurde und ein ACK vom Slave
zurückkommt, wird die Slave-Adresse im aktuellen Puffer auf 0xFF gesetzt
(um anzuzeigen, dass dieser fertig abgearbeitet wurde) und der Leseindex
des Puffers erhöht.
Wenn die Slave-Adresse des nun aktuellen Puffers nicht 0xFF ist, wird
eine Repeated-Start-Bedingung ausgeführt und die Daten dieses Puffers
abgearbeitet. Wenn die Slave-Adresse 0xFF ist, wird damit angezeigt,
dass sich nicht (mehr) gültige Daten im Puffer befinden und eine
Stop-Bedingung ausgeführt. Danach wird wieder TWINT gesetzt und die ISR
verlassen.
Der Read- (Receiver) Vorgang läuft ähnlich ab, mit dem Unterschied, dass
eben die Daten vom Bus (von TWDR) in den Puffer kopiert werden. Sobald
die in i2c_send() angegebene Anzahl der Datenbytes empfangen wurde, wird
ein NAK vom Master ausgegeben.
Ich hoffe, ich konnte hier meine Absichten ausreichend verständlich
schildern...
Ich kann in meinem Programm einfach nicht den Fehler finden.
restfet schrieb:> Die zweite ISR(TWI_vect) ist für Debugging-Zwecke gedacht, die den Wert> von TWSR auf Ports rausschreibt bzw. über den UART an den PC sendet.
Ist das echter Quellcode, oder hast du den eigens fürs Forum
zusammengeklebt? Immerhin gibts 2 gleichnamige ISRs im gleichen File,
ohne dass eine davon ausgeblendet wäre.
Ja, das passt schon so. Das einzige, das ich daran fürs Forum geändert
habe, waren die Tabulatoren, die ich gegen 4 mal Leerzeichen ersetzt
habe ;-)
A. K. schrieb:>2 gleichnamige ISRs im gleichen File
siehe
restfet schrieb:> Die zweite ISR(TWI_vect) ist für Debugging-Zwecke gedacht, die den Wert> von TWSR auf Ports rausschreibt bzw. über den UART an den PC sendet.>> Testweise hab ich mal ein Programm geschrieben, das mit der Debug-ISR> arbeitet. Nachdem die ISR abgearbeitet wurde, stehen an PORTA 0xB3 und> an PORTC 0x20. Über den UART wurden 7 Leerzeichen (?) und ein Rufzeichen> gesendet.
Ich würde empfehlen, in der ISR nicht einfach nur einzelne Bits von TWCR
zu setzen, sondern dieses Register stets komplett mit dem gewünschten
Inhalt zu schreiben. So macht das meiner Erinnerung nach auch Atmels
Vorlage, und so macht es auch mein Code.
So vermisse ich bei dir beispielsweise die Handhabung vom TWSTA, das
setzt sich nämlich nicht von allein zurück.
A. K. schrieb:> Was soll das> TWSR |= (0xF8);> bringen? Diese Bits sind durchweg nur lesbar.
Oha, das hab ich ganz übersehn. Damit wollte ich TWSR resetten.
A. K. schrieb:> So vermisse ich bei dir beispielsweise die Handhabung vom TWSTA, das> setzt sich nämlich nicht von allein zurück.
Damit hast du mich wohl auf die richtige Fährte gelockt! Jetzt
funktionierts schon viel besser!
Melde mich hier mal wieder zurück.
Ich habe zwischenzeitlich versucht an meinem I2C-Interface
weiterzuarbeiten und habe alles ein bisschen umgebaut. Im Wesentlichen
solls jetzt drei verschiedene Funktionen für die Übertragung geben: zwei
zum Versenden (Master Transmitter) und eine zum Empfangen (Master
Receiver). Zusätzlich noch eine Initialisierungsfunktion, eine um den
Status des Interface zu überprüfen und eine um die Daten aus dem Puffer
zu holen.
Das funktioniert soweit auch so wie ich es mir vorstelle. Nur leider
habe ich es nicht geschafft, das ganze gepuffert hinzubekommen. Somit
muss ich immer die aktuelle Übertragung abwarten, bis ich mit der
nächsten beginnen kann. Für komplexere Übertragungen muss ich mir im
Hauptprogramm umfangreiche State Machines zusammenfrickeln.
Ich habe mich auch schon an einer gepufferten Version versucht, nur
denke ich, dass mir dabei der Compiler einen Strich durch die Rechnung
macht und die Funktionalität "wegoptimiert". Kann mir hier jemand sagen,
was ich dabei anders machen muss, damit das funktioniert?
Andere Verbesserungsvorschläge werden auch gerne angenommen ;-)
Im Anhang finden sich meine beiden Versionen des Interfaces, die
funktionierende ungepufferte und mein nicht funktionierender Versuch
eines gepufferten Interfaces.
Hier kurz meine Vorstellung, wie das alles im Hauptprogramm zur
Anwendung kommen soll:
1
#define SLAVE_ADRESSE 0x34
2
3
voidmain()
4
{
5
uint8_tdataArray[3]={1,2,3};
6
uint8_tstateMachine=0;
7
8
i2c_initMaster(100);
9
10
while(1){
11
if((i2c_getState()&I2C_ST_IDLE)==I2C_ST_IDLE){
12
switch(stateMachine){
13
case0:
14
/* einzelnes Byte senden */
15
i2c_transmitSingle(SLAVE_ADRESSE,0xAA);
16
stateMachine++;
17
break;
18
case1:
19
/* drei Bytes aus dataArray senden */
20
i2c_transmitMultiple(SLAVE_ADRESSE,dataArray,3);
21
stateMachine++;
22
break;
23
case2:
24
/* zwei Bytes sollen empfangen werden */
25
i2c_receive(SLAVE_ADRESSE,2);
26
stateMachine++;
27
break;
28
}
29
30
if((i2c_getState()&I2C_DTA_RDY)==I2C_DTA_RDY){
31
/* zuvor empfangene Bytes werden in dataArray abgelegt */
32
memcpy(dataArray,i2c_getData().data,2);
33
}
34
}
35
36
/*
37
... etwas anderes machen ...
38
*/
39
}
40
}
Wenn alles gepuffert wird, könnte es so aussehen...
Bin nur mal kurz quer drüber:
Kein START jenseits der ersten message.
Der Code enthält Sequenzen, die Chaos produzieren, wenn sie von der ISR
unterbrochen werden. Versuch mal, dich mit dem Gedanken anzufreunden,
dass Interrupts überall auftreten können, wo sie nicht explizit
ausgeschlossen sind.
A. K. schrieb:> Kein START jenseits der ersten message.
Ja, in der ISR würde ein Flag in der state-Variable gesetzt werden, das
anzeigt, dass noch mehr Daten im Puffer vorhanden sind. Im Hauptprogramm
wäre dann die state-Variable abgefragt worden und entsprechend eine
Start-Bedingung ausgelöst worden.
Ich hab das jetzt wieder so geändert, dass in der ISR ein Start
ausgelöst wird, wenns mehr zu versenden gibt.
> Der Code enthält Sequenzen, die Chaos produzieren, wenn sie von der ISR> unterbrochen werden. Versuch mal, dich mit dem Gedanken anzufreunden,> dass Interrupts überall auftreten können, wo sie nicht explizit> ausgeschlossen sind.
Ich hab die Interrupts in den I2C-Funktionen mal global ausgeschaltet.
Die Änderungen haben aber leider nichts gebracht.
Anbei auch ein Oszi-Screenshot einer Übertragung. Diese wurde durch
folgenden Code prodziert..: