# I2C transmit & receive between ALS OPT3006 & 8051

I'd like to ask for solution to let the microcontroller (MCU) get data from ALS (ambient light sensor).I'm wondering why my MCU can't get ACK (acknowledge signal) low? In this program I have 2 functions: ALS_config() is to send command from MCU to modify settings in the ALS. MCU_read() is to let MCU read lux data from ALS.

Currently, ALS_config() can get ACK = 0 after the data byte was sent. It means that it works well. But MCU_read() function get ACK = 1, means no acknowledge signal. I think that my program is in the right form compare with the conventional I2C protocol. http://www.robot-electronics.co.uk/i2c-tutorial

void main(void)
{

MCU_Initial();

Initial_Variable();

FPGA_TEST();
delay1m(5);

while(1)
{
RS232Control();

ALS_to_MCU();
if(flagstep==1 || flagstep_down==1)
{
Main_Pattern();
}

}
}

void I2C_START(void)
{
delay1m(6);
ACK_f = 0;
I2C_SDA = 1;
delay1m(2);
I2C_SCL = 1;
delay1m(8);
I2C_SDA = 0;
delay1m(5);
I2C_SCL = 0;
delay1m(5);
}

//=======================================================================================
// I2C_1 Ack (EEPROM)
//=======================================================================================
void I2C_ACK(void)
{
delay1m(3);
I2C_SDA = 1;
delay1m(1);
I2C_SCL = 1;
delay1m(3);
if(!I2C_SDA)
ACK_f = 1;
delay1m(2);
I2C_SCL = 0;
delay1m(1);
I2C_SDA = 0;
delay1m(5);
}

void I2C_NAK()
{
I2C_SDA = 1;
I2C_SCL = 1;
delay1m(1);
I2C_SCL = 0;
I2C_SDA = 1;
}

//=======================================================================================
// I2C_1 Stop (EEPROM)
//=======================================================================================
void I2C_STOP(void)
{
I2C_SCL = 0;
I2C_SDA = 0;
delay1m(8);
I2C_SCL = 1;
delay1m(2);
I2C_SDA = 1;
}

void I2C_RESET(void)
{
unsigned char x;

I2C_START();
for(x=0x00;x<=0x09;x++) //EEPROM restart 9 times is avoided unexpected state
{
I2C_SDA = 1;
I2C_SCL = 0;
delay1m(2);
I2C_SCL = 1;
delay1m(2);
}
I2C_START();
I2C_STOP();
delay1m(2);
}

//=======================================================================================
void I2C_TX(unsigned char Tx_data)
{
unsigned char x;
for(x=0;x<8;x++)
{
I2C_SCL = 0;
delay1m(1);
if(Tx_data & 0x80)
I2C_SDA = 1;
else
I2C_SDA = 0;
Tx_data <<= 1;
delay1m(1);
I2C_SCL = 1;
delay1m(2);
}
I2C_SCL = 0;
}

//=======================================================================================
unsigned char I2C_RX(void)
{
unsigned char x;
I2C_SDA = 1;
delay1m(2);
for(x=0;x<8;x++)
{
I2C_rx_buf <<= 1;
I2C_SCL = 1;
delay1m(1);
if(I2C_SDA)
I2C_rx_buf |= 0x01;
else
I2C_rx_buf &= 0xfe;
delay1m(2);
I2C_SCL = 0;
delay1m(3);
}
return I2C_rx_buf;
delay1m(2);

}

/////        Configuration register     ///////////
void ALS_config(unsigned char slave_write,unsigned char dataA,unsigned char dataB)
{
I2C_START();
I2C_TX(slave_write);
I2C_ACK();
I2C_TX(0x01);
I2C_ACK();
I2C_TX(dataA);
//  I2C_RX();
I2C_ACK();
I2C_TX(dataB);
//  I2C_RX();
I2C_ACK();
I2C_STOP();
}

