Ir al contenido

← Comunicación UART | Contenido | Siguiente: Ordenadores de placa única →

Comunicación I2C

Descripción detallada de la interfaz I2C para el control de DimmerLink.

⚠️ ¡Importante! El controlador se envía de fábrica con la interfaz UART habilitada por defecto. Utilice el comando UART SWITCH_I2C (0x5B) para cambiar al modo I2C — consulte la documentación UART. Tras el cambio, el controlador arrancará con la interfaz I2C.




Parámetros de conexión

Parámetro Valor
Modo Slave
Dirección 0x50 (7 bits)
Velocidad 100 kHz (Standard Mode)
Pull-up 4,7 kΩ en SDA y SCL

ℹ️ Nota: La interfaz I2C está disponible inmediatamente después del encendido.




Modelo de registros

DimmerLink utiliza un modelo de acceso basado en registros — las operaciones de lectura y escritura se realizan por direcciones de registro.


Mapa de registros

Dirección Nombre L/E Descripción
0x00 STATUS L Estado del dispositivo
0x01 COMMAND E Comandos de control
0x02 ERROR L Último código de error
0x03 VERSION L Versión del firmware
0x10 DIM0_LEVEL L/E Brillo del dimmer 0 (0–100%)
0x11 DIM0_CURVE L/E Curva del dimmer 0
0x20 AC_FREQ L Frecuencia de la red (50/60 Hz)
0x21 AC_PERIOD_L L Período de la red, byte bajo
0x22 AC_PERIOD_H L Período de la red, byte alto
0x23 CALIBRATION L Estado de calibración
0x30 I2C_ADDRESS L/E Dirección I2C actual (0x08–0x77)



Descripción de los registros


STATUS (0x00) — Estado del dispositivo

Bit Nombre Descripción
0 READY 1 = El dispositivo está listo
1 ERROR 1 = La última operación falló
2-7 Reservado


COMMAND (0x01) — Comandos de control

Valor Comando Descripción
0x00 NOP Sin operación
0x01 RESET Reinicio por software
0x02 RECALIBRATE Recalibrar frecuencia
0x03 SWITCH_UART Cambiar interfaz a UART


ERROR (0x02) — Código de error

Código Nombre Descripción
0x00 OK Sin errores
0xF9 ERR_SYNTAX Dirección de registro inválida
0xFC ERR_NOT_READY Error de escritura en EEPROM
0xFD ERR_INDEX Índice de dimmer inválido
0xFE ERR_PARAM Valor de parámetro inválido


DIM0_LEVEL (0x10) — Brillo

  • Lectura: devuelve el brillo actual (0–100)
  • Escritura: establece el brillo (0–100)
Valor Brillo
0 Apagado
50 50%
100 Brillo máximo


DIM0_CURVE (0x11) — Curva de atenuación

Valor Curva Aplicación
0 LINEAR Universal
1 RMS Incandescente, halógena
2 LOG LED (coincide con la percepción del ojo)


AC_FREQ (0x20) — Frecuencia de la red

  • Lectura: 50 o 60 (Hz)


I2C_ADDRESS (0x30) — Dirección I2C del dispositivo

  • Lectura: devuelve la dirección I2C actual
  • Escritura: establece la nueva dirección I2C
Parámetro Valor
Rango 0x08 – 0x77
Por defecto 0x50

⚠️ Importante: Después de escribir una nueva dirección, el dispositivo responde inmediatamente en la nueva dirección. La dirección anterior deja de funcionar. La nueva dirección se guarda en la EEPROM.

Reserved addresses (not allowed):
- 0x00-0x07: General Call, START byte, CBUS, HS-mode
- 0x78-0x7F: 10-bit addressing, Device ID


Cambio de la dirección I2C

Arduino:

cpp
// Change address from 0x50 to 0x51
void changeAddress(uint8_t newAddr) {
    Wire.beginTransmission(0x50);  // Current address
    Wire.write(0x30);               // I2C_ADDRESS register
    Wire.write(newAddr);            // New address
    Wire.endTransmission();

    // Device now responds on newAddr!
}

Raspberry Pi (CLI):

bash
# Change address to 0x51
i2cset -y 1 0x50 0x30 0x51
# Device is now at address 0x51
i2cdetect -y 1  # Verify

⚠️ Advertencia: Después de cambiar la dirección, ¡el dispositivo deja inmediatamente de responder en la dirección anterior!



