Die I2C-Kommunikation startet nicht, es sei denn, der SDA-Pullup-Widerstand wird wieder eingefügt

Ich arbeite an einem Projekt, bei dem ein PIC12LF1552-Mikroprozessor I2C verwendet, um mit einem TMP75-Temperatursensor zu sprechen. Wie der Titel schon sagt, tritt immer wieder ein sehr seltsames Problem auf.

Überblick

Jedes Mal, wenn ich das Mikro einschalte, bleiben SDA (Datenleitung) und SCL (Taktleitung) niedrig. Nach der Wartezeit von 18 Sekunden im Code gehen beide Leitungen hoch, wenn die I2C_init-Funktion den I2C-Bus startet. An diesem Punkt weiß ich, dass das Mikro in das Konfigurationsregister im Temperatursensor schreiben sollte. Die Endlosschleife blinkt jedes Mal, wenn das Mikro auf den Temperatursensor schreibt, eine LED.

Das Problem ist, dass die I2C-Leitungen hoch bleiben, es sei denn, ich entferne den SDA-Pullup-Widerstand und stecke ihn dann wieder ein. Sobald der SDA-Pullup wieder angeschlossen ist, erfasst das Oszilloskop eine vollkommen normale I2C-Kommunikation, und alles funktioniert einwandfrei. (Siehe unten)

Umfang Screenshot 4.7k Klimmzüge

Zielfernrohr erschossen

Scope Screenshot 1k Klimmzüge

Zielfernrohrschuss2

Pullup-Widerstände

Ich verwende jetzt 1k-Pullup-Widerstände, aber im obigen Screenshot habe ich 4,7k verwendet, was zu groß ist. Mit 1k-Widerständen sind die Taktleitung und die Datenleitung viel quadratischer und sehen richtiger aus. Wenn die internen schwachen Pullups auf dem Mikro aktiviert sind, würde die I2C-Kommunikation überhaupt nicht funktionieren.

Zusätzliche Information

Ich habe überprüft, dass jedes einzelne Bit des gesendeten I2C-Schreibbefehls korrekt ist. Ich habe auch einen Raspberry Pi verwendet, um den TMP75 erfolgreich zu lesen und zu schreiben.

Die im Mikro eingestellte I2C-Busgeschwindigkeit beträgt 100 kHz. Der TMP75 kann bis zu 400 kHz verarbeiten, das ist also kein Problem.

Das erneute Einfügen des SDA-Pullups ermöglicht die normale I2C-Kommunikation. Das erneute Einfügen des SCL-Pullups, während der I2C "hängt", behebt das Problem jedoch nicht.

Ich habe mehrere Professoren an meiner Hochschule nach diesem Problem gefragt, aber keiner von ihnen hat eine Lösung gefunden.

Frage

Meine Frage ist also: Was könnte möglicherweise dazu führen, dass der SDA-Pullup erneut eingefügt werden muss, damit der I2C mit der Kommunikation beginnen kann?

Code als Referenz

Haupt c

#include <xc.h>
#include "i2c.h"

//PIC12LF1552 Configuration Bit Settings
// 'C' source line config statements

#define _XTAL_FREQ 16000000
#define I2C_SLAVE 0x48

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = ON         // Flash Program Memory Code Protection (Program memory code protection is enabled)
#pragma config BOREN = OFF       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LPBOR = OFF      // Low-Power Brown Out Reset (Low-Power BOR is disabled)
#pragma config LVP = ON         // Low-Voltage Programming Enable (Low-voltage programming enabled)

// Send a command to I2C chip
void i2c_write_command(unsigned char address, unsigned char command)
{
i2c_Start();                        // Send Start
i2c_Address(I2C_SLAVE, I2C_WRITE);  // Send slave address - write operation
i2c_Write(address);                 // Send register address 
i2c_Write(command);                 // Send register value (command)
i2c_Stop();                         // Send Stop
}

// Read a char from I2C chip
unsigned char i2c_read_command(unsigned char address)
{
unsigned char read_byte;
// Read one byte
i2c_Start();                        // send Start
i2c_Address(I2C_SLAVE, I2C_WRITE);  // Send slave address - write operation
i2c_Write(address);                 // Set register for read
i2c_Restart();                      // Restart
i2c_Address(I2C_SLAVE, I2C_READ);   // Send slave address - read operation  
read_byte = i2c_Read(0);            // Read one byte
                      // If more than one byte to be read, (0) should
                      // be on last byte only
                      // e.g.3 bytes= i2c_Read(1); i2c_Read(1); i2c_Read(0);
i2c_Stop();                         // send Stop
return read_byte;                   // return byte. 
                                    // If reading more than one byte
                                    // store in an array
}

