Quellcode der I2C-Kommunikation verstehen

In meinem Universitätsstudium haben wir also einige Quellcode-Beispiele erhalten, die mit einem OPT3001-Lichtsensor arbeiten. Unser Studium konzentriert sich nicht auf ingenieurwissenschaftliche Aufgaben im Allgemeinen, sondern es handelt sich um eine Vorlesung, die einige der Elektrotechnik-Fächer berührt. Ich studiere Physik, falls sich jemand fragt.

Ich bin mit den Kommunikationsprotokollen der Elektrotechnik nicht vertraut, habe aber online einige Anleitungen zur I2C-Kommunikation gelesen, um mir zu helfen, den angegebenen Quellcode zu verstehen. Ich habe nur einige Kenntnisse in der Android-Programmierung - aber auch Autodidakt.

Ich würde gerne mehr verstehen, deshalb frage ich Sie, ob Sie mir helfen können. Ich werde nur eine Funktion des gesamten Codes kopieren, damit es nicht zu lang wird, und wenn Sie mich mit einigen Kommentaren zu jeder Zeile anleiten können, was passiert, bin ich wirklich dankbar!

void UpdateLight(void)
{
    uint8_t error = 0;
    // start conversion
    I2C_Start();
    error |= I2C_Write(OPT_ADDR_W);
    error |= I2C_Write(OPT_CONFIG_REG);
    error |= I2C_Write(OPT_CONFIGURATION_H);
    error |= I2C_Write(OPT_CONFIGURATION_L);
    I2C_Stop();

    // wait for conversion
    _delay_ms(150);

    // set result register
    I2C_Start();
    error |= I2C_Write(OPT_ADDR_W);
    error |= I2C_Write(OPT_RESULT_REG);
    I2C_Stop();

    // read data and update scratchpad
    I2C_Start();
    error |= I2C_Write(OPT_ADDR_R);
    if (error == 0)
    {
        scratchpad[1] = I2C_Read(1);
        scratchpad[0] = I2C_Read(0);
    }
    else
    {
        // report error value
        scratchpad[1] = 0xFF;
        scratchpad[0] = 0xFF;
    }
    I2C_Stop();
}

Einige Definitionen, die im obigen Code verwendet werden:

volatile uint8_t scratchpad[9] = {0x50, 0x05, 0x0, 0x0, 0x7f, 0xff, 0x00, 0x10, 0x0};   // initial scratchpad

Dies ist an meiner Universität nicht erforderlich, es ist nur eine Übung, bei der es vom Professor vorgefertigt wurde, und alles, was wir tun müssen, ist, diesen Lichtsensor zu verwenden, um die Beleuchtung zu messen. Aber ich wollte mir möglichst noch etwas beibringen.

Ich kenne das Datenblatt von OPT3001, daher verstehe ich die erforderlichen Adressen ein wenig, aber auch nicht vollständig.

Hier ist ein Link zum Datenblatt, falls jemand benötigt: OPT3001-Datenblatt

Dankbar für jede Hilfe/Kommentar/Eingabe. Hoffe, das gilt immer noch für die StachExchange-Regeln - wusste nicht, wo ich diese Frage sonst posten sollte.

Worüber möchten Sie mehr erfahren? Was ist die Frage genau?
@MathieuL Was ich nicht verstehe, ist die kommentierte Zeile "conversion", ich bin mir nicht sicher, wie das bedeutet, etwas zu konvertieren? Für mich scheint es nur das Auswählen des Geräts und der Adresse zu sein?
Ich denke da ist ein Fehler im Code. I2C erfordert normalerweise einen restartHandshake zwischen den Richtungswechseln.

Antworten (3)

Es scheint sehr selbstverständlich zu sein, aber das muss meine Bestätigungsverzerrung sein ;D

Erstens sind die Zeilen, die mit // beginnen, Kommentare. Ganz zu Ihrem Vorteil. Ebenso wie jeder Text nach einem # (das hier nicht verwendet wird).

