PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : TWI-Bussystem



ikarus_177
08.08.2009, 13:18
Hallo allerseits und einen schönen Samstag,

ich möchte gerne etwa 8 Controller an ein TWI/I²C - Bussystem hängen, um Daten zwischen denselben austauschen zu können. Vorerst - zu Testzwecken und Einarbeitung - habe ich den Aufbau auf 2 Controller reduziert. Ein RN-Control und ein Mega32 auf einem Steckbrett sollen miteinandern ins Gespräch kommen. Ich habe SCL und SDA der beiden µC's verbunden und mit 10k Pull-Up-Widerständen versehen. Die Leitungslänge des Busses beträgt etwas mehr als 40cm.

Nun habe ich mich im Wiki schlau gemacht, welche Register usw. man braucht, um die nötigen Einstellungen vorzunehmen. Hier (http://rn-wissen.de/index.php/TWI) findet man ja eine sehr schöne Zusammenstellung. Bei der Umsetzung in C habe ich mich an diesen (http://rn-wissen.de/index.php/TWI_Praxis) Programmbeispielen, am Datenblatt sowie an den ApplicationNotes 311 (http://www.atmel.com/dyn/resources/prod_documents/doc2565.pdf) und 315 (http://www.atmel.com/dyn/resources/prod_documents/doc2564.pdf) orientiert und mal einige Zeilen Code geschrieben.

Leider gibts noch irgendwo einen Haken, den ich bis jetzt nicht entdecken konnte. Es scheint (für mich) so, als würde der Slave zwar angesprochen werden (kurzes Aufblitzen der LEDs an den Busleitungen), aber keine sinnvollen Daten empfangen können. Zu Testzwecken habe ich mir das empfangene Byte an einem Port ausgeben lassen, an dem ich aber stets den Wert Null messe. Nach stundenlangem Ausprobieren, kontrollieren und ausbessern kann ich keine Fehler mehr entdecken. Aber meist ist es ja so, dass einem die eigenen Fehler am wenigsten auffallen, und drum möchte ich euch bitten, vielleicht mal einen kurzen Blick auf meinen Code zu werfen, vielleicht fällt euch ja was auf.

Hier die Zeilen für den Master:
#include <avr/io.h>
#include <avr/interrupt.h>
#include <inttypes.h>
#include <util/twi.h>

void i2c_init (void);
uint8_t i2c_sendbyte (uint8_t Adress, uint8_t Data);
uint8_t i2c_status (void);

volatile uint8_t TWIStatus = 0;

int main (void)
{
DDRD = 0xFF;
uint8_t Status = 0;

i2c_init();

Status = i2c_sendbyte (26,15);

PORTD = Status; //Ausgabe des Status zu Debugzwecken

while(1);

return(0);
}

void i2c_init (void)
{
//Einstellen des Bustaktes auf 400kHz:
TWSR = 0;
TWBR = 12;
}

uint8_t i2c_sendbyte (uint8_t Adress, uint8_t Data)
{
uint8_t TWIStatus = 0; //Variable für Fehlercode

//Senden der Startbedingung und zurücksetzen des Flags:
TWCR = (1 << TWSTA) | (1 << TWINT) | (1 << TWEN);

//Warten auf Abschluss der Aktion:
TWIStatus = i2c_status();

/* -------------------------------------------------------------------------- */
/* Falls der Bus nicht gerade als "busy" gesetzt ist, wurde das TWI-Interrupt */
/* Bit gesetzt, der Statuswert kann nun ausgelesen werden. */
/* Ist kein Fehler aufgetreten, kann die Adresse mit gesetztem Write-Bit */
/* ausgegeben werden. */
/* -------------------------------------------------------------------------- */

if ((TWIStatus == 0x08) || (TWIStatus == 0x10))
{
//Zugriff auf Bus erlaubt, nun wird die Slaveadresse mit gesetztem Write-Bit ausgegeben:
TWDR = (Adress << 1) & 0xFE;
TWCR = (1 << TWINT) | (1 << TWEN); //TWINT-Bit zurücksetzen, um nächste Aktion auszulösen

//Warten auf Abschluss der Aktion:
TWIStatus = i2c_status();

if ((TWIStatus == 0x18) || (TWIStatus == 0x20))
{
//Slaveadresse wurde erfolgreich gesendet, nun können Daten verschickt werden:
TWDR = Data;
TWCR = (1 << TWINT) | (1 << TWEN); //TWINT-Bit zurücksetzen, um nächste Aktion auszulösen

//Warten auf Abschluss der Aktion:
TWIStatus = i2c_status();

if ((TWIStatus == 0x28) || (TWIStatus == 0x30))
{
TWIStatus = 0; //kein Fehler
}

}
}

//Senden der Stoppbedingung:
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
return(TWIStatus);
}

uint8_t i2c_status()
{
//Warten, bis das Interrupt-Bit gesetzt und so das erfolgreiche Ende einer Busaktion angekündigt wurde
while(!(TWCR & (1 << TWINT)));

//Ausmaskieren der Prescaler-Bits und Rückgabe des Statuswertes
return(TWSR & 0xF8);
}




... und für den Slave:


#include <avr/io.h>
#include <avr/interrupt.h>
#include <inttypes.h>
#include <util/twi.h>

uint8_t i2c_getbyte(void);
void i2c_init(uint8_t Adress);
uint8_t i2c_status(void);

int main (void)
{
DDRD = 0xFF;

uint8_t buffer = 0;
i2c_init(26);

buffer = i2c_getbyte(); //Empfangen des Bytes
PORTD = buffer; //Ausgabe zur Kontrolle

while(1);

return(0);
}

void i2c_init(uint8_t Adress)
{
TWSR = 0;
TWAR = (Adress << 1);
TWCR = (1 << TWEA) | (1 << TWEN);
TWDR = 0xFF;
}

uint8_t i2c_getbyte(void)
{
uint8_t Status = 0;
uint8_t Byte = 0;

//Warten, bis der Slave angesprochen wurde
Status = i2c_status();

Byte = Status;

TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWEN); //TWINT-Bit zurücksetzen, um nächste Aktion auszulösen

return(Byte);
}