void main(void) {

unsigned char temp1;
int i=0;

OSCCON = 0b01111010;                // set internal osc to 16MHz
TRISA = 0b000000;                   // set ports to outputs
PORTA = 0b000000;
//OPTION_REG = 0b01011000;            //or hex 0x58, enable pull up resistors
//WPUA1 = 1;                          //enable individual pull ups
//WPUA2 = 1;

__delay_ms(9000);
__delay_ms(9000);

i2c_Init();                         // Start I2C as Master 100KHz
i2c_write_command(0x1,0x78);        // Write configuration to register
//temp1 = i2c_read_command(0x0);
//__delay_ms(5000);

while(1)
{
    RA4 = 1;
    __delay_ms(500);
    RA4 = 0;
    __delay_ms(500);
    i2c_write_command(0x01,0x78);
}
return;
}

i2c.h BEARBEITEN: AKTUALISIERT

#define I2C_WRITE 0
#define I2C_READ 1

// Initialise MSSP port. (12F1822 - other devices may differ)
void i2c_Init(void){

   // Initialise I2C MSSP
   // Master 100KHz
   TRISA1=1;                // set SCL and SDA pins as inputs
   TRISA2=1;

   SSPCON1 = 0b00101000;    // I2C enabled, Master mode
   SSPCON2 = 0x00;      // I2C Master mode, clock = FOSC/(4 * (SSPADD + 1)) 
   SSPADD = 39;         // 100Khz @ 16Mhz Fosc
   SSPSTAT = 0b11000000;    // Slew rate disabled

}

// i2c_Wait - wait for I2C transfer to finish
void i2c_Wait(void)
{
    while ( ( SSP1CON2 & 0x1F ) || ( SSPSTAT & 0x04 ) );
}

// i2c_Start - Start I2C communication
void i2c_Start(void)
{
    //i2c_Wait();
    SEN=1;  //Indicates that start bit has been detected last
    while(SEN);
}

// i2c_Restart - Re-Start I2C communication
void i2c_Restart(void){
    //i2c_Wait();
    RSEN=1; //Enables receiver mode for I2C master mode only 
    while(RSEN);
}

// i2c_Stop - Stop I2C communication
void i2c_Stop(void)
{
    //i2c_Wait();
    PEN=1;  //Initiate stop condition on SDA and SCL pins, auto cleared by hardware
    while(PEN);
}

// i2c_Write - Sends one byte of data
void i2c_Write(unsigned char data)
{
    SSPBUF = data;  //MSSP1 Receive Buffer/Transmit Register
    while(BF);       // wait till complete data is sent from buffer 
    i2c_Wait();
}

// i2c_Address - Sends Slave Address and Read/Write mode
// mode is either I2C_WRITE or I2C_READ
void i2c_Address(unsigned char address, unsigned char mode)
{
    unsigned char l_address;

    l_address=address<<1;
    l_address+=mode;
    SSPBUF = l_address;
    while(BF);       // wait till complete data is sent from buffer
    i2c_Wait();
}

// i2c_Read - Reads a byte from Slave device
unsigned char i2c_Read(unsigned char ack)
{
    // Read data from slave
    // ack should be 1 if there is going to be more data read
    // ack should be 0 if this is the last byte of data read
    unsigned char i2cReadData;

    //i2c_Wait();
    RCEN=1;
    while(!BF);      // wait for buffer full //i2c_Wait();
    i2cReadData = SSPBUF;
    i2c_Wait();
    if ( ack ) ACKDT=0;         // Ack
    else       ACKDT=1;         // NAck
    ACKEN=1;                    // send acknowledge sequence

    return( i2cReadData );
}

Vielen Dank für Ihre Zeit.

EDIT: i2c.h aktualisiert EDIT2: Screenshot mit 1k-Pullup-Widerständen hochgeladen.

