I2C-Kommunikation mit AVR - wie lassen sich die Leitungen „schweben“?

Ich versuche, I2C mit einem AVR-Controller ohne die ausgewiesenen internen TWI-Register zu implementieren.

Das I2C-Protokoll erfordert, dass das Master-Gerät zunächst die SDA- und SCL-Leitungen ansteuert, um eine Kommunikationstransaktion zu adressieren und zu beginnen. Das Problem, das ich habe, ist, dass Sie, nachdem eine Adresse über die serielle Leitung gesendet wurde, die Datenleitung "schweben" lassen müssen (dh vom Pull-up-Widerstand hochgezogen werden), damit der Slave die Leitung niedrig treibt für ein ACK-Signal.

Ich kann das I2C-Gerät erfolgreich adressieren, aber wenn ich versuche, die Leitung "schweben" zu lassen, habe ich ein paar Probleme. Ich hatte den Eindruck, dass in einem Eingangs-HIGH-Z-Zustand (Tri-State, dh DDRX 0, PORTX 1 für einen bestimmten Pin) im Wesentlichen wie folgt modelliert würde:

Geben Sie hier die Bildbeschreibung ein

Der Zustand der SDA-Leitung bleibt jedoch hoch, obwohl das I2C-Gerät mit einem ACK-Bit antwortet, indem es die Leitung auf niedrig treibt. Ich weiß, dass dies wahr ist, weil ich eine beträchtliche Verzögerung eingestellt habe, nachdem ich den Pin als Eingang festgelegt habe. Während dieser Verzögerung bleibt die Leitung hoch. Wenn ich jedoch das Kabel entferne, das die MCU mit der SDA-Leitung verbindet, fällt es aufgrund des Slave-Geräts auf LOW.

Kurz gesagt, wie gehen Sie mit dem „Loslassen“ einer Leitung um sicherzustellen, dass ihr Potenzial von den Geräten auf Ihrer I2C-Datenleitung angetrieben wird und nicht von der MCU beeinflusst wird?

/* Pin and direction register manipulations */ 
#define I2C_CLKDEL              10 //10uS
#define I2C_PORT                PORTB
#define SDA                     PB0 
#define SCL                     PB1
#define set_high(port, pin)     (port    |=  (1<<pin))
#define set_low(port, pin)      (port    &=  ~(1<<pin))
#define set_in(portDDR, pin)    (portDDR &=  ~(1<<pin))
#define set_out(portDDR, pin)   (portDDR |=  (1<<pin))

void clkStrobe(void){
    set_high(I2C_PORT, SCL); 
    _delay_us(I2C_CLKDEL); 
    set_low(I2C_PORT, SCL); 
    _delay_us(I2C_CLKDEL);
}

uint8_t sendByte(uint8_t byte){ //MSB first
    uint8_t count = 8, ack; 
    set_low(PORTB, SCL);  

    while( count-- ){
        if( byte & 0x80 )
            set_high(PORTB, SDA);
        else
            set_low(PORTB, SDA); 
        byte <<= 1;
        _delay_ms(I2C_CLKDEL); 
        clkStrobe();  
    }
    //set as input and read in the I2C port data
    set_in(I2C_PORT, SDA);
        _delay_ms(1000); 
    ack = PINB;  
    set_out(I2C_PORT, SDA); 
    clkStrobe();    //clock in the ACK bit 

    return (ack & (1<<SDA)); //return ACK bit 
}

Eine große Verzögerung wurde eingefügt, nachdem SDA auf der MCU als Eingang zum Testen festgelegt wurde. Die Linie bleibt hoch. Wenn die SDA-Leitung von der MCU entfernt wird (Draht nicht mehr angeschlossen), wird die SDA-Leitung durch das adressierte I2C-Gerät auf Low gesetzt (Gerät sendet ACK). Offensichtlich mache ich hier etwas falsch.

Setzen Sie das DDR-Bit für den Pin auf 0, wenn Sie ein ACK erwarten.
Genau das mache ich - setze den Pin als INPUT (was eine High-Z-Tri-State-Konfiguration ist). Die Linie bleibt jedoch unabhängig davon hoch. Ich habe den Code gepostet.
Wo? In diesem Code gibt es keinen Verweis auf das DDRB-Register.
Aktualisiert mit #define-Anweisungen.
Eventuell ein Taktungsproblem? Sind Sie sicher, dass Sie einen 9. Taktzyklus an SCL senden?
@Dzarda, ich habe die Taktung in ein separates Unterprogramm gesteckt und die SDA- und SCL-Leitungen tatsächlich durch Transistoren verbunden, um LEDs anzusteuern, damit ich weiß, was passiert. Es scheint eine Uhr zu geben, aber die SDA-Leitung bleibt hoch, obwohl der SDA-Pin als Eingang auf die MCU gelegt wird. Und es lohnt sich zu wiederholen, dass, wenn ich die Verbindung zwischen der SDA-Leitung und der MCU während der Verzögerung trenne (im obigen Code gezeigt, möglicherweise auf 5000 ms erweitert), die Leitung auf einen niedrigen Pegel gebracht wird. Somit hat das I2C-Gerät die Leitung die ganze Zeit niedrig gefahren, aber die MCU hatte anscheinend immer noch einen gewissen Einfluss auf die Leitung, obwohl sie eingegeben wurde.
Interessant ... Sie könnten versuchen, den Gesamtsystemverbrauch zu messen, um zu bestätigen, dass Ihre beiden Geräte in entgegengesetzte Richtungen fahren. Der Code sieht für mich ok aus...

Antworten (1)

Für set_in übergeben Sie I2C_PORT, was PORTB, aber Sie müssen übergeben DDRB.