Comprender el código fuente de la comunicación I2C

Entonces, en mis estudios universitarios, recibimos algunas muestras de código fuente que funcionan con un sensor de luz OPT3001. Nuestros estudios no se enfocan en tareas de ingeniería en general, pero esta es una lección que toca algunos de los temas de Ingeniería Eléctrica. Yo estudio Física por si alguien se pregunta.

No estoy familiarizado con los protocolos de comunicación de ingeniería eléctrica, pero he leído algunas guías en línea sobre la comunicación I2C para ayudarme a comprender el código fuente dado. Solo tengo algunos conocimientos de programación de Android, pero también soy autodidacta.

Me gustaría entender más, por eso te pregunto, si me puedes ayudar. Solo copiaré una función de todo el código para que no sea demasiado largo y si me puede guiar con algunos comentarios en cada línea, ¡lo que está sucediendo le estaré muy agradecido!

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

Algunas definiciones que se utilizan en el código anterior:

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

Esto no es un requisito para saberlo en mi universidad, es solo un ejercicio en el que está preparado previamente por el profesor y todo lo que tenemos que hacer es usar este sensor de luz para medir la iluminación. Pero quería enseñarme algo más si cabe.

Estoy familiarizado con la hoja de datos de OPT3001, por lo que entiendo un poco las direcciones necesarias, pero no del todo.

Aquí hay un enlace a la hoja de datos si alguien necesita: hoja de datos OPT3001

Agradecido por cualquier ayuda/comentario/entrada. Espero que esto todavía se aplique a las reglas de StachExchange; no sabía dónde más publicar esta pregunta.

¿Sobre qué quieres saber más? ¿Cuál es la pregunta exactamente?
@MathieuL Lo que no entiendo es la línea comentada "conversión", no estoy seguro de qué significa eso para convertir algo. ¿Me parece que solo está seleccionando el dispositivo y la dirección?
Creo que hay un error en el código. I2C normalmente requiere un restartapretón de manos entre el cambio de dirección.

Respuestas (3)

Parece ser muy evidente, pero ese debe ser mi sesgo de confirmación; D

Primero, las líneas que comienzan con // son Comentarios. Totalmente para su beneficio. Como cualquier texto después de un # (que no se usa aquí).

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.

Tenga en cuenta que el OPT3001 tiene registros de 16 bits. Son dos bytes de 8 bits. i2c estándar funciona en bytes de 8 bits. Por lo tanto, debe leer dos bytes para obtener los resultados completos. Por lo general, con un orden de byte más significativo, los bits más altos (15-8) se denominan byte alto/primer.

I2C funciona direccionando el dispositivo en modo de escritura, diciéndole que cambie a un registro/dirección interna y luego escribiendo o leyendo desde ese punto. Este dispositivo no es diferente. Eso es lo básico de I2C

¿Se especifica este byte superior e inferior en la documentación? ¿O qué representan en la Comunicación I2C? Gracias por responder.
@AndroidNFC actualizado. El OPT utiliza registros de 16 bits. Entonces 2 bytes.
¿Podría explicarme también la primera parte, el apretón de manos? ¿Por qué es realmente necesario? Porque veo que Escribir dirección de dispositivo aparece más de una vez durante esta función.
@AndroidNFC i2c usa una condición de inicio, un apretón de manos, para indicar a los dispositivos que presten atención al siguiente mensaje (la dirección del dispositivo). Se usa porque i2c es un bus que puede tener varios dispositivos y no deberían responder aleatoriamente. Es una forma de controlar el autobús. Básicamente, despierta y escucha tu nombre. Si no se llama su nombre, aléjese hasta la próxima parada y condiciones de inicio.
Ah tiene más sentido ahora! Pero, ¿por qué entonces también escribir CONFIG_REG y otras cosas de CONFIG, mientras lo "despiertas"? ¿No es suficiente la dirección del dispositivo?
@AndroidNFC al escribir la dirección llama su atención. Escribir en el registro de configuración le dice que debe comenzar a tomar una muestra ligera (iniciar una conversión). Luego tiene que decirle que cambie al registro de resultados para que podamos leer los resultados.
¡¡Ah!! Entonces, en realidad, la primera parte ya le dice al sensor que obtenga Light Sample. El siguiente es decirle que "despierte" su registro de resultados... y la última parte es decirle que lea del registro de resultados. Básicamente, ¿la "conversión" significa decirle al sensor que comience a medir la iluminación?
@AndroidNFC no tanto para despertar, sino para cambiar. Como cambiar a una página diferente en un libro. Pero bingo, eso es lo que significa conversión.
Muchas gracias, lo marcaré como respuesta. Si lo desea, puede editarlo para poner algunas de estas discusiones de chat allí y luego es una explicación realmente amigable para principiantes para principiantes reales.

Un patrón típico para comunicarse con dispositivos I2C es que para escribir información en el dispositivo se debe emitir un "Inicio I2C" al bus, enviar un byte que diga que se quiere escribir en un dispositivo en particular junto con uno o dos bytes que identifiquen un dirección dentro del dispositivo que uno quiere escribir, y luego siga todo eso con los datos que se escribirán. Después de hacer todo eso, se debe emitir una "Parada I2C" al autobús. Cada vez que se escribe un byte en un dispositivo I2C, el controlador informará si el dispositivo indicó que lo recibió. Si el dispositivo no indica la recepción exitosa de ningún byte, toda la operación debe considerarse un fracaso.

Para leer desde dispositivos I2C, se comienza "escribiendo" la dirección desde la que se desea leer, utilizando el procedimiento anterior pero sin enviar ningún dato después de la dirección. Después de haber hecho eso, uno debe emitir un "I2C Stop" y un "I2C Start" seguido de un byte que dice que uno querrá leer el dispositivo en lugar de escribirlo. Esto a su vez debe ser seguido por solicitudes para leer los datos y luego una "Parada I2C". Tal como se implementa comúnmente, el controlador debe indicar a la rutina de lectura de bytes si cada byte de datos solicitado será el último. Parece que esta rutina particular de lectura de bytes usa un valor de parámetro de 1 para indicar que seguirán más datos y 0 para indicar la última solicitud de una transacción.

No sé exactamente cómo está haciendo todo su implementación, pero parece que sigue el patrón descrito anteriormente; con suerte, eso te ayudará a comenzar.

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.
}