Warten Sie darauf, dass etwas passiert, während (etwas); Stil? Möglicherweise warten Sie versehentlich auf einen niedrigen Zustand, der nie eintritt.
Fügen Sie eine Debug-Ausgabe hinzu oder drehen Sie ein GPIO, das Sie mit einem Bereich sehen können, damit Sie sehen können, was Ihr Code tut - zum Beispiel könnte es so, wie Daniel vorgeschlagen hat, in i2c_Wait() stecken bleiben, bis Sie es durch manuelle Manipulation von freigeben Bus. Überprüfen Sie die Datenblattdefinitionen der Statusbits, die Sie lesen.
Ich glaube nicht, dass der Code in i2c_wait() hängen bleibt. In der Endlosschleife schalte ich eine LED ein und aus. Die LED schaltet immer um, ob der Bus funktioniert oder nicht. (Bevor ich also die SDA-Leitung repariere, blinkt die LED genauso wie nachdem ich sie repariere)
Was meinst du mit "hoch stecken"? Wenn I2C im Leerlauf ist, sollten SCL und SDA hoch sein. Warum prüfen Sie Ihre Adress- und Befehlsbyteübertragungen nicht auf ACK/NACK? Sie scheinen nur bei Ihrem Readback nach ACK/NACK zu suchen, was bedeutet, dass Sie davon ausgehen, dass der Rest der Kommunikation zu 100 % perfekt ist ...
Ihre SDA-Leitung ist verdächtig. Ein 10k-Pullup-Widerstand ist normal, ein 4,7k-Pullup ist stark, ich habe noch nie eine Schaltung gesehen, die einen 1k-Pullup erfordert. Dies wird verstärkt, da die SCL-Leitung auf die gleiche Weise verbunden werden sollte und bei 4,7 k schöne scharfe Kanten hat. Etwas zieht SDA herunter, Sie sollten es finden und stoppen.
Adam Lawrence: Ok, also werde ich einige Überprüfungen für die Bestätigungsbits zu diesen Anweisungen hinzufügen. Wenn ich mir jedoch das Oszilloskop ansehe, kann ich deutlich die Bestätigungsbits sehen, die vom TMP75-Sensor kommen, sodass die I2C-Kommunikation immer noch einwandfrei funktioniert. lod: Die 2 Geräte, die ich verwende, sind beide 3,3-V-Geräte, daher scheint die Verwendung eines 1k-Pullups für sie besser zu funktionieren als ein 4,7k-Gerät. Unter Verwendung der Gleichung von hier: electronic.stackexchange.com/questions/1849/… Wenn Vcc = 3,3 V, beträgt die minimale Widerstandsgröße 967 Ohm. Hochladen eines neuen 1k-Pullup-Screenshots

Antworten (3)

Ihr Problem ist, dass Sie i2c_wait () -Befehle durch Ihren Code gestreut haben.

Dies ist ein Mikrocontroller, es ist einfach, es macht genau das, was Sie wollen. Der Zustand muss nicht ständig überprüft werden. Sie sollten i2c_wait() nur aufrufen müssen, nachdem Sie mit SSPBUF interagiert haben. Schauen Sie sich http://www.8051projects.net/wiki/I2C_Implementation_on_PIC an

Indem Sie SCL hoch halten und SDA umschalten (Entfernen und Ersetzen des Widerstands), senden Sie ein Startbit, gefolgt von einem Stoppbit. Es ist nicht klar, warum dies die von Ihnen überprüften Register repariert, da Chris Stratton vorgeschlagen hat, dass Sie die Register löschen könnten, wenn Sie sich wirklich darum kümmern würden.

Ich werde versuchen, einige der i2c_wait zu löschen und mir morgen Ihren Link ansehen. Danke.
Update: Auch nach dem Löschen des größten Teils von I2C_wait() tritt das gleiche Problem weiterhin auf. Wenn der SDA-Pullup-Widerstand nicht wieder eingefügt wird, startet die I2C-Kommunikation nicht. Nicht sicher, welcher Teil des Codes der Überfall sein könnte, da das MSSP-Modul einen Großteil der Arbeit erledigt.
Wenn es sich nicht um eine Endlosschleife in Ihrem Code handelt, was das Entfernen der Schleife beweist und Ihre blinkende LED stark darauf hindeutet ... Dann muss es etwas mit der Initialisierung des Kern-I2C-Moduls sein. Ich fürchte, Sie haben das Ende meiner (eher begrenzten) PIC-Kenntnisse erreicht.

Nun, das ist vielleicht keine vollständige Antwort, aber es löst das Problem. Die Problemumgehung für dieses Problem bestand darin, die SDA-Leitung manuell umzuschalten, nachdem ich den i2c-Bus initialisiert hatte. Hier ist der Code, den ich nach dem Aufruf von i2c_init() hinzugefügt habe.

TRISA2 = 0;
RA2 = 1;
RA2 = 0;
__delay_ms(10);
RA2 = 1;
TRISA2 = 1;

Durch manuelles Umschalten der SDA-Leitung wird das Problem behoben. Ich kann den TMP75-Temperatursensor jetzt problemlos lesen und beschreiben. Danke für die Hilfe an alle.

Aus dem Datenblatt Ihres Temperatursensors:

7.3.2.8 Timeout-Funktion Der TMP175 setzt die serielle Schnittstelle zurück, wenn entweder SCL > oder SDA für 54 ms (typisch) zwischen einem START- und STOP-Zustand niedrig gehalten wird. Der TMP175 gibt den Bus frei, wenn er auf Low gezogen wird, und wartet auf eine START-Bedingung. Um eine Aktivierung der Timeout-Funktion zu vermeiden, muss für die SCL-Betriebsfrequenz eine Kommunikationsgeschwindigkeit von mindestens 1 kHz eingehalten werden.

Vielleicht greift Ihr Sensor nach dem Bus und lässt ihn nicht los, und Ihre Pullup-Manipulation reicht aus, um ihn loszulassen.

In jedem Fall ist es an der Zeit, die Ärmel hochzukrempeln und ernsthaft zu debuggen.

Eine gute Möglichkeit, diese Art von Dingen zu debuggen, ist ein Oszilloskop mit einem erweiterten seriellen Triggermodul, sodass Sie auf ein Muster triggern können, um nach dieser Verzögerung von 54 ms zu suchen.

Alternativ gibt es an dieser Stelle kaum eine Entschuldigung dafür, nicht zu wissen, wo oder wie Ihr Programm hängt. Schalten Sie zumindest ein digitales Bit in main um, um sich davon zu überzeugen, dass die While-Schleife nicht ausgeführt wird. Sobald dies erledigt ist und Sie feststellen, dass dies nicht der Fall ist, schalten Sie Bits ein, wenn Sie in eine Subroutine eintreten, und aus, wenn Sie sie verlassen. Kurz gesagt, Sie sollten zumindest wissen, WO Sie hängen. Schalten Sie danach ein digitales Bit ein, das als Scope-Trigger für den Kommunikationsabschnitt dient, mit dem Sie Probleme haben, damit Sie ein sehr klares Bild davon bekommen, was vor sich geht.

Vielleicht noch besser, verwenden Sie Ihre Timer, um jede Kommunikations-Subroutine zu timen, und beenden Sie sie mit einem Fehlercode. Eventuell den Fehlercode auf einem DIO flashen.

Danke für den Kommentar. Erstens verwende ich TMP75, und die Timeout-Funktion gilt nur für TMP175. Außerdem werden SCL und SDA nicht niedrig gehalten, sobald der Bus initialisiert ist, gehen beide Leitungen hoch, wie sie sollten. Außerdem hängt der Code NICHT. In der while-Schleife im Hauptteil ist RA4 ein Ausgang zu einer LED. Unabhängig davon, ob der Bus funktioniert oder nicht (auch bekannt als bevor oder nachdem ich den SDA-Pullup wieder einfüge), schaltet die LED immer um. Dies bedeutet, dass der i2c_write-Befehl ausgeführt wird, aber nicht auf dem Bus angezeigt wird. Deshalb bin ich so verwirrt. Der Code läuft gut, aber er funktioniert nicht, bis ich SDA wieder einfüge.
Nach weiterem Lesen werde ich die Möglichkeit untersuchen, dass der TMP75 am Bus festhält, obwohl ich mir ziemlich sicher bin, dass dies nicht der Fall ist. Erstens bezieht sich dieser Teil des Datenblatts auf TMP175, während ich einen TMP75 habe. Zweitens ist mein Oszilloskop auf Trigger mit fallender Flanke eingestellt, und es erkennt nie etwas, bis ich den SDA-Pullup wieder einfüge, was bedeuten würde, dass eine Startbedingung nie gesendet wird, bis ich diesen Pullup wieder einfüge. Sowohl SDA als auch SCL bleiben hoch, bis ich sie "repariere". Außerdem bin ich mir nicht sicher, ob ich Zugang zu einem Oszilloskop mit einem erweiterten seriellen Triggermodul habe, ich muss den Labortechniker fragen.
Welche anderen Peripheriegeräte sind mit Ihren I2C-Pins verbunden? Müssen Sie sie als Dig-Eingänge konfigurieren, bevor Sie I2C initialisieren? Es würde NICHTS geben, was Ihren hochgebundenen Bus nach unten ziehen sollte, bevor I2C startet.
Es gibt nur den Pic (i2c-Master) und den TMP75 (i2c-Slave) am i2c-Bus. Das einzige, woran ich denken könnte, würde die i2c-Leitungen niedrig ziehen, bevor i2c_init() der Teil wäre, in dem alle Pic-Mikrostifte zuerst als Ausgänge konfiguriert werden. Sobald i2c_init() aufgerufen wird, gehen beide i2c-Leitungen hoch, genau wie sie sollten.
@Rdd versuchen Sie, sie zuerst nicht als Ausgänge zu konfigurieren