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.

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
| Constante | Valor | Descripción |
|---|---|---|
I2C.PORT0 | 0 | Bus I2C principal |
I2C.PORT1 | 1 | Bus 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ámetro | Tipo | Descripción |
|---|---|---|
puerto | integer | Puerto I2C (0 o 1) |
sda | integer | Pin para la línea de datos (SDA) |
scl | integer | Pin para la línea de reloj (SCL) |
frecuencia | integer | Frecuencia del bus en Hz (típicamente 100000 o 400000) |
Retorno
Devuelve true si la inicialización fue exitosa, false en caso contrario.
- 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ámetro | Tipo | Descripción |
|---|---|---|
puerto | integer | Puerto 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ámetro | Tipo | Descripción |
|---|---|---|
puerto | integer | Puerto I2C |
direccion | integer | Dirección del dispositivo (7 bits, 0x00-0x7F) |
datos | list | Lista de bytes a enviar |
Retorno
Devuelve un código de error:
| Código | Significado |
|---|---|
| 0 | Éxito |
| 1 | Datos demasiado largos para el buffer |
| 2 | NACK recibido en dirección |
| 3 | NACK recibido en datos |
| 4 | Otro 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ámetro | Tipo | Descripción |
|---|---|---|
puerto | integer | Puerto I2C |
direccion | integer | Dirección del dispositivo |
longitud | integer | Nú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ámetro | Tipo | Descripción |
|---|---|---|
puerto | integer | Puerto I2C |
direccion | integer | Dirección del dispositivo |
datos_escritura | list | Bytes a escribir (típicamente dirección de registro) |
longitud_lectura | integer | Número de bytes a leer después |
Retorno
Devuelve una lista con los bytes leídos, o nil si la operación falló.
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ámetro | Tipo | Descripción |
|---|---|---|
puerto | integer | Puerto I2C |
direccion | integer | Dirección del dispositivo |
registro | integer | Dirección del registro (0-255) |
valor | integer | Valor 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ámetro | Tipo | Descripción |
|---|---|---|
puerto | integer | Puerto I2C |
direccion | integer | Dirección del dispositivo |
registro | integer | Direcció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ámetro | Tipo | Descripción |
|---|---|---|
puerto | integer | Puerto I2C |
direccion | integer | Dirección del dispositivo |
registro | integer | Dirección del registro |
littleEndian | boolean | (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.
- 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ámetro | Tipo | Descripción |
|---|---|---|
puerto | integer | Puerto I2C |
direccion | integer | Dirección del dispositivo |
registro | integer | Dirección del registro |
valor | integer | Valor de 16 bits a escribir (0-65535) |
littleEndian | boolean | (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ámetro | Tipo | Descripción |
|---|---|---|
puerto | integer | Puerto I2C |
ms | integer | Tiempo 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ámetro | Tipo | Descripción |
|---|---|---|
puerto | integer | Puerto 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
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.
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
| Plataforma | Soporte | Notas |
|---|---|---|
| ESP32 | ✅ | Soporte completo con dos buses independientes |
| Windows (SDL2) | 🔶 | Simulación (stub) para desarrollo |
| Emscripten (Web) | 🔶 | Simulación (stub) para desarrollo |