I2C Start hand shake
Write device's Address
Write device's configuration address
Write the higher byte of the configuration desired
Write the lower byte of the configuration desired.
I2C Stop hand shake

Wait a bit.

Start
write device address
switch to device's results address
stop

Start
Read from device address
if no error
read the first byte from the results
read the second byte
Stop.

Beachten Sie, dass der OPT3001 16-Bit-Register hat. Das sind zwei 8-Bit-Bytes. Standard i2c arbeitet in 8-Bit-Bytes. Sie müssen also zwei Bytes lesen, um die vollständigen Ergebnisse zu erhalten. Typischerweise werden bei einer höchstwertigen Byte-Reihenfolge die höheren Bits (15–8) als das hohe/erste Byte bezeichnet.

I2C funktioniert, indem es das Gerät im Schreibmodus adressiert, ihm sagt, dass es zu einem Register/einer internen Adresse wechseln soll, und dann von diesem Punkt aus schreibt oder liest. Dieses Gerät ist nicht anders. Das sind die Grundlagen von I2C

sind dieses höhere Byte und das untere Byte in der Dokumentation angegeben? Oder was stellen sie in der I2C-Kommunikation dar? Danke für die Antwort.
@AndroidNFC aktualisiert. Das OPT verwendet 16-Bit-Register. Also 2 Bytes.
Könnten Sie mir bitte auch den ersten Teil erklären – den Händedruck? Warum ist das wirklich nötig? Weil ich sehe, dass Write Device Address während dieser Funktion mehr als einmal erscheint.
@AndroidNFC i2c verwendet eine Startbedingung, einen Handshake, um Geräten zu signalisieren, auf die nächste Nachricht (die Geräteadresse) zu achten. Es wird verwendet, weil i2c ein Bus ist, der mehrere Geräte haben kann und diese nicht zufällig antworten sollten. Es ist eine Möglichkeit, den Bus zu steuern. Wachen Sie im Grunde auf und hören Sie auf Ihren Namen. Wenn Ihr Name nicht aufgerufen wird, steigen Sie bis zur nächsten Stopp- und Startbedingung aus.
Ah macht jetzt mehr Sinn! Aber warum dann auch noch CONFIG_REG und anderes CONFIG-Zeug schreiben, während man ihn "aufweckt" ? Reicht die Geräteadresse dafür nicht aus?
@AndroidNFC das Schreiben der Adresse erregt seine Aufmerksamkeit. Das Schreiben in das Konfigurationsregister teilt ihm mit, dass es mit der Entnahme einer leichten Probe beginnen soll (eine Konvertierung starten). Dann muss es ihm sagen, dass es zum Ergebnisregister wechseln soll, damit wir die Ergebnisse lesen können.
Aha!! Der erste Teil sagt dem Sensor also bereits, dass er Light Sample erhalten soll. Der nächste ist, ihm zu sagen, dass er sein Ergebnisregister "aufwecken" soll ... und der letzte Teil ist, ihm zu sagen, dass er aus dem Ergebnisregister lesen soll. Im Grunde bedeutet die "Konvertierung", dem Sensor bereits zu sagen, dass er mit der Messung der Beleuchtung beginnen soll?
@AndroidNFC nicht so sehr wecken, sondern umschalten. So wie in einem Buch auf eine andere Seite wechseln. Aber Bingo, das ist Konversion.
Vielen Dank, ich werde diese als Antwort markieren. Wenn Sie möchten, können Sie es bearbeiten, um einige dieser Chat-Diskussionen dort einzufügen, und dann ist es eine wirklich neulingsfreundliche Erklärung für echte Anfänger!