uint8_t i2c_status()
{
//Warten, bis das Interrupt-Bit gesetzt und so das erfolgreiche Ende einer Busaktion angekündigt wurde
while(!(TWCR & (1 << TWINT)));

//Ausmaskieren der Prescaler-Bits und Rückgabe des Statuswertes
return(TWSR & 0xF8);
}



Herzlichen Dank fürs Lesen; vielleicht hat ja einer eine Idee.

Schönes Wochenende,
ikarus_177

PicNick
08.08.2009, 18:16
Bevor ich die Story der ganzen Bits nachlese:
Du solltest auf jeden Fall beim Slave das Empfangen INNERHALB der Schleife haben. So macht er ja nur einen einzigen Versuch, dann musst du wieder resetten.
Also etwa so :


while(1)
{
buffer = i2c_getbyte(); //Empfangen des Bytes
PORTD = buffer; //Ausgabe zur Kontrolle
}
return(0);


Beim Empfangen vernisse ich auch das Lesen von den Daten selbst (TWDR)

ikarus_177
08.08.2009, 20:39
Hi PicNick,

ich hab' mal die Endlosschleife beim Slave ergänzt (das fehlende TWDR war zu Testzwecken, er sollte mir den Statuscode zurückgeben); so schauts momentan aus:

Slave:
#include <avr/io.h>
#include <avr/interrupt.h>
#include <inttypes.h>
#include <util/twi.h>

uint8_t i2c_getbyte(void);
void i2c_init(uint8_t Adress);
uint8_t i2c_status(void);

int main (void)
{
DDRD = 0xFF;

uint8_t buffer = 0;
i2c_init(26);

while(1)
{
buffer = i2c_getbyte(); //Empfangen des Bytes
PORTD = buffer; //Ausgabe zur Kontrolle
}

return(0);


void i2c_init(uint8_t Adress)
{
TWSR = 0;
TWAR = (Adress << 1);
TWCR = (1 << TWEA) | (1 << TWEN) | (0 << TWSTA) | (0 << TWSTO);
}

uint8_t i2c_getbyte(void)
{
uint8_t Status = 0;
uint8_t Byte = 0;

//Warten, bis der Slave angesprochen wurde
Status = i2c_status();

Byte = TWDR;

TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWEN) | (0 << TWSTA) | (0 << TWSTO); //TWINT-Bit zurücksetzen, um nächste Aktion auszulösen

return(Byte);
}

uint8_t i2c_status()
{
//Warten, bis das Interrupt-Bit gesetzt und so das erfolgreiche Ende einer Busaktion angekündigt wurde
while(!(TWCR & (1 << TWINT)));

//Ausmaskieren der Prescaler-Bits und Rückgabe des Statuswertes
return(TWSR & 0xF8);
}



Master:
#include <avr/io.h>
#include <avr/interrupt.h>
#include <inttypes.h>
#include <util/twi.h>

void i2c_init (void);
uint8_t i2c_sendbyte (uint8_t Adress, uint8_t Data);
uint8_t i2c_status (void);

volatile uint8_t TWIStatus = 0;

int main (void)
{
DDRD = 0xFF;
uint8_t Status = 0;

i2c_init();

Status = i2c_sendbyte (26,15);

PORTD = Status; //Ausgabe des Status zu Debugzwecken

while(1);

return(0);
}

void i2c_init (void)
{
//Einstellen des Bustaktes auf 400kHz:
TWSR = 0;
TWBR = 12;
}

