2017-06-28 4 views
0

Ich habe versucht, meine ATTINY85 zu bit-bang I2C (lesen/schreiben). Ich habe die folgende Konfiguration:I2C bizarre Verzögerung Problem beim Lesen

PB0 = SDA 
PB1 = LED 
PB2 = SCL 

ich in der Lage bin ohne Probleme zu schreiben, aber das Lesen funktioniert nur, wenn ich meine Verzögerung() 'Funktion innerhalb der Leseschleife habe, so weit, so gut:

char i2c_read(void) 
{ 
    uint8_t B = 0; 
    DDRB &= 0b11111110; // switch PB0 to input 

    for (int bit = 0; bit < 0x08; bit++) 
    {   
     delay(); // <--!!!!!!!!! the root of all evil 

     SIGNAL_HIGH(PORT_SCL); 

     B <<= 1; 

     if(PINB & (1 << PB0)) 
     { 
      B |= 1;    
     } 
     else 
     { 
      B |= 0;    
     } 

     SIGNAL_LOW(PORT_SCL); 
    } 

    DDRB |= 0b00000001; // switch PB0 as output 

    i2c_nack(); 

    return B; 
} 

Wenn ich die Verzögerung() entferne, funktioniert I2C nicht mehr und ich kann nicht vom Gerät lesen (das Gerät antwortet nicht). Scheint logisch, aber der Grund, warum ich die Verzögerung() entfernen möchte, ist, dass es keine echte Verzögerung ist, es schaltet einfach eine LED ein und aus, die sich an einem anderen Pin befindet (PB1), I2C-Leitungen sind an PB0 und PB2 .

Die _delay_ms war zu langsam, also habe ich PB1 Pin ein- und ausgeschaltet, um eine kleine Verzögerung zu machen und das ist die einzige Möglichkeit. Hier sind die Inhalte meiner Verzögerungsfunktion, alles funktioniert gut, wenn ich es so lassen:

void delay() 
{ 
    LED_ON(); 
    LED_OFF(); 
} 

void LED_ON(void) 
{ 
    PORTB |= 0b00000010; // PB1 
} 

void LED_OFF(void) 
{ 
    PORTB &= 0b11111101; // PB1 
} 

Ich vermuten, dass ich wahrscheinlich ‚genagelt‘ eine perfekte Verzögerung, die die entsprechende Signallänge von dem anderen Gerät erwartet erzeugt, so ich habe versucht, die gleiche Verzögerung mit der for-Schleife und Oszilloskop zu machen:

void delay() 
{ 
    for(int i=0; i<20; i++){ } 
} 

Kein Glück, I2C Lesung nicht mehr funktioniert ..

Dann habe ich beschlossen, die LED auf einen anderen PIN zu wechseln und das PB1 verlassen ganz allein um zu sehen, ob es sich um eine Verzögerung handelt oder Pin/Schaltung bezogen:

void delay() 
{ 
    LED_ON(); 
    LED_OFF(); 
} 

void LED_ON(void) 
{ 
    PORTB |= 0b00001000; // PB3 
} 


void LED_OFF(void) 
{ 
    PORTB &= 0b11110111; // PB3 
} 

Und seltsamerweise hörte I2C auf, wieder zu arbeiten! Es funktioniert nur, wenn ich PB1 hoch/niedrig setze. Ich kann immer noch nicht verstehen, ob ich zufällig die perfekte Verzögerung gefunden habe, und es ist einfach so, dass das Einschalten von PB1 weniger Zeit in Anspruch nimmt als das Drehen des PB3 oder etwas mit der Schaltung selbst und LED, die eine Art Pull- Up/Pull-Down-Funktionalität (vergib meine Ignoranz, ich bin ein Anfänger) auf dem I2C, aber dann wieder PB1 ist überhaupt nicht mit den I2C-Linien verbunden.

Kann jemand bitte ein wenig Licht darauf werfen, warum es nur funktioniert, wenn ich PB1 an/aus einschalte, anstatt eine wirkliche Verzögerung zu machen? Vielen Dank!

Die vollständige Quelle:

#define PORT_SDA PB0 
#define PORT_SCL PB2 

#define SIGNAL_HIGH(PORT) PORTB |= (1 << PORT) 
#define SIGNAL_LOW(PORT) PORTB &= ~(1 << PORT) 

void delay(); 
void LED_ON(void); 
void LED_OFF(void); 

void i2c_init(void); 
void i2c_start(void); 
char i2c_read(void); 
void i2c_stop(void); 
void i2c_nack(void); 
void i2c_ack(void); 
void i2c_ack_slave(void); 
void i2c_write(uint8_t byte); 

void i2c_init() 
{ 
    DDRB = 0b00000010; // TODO: should be removed once the weird delay issue is solved 
    DDRB |= (1 << PORT_SDA); 
    DDRB |= (1 << PORT_SCL); 
} 

void i2c_start(void) 
{ 
    SIGNAL_LOW( PORT_SCL); 
    SIGNAL_HIGH(PORT_SDA); 
    SIGNAL_HIGH(PORT_SCL); 
    SIGNAL_LOW( PORT_SDA); 
    SIGNAL_LOW( PORT_SCL); 
} 

