← 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:
// 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):
# 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:
Wire.beginTransmission(0x50);
Wire.write(0x01); // COMMAND register
Wire.write(0x03); // SWITCH_UART
Wire.endTransmission();
// I2C no longer works, use UART
Raspberry Pi:
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):
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
START → [Address+W] → ACK → [Register] → ACK → [Data] → ACK → STOP
Esempio — impostare la luminosità al 50%:
START → 0xA0 → ACK → 0x10 → ACK → 0x32 → ACK → STOP
Lettura da un registro
START → [Address+W] → ACK → [Register] → ACK →
RESTART → [Address+R] → ACK → [Data] → NACK → STOP
Esempio — leggere la luminosità:
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)
#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
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)
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)
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:
i2cdetect -y 1
Output previsto — indirizzo 50 nella tabella.
Arduino:
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)
# 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?
- Interfaccia UART — metodo di controllo alternativo
- Raspberry Pi — approfondimento sui computer a scheda singola
- Esempi di codice — script pronti all'uso
- FAQ — risoluzione dei problemi
← Comunicazione UART | Indice | Avanti: Computer a scheda singola →