Módulo 1.5

Protocolos serie: I2C

Comunicación con sensores y periféricos

Santi Scagliusi, PhD

Comenzar arrow_downward
cable
2 hilos
SDA + SCL
hub
Multi-dispositivo
Hasta 127 dispositivos
speed
Velocidades
100K - 1M bps
sensors
Sensores
BME280, MPU6050...

¿Qué es I2C?

Inter-Integrated Circuit: protocolo de comunicación serie síncrono de 2 hilos.

info

También conocido como TWI

Desarrollado por Philips (ahora NXP) en 1982. Muy utilizado para conectar sensores, memorias EEPROM, displays y otros periféricos de baja velocidad.

Características principales

  • check_circle
    Comunicación síncrona - Reloj compartido (SCL)
  • check_circle
    Solo 2 líneas - SDA (datos) y SCL (reloj)
  • check_circle
    Arquitectura Controller-Target - Antes llamado Master-Slave
  • check_circle
    Direccionamiento - 7 bits (128 direcciones) o 10 bits

speed Velocidades estándar

directions_walk
Standard Mode
100 kbps
directions_run
Fast Mode
400 kbps
bolt
Fast Mode Plus
1000 kbps (1 Mbps)
lightbulb
Nota: La mayoría de sensores funcionan en Fast Mode (400 kbps). Verifica la hoja de datos del dispositivo para conocer la velocidad máxima soportada.

Cableado I2C

Todos los dispositivos comparten las mismas dos líneas de comunicación.

Topología de bus I2C

VCC (3.3V)
R
R
SDA
SCL
Controller
nRF52833
SDA SCL
Target
Sensor
0x4A
Target
EEPROM
0x50
schedule

SCL - Serial Clock

Generado por el Controller

Línea de reloj que sincroniza la transferencia de datos. El Controller genera la señal de reloj; los Targets la siguen.

swap_horiz

SDA - Serial Data

Bidireccional

Línea de datos bidireccional. Tanto el Controller como los Targets pueden transmitir y recibir datos por esta línea.

warning
Resistencias pull-up obligatorias: Las líneas I2C son de tipo open-drain. Se necesitan resistencias de 2.2k-10k ohm conectadas a VCC para que funcione correctamente.
info
Nota: Muchos módulos de sensores ya incluyen las resistencias pull-up. Verifica antes de añadir las tuyas para evitar valores demasiado bajos.

Direcciones I2C

Cada dispositivo en el bus tiene una dirección única de 7 bits.

Estructura del byte de dirección

A6
A5
A4
A3
A2
A1
A0
R/W
Dirección (7 bits)
R/W (1 bit)
R/W = 0
Escritura (Write)
R/W = 1
Lectura (Read)

Ejemplo: Dirección 0x4A

1
0
0
1
0
1
0
0

Dirección 0x4A + bit de escritura = 0x94

list Direcciones comunes de sensores

BME280
Temp/Humedad/Presión
0x76 / 0x77
MPU6050
Acelerometro/Giroscopio
0x68 / 0x69
SSD1306
Display OLED
0x3C / 0x3D
AT24C32
EEPROM
0x50 - 0x57
tips_and_updates
Consejo: Muchos sensores tienen un pin de selección de dirección (AD0/SDO) que permite cambiar entre dos direcciones predefinidas. Útil para conectar dos sensores iguales al mismo bus.

Señales I2C

La comunicación I2C sigue un patrón específico de inicio, datos y parada.

Trama I2C completa

Idle
HIGH
Start
S
Addr[6:0]
7 bits
R/W
1 bit
ACK
A
Data[7:0]
8 bits
ACK
A
...
más datos
Stop
P
Start/Stop: Condiciones especiales
Address: Dirección del target
ACK: Reconocimiento
Data: Bytes de datos
play_arrow

Condición de Start

SDA baja mientras SCL está alto. Indica el inicio de una transacción.

check

ACK (Acknowledge)

El receptor pone SDA en bajo para confirmar la recepción correcta del byte.

stop

Condición de Stop

SDA sube mientras SCL está alto. Indica el fin de la transacción.

play_circle Simulador interactivo: Transferencia I2C
Paso 0/12
SCL
SDA
HIGH (1)
LOW (0)
Transición
Presiona "Siguiente paso" para comenzar
Recorrerás cada fase de una transferencia I2C completa: escritura de la dirección 0x4A seguida de un byte de datos 0x8C.
school
Regla clave: SDA solo puede cambiar cuando SCL esta LOW. Las unicas excepciones son las condiciones de START y STOP, donde SDA cambia mientras SCL esta HIGH. Esto es lo que las hace "especiales" y reconocibles por todos los dispositivos del bus.