uint8_t i2c_sendbyte (uint8_t Adress, uint8_t Data)
{
uint8_t TWIStatus = 0; //Variable für Fehlercode

//Senden der Startbedingung und zurücksetzen des Flags:
TWCR = (1 << TWSTA) | (1 << TWEN);

//Warten auf Abschluss der Aktion:
TWIStatus = i2c_status();

/* -------------------------------------------------------------------------- */
/* Falls der Bus nicht gerade als "busy" gesetzt ist, wurde das TWI-Interrupt */
/* Bit gesetzt, der Statuswert kann nun ausgelesen werden. */
/* Ist kein Fehler aufgetreten, kann die Adresse mit gesetztem Write-Bit */
/* ausgegeben werden. */
/* -------------------------------------------------------------------------- */

PORTD = TWIStatus;

if ((TWIStatus == 0x08) || (TWIStatus == 0x10))
{
//Zugriff auf Bus erlaubt, nun wird die Slaveadresse mit gesetztem Write-Bit ausgegeben:
TWDR = (Adress << 1) & 0xFE;
TWCR = (1 << TWINT) | (1 << TWEN) | (0 << TWSTA); //TWINT-Bit zurücksetzen, um nächste Aktion auszulösen

//Warten auf Abschluss der Aktion:
TWIStatus = i2c_status();

PORTD = TWIStatus;


if (TWIStatus == 0x18)
{
//Slaveadresse wurde erfolgreich gesendet, nun können Daten verschickt werden:
TWDR = Data;
TWCR = (1 << TWINT) | (1 << TWEN); //TWINT-Bit zurücksetzen, um nächste Aktion auszulösen

//Warten auf Abschluss der Aktion:
TWIStatus = i2c_status();

PORTD = TWIStatus;

if ((TWIStatus == 0x28) || (TWIStatus == 0x30))
{
TWIStatus = 255; //kein Fehler
}

}
}

//Senden der Stoppbedingung:
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
return(TWIStatus);
}

uint8_t i2c_status()
{
//Warten, bis das Interrupt-Bit gesetzt und so das erfolgreiche Ende einer Busaktion angekündigt wurde
while(!(TWCR & (1 << TWINT)));

//Ausmaskieren der Prescaler-Bits und Rückgabe des Statuswertes
return(TWSR & 0xF8);
}




Ich habe mir die Statuscodes beim Master mal angesehen, da schauts so aus, als ob er kein ACK vom Slave bekommt, obwohl bei diesem das TWEA-Bit gesetzt ist.

Viele Grüße

PicNick
08.08.2009, 21:12
Verstehe, den Status auf das Port auszugeben ist keine schlechte Idee, denn wenn der Status passt, kannst du das Byte immer noch lesen.

In der Mega-PDF ist ja bei TWI eine ganze Speisekarte von Zuständen.
Sie dort mal nach, was du denn kriegst.

(ich bin aber für heute weg, ausserdem muss ich mich auch erstmal schlauer machen, da ich das TWI zeugs schon eine Weile nicht mehr programmiert habe)

ikarus_177
09.08.2009, 11:28
Hi,

laut ATMega-Datenblatt und Statuscodes schauts so aus, als ob der Master kein ACK vom Slave bekommt (Statuscode 0x20), der Slave gibt mir stets den Code 0 aus, was eine "illegal bus operation" bedeuten soll, also START bzw. STOP an den falschen Stellen.

Viele Grüße

hosti
09.08.2009, 14:44
hast du dir meinen code angesehen?

ikarus_177
10.08.2009, 17:29
Ja, hab ich gemacht. Vom Aufbau her ist der Teil für den Master auch ganz ähnlich wie meiner. Das Empfangen hast du ja mit den Interrupts gemacht, das würde ich bei mir gerne vermeiden.

Noch eine kleine Frage: muss der Slave eigentlich verbindlich ein ACK senden? Oder nur, wenn ich das so vorsehe, bzw. braucht die TWI-Hardware dieses Signal, um korrekt weitermachen zu können?

Viele Grüße

ikarus_177
22.08.2009, 12:10
Hallo,

ich hab in den letzten Tagen nicht recht viel Zeit gehabt, da ich momentan ein Ferialpraktikum mache. Heute hab ich mich aber nochmals hingesetzt, und statt den 10k nun zwei 1k Widerstände als Pull-up's verwendet.

Mit dem selben Code hab ich nun schon was erreicht, das man ansatzweise als gelungene Übertragung bezeichnen könnte ;-)
Der Slave empfängt zwar Daten, die empfangenen Bytes sind jedoch genau das doppelte dessen, das der Master weggeschickt hat (bzw. um 1 nach links geshiftet). Sehr seltsam das ganze :-k

Gibt es dafür eine "natürliche" Erklärung?

Viele Grüße