void i2c_stop(void) 
{ 
    SIGNAL_LOW( PORT_SCL); 
    SIGNAL_LOW( PORT_SDA); 
    SIGNAL_HIGH(PORT_SCL); 
    SIGNAL_HIGH(PORT_SDA); 
} 

void i2c_ack(void) 
{ 
    SIGNAL_LOW( PORT_SDA); 
    SIGNAL_HIGH(PORT_SCL); 
    SIGNAL_LOW( PORT_SCL); 
    SIGNAL_HIGH(PORT_SDA); 
} 

void i2c_nack(void) 
{ 
    SIGNAL_HIGH(PORT_SDA); 
    SIGNAL_HIGH(PORT_SCL); 
    SIGNAL_LOW( PORT_SCL); 
} 

void i2c_ack_slave(void) 
{ 
    SIGNAL_HIGH(PORT_SCL); 
    SIGNAL_LOW(PORT_SCL); 
} 

void i2c_write(uint8_t byte) 
{ 
    uint8_t bit; 

    for (bit = 0; bit < 0x08; bit++) 
    { 
     if((byte << bit) & 0x80) 
      SIGNAL_HIGH(PORT_SDA); 
     else 
      SIGNAL_LOW(PORT_SDA); 

     SIGNAL_HIGH(PORT_SCL); 
     SIGNAL_LOW(PORT_SCL); 
    } 

    // Clear both lines (needed?) 
    SIGNAL_LOW(PORT_SCL); 
    SIGNAL_LOW(PORT_SDA); 

    i2c_ack(); 
} 

char i2c_read(void) 
{ 
    uint8_t B = 0; 
    DDRB &= 0b11111110; // switch PB0 to input 

    for (int bit = 0; bit < 0x08; bit++) 
    {   
     delay(); // <-- the root of all evil 

     SIGNAL_HIGH(PORT_SCL); 

     B <<= 1; 

     if(PINB & (1 << PB0)) 
     { 
      B |= 1;    
     } 
     else 
     { 
      B |= 0;    
     } 

     SIGNAL_LOW(PORT_SCL); 
    } 

    DDRB |= 0b00000001; // switch PB0 as output 

    i2c_nack(); 

    return B; 
} 


void delay() 
{ 
    LED_ON(); 
    LED_OFF(); 
} 


void LED_ON(void) 
{ 
    PORTB |= 0b00000010; 
} 


void LED_OFF(void) 
{ 
    PORTB &= 0b11111101; 
} 
+0

Sie lesen im Wesentlichen die SDA-Linie an der steigenden Flanke der SCL-Linie; Vielleicht ist Ihr Gerät dann noch nicht bereit? Haben Sie versucht SDA näher an der fallenden Kante zu lesen? Das heißt, SIGNAL_HIGH (PORT_SCL); _delay_us (1); B = (B << 1) | !! (PINB & (1 << PB0)); SIGNAL_LOW (PORT_SCL); _delay_us (1); '(achtmal wiederholt)? –

Antwort

1

I2c definiert eine Reihe von Mindestzeiten für Signale - hier wichtig, die HIGH und LOW-Zeit von SCL sind - die Zeit, zu der SCL sollte vor dem nächsten Übergang stabil sein entgegengesetzter Zustand ist erlaubt. Diese Zeiten sind typisch ~ 5μs, genaue Werte sind dem Datenblatt zu entnehmen.

Der Loop-around am Ende Ihrer read-Schleife dauert etwas zwischen 2 und 3 Anweisungen, je nachdem, was der Compiler tut. Ein AVR-Befehl dauert, abhängig von Ihrer Taktrate, etwa ~ 200ns, also (ohne Verzögerung) SCL ist niedrig für ca. 600ns, geben oder nehmen - was viel zu kurz ist, zumindest scheinbar für Ihr spezielles "anderes-Endgerät" ".

Wenn Sie in der aufgerufenen Funktion einen Funktionsaufruf und einen Portzugriff eingefügt haben, haben Sie genügend Anweisungen eingegeben, um SCL lange genug NIEDRIG zu halten, damit es ordnungsgemäß funktioniert.

In Ihrem Code ist die HIGH-Zeit nicht so sehr das Problem, weil Sie den AVR mehr Anweisungen ausführen lassen, während SCL hoch ist. Offensichtlich reicht genug Zeit, um Ihren SCL lange genug zu halten.

Die Tatsache, dass Sie einen Port-Pin in Ihrer Verzögerungsfunktion umschalten, ist hier nicht relevant - Die einzige Relevanz ist, dass Sie etwas Zeit brauchen, während SCL niedrig ist. Offensichtlich, was Sie derzeit tun, ist eine Verschwendung von einem Port-Pin, nur um etwas Zeit zu verbringen warten - Verwenden Sie delay_us, experimentieren mit der Verzögerung statt dessen. Aber überprüfen Sie "das andere Ende" Datenblatt für das genaue Timing benötigt, 4-5μs sollte in Ordnung sein.

Warum hat Ihre Verzögerungsschleife nicht funktioniert? Es wurde höchstwahrscheinlich von dem Compiler optimiert, der erkannt hat, dass Sie in dieser leeren Schleife nichts Relevantes getan haben.

Idealerweise sollten Sie versuchen, SDA ungefähr in der Mitte der HIGH-Phase von SCL zu lesen - mit einer nicht gerollten Schleife für 8 Bit und einigen delay_us Spreads, die perfekt funktionieren sollten.