{
unsigned char i, ALS_MSB, ALS_LSB;
unsigned int ALS_lux;

//////                      Partial write   ////////
I2C_START();
I2C_TX(slave_write);
I2C_ACK();
I2C_TX(0x00);
I2C_ACK();
//      I2C_STOP();

I2C_START();
I2C_ACK();
I2C_RX();
ALS_LSB = I2C_rx_buf;
ALS_MSB = I2C_rx_buf<<8;
I2C_ACK();

/*  This part doesn't work. So I marked it.
I2C_SDA = 0;
delay1m(1);
I2C_SCL = 1;
delay1m(1);
I2C_SCL = 0;
delay1m(1);
I2C_SDA = 0;
*/
delay1m(1);

I2C_RX();
ALS_MSB = I2C_rx_buf;
I2C_ACK();
//      I2C_NAK();
I2C_STOP();

ALS_lux = ALS_MSB | ALS_LSB;

}

//                  MCU read data from ALS
//////////////////////////////////

//void ALS_to_MCU(unsigned char slave_write,unsigned char slave_read,unsigned char dataA,unsigned char dataB)
void ALS_to_MCU(void)
{
ALS_config(0x88,0xC4,0x10);
}

• (a) In MCU_read() you commented-out a section: /* This part doesn't work.How did you decide that specific code "doesn't work"? You said in your question that you only know the function doesn't give the expected result. (b) I have concerns about some parts of your I2C code (and there is a bug at the end of MCU_read()) but at this point I suggest you don't look only at the code. Instead use a logic analyser or oscilloscope to view what actually happens on the I2C bus, and compare it step-by-step to your code and the OPT3006 datasheet. – SamGibson Aug 21 '17 at 4:55
• I mean the part inside the /* .... */ section can not make the SDA & SCL waveform different. Even if I put those commands in my program or not, the output are the same. I just use it for testing. I always test by using an oscilloscope. When I write into the ALS configuration register, after the data byte send finished, I got ACK signal (SDA=0). But when I read data from ALS, it's from the result register of OPT3006, after the data byte (MSB) sending finished, I don't get ACK signal (SDA=1. It should be SDA=0). – Hector Ta Aug 21 '17 at 5:19
• Thanks. I think I understood enough to identify at least a couple of bugs, so I've written that as an answer. – SamGibson Aug 21 '17 at 6:24

I don't have time to go through all of your code, but the code seems wrong in a few places. Here are two examples:

void MCU_read(unsigned char slave_write,unsigned char slave_read)
{
unsigned char i, ALS_MSB, ALS_LSB;
unsigned int ALS_lux;

//////                      Partial write   ////////
I2C_START();
I2C_TX(slave_write);
I2C_ACK();
I2C_TX(0x00);
I2C_ACK();
//      I2C_STOP();

I2C_START();
I2C_ACK();
I2C_RX();
ALS_LSB = I2C_rx_buf;
ALS_MSB = I2C_rx_buf<<8;
I2C_ACK();
[...]

• You read one byte in I2C_RX() but then try to use same byte twice as ALS_LSB and ALS_MSB. That makes no sense.
void I2C_ACK(void)
{
delay1m(3);
I2C_SDA = 1;
delay1m(1);
I2C_SCL = 1;
delay1m(3);
if(!I2C_SDA)
ACK_f = 1;
delay1m(2);
I2C_SCL = 0;
delay1m(1);
I2C_SDA = 0;
delay1m(5);
}

• When reading from the OPT3006 in MCU_read(), it is the I²C Master which must send the ACK after receiving each byte except the last one. But look at your I2C_ACK() function above - it is only written to check whether the I²C Slave has sent an ACK (and then set ACK_f if an ACK was detected from the Slave). You need to create an ACK function (or improve the one you already have) and then use it when the Master must send ACK.

That is why you were not detecting an ACK from the Slave during MCU_read() - the Slave is not supposed to send one to the Master! :-) It is the Master who must ACK each received byte from the Slave until the last byte, which the Master should usually NACK (although the datasheet says this device will also allow the Master to ACK) and then STOP. See Figure 21 in the OPT3006 datasheet which shows an example of I²C Read by the Master. (You might also want to clarify your understanding of the definition of "Master Transmitter" and "Master Receiver" in the I²C Specification.)

I don't guarantee that those are the only bugs. However you can now see that your code does not match the requirements in Figure 21 of the OPT3006 datasheet, so that gives you a place to start your debugging.