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.

JARU I2C Module

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
ESP32Soporte completo con dos buses independientes
Windows (SDL2)🔶Simulación (stub) para desarrollo
Emscripten (Web)🔶Simulación (stub) para desarrollo