1
\$\begingroup\$

So in my University studies we recieved few source code samples that are working with a OPT3001 Light Sensor. Our studies do not focus on engineering tasks in general, but this is a lecture that touches some of the Electrical Engineering subjects. I study Physics if someone wondering.

I am not familiar with Electrical Engineering communication protocols but I have read few guides online about I2C communication in order to aid me to understand the given source code. I have only some knowledge of Android programming - but also self-taught.

I would like to understand more, that is why I ask you, if you can help me. I will just copy one function of the entire code so it won't be too long and if you can guide me with some comments on each line what is happening I will be really thankful!

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();
}

Some definitions that are used in above code:

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

This is not a requirement to know on my university, it is just an exercise where it is pre-made by professor and all we need to do is to use this Light Sensor to measure illumination. But I wanted to teach me something more if possible.

I am familiar with the Datasheet of OPT3001 so I do understand a bit the necessary addresses, but also not completly.

Here is a link to datasheet if someone needs: OPT3001 datasheet

Gratefull for any help / comment / input. Hope this still applies to the StachExchange rules - did not know where else to post this question.

\$\endgroup\$
3
  • \$\begingroup\$ What do you want to know more about? What is the question precisely? \$\endgroup\$
    – MathieuL
    Aug 12, 2015 at 18:28
  • \$\begingroup\$ @MathieuL What I do not undestand is the commented line "conversion", I am not sure how is that mean to convert something ? To me it seems it's just selecting the device and address? \$\endgroup\$ Aug 12, 2015 at 18:32
  • \$\begingroup\$ I think there is an error in the code. I2C normally requires a restart handshake between the change of direction. \$\endgroup\$
    – Paebbels
    Aug 12, 2015 at 20:39

3 Answers 3

3
\$\begingroup\$

It seems to be very self evident, but that must be my confirmation bias ;D

First, the lines starting with // are Comments. Wholly For your benefit. As is any text after a # (which is not used here).

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.

Note the OPT3001 has 16 bit registers. That's two 8-bit Bytes. Standard i2c works in 8-bit bytes. So you have to read two bytes to get the full results. Typically, with a Most Significant Byte order, the higher bits (15-8) are called the high/first byte.

I2C works by addressing the device in write mode, telling it to change to a register/internal address, then writing or reading from that point. This device is no different. That's the basics of I2C

\$\endgroup\$
9
  • \$\begingroup\$ are this Higher byte and Lower byte specified within documentation ? Or what do they represent in I2C Communication? Thanks for answering. \$\endgroup\$ Aug 12, 2015 at 18:39
  • \$\begingroup\$ @AndroidNFC updated. The OPT uses 16 bit registers. So 2 bytes. \$\endgroup\$
    – Passerby
    Aug 12, 2015 at 18:45
  • \$\begingroup\$ Could you also please explain me the first part - the handshake ? Why is that one really necessary ? Because I see that Write Device Address appears more than once during this function. \$\endgroup\$ Aug 12, 2015 at 18:52
  • \$\begingroup\$ @AndroidNFC i2c uses a Start condition, a handshake, to signal devices to pay attention to the next message (the device address). It's used because i2c is a bus that can have multiple devices, and they shouldn't randomly reply. It's a way to control the bus. Basically, wake up and listen for your name. If your name isn't called, bug off until the next stop and start conditions. \$\endgroup\$
    – Passerby
    Aug 12, 2015 at 18:55
  • \$\begingroup\$ Ah makes more sense now! But why then also write CONFIG_REG and other CONFIG stuff, while "waking him up" ? Isn't the device address enough for it? \$\endgroup\$ Aug 12, 2015 at 18:59
2
\$\begingroup\$

A typical pattern for communicating with I2C devices is that to write information to the device one should issue an "I2C Start" to the bus, output a byte that says one wants to write to a particular device along with one or two bytes that identify an address within the device one wants to write, and then follow all that with the data to be written. After doing all of that, one should issue an "I2C Stop" to the bus. Any time a byte is written to an I2C Device, the controller will report whether the device indicated that it has received it. If the device does not indicate successful receipt of any byte, the entire operation should be deemed a failure.

To read from I2C devices, one starts by "writing" the address from which one will want to read, using the above procedure but without sending any data after the address. After having done that, one should issue an "I2C Stop" and "I2C Start" followed by a byte that says one will want to read the device rather than write it. This should in turn be followed by requests to read the data, and then an "I2C Stop". As commonly implemented, the controller must indicate to the read-byte routine whether each byte of data requested will be the last. It seems this particular read-byte routine uses a parameter value of 1 to indicate that more data will follow, and 0 to indicate the last request for a transaction.

I don't know how exactly your implementation is doing everything, but it looks like it's following the pattern described above; hopefully that will get you started.

\$\endgroup\$
1
\$\begingroup\$
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.
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.