Ein typisches Muster für die Kommunikation mit I2C-Geräten besteht darin, dass man zum Schreiben von Informationen auf das Gerät einen "I2C-Start" an den Bus ausgeben sollte, ein Byte ausgeben sollte, das besagt, dass man auf ein bestimmtes Gerät schreiben möchte, zusammen mit einem oder zwei Bytes, die es identifizieren Adresse innerhalb des Geräts, das man schreiben möchte, und dann folgen all dem die zu schreibenden Daten. Nach all dem sollte man dem Bus ein "I2C Stop" ausgeben. Jedes Mal, wenn ein Byte auf ein I2C-Gerät geschrieben wird, meldet der Controller, ob das Gerät angezeigt hat, dass es es empfangen hat. Wenn das Gerät keinen erfolgreichen Empfang irgendeines Bytes anzeigt, sollte die gesamte Operation als fehlgeschlagen angesehen werden.

Um von I2C-Geräten zu lesen, beginnt man mit dem "Schreiben" der Adresse, von der man lesen möchte, unter Verwendung des obigen Verfahrens, aber ohne irgendwelche Daten nach der Adresse zu senden. Danach sollte man ein "I2C Stop" und "I2C Start" ausgeben, gefolgt von einem Byte, das besagt, dass man das Gerät lesen möchte, anstatt es zu schreiben. Darauf sollten wiederum Anfragen zum Auslesen der Daten und dann ein „I2C Stop“ folgen. Wie allgemein implementiert, muss der Controller der Byte-Lese-Routine anzeigen, ob jedes angeforderte Datenbyte das letzte sein wird. Es scheint, dass diese spezielle Read-Byte-Routine einen Parameterwert von 1 verwendet, um anzuzeigen, dass weitere Daten folgen werden, und 0, um die letzte Anforderung für eine Transaktion anzuzeigen.

Ich weiß nicht, wie genau Ihre Implementierung alles macht, aber es sieht so aus, als würde es dem oben beschriebenen Muster folgen. hoffentlich hilft dir das weiter.

uint8_t error = 0;   //set error to 0, clear it
// start conversion
I2C_Start();   //call function to send start bit onto I2C bus to signify start of transaction
error |= I2C_Write(OPT_ADDR_W);  //write device id of slave onto bus with write bit set remember that I2C is 7 bits with the last bit for read or write (well there are 10bit but lets assume this is 7 bit.
error |= I2C_Write(OPT_CONFIG_REG);//put the address of the 16bit config register in the slave onto the bus
error |= I2C_Write(OPT_CONFIGURATION_H);//put the high byte of the config register onto the bus to set it
error |= I2C_Write(OPT_CONFIGURATION_L);//put the low byte of the config register onto the bus to set it
I2C_Stop();//put the stop bit onto the I2C bus to signify the end of the transmission

// wait for conversion
_delay_ms(150); //wait for your slave device to do what it does

// set result register
I2C_Start();  //start another transaction on the I2C bus
error |= I2C_Write(OPT_ADDR_W); //put the device id of slave on the bus again with write bit set
error |= I2C_Write(OPT_RESULT_REG);//send slave the address for the result register, you are in effect prepping the device by giving it this address, in the next step you will read from it
I2C_Stop();//put the stop bit out on the bus again

// read data and update scratchpad
I2C_Start();//now put the start bit out again, I can't remember this may technically be a restart
error |= I2C_Write(OPT_ADDR_R); //put the device ID of the slave out onto the bus with the read bit set.  You are telling the sensor I now want to read the 16bit result value from you
if (error == 0)//if none of the previous function calls resulted in any errors at all
{
    scratchpad[1] = I2C_Read(1);//read 1 byte from the I2C bus and store in scratchpad
    scratchpad[0] = I2C_Read(0);//read 2nd byte from the I2C bus and store in scratchpad
}
else
{   //whoops you had some kind of error previosly
    // report error value
    scratchpad[1] = 0xFF;//set scratch pad to known value
    scratchpad[0] = 0xFF;//set scratch pad to known value
}
I2C_Stop();//put stop bit out to end transaction no matter what otherwise slave will think transaction never ended.
}