Passa al contenuto

← Comunicazione UART | Indice | Avanti: Computer a scheda singola →

Comunicazione I2C

Descrizione dettagliata dell'interfaccia I2C per il controllo di DimmerLink.

⚠️ Importante! Il controller viene fornito dalla fabbrica con l'interfaccia UART abilitata di default. Utilizzare il comando UART SWITCH_I2C (0x5B) per passare alla modalità I2C — vedere la documentazione UART. Dopo il passaggio, il controller si avvierà con l'interfaccia I2C.




Parametri di connessione

Parametro Valore
Modalità Slave
Indirizzo 0x50 (7 bit)
Velocità 100 kHz (Standard Mode)
Pull-up 4,7 kΩ su SDA e SCL

ℹ️ Nota: L'interfaccia I2C è disponibile immediatamente dopo l'accensione.




Modello a registri

DimmerLink utilizza un modello di accesso basato su registri — le operazioni di lettura e scrittura avvengono tramite indirizzi di registro.


Mappa dei registri

Indirizzo Nome L/S Descrizione
0x00 STATUS L Stato del dispositivo
0x01 COMMAND S Comandi di controllo
0x02 ERROR L Ultimo codice di errore
0x03 VERSION L Versione del firmware
0x10 DIM0_LEVEL L/S Luminosità del dimmer 0 (0–100%)
0x11 DIM0_CURVE L/S Curva del dimmer 0
0x20 AC_FREQ L Frequenza di rete (50/60 Hz)
0x21 AC_PERIOD_L L Periodo di rete, byte basso
0x22 AC_PERIOD_H L Periodo di rete, byte alto
0x23 CALIBRATION L Stato della calibrazione
0x30 I2C_ADDRESS L/S Indirizzo I2C attuale (0x08–0x77)



Descrizione dei registri


STATUS (0x00) — Stato del dispositivo

Bit Nome Descrizione
0 READY 1 = Il dispositivo è pronto
1 ERROR 1 = L'ultima operazione è fallita
2-7 Riservato


COMMAND (0x01) — Comandi di controllo

Valore Comando Descrizione
0x00 NOP Nessuna operazione
0x01 RESET Reset software
0x02 RECALIBRATE Ricalibrazione della frequenza
0x03 SWITCH_UART Passaggio dell'interfaccia a UART


ERROR (0x02) — Codice di errore

Codice Nome Descrizione
0x00 OK Nessun errore
0xF9 ERR_SYNTAX Indirizzo di registro non valido
0xFC ERR_NOT_READY Errore di scrittura EEPROM
0xFD ERR_INDEX Indice del dimmer non valido
0xFE ERR_PARAM Valore del parametro non valido


DIM0_LEVEL (0x10) — Luminosità

  • Lettura: restituisce la luminosità attuale (0–100)
  • Scrittura: imposta la luminosità (0–100)
Valore Luminosità
0 Spento
50 50%
100 Luminosità massima


DIM0_CURVE (0x11) — Curva di dimmerizzazione

Valore Curva Applicazione
0 LINEAR Universale
1 RMS Incandescenza, alogene
2 LOG LED (corrisponde alla percezione dell'occhio)


AC_FREQ (0x20) — Frequenza di rete

  • Lettura: 50 o 60 (Hz)


I2C_ADDRESS (0x30) — Indirizzo I2C del dispositivo

  • Lettura: restituisce l'indirizzo I2C attuale
  • Scrittura: imposta il nuovo indirizzo I2C
Parametro Valore
Intervallo 0x08 – 0x77
Predefinito 0x50

⚠️ Importante: Dopo la scrittura di un nuovo indirizzo, il dispositivo risponde immediatamente al nuovo indirizzo. Il vecchio indirizzo non funziona più. Il nuovo indirizzo viene salvato nella EEPROM.

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


Modifica dell'indirizzo 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

⚠️ Avviso: Dopo la modifica dell'indirizzo, il dispositivo smette immediatamente di rispondere al vecchio indirizzo!



Passaggio a UART

Se è necessario passare da 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) — Periodo di rete

Periodo della semionda in microsecondi (valore a 16 bit):

python
period_us = (AC_PERIOD_H << 8) | AC_PERIOD_L
Frequenza Periodo previsto
50 Hz ~10000 µs
60 Hz ~8333 µs

📘 Per la maggior parte delle applicazioni, il registro AC_FREQ (0x20) è sufficiente.


CALIBRATION (0x23) — Stato della calibrazione

Valore Stato
0 Calibrazione in corso
1 Calibrazione completata



Operazioni I2C


Scrittura in un registro

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

Esempio — impostare la luminosità al 50%:

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


Lettura da un registro

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

Esempio — leggere la luminosità:

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

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




Esempi di codice


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()



Confronto con UART

Aspetto I2C UART
Vantaggi Accesso ai registri, codice più semplice Funziona con i bridge
Esempio di scrittura write_byte_data(0x50, 0x10, 50) write([0x02, 0x53, 0x00, 50])
Esempio di lettura read_byte_data(0x50, 0x10) Inviare comando, leggere risposta
Codice di errore Lettura dal registro ERROR Restituito come risposta



Debug


Ricerca del dispositivo

Raspberry Pi:

bash
i2cdetect -y 1

Output previsto — indirizzo 50 nella tabella.

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);
        }
    }
}


Problemi comuni

Problema Causa Soluzione
Dispositivo non trovato Nessun pull-up Aggiungere 4,7 kΩ su SDA e SCL
Dispositivo non trovato Indirizzo errato Verificare 0x50 (o l'indirizzo modificato)
ERROR = 0xFE Parametro non valido level > 100, curve > 2 o indirizzo fuori dall'intervallo 0x08–0x77
Connessione instabile Cavi lunghi Accorciare i cavi o ridurre il valore del pull-up
Nessuna risposta dopo cambio indirizzo Utilizzo del vecchio indirizzo Riconnettersi con il nuovo indirizzo


Lettura/scrittura da riga di comando (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?

← Comunicazione UART | Indice | Avanti: Computer a scheda singola →