Se rendre au contenu

← Communication UART | Sommaire | Suivant : Ordinateurs monocartes →

Communication I2C

Description détaillée de l'interface I2C pour le contrôle de DimmerLink.

⚠️ Important ! Le contrôleur est livré d'usine avec l'interface UART activée par défaut. Utilisez la commande UART SWITCH_I2C (0x5B) pour passer en mode I2C — voir la documentation UART. Après le basculement, le contrôleur démarrera avec l'interface I2C.




Paramètres de connexion

Paramètre Valeur
Mode Slave
Adresse 0x50 (7 bits)
Vitesse 100 kHz (Standard Mode)
Pull-up 4,7 kΩ sur SDA et SCL

ℹ️ Remarque : L'interface I2C est disponible immédiatement après la mise sous tension.




Modèle de registres

DimmerLink utilise un modèle d'accès basé sur les registres — les opérations de lecture et d'écriture se font par adresses de registres.


Carte des registres

Adresse Nom L/É Description
0x00 STATUS L État de l'appareil
0x01 COMMAND É Commandes de contrôle
0x02 ERROR L Dernier code d'erreur
0x03 VERSION L Version du firmware
0x10 DIM0_LEVEL L/É Luminosité du variateur 0 (0–100%)
0x11 DIM0_CURVE L/É Courbe du variateur 0
0x20 AC_FREQ L Fréquence du réseau (50/60 Hz)
0x21 AC_PERIOD_L L Période du réseau, octet bas
0x22 AC_PERIOD_H L Période du réseau, octet haut
0x23 CALIBRATION L État du calibrage
0x30 I2C_ADDRESS L/É Adresse I2C actuelle (0x08–0x77)



Description des registres


STATUS (0x00) — État de l'appareil

Bit Nom Description
0 READY 1 = L'appareil est prêt
1 ERROR 1 = La dernière opération a échoué
2-7 Réservé


COMMAND (0x01) — Commandes de contrôle

Valeur Commande Description
0x00 NOP Aucune opération
0x01 RESET Réinitialisation logicielle
0x02 RECALIBRATE Recalibrer la fréquence
0x03 SWITCH_UART Basculer l'interface vers UART


ERROR (0x02) — Code d'erreur

Code Nom Description
0x00 OK Aucune erreur
0xF9 ERR_SYNTAX Adresse de registre invalide
0xFC ERR_NOT_READY Erreur d'écriture EEPROM
0xFD ERR_INDEX Index de variateur invalide
0xFE ERR_PARAM Valeur de paramètre invalide


DIM0_LEVEL (0x10) — Luminosité

  • Lecture : renvoie la luminosité actuelle (0–100)
  • Écriture : définit la luminosité (0–100)
Valeur Luminosité
0 Éteint
50 50%
100 Pleine luminosité


DIM0_CURVE (0x11) — Courbe de variation

Valeur Courbe Application
0 LINEAR Universelle
1 RMS Incandescent, halogène
2 LOG LED (correspond à la perception de l'œil)


AC_FREQ (0x20) — Fréquence du réseau

  • Lecture : 50 ou 60 (Hz)


I2C_ADDRESS (0x30) — Adresse I2C de l'appareil

  • Lecture : renvoie l'adresse I2C actuelle
  • Écriture : définit la nouvelle adresse I2C
Paramètre Valeur
Plage 0x08 – 0x77
Par défaut 0x50

⚠️ Important : Après l'écriture d'une nouvelle adresse, l'appareil répond immédiatement sur la nouvelle adresse. L'ancienne adresse ne fonctionne plus. La nouvelle adresse est enregistrée dans l'EEPROM.

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


Modification de l'adresse 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

⚠️ Avertissement : Après le changement d'adresse, l'appareil cesse immédiatement de répondre sur l'ancienne adresse !



Basculer vers UART

Si vous devez passer de I2C à 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) — Période du réseau

Période de la demi-onde en microsecondes (valeur 16 bits) :

python
period_us = (AC_PERIOD_H << 8) | AC_PERIOD_L
Fréquence Période attendue
50 Hz ~10000 µs
60 Hz ~8333 µs

📘 Pour la plupart des applications, le registre AC_FREQ (0x20) est suffisant.


CALIBRATION (0x23) — État du calibrage

Valeur État
0 Calibrage en cours
1 Calibrage terminé



Opérations I2C


Écriture dans un registre

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

Exemple — définir la luminosité à 50% :

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


Lecture d'un registre

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

Exemple — lire la luminosité :

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

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




Exemples de code


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



Comparaison avec UART

Aspect I2C UART
Avantages Accès par registres, code plus simple Fonctionne avec des ponts
Exemple d'écriture write_byte_data(0x50, 0x10, 50) write([0x02, 0x53, 0x00, 50])
Exemple de lecture read_byte_data(0x50, 0x10) Envoyer la commande, lire la réponse
Code d'erreur Lecture depuis le registre ERROR Renvoyé dans la réponse



Dépannage


Recherche de l'appareil

Raspberry Pi :

bash
i2cdetect -y 1

Résultat attendu — adresse 50 dans le tableau.

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


Problèmes courants

Problème Cause Solution
Appareil non trouvé Pas de pull-up Ajouter 4,7 kΩ sur SDA et SCL
Appareil non trouvé Mauvaise adresse Vérifier 0x50 (ou l'adresse modifiée)
ERROR = 0xFE Paramètre invalide level > 100, curve > 2 ou adresse en dehors de 0x08–0x77
Connexion instable Câbles trop longs Raccourcir les câbles ou réduire la valeur du pull-up
Pas de réponse après changement d'adresse Utilisation de l'ancienne adresse Se reconnecter avec la nouvelle adresse


Lecture/écriture en ligne de commande (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?

← Communication UART | Sommaire | Suivant : Ordinateurs monocartes →