Cambio a UART

Si necesita cambiar de I2C a UART:

Arduino:

cpp
Wire.beginTransmission(0x50);
Wire.write(0x01);   // COMMAND register
Wire.write(0x03);   // SWITCH_UART
Wire.endTransmission();
// I2C no longer works, use UART

Raspberry Pi:

bash
i2cset -y 1 0x50 0x01 0x03
# Now control via UART only


AC_PERIOD_L/H (0x21, 0x22) — Período de la red

Período de semionda en microsegundos (valor de 16 bits):

python
period_us = (AC_PERIOD_H << 8) | AC_PERIOD_L
Frecuencia Período esperado
50 Hz ~10000 µs
60 Hz ~8333 µs

📘 Para la mayoría de las aplicaciones, el registro AC_FREQ (0x20) es suficiente.


CALIBRATION (0x23) — Estado de calibración

Valor Estado
0 Calibración en progreso
1 Calibración completada



Operaciones I2C


Escritura en un registro

python
START → [Address+W] → ACK → [Register] → ACK → [Data] → ACK → STOP

Ejemplo — establecer el brillo al 50%:

python
START → 0xA0 → ACK → 0x10 → ACK → 0x32 → ACK → STOP


Lectura de un registro

python
START → [Address+W] → ACK → [Register] → ACK →
RESTART → [Address+R] → ACK → [Data] → NACK → STOP

Ejemplo — leer el brillo:

python
START → 0xA0 → ACK → 0x10 → ACK →
RESTART → 0xA1 → ACK → [data] → NACK → STOP

Addresses:
- Write: 0xA0 (0x50 << 1)
- Read: 0xA1 (0x50 << 1 | 1)




Ejemplos de código


Arduino (Wire.h)

cpp
#include 

#define DIMMER_ADDR   0x50
#define REG_STATUS    0x00
#define REG_ERROR     0x02
#define REG_LEVEL     0x10
#define REG_CURVE     0x11
#define REG_FREQ      0x20

void setup() {
    Serial.begin(115200);
    Wire.begin();

    if (isReady()) {
        Serial.println("DimmerLink ready!");
        Serial.print("Mains frequency: ");
        Serial.print(getFrequency());
        Serial.println(" Hz");
    }
}

// Check device readiness
bool isReady() {
    Wire.beginTransmission(DIMMER_ADDR);
    Wire.write(REG_STATUS);
    Wire.endTransmission(false);

    Wire.requestFrom(DIMMER_ADDR, 1);
    if (Wire.available()) {
        return (Wire.read() & 0x01) != 0;
    }
    return false;
}

// Set brightness (0-100%)
bool setLevel(uint8_t level) {
    if (level > 100) return false;

    Wire.beginTransmission(DIMMER_ADDR);
    Wire.write(REG_LEVEL);
    Wire.write(level);
    return Wire.endTransmission() == 0;
}

// Get current brightness
int getLevel() {
    Wire.beginTransmission(DIMMER_ADDR);
    Wire.write(REG_LEVEL);
    Wire.endTransmission(false);

    Wire.requestFrom(DIMMER_ADDR, 1);
    if (Wire.available()) {
        return Wire.read();
    }
    return -1;
}

// Set curve (0=LINEAR, 1=RMS, 2=LOG)
bool setCurve(uint8_t curve) {
    if (curve > 2) return false;

    Wire.beginTransmission(DIMMER_ADDR);
    Wire.write(REG_CURVE);
    Wire.write(curve);
    return Wire.endTransmission() == 0;
}

// Get curve type
int getCurve() {
    Wire.beginTransmission(DIMMER_ADDR);
    Wire.write(REG_CURVE);
    Wire.endTransmission(false);

    Wire.requestFrom(DIMMER_ADDR, 1);
    if (Wire.available()) {
        return Wire.read();
    }
    return -1;
}

// Get mains frequency
int getFrequency() {
    Wire.beginTransmission(DIMMER_ADDR);
    Wire.write(REG_FREQ);
    Wire.endTransmission(false);

    Wire.requestFrom(DIMMER_ADDR, 1);
    if (Wire.available()) {
        return Wire.read();
    }
    return -1;
}

// Get error code
uint8_t getError() {
    Wire.beginTransmission(DIMMER_ADDR);
    Wire.write(REG_ERROR);
    Wire.endTransmission(false);

    Wire.requestFrom(DIMMER_ADDR, 1);
    if (Wire.available()) {
        return Wire.read();
    }
    return 0xFF;
}

