Saltar al contenido principal

Módulo I2C

El módulo I2C permite la comunicación con dispositivos mediante el protocolo I2C (Inter-Integrated Circuit), también conocido como TWI (Two-Wire Interface). Es ideal para conectar sensores, memorias EEPROM, displays OLED, acelerómetros y otros periféricos al ESP32.

Captura del módulo I2C de JARU para escanear buses y comunicarse con sensores y periféricos

El ESP32 dispone de dos buses I2C independientes (PORT0 y PORT1), permitiendo conectar múltiples dispositivos simultáneamente en diferentes configuraciones de pines.

Uso​

use I2C

Constantes​

ConstanteValorDescripción
I2C.PORT00Bus I2C principal
I2C.PORT11Bus I2C secundario

Funciones​

init​

La función I2C.init(puerto, sda, scl, frecuencia) inicializa un bus I2C con los pines y frecuencia especificados.

use I2C

// Inicializar I2C en el puerto 0, SDA=21, SCL=22, 100kHz
var ok = I2C.init(I2C.PORT0, 21, 22, 100000)

if (ok) then
println("I2C inicializado correctamente")
end
ParámetroTipoDescripción
puertointegerPuerto I2C (0 o 1)
sdaintegerPin para la línea de datos (SDA)
sclintegerPin para la línea de reloj (SCL)
frecuenciaintegerFrecuencia del bus en Hz (típicamente 100000 o 400000)

Retorno​

Devuelve true si la inicialización fue exitosa, false en caso contrario.

Frecuencias comunes
  • 100000 (100 kHz): Modo estándar, compatible con todos los dispositivos
  • 400000 (400 kHz): Modo rápido, para dispositivos que lo soporten

scan​

La función I2C.scan(puerto) escanea el bus I2C en busca de dispositivos conectados. Útil para diagnóstico y descubrimiento de direcciones.

use I2C

I2C.init(I2C.PORT0, 21, 22, 100000)

var dispositivos = I2C.scan(I2C.PORT0)

println("Dispositivos encontrados: ", len(dispositivos))
foreach (var addr in dispositivos)
println(" - 0x", hex(addr))
end
ParámetroTipoDescripción
puertointegerPuerto I2C a escanear

Retorno​

Devuelve una lista con las direcciones (1-127) de los dispositivos que respondieron.

write​

La función I2C.write(puerto, direccion, datos) envía una lista de bytes a un dispositivo I2C.

use I2C

// Enviar comando a un display OLED (dirección 0x3C)
var comando = [0x00, 0xAE] // Display OFF
var error = I2C.write(I2C.PORT0, 0x3C, comando)

if (error == 0) then
println("Comando enviado correctamente")
end
ParámetroTipoDescripción
puertointegerPuerto I2C
direccionintegerDirección del dispositivo (7 bits, 0x00-0x7F)
datoslistLista de bytes a enviar

Retorno​

Devuelve un código de error:

CódigoSignificado
0Éxito
1Datos demasiado largos para el buffer
2NACK recibido en dirección
3NACK recibido en datos
4Otro error

read​

La función I2C.read(puerto, direccion, longitud) lee bytes de un dispositivo I2C.

use I2C

// Leer 6 bytes de un acelerómetro
var datos = I2C.read(I2C.PORT0, 0x68, 6)

if (datos != nil) then
println("Bytes recibidos: ", len(datos))
foreach (var b in datos)
println(" ", b)
end
end
ParámetroTipoDescripción
puertointegerPuerto I2C
direccionintegerDirección del dispositivo
longitudintegerNúmero de bytes a leer

Retorno​

Devuelve una lista con los bytes leídos.

transfer​

La función I2C.transfer(puerto, direccion, datos_escritura, longitud_lectura) realiza una escritura seguida de una lectura sin liberar el bus (Repeated Start). Es el patrón más común para leer registros de sensores.

use I2C

// Leer el registro WHO_AM_I (0x75) de un MPU6050
var registro = [0x75]
var respuesta = I2C.transfer(I2C.PORT0, 0x68, registro, 1)