I2C en Zephyr

Configuración y uso del driver I2C en el nRF Connect SDK.

settings Configuración en prj.conf

prj.conf
# Habilitar driver I2C
CONFIG_I2C=y

code Incluir cabecera

src/main.c
#include <zephyr/drivers/i2c.h>
info
Estructura clave: i2c_dt_spec contiene el puntero al dispositivo del bus y la dirección del target.

Funciones principales del API

I2C_DT_SPEC_GET(node_id)
Obtener especificación I2C desde DeviceTree
device_is_ready(dev_i2c.bus)
Verificar que el bus I2C está listo
i2c_write_dt(&dev, buf, len)
Escribir datos al dispositivo I2C
i2c_read_dt(&dev, buf, len)
Leer datos del dispositivo I2C
i2c_write_read_dt(&dev, wr, wr_len, rd, rd_len)
Escritura seguida de lectura (sin Stop intermedio)

Configuración en DeviceTree

Los dispositivos I2C se declaran como nodos hijos del controlador I2C.

account_tree Jerarquía DeviceTree

&i2c0 { ... }
Controlador I2C (bus)
mysensor: mysensor@4a { ... }
Dispositivo en dirección 0x4A
info
Convención: El sufijo @4a en el nombre del nodo indica la dirección I2C en hexadecimal. Debe coincidir con el valor de reg.
boards/nrf52833dk_nrf52833.overlay
/* Añadir sensor al bus I2C0 */
&i2c0 {
status = "okay";
mysensor: mysensor@4a {
compatible = "i2c-device";
reg = < 0x4a >;
label = "MYSENSOR";
};
};

Propiedades del nodo

compatible Tipo de dispositivo (usar driver genérico o específico)
reg Dirección I2C del dispositivo (7 bits)
label Etiqueta legible (opcional)

Inicializar dispositivo I2C

Obtener la referencia al dispositivo y verificar que está disponible.

src/main.c
#include <zephyr/kernel.h>
#include <zephyr/drivers/i2c.h>
/* Obtener nodo del DeviceTree */
#define I2C_NODE DT_NODELABEL(mysensor)
/* Crear especificación I2C */
static const struct i2c_dt_spec dev_i2c =
I2C_DT_SPEC_GET(I2C_NODE);
int main(void) {
/* Verificar que el bus está listo */
if (!device_is_ready(dev_i2c.bus)) {
printk("Bus I2C %s no disponible!\n",
dev_i2c.bus->name);
return -1;
}
printk("I2C listo en dirección 0x%02X\n",
dev_i2c.addr);
/* Continuar con operaciones I2C... */
return 0;
}

Pasos de inicialización

1
Obtener nodo DeviceTree
DT_NODELABEL() obtiene el nodo por su etiqueta
2
Crear i2c_dt_spec
I2C_DT_SPEC_GET() extrae bus y dirección
3
Verificar disponibilidad
device_is_ready() confirma que el driver está inicializado
warning
Importante: Siempre verifica device_is_ready() antes de usar el dispositivo. Puede fallar si el DeviceTree no está configurado correctamente.

Operaciones de escritura

Escribir datos en registros del dispositivo I2C.

upload Función i2c_write_dt()

Envia un buffer de bytes al dispositivo. El primer byte suele ser la dirección del registro a escribir.

Prototipo
int i2c_write_dt(
const struct i2c_dt_spec *spec,
const uint8_t *buf,
uint32_t num_bytes
);
info
Retorno: 0 si tiene exito, código de error negativo si falla.
Ejemplo: Escribir 0x8C en registro 0x03
/* Buffer: [registro, valor] */
uint8_t config[2] = {0x03, 0x8C};
int ret = i2c_write_dt(&dev_i2c,
config, sizeof(config));
if (ret != 0) {
printk("Error I2C write: %d\n", ret);
}

Secuencia en el bus

S 0x4A+W ACK 0x03 ACK 0x8C ACK P

Operaciones de lectura

Diferentes métodos para leer datos de dispositivos I2C.

i2c_read_dt(&dev, buf, len)

Lectura simple. El dispositivo envia datos inmediatamente después del byte de dirección.

i2c_burst_read_dt(&dev, start_addr, buf, len)

Lectura en ráfaga. Primero escribe la dirección del registro inicial, luego lee múltiples bytes consecutivos.

i2c_write_read_dt(&dev, wr, wr_len, rd, rd_len)

Escritura seguida de lectura sin condición de Stop intermedia. Muy útil para leer registros específicos.

Ejemplos de lectura
/* 1. Lectura simple */
uint8_t data[4];
i2c_read_dt(&dev_i2c, data, 4);
/* 2. Burst read desde registro 0xFA */
uint8_t temp_data[3];
i2c_burst_read_dt(&dev_i2c,
0xFA, /* registro inicial */
temp_data, 3);
/* 3. Write-read: leer registro 0xD0 */
uint8_t reg = 0xD0;
uint8_t chip_id;
i2c_write_read_dt(&dev_i2c,
®, 1, /* escribir registro */
&chip_id, 1); /* leer valor */
tips_and_updates
Recomendación: Usa i2c_write_read_dt() para leer registros. Es el método más común y evita condiciones de carrera.

Ejemplo completo: BME280

Leer el Chip ID y datos de temperatura de un sensor BME280.

src/main.c - Includes y setup
#include <zephyr/kernel.h>
#include <zephyr/drivers/i2c.h>
#define BME280_NODE DT_NODELABEL(bme280)
#define BME280_REG_CHIP_ID 0xD0
#define BME280_REG_TEMP 0xFA
#define BME280_CHIP_ID 0x60
static const struct i2c_dt_spec bme280 =
I2C_DT_SPEC_GET(BME280_NODE);
Overlay para BME280
&i2c0 {
status = "okay";
bme280: bme280@76 {
compatible = "bosch,bme280";
reg = < 0x76 >;
};
};

Registros del BME280

Chip ID 0xD0
Control 0xF4
Temp MSB 0xFA
Temp LSB 0xFB

BME280: Funcion main()

Inicializacion, lectura de Chip ID y datos de temperatura.

src/main.c - main()
int main(void) {
if (!device_is_ready(bme280.bus)) {
printk("I2C no disponible\n");
return -1;
}
/* Leer Chip ID */
uint8_t reg = BME280_REG_CHIP_ID;
uint8_t chip_id;
i2c_write_read_dt(&bme280, ®, 1,
&chip_id, 1);
if (chip_id != BME280_CHIP_ID) {
printk("Chip ID incorrecto\n");
return -1;
}
Lectura de temperatura
printk("BME280 detectado!\n");
/* Leer temperatura (3 bytes) */
uint8_t temp_raw[3];
i2c_burst_read_dt(&bme280,
BME280_REG_TEMP, temp_raw, 3);
/* Combinar bytes (20 bits) */
int32_t adc_T =
((int32_t)temp_raw[0] << 12) |
((int32_t)temp_raw[1] << 4) |
(temp_raw[2] >> 4);
printk("Temp raw: %d\n", adc_T);
return 0;
}
info
La conversion a grados Celsius requiere aplicar la formula de compensacion usando coeficientes de calibracion.
check_circle

Lo que aprendiste hoy

cable Protocolo I2C

  • check Comunicacion sincrona de 2 hilos (SDA + SCL)
  • check Arquitectura Controller-Target
  • check Direccionamiento de 7 bits

code API de Zephyr

  • check Configuracion via DeviceTree overlay
  • check Funciones i2c_write_dt() e i2c_read_dt()
  • check Lectura de registros con i2c_write_read_dt()
tips_and_updates

"I2C es el protocolo estandar para conectar sensores en sistemas embebidos."

Con solo 2 hilos puedes comunicarte con multiples dispositivos en el mismo bus.

APIs principales

I2C_DT_SPEC_GET(node_id)
Obtener especificacion I2C desde DeviceTree
device_is_ready(dev.bus)
Verificar disponibilidad del bus
i2c_write_dt(&dev, buf, len)
Escribir datos al dispositivo
i2c_read_dt(&dev, buf, len)
Leer datos del dispositivo
i2c_burst_read_dt(&dev, addr, buf, len)
Lectura en rafaga desde registro
i2c_write_read_dt(&dev, wr, wr_len, rd, rd_len)
Escritura seguida de lectura
arrow_forward Siguiente modulo

1.6 Multithreading en Zephyr

Aprenderemos a crear y gestionar multiples hilos de ejecucion en nuestras aplicaciones.