void loop() {
    // Smooth brightness change
    for (int level = 0; level <= 100; level += 10) {
        setLevel(level);
        Serial.print("Brightness: ");
        Serial.print(level);
        Serial.println("%");
        delay(500);
    }

    delay(1000);

    for (int level = 100; level >= 0; level -= 10) {
        setLevel(level);
        delay(500);
    }

    delay(1000);
}


Python (smbus2) — Raspberry Pi

python
from smbus2 import SMBus
import time

class DimmerLink:
    ADDR = 0x50

    # Registers
    REG_STATUS = 0x00
    REG_COMMAND = 0x01
    REG_ERROR = 0x02
    REG_VERSION = 0x03
    REG_LEVEL = 0x10
    REG_CURVE = 0x11
    REG_FREQ = 0x20

    # Curves
    CURVE_LINEAR = 0
    CURVE_RMS = 1
    CURVE_LOG = 2

    def __init__(self, bus_number=1):
        self.bus = SMBus(bus_number)

    def is_ready(self):
        """Check if device is ready"""
        status = self.bus.read_byte_data(self.ADDR, self.REG_STATUS)
        return (status & 0x01) != 0

    def get_version(self):
        """Get firmware version"""
        return self.bus.read_byte_data(self.ADDR, self.REG_VERSION)

    def set_level(self, level):
        """Set brightness 0-100%"""
        if not 0 <= level <= 100:
            raise ValueError("Level must be 0-100")
        self.bus.write_byte_data(self.ADDR, self.REG_LEVEL, level)

    def get_level(self):
        """Get current brightness"""
        return self.bus.read_byte_data(self.ADDR, self.REG_LEVEL)

    def set_curve(self, curve):
        """Set curve: 0=LINEAR, 1=RMS, 2=LOG"""
        if curve not in [0, 1, 2]:
            raise ValueError("Curve must be 0, 1, or 2")
        self.bus.write_byte_data(self.ADDR, self.REG_CURVE, curve)

    def get_curve(self):
        """Get curve type"""
        return self.bus.read_byte_data(self.ADDR, self.REG_CURVE)

    def get_frequency(self):
        """Get mains frequency (50 or 60 Hz)"""
        return self.bus.read_byte_data(self.ADDR, self.REG_FREQ)

    def get_error(self):
        """Get last error code"""
        return self.bus.read_byte_data(self.ADDR, self.REG_ERROR)

    def reset(self):
        """Software reset"""
        self.bus.write_byte_data(self.ADDR, self.REG_COMMAND, 0x01)

    def close(self):
        self.bus.close()

# Usage example
if __name__ == "__main__":
    dimmer = DimmerLink()

    if dimmer.is_ready():
        print(f"Firmware version: {dimmer.get_version()}")
        print(f"Mains frequency: {dimmer.get_frequency()} Hz")

        # Smooth brightness change
        for level in range(0, 101, 10):
            dimmer.set_level(level)
            print(f"Brightness: {level}%")
            time.sleep(0.5)

        # Set RMS curve for incandescent bulbs
        dimmer.set_curve(DimmerLink.CURVE_RMS)
        print("Curve: RMS")

    dimmer.close()


MicroPython (ESP32, Raspberry Pi Pico)

python
from machine import I2C, Pin
import time

class DimmerLink:
    ADDR = 0x50

    # Registers
    REG_STATUS = 0x00
    REG_COMMAND = 0x01
    REG_ERROR = 0x02
    REG_LEVEL = 0x10
    REG_CURVE = 0x11
    REG_FREQ = 0x20

    def __init__(self, i2c_id=0, scl_pin=22, sda_pin=21):
        """
        i2c_id: I2C bus number (usually 0)
        scl_pin, sda_pin: I2C pins
            - ESP32: scl=22, sda=21
            - Raspberry Pi Pico: scl=5, sda=4
        """
        self.i2c = I2C(i2c_id, scl=Pin(scl_pin), sda=Pin(sda_pin), freq=100000)

    def is_ready(self):
        """Check if device is ready"""
        data = self.i2c.readfrom_mem(self.ADDR, self.REG_STATUS, 1)
        return (data[0] & 0x01) != 0

    def set_level(self, level):
        """Set brightness 0-100%"""
        self.i2c.writeto_mem(self.ADDR, self.REG_LEVEL, bytes([level]))

    def get_level(self):
        """Get current brightness"""
        data = self.i2c.readfrom_mem(self.ADDR, self.REG_LEVEL, 1)
        return data[0]

    def set_curve(self, curve):
        """Set curve: 0=LINEAR, 1=RMS, 2=LOG"""
        self.i2c.writeto_mem(self.ADDR, self.REG_CURVE, bytes([curve]))

    def get_curve(self):
        """Get curve type"""
        data = self.i2c.readfrom_mem(self.ADDR, self.REG_CURVE, 1)
        return data[0]

    def get_frequency(self):
        """Get mains frequency"""
        data = self.i2c.readfrom_mem(self.ADDR, self.REG_FREQ, 1)
        return data[0]

# Usage example
dimmer = DimmerLink()

if dimmer.is_ready():
    print(f"Mains frequency: {dimmer.get_frequency()} Hz")

    while True:
        for level in range(0, 101, 10):
            dimmer.set_level(level)
            print(f"Brightness: {level}%")
            time.sleep(0.5)

        for level in range(100, -1, -10):
            dimmer.set_level(level)
            time.sleep(0.5)


CircuitPython (Adafruit)

python
import board
import busio
import time

class DimmerLink:
    ADDR = 0x50
    REG_LEVEL = 0x10
    REG_CURVE = 0x11
    REG_FREQ = 0x20

    def __init__(self):
        self.i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
        # Wait with timeout
        timeout = 100
        while not self.i2c.try_lock():
            timeout -= 1
            if timeout == 0:
                raise RuntimeError("I2C bus is busy")
            time.sleep(0.01)
            pass

    def set_level(self, level):
        """Set brightness 0-100%"""
        self.i2c.writeto(self.ADDR, bytes([self.REG_LEVEL, level]))

    def get_level(self):
        """Get current brightness"""
        result = bytearray(1)
        self.i2c.writeto_then_readfrom(self.ADDR, bytes([self.REG_LEVEL]), result)
        return result[0]

    def get_frequency(self):
        """Get mains frequency"""
        result = bytearray(1)
        self.i2c.writeto_then_readfrom(self.ADDR, bytes([self.REG_FREQ]), result)
        return result[0]

    def close(self):
        self.i2c.unlock()

# Usage example
dimmer = DimmerLink()

print(f"Mains frequency: {dimmer.get_frequency()} Hz")

for level in range(0, 101, 10):
    dimmer.set_level(level)
    print(f"Brightness: {level}%")
    time.sleep(0.5)

dimmer.close()



Comparación con UART

Aspecto I2C UART
Ventajas Acceso por registros, código más simple Funciona con puentes
Ejemplo de escritura write_byte_data(0x50, 0x10, 50) write([0x02, 0x53, 0x00, 50])
Ejemplo de lectura read_byte_data(0x50, 0x10) Enviar comando, leer respuesta
Código de error Lectura del registro ERROR Devuelto como respuesta



Depuración


Búsqueda del dispositivo

Raspberry Pi:

bash
i2cdetect -y 1

Resultado esperado — dirección 50 en la tabla.

Arduino:

cpp
void scanI2C() {
    for (uint8_t addr = 1; addr < 127; addr++) {
        Wire.beginTransmission(addr);
        if (Wire.endTransmission() == 0) {
            Serial.print("Device found: 0x");
            Serial.println(addr, HEX);
        }
    }
}


Problemas comunes

Problema Causa Solución
Dispositivo no encontrado Sin pull-up Agregar 4,7 kΩ en SDA y SCL
Dispositivo no encontrado Dirección incorrecta Verificar 0x50 (o la dirección modificada)
ERROR = 0xFE Parámetro inválido level > 100, curve > 2 o dirección fuera del rango 0x08–0x77
Conexión inestable Cables largos Acortar cables o reducir el valor del pull-up
Sin respuesta tras cambio de dirección Usando la dirección anterior Reconectar usando la nueva dirección


Lectura/escritura desde línea de comandos (Linux)

bash
# Read brightness
i2cget -y 1 0x50 0x10

# Set brightness to 50%
i2cset -y 1 0x50 0x10 0x32

# Read mains frequency
i2cget -y 1 0x50 0x20



What's Next?

← Comunicación UART | Contenido | Siguiente: Ordenadores de placa única →