if (respuesta != nil) then
println("WHO_AM_I: 0x", hex(respuesta[0]))
end
ParámetroTipoDescripción
puertointegerPuerto I2C
direccionintegerDirección del dispositivo
datos_escrituralistBytes a escribir (típicamente dirección de registro)
longitud_lecturaintegerNúmero de bytes a leer después

Retorno​

Devuelve una lista con los bytes leídos, o nil si la operación falló.

Repeated Start

El Repeated Start es esencial para muchos sensores I2C. Permite escribir la dirección del registro y leer su valor sin que otro dispositivo pueda interrumpir la comunicación.

writeReg​

La función I2C.writeReg(puerto, direccion, registro, valor) escribe un byte en un registro específico. Es una versión optimizada que evita crear listas.

use I2C

// Configurar el registro de control de un sensor
var error = I2C.writeReg(I2C.PORT0, 0x68, 0x6B, 0x00) // Despertar MPU6050

if (error == 0) then
println("Registro configurado")
end
ParámetroTipoDescripción
puertointegerPuerto I2C
direccionintegerDirección del dispositivo
registrointegerDirección del registro (0-255)
valorintegerValor a escribir (0-255)

Retorno​

Devuelve el código de error (0 = éxito).

readReg​

La función I2C.readReg(puerto, direccion, registro) lee un byte de un registro específico. Es una versión optimizada que devuelve directamente el valor.

use I2C

// Leer temperatura de un sensor
var temp = I2C.readReg(I2C.PORT0, 0x48, 0x00)

if (temp != nil) then
println("Temperatura raw: ", temp)
end
ParámetroTipoDescripción
puertointegerPuerto I2C
direccionintegerDirección del dispositivo
registrointegerDirección del registro a leer

Retorno​

Devuelve el valor del registro (0-255) o nil si falló la lectura.

readReg16​

La función I2C.readReg16(puerto, direccion, registro [, littleEndian]) lee un valor de 16 bits (2 bytes) de un registro.

use I2C

// Leer valor de 16 bits en Big Endian (por defecto)
var valor = I2C.readReg16(I2C.PORT0, 0x68, 0x3B)

// Leer en Little Endian
var valorLE = I2C.readReg16(I2C.PORT0, 0x48, 0x00, true)
ParámetroTipoDescripción
puertointegerPuerto I2C
direccionintegerDirección del dispositivo
registrointegerDirección del registro
littleEndianboolean(Opcional) Si true, interpreta como Little Endian. Por defecto: false (Big Endian)

Retorno​

Devuelve el valor de 16 bits (0-65535) o nil si falló la lectura.

Orden de bytes
  • Big Endian (por defecto): El byte más significativo (MSB) se lee primero. Común en sensores como MPU6050, BMP280.
  • Little Endian: El byte menos significativo (LSB) se lee primero. Usado por algunos sensores de temperatura.

writeReg16​

La función I2C.writeReg16(puerto, direccion, registro, valor [, littleEndian]) escribe un valor de 16 bits en un registro.

use I2C

// Escribir valor de 16 bits en Big Endian
var error = I2C.writeReg16(I2C.PORT0, 0x50, 0x00, 0x1234)

// Escribir en Little Endian
var errorLE = I2C.writeReg16(I2C.PORT0, 0x50, 0x00, 0x1234, true)
ParámetroTipoDescripción
puertointegerPuerto I2C
direccionintegerDirección del dispositivo
registrointegerDirección del registro
valorintegerValor de 16 bits a escribir (0-65535)
littleEndianboolean(Opcional) Si true, escribe en Little Endian

Retorno​

Devuelve el código de error (0 = éxito).

setTimeOut​

La función I2C.setTimeOut(puerto, ms) configura el tiempo máximo de espera para las operaciones I2C. Útil para evitar bloqueos si un dispositivo no responde.

use I2C

I2C.init(I2C.PORT0, 21, 22, 100000)
I2C.setTimeOut(I2C.PORT0, 100) // Timeout de 100ms
ParámetroTipoDescripción
puertointegerPuerto I2C
msintegerTiempo de espera en milisegundos

Retorno​

Devuelve true si se configuró correctamente.

close​

La función I2C.close(puerto) libera los recursos del bus I2C.

use I2C

// Al finalizar el uso del bus
I2C.close(I2C.PORT0)
ParámetroTipoDescripción
puertointegerPuerto I2C a cerrar

Retorno​

Devuelve true si se cerró correctamente, false si el puerto era inválido.

Ejemplo completo: Leer sensor MPU6050​

use I2C

// Configuración
var MPU_ADDR = 0x68
var PWR_MGMT = 0x6B
var ACCEL_XOUT = 0x3B

// Inicializar I2C
if (!I2C.init(I2C.PORT0, 21, 22, 400000)) then
println("Error al inicializar I2C")
exit(1)
end

// Verificar que el sensor está conectado
var dispositivos = I2C.scan(I2C.PORT0)
var encontrado = false
foreach (var addr in dispositivos)
if (addr == MPU_ADDR) then
encontrado = true
end
end

if (!encontrado) then
println("MPU6050 no encontrado")
exit(1)
end

// Despertar el sensor (salir de modo sleep)
I2C.writeReg(I2C.PORT0, MPU_ADDR, PWR_MGMT, 0x00)
pause(100)

// Leer aceleración en bucle
while (true)
// Leer 6 bytes: ACCEL_X (2), ACCEL_Y (2), ACCEL_Z (2)
var datos = I2C.transfer(I2C.PORT0, MPU_ADDR, [ACCEL_XOUT], 6)

if (datos != nil and len(datos) == 6) then
// Combinar bytes (Big Endian, con signo)
var ax = (datos[0] << 8) | datos[1]
var ay = (datos[2] << 8) | datos[3]
var az = (datos[4] << 8) | datos[5]

// Convertir a signed si es necesario
if (ax > 32767) then ax = ax - 65536 end
if (ay > 32767) then ay = ay - 65536 end
if (az > 32767) then az = az - 65536 end

println("Accel X:", ax, " Y:", ay, " Z:", az)
end

pause(100)
end

Ejemplo: Escribir en EEPROM 24C32​

use I2C

var EEPROM_ADDR = 0x50

I2C.init(I2C.PORT0, 21, 22, 100000)

// Escribir byte en dirección 0x0000
// La EEPROM 24C32 usa direcciones de 16 bits
var direccionAlta = 0x00
var direccionBaja = 0x00
var dato = 0x42

var error = I2C.write(I2C.PORT0, EEPROM_ADDR, [direccionAlta, direccionBaja, dato])

if (error == 0) then
println("Dato escrito correctamente")
pause(10) // Esperar ciclo de escritura de la EEPROM

// Leer el dato de vuelta
I2C.write(I2C.PORT0, EEPROM_ADDR, [direccionAlta, direccionBaja])
var leido = I2C.read(I2C.PORT0, EEPROM_ADDR, 1)

if (leido != nil) then
println("Dato leído: 0x", hex(leido[0]))
end
end

Consideraciones​

Resistencias Pull-up

El bus I2C requiere resistencias pull-up en las líneas SDA y SCL (típicamente 4.7kΩ a 10kΩ). Muchos módulos ya las incluyen, pero si conectas dispositivos directamente, asegúrate de añadirlas.

Direcciones I2C

Las direcciones I2C son de 7 bits (0x00-0x7F). Algunos datasheets muestran direcciones de 8 bits (incluyendo el bit R/W). En ese caso, divide la dirección entre 2 para obtener la dirección de 7 bits.

Plataformas soportadas​

PlataformaSoporteNotas
ESP32✅Soporte completo con dos buses independientes
Windows (SDL2)🔶Simulación (stub) para desarrollo
Emscripten (Web)🔶Simulación (stub) para desarrollo