Zum Inhalt springen

← UART-Kommunikation | Inhaltsverzeichnis | Weiter: Einplatinencomputer →

I2C-Kommunikation

Detaillierte Beschreibung der I2C-Schnittstelle zur Steuerung von DimmerLink.

⚠️ Wichtig! Der Controller wird ab Werk mit standardmäßig aktivierter UART-Schnittstelle ausgeliefert. Verwenden Sie den UART-Befehl SWITCH_I2C (0x5B), um in den I2C-Modus zu wechseln — siehe UART-Dokumentation. Nach dem Umschalten startet der Controller mit der I2C-Schnittstelle.




Verbindungsparameter

Parameter Wert
Modus Slave
Adresse 0x50 (7-Bit)
Geschwindigkeit 100 kHz (Standard Mode)
Pull-up 4,7 kΩ an SDA und SCL

ℹ️ Hinweis: Die I2C-Schnittstelle ist sofort nach dem Einschalten verfügbar.




Registermodell

DimmerLink verwendet ein registerbasiertes Zugriffsmodell — Lese- und Schreiboperationen erfolgen über Registeradressen.


Registerkarte

Adresse Name L/S Beschreibung
0x00 STATUS L Gerätestatus
0x01 COMMAND S Steuerbefehle
0x02 ERROR L Letzter Fehlercode
0x03 VERSION L Firmware-Version
0x10 DIM0_LEVEL L/S Helligkeit Dimmer 0 (0–100%)
0x11 DIM0_CURVE L/S Dimmkurve Dimmer 0
0x20 AC_FREQ L Netzfrequenz (50/60 Hz)
0x21 AC_PERIOD_L L Netzperiode, Low-Byte
0x22 AC_PERIOD_H L Netzperiode, High-Byte
0x23 CALIBRATION L Kalibrierungsstatus
0x30 I2C_ADDRESS L/S Aktuelle I2C-Adresse (0x08–0x77)



Registerbeschreibungen


STATUS (0x00) — Gerätestatus

Bit Name Beschreibung
0 READY 1 = Gerät ist bereit
1 ERROR 1 = Letzte Operation fehlgeschlagen
2-7 Reserviert


COMMAND (0x01) — Steuerbefehle

Wert Befehl Beschreibung
0x00 NOP Keine Operation
0x01 RESET Software-Reset
0x02 RECALIBRATE Frequenz neu kalibrieren
0x03 SWITCH_UART Schnittstelle auf UART umschalten


ERROR (0x02) — Fehlercode

Code Name Beschreibung
0x00 OK Keine Fehler
0xF9 ERR_SYNTAX Ungültige Registeradresse
0xFC ERR_NOT_READY EEPROM-Schreibfehler
0xFD ERR_INDEX Ungültiger Dimmer-Index
0xFE ERR_PARAM Ungültiger Parameterwert


DIM0_LEVEL (0x10) — Helligkeit

  • Lesen: gibt aktuelle Helligkeit zurück (0–100)
  • Schreiben: setzt Helligkeit (0–100)
Wert Helligkeit
0 Aus
50 50%
100 Volle Helligkeit


DIM0_CURVE (0x11) — Dimmkurve

Wert Kurve Anwendung
0 LINEAR Universell
1 RMS Glühlampen, Halogen
2 LOG LED (entspricht der Augenwahrnehmung)


AC_FREQ (0x20) — Netzfrequenz

  • Lesen: 50 oder 60 (Hz)


I2C_ADDRESS (0x30) — I2C-Adresse des Geräts

  • Lesen: gibt aktuelle I2C-Adresse zurück
  • Schreiben: setzt neue I2C-Adresse
Parameter Wert
Bereich 0x08 – 0x77
Standard 0x50

⚠️ Wichtig: Nach dem Schreiben einer neuen Adresse antwortet das Gerät sofort auf der neuen Adresse. Die alte Adresse funktioniert nicht mehr. Die neue Adresse wird im EEPROM gespeichert.

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


I2C-Adresse ändern

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

⚠️ Warnung: Nach dem Ändern der Adresse hört das Gerät sofort auf, auf der alten Adresse zu antworten!



Umschalten auf UART

Wenn Sie von I2C auf UART wechseln müssen:

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) — Netzperiode

Halbwellenperiode in Mikrosekunden (16-Bit-Wert):

python
period_us = (AC_PERIOD_H << 8) | AC_PERIOD_L
Frequenz Erwartete Periode
50 Hz ~10000 µs
60 Hz ~8333 µs

📘 Für die meisten Anwendungen ist das Register AC_FREQ (0x20) ausreichend.


CALIBRATION (0x23) — Kalibrierungsstatus

Wert Status
0 Kalibrierung läuft
1 Kalibrierung abgeschlossen



I2C-Operationen


Schreiben in ein Register

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

Beispiel — Helligkeit auf 50% setzen:

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


Lesen aus einem Register

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

Beispiel — Helligkeit lesen:

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

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




Codebeispiele


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



Vergleich mit UART

Aspekt I2C UART
Vorteile Registerzugriff, einfacherer Code Funktioniert mit Bridges
Schreibbeispiel write_byte_data(0x50, 0x10, 50) write([0x02, 0x53, 0x00, 50])
Lesebeispiel read_byte_data(0x50, 0x10) Befehl senden, Antwort lesen
Fehlercode Aus ERROR-Register lesen Wird als Antwort zurückgegeben



Fehlerbehebung


Gerät finden

Raspberry Pi:

bash
i2cdetect -y 1

Erwartete Ausgabe — Adresse 50 in der Tabelle.

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


Häufige Probleme

Problem Ursache Lösung
Gerät nicht gefunden Kein Pull-up 4,7 kΩ an SDA und SCL hinzufügen
Gerät nicht gefunden Falsche Adresse 0x50 prüfen (oder geänderte Adresse)
ERROR = 0xFE Ungültiger Parameter level > 100, curve > 2 oder Adresse außerhalb 0x08–0x77
Instabile Verbindung Lange Leitungen Leitungen kürzen oder Pull-up-Wert verringern
Keine Antwort nach Adressänderung Alte Adresse wird verwendet Mit neuer Adresse verbinden


Lesen/Schreiben über Kommandozeile (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?

← UART-Kommunikation | Inhaltsverzeichnis | Weiter: Einplatinencomputer →