Hallo!
Ich möchte einen I2C Sensor mit einem ATMega328 auslesen.
Leider bleibt mein Programm bei der Startbedingung in der Zeile
while(!(TWSR&0xF8) != START); // START = 0x08
hängen. Kann mir jemand einen Tip geben?
Hier meine Funktion für die Startbedingung:
1
/*** Function to send a START Condition ***/
2
voidTWI_START(void)
3
{
4
// Send a START condition
5
TWCR=(1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
6
// Wait for TWINT Flag set. This indicates that the START condition has been transmitted
7
while(!(TWCR&(1<<TWINT)));
8
// Check value of TWI statusregister. Mask prescaler bits. If status different from START go to ERROR
Die TWI Schnittstelle ist recht kompliziert zu benutzen. Ich denke, es
kann sehr hilfreich sein, zumindest bei den ersten Versuchen das I²C
Protokoll "manuell" zu implementieren, indem du zwei IO Pins direkt
ansteuerst (Bit-Banging).
Bei mir hat das bisher immer so gut geklappt, dass ich den Code danach
gar nicht mehr auf TWI ändern wollte. Ich habe ca. 15 Projekte mit
Bit-Banging gemacht, aber nur 2 mit TWI.
Stefan U. schrieb:> Die TWI Schnittstelle ist recht kompliziert zu benutzen. Ich denke, es> kann sehr hilfreich sein, zumindest bei den ersten Versuchen das I²C> Protokoll "manuell" zu implementieren, indem du zwei IO Pins direkt> ansteuerst (Bit-Banging).>> Bei mir hat das bisher immer so gut geklappt, dass ich den Code danach> gar nicht mehr auf TWI ändern wollte. Ich habe ca. 15 Projekte mit> Bit-Banging gemacht, aber nur 2 mit TWI.
Natürlich geht das "manuell" besser, weil entscheidende Aktionen auf dem
TWI damit übergangen werden können und eine Fehlermeldung dafür dann
nicht existiert. Das funktioniert zwar bei den meisten I2C-Chips, jedoch
nicht immer. Ist also nicht wirklich eine Lösung.
Der TWI ist eigentlich gar nicht so kompliziert wie er aussieht. Die TWI
Schnittstelle im ATmega ist sehr brauchbar. Alle Aktionen die auf dem
TWI Bus stattfinden werden durch das TWINT Flag gemeldet. Am besten ist,
den Interrupt TWIE in TWCR setzen. Jedes mal wenn auf dem TWI eine
Aktion stattfindet, wird dann ein Interrupt ausgelöst. In der Interrupt
Routine lädst du das Statusregister TWSR und blendest die ersten 3 Bit´s
aus. Weil diese ersten 3 Bit´s andere Aufgeben haben! Also Bit0, Bit1
und Bit2 ausblenden! Dann bleibt ein Wert in den restlich verbliebenen
Bit´s übrig, der dann eine Art "Hausnummer" für die gerade erkannte
Aktion auf dem TWI darstellt. Ein Beispiel:
Bleibt nach Ausblenden der unteren 3 Bit´s hexadezimal 60 (also 0x60) im
TWSR Register übrig, dann wurde die Adresse für Schreiben empfangen und
wenn die empfangene Adresse mit dem Wert im Adressregister TWAR
übereinstimmt, wird ein ACK zur Bestätigung vom TWI automatisch
generiert. Würde aber z.B. im Statusregister TWSR der Wert 0x80 übrig
bleiben, so wären Daten empfangen wurden, die nun aus dem TWDR Register
ausgelesen werden können und es wurde ein ACK gesendet, was bedeutet, es
waren noch nicht die letzten Daten. Bei dem Wert 0x88 wären dann Daten
empfangen wurden die aber kein ACK hatten. Das wäre der Fall wenn der
Master damit das Ende des Datensendens kenntlich macht.
Also du siehst, es ist gar nicht so schwer, wenn man es verstanden hat.
Sobald eine Aktion auf dem TWI stattfindet, dann wird ein IRQ ausgelöst,
nachgesehen welcher Wert im TWSR Register steht und dann anhand dieses
Wertes in die entsprechend benötigte Routine gesprungen, die dann z.B.
dem Datenregister TWDR das benötigte Byte übergibt, oder in einer
anderen Routine den empfangenen Wert aus dem TWDR Register holt.
Wichtig bei dem TWI ist es aber immer, nach dem setzen des TWINT Flags
durch eine Aktion auf dem TWI Bus, dieses auch wieder per "Hand" zu
löschen. Also eine "1" darauf schreiben! Denn das TWINT wird nicht
automatisch zurück gesetzt.
Wenn du den TWI nutzt, dann kannst du auch alles was sich I2C nennt auch
anschließen! Mehr Informationen über die Werte in dem TWSR Register und
deren Bedeutung findest du in den Datenblättern und auch hier im Forum.
Noch ein Tipp. Wenn man den TWI soweit programmiert hat und ein
"Testprogramm" erstellt, wo gleich nach dem "Stop" sofort wieder ein
neues "Start" folgt, kann es sein, das der TWI das "Stop" noch gar nicht
fertig abgearbeitet hat, weil der Clock des TWI nur 100kHz oder 400kHz
beträt, der Takt für den ATmega aber 8MHz oder mehr und der neue "Start"
noch nicht generiert werden kann. Deshalb sollte man bei einem solchen
"Testprogramm" immer das Timing des TWI nicht aus den Augen verlieren!
Auch gibt es den "Restart" der nicht umsonst existiert und in manchen
Protokollen auch dringend benötigt wird!
noch ein anderer schrieb:> Der TWI ist eigentlich gar nicht so kompliziert wie er aussieht. Die TWI> Schnittstelle im ATmega ist sehr brauchbar. Alle Aktionen die auf dem> TWI Bus stattfinden werden durch das TWINT Flag gemeldet.
Das ist leider nicht richtig (ich wünschte, es wäre tatsächlich so, wie
du schreibst, das würde vieles vereinfachen).
> Noch ein Tipp. Wenn man den TWI soweit programmiert hat und ein> "Testprogramm" erstellt, wo gleich nach dem "Stop" sofort wieder ein> neues "Start" folgt, kann es sein, das der TWI das "Stop" noch gar nicht> fertig abgearbeitet hat
Genau das ist der Knackpunkt: das erfolgreiche Erreichen von
Stop-Zustands wird entgegen der sonstigen durchdachten Logik nicht per
Interrupt gemeldet, sondern ist nur per Polling von TWSTO zu ermitteln.
Unterläßt man aber die Verifizierung von Stop, knallt es im Problemfall
beim nächsten Start, also genau dann, wenn man üblicherweise den Bus
dringend benötigt. Vorher hätte man aber vielleicht viele Stunden Zeit
gehabt, den Bus "wiederzubeleben", wenn man denn nur gewusst hätte, dass
er tot ist...
Also: man braucht immer einen Timer-Interrupt zusätzlich, um die Sache
komplett interruptgesteuert abwickeln zu können. Nunja, der hilft (mit
der entsprechenden Behandlung in der state machine) auch gegen viele
andere denkbare Probleme auf dem I2C-Bus, ist also sowieso dringend
empfehlenswert. Aber das fehlende Interrupt-Event für "Stop erreicht"
macht die state machine deutlich komplexer, als sie sein könnte, wenn es
das Event gäbe.
Weil man halt im Timer-Interrupt TWSTO pollen muß, das aber wiederum nur
dann, wenn ein Stop überhaupt intendiert war, was wiederum eine
Hilfsvariable erfordert, damit die state machine das zuverlässig wissen
kann, während alles andere problemlos allein aus den Registern der
I2C-Hardware zu entnehmen wäre...
Irgendwie Mist. Aber eine bessere Lösung ist mir bisher nicht
eingefallen.
Eine eigene Frage?
Dieser Thread ist über 8 Jahre alt.
Stell sie in einem eigenen Thread – dort lesen mehr Leute mit, und sie wird öfter und schneller beantwortet.