Перейти к содержимому

← Интерфейс UART | Содержание | Далее: Одноплатные компьютеры →

Интерфейс I2C

Подробное описание интерфейса I2C для управления DimmerLink.

⚠️ Важно! Контроллер поставляется с завода с включённым по умолчанию интерфейсом UART. Используйте UART-команду SWITCH_I2C (0x5B) для переключения в режим I2C — см. документацию UART. После переключения контроллер будет запускаться с интерфейсом I2C.




Параметры подключения

Параметр Значение
Режим Slave
Адрес 0x50 (7 бит)
Скорость 100 кГц (Standard Mode)
Подтяжка 4,7 кОм на SDA и SCL

ℹ️ Примечание: Интерфейс I2C доступен сразу после подачи питания.




Регистровая модель

DimmerLink использует регистровую модель доступа — операции чтения и записи выполняются по адресам регистров.


Карта регистров

Адрес Имя Чт/Зп Описание
0x00 STATUS Чт Состояние устройства
0x01 COMMAND Зп Управляющие команды
0x02 ERROR Чт Код последней ошибки
0x03 VERSION Чт Версия прошивки
0x10 DIM0_LEVEL Чт/Зп Яркость диммера 0 (0–100%)
0x11 DIM0_CURVE Чт/Зп Кривая диммера 0
0x20 AC_FREQ Чт Частота сети (50/60 Гц)
0x21 AC_PERIOD_L Чт Период сети, младший байт
0x22 AC_PERIOD_H Чт Период сети, старший байт
0x23 CALIBRATION Чт Статус калибровки
0x30 I2C_ADDRESS Чт/Зп Текущий I2C-адрес (0x08–0x77)



Описание регистров


STATUS (0x00) — Состояние устройства

Бит Имя Описание
0 READY 1 = Устройство готово
1 ERROR 1 = Последняя операция завершилась ошибкой
2-7 Зарезервировано


COMMAND (0x01) — Управляющие команды

Значение Команда Описание
0x00 NOP Нет операции
0x01 RESET Программный сброс
0x02 RECALIBRATE Повторная калибровка частоты
0x03 SWITCH_UART Переключение интерфейса на UART


ERROR (0x02) — Код ошибки

Код Имя Описание
0x00 OK Ошибок нет
0xF9 ERR_SYNTAX Недопустимый адрес регистра
0xFC ERR_NOT_READY Ошибка записи в EEPROM
0xFD ERR_INDEX Недопустимый индекс диммера
0xFE ERR_PARAM Недопустимое значение параметра


DIM0_LEVEL (0x10) — Яркость

  • Чтение: возвращает текущую яркость (0–100)
  • Запись: задаёт яркость (0–100)
Значение Яркость
0 Выключено
50 50%
100 Полная яркость


DIM0_CURVE (0x11) — Кривая диммирования

Значение Кривая Применение
0 LINEAR Универсальная
1 RMS Лампы накаливания, галогенные
2 LOG LED (соответствует восприятию глаза)


AC_FREQ (0x20) — Частота сети

  • Чтение: 50 или 60 (Гц)


I2C_ADDRESS (0x30) — I2C-адрес устройства

  • Чтение: возвращает текущий I2C-адрес
  • Запись: задаёт новый I2C-адрес
Параметр Значение
Диапазон 0x08 – 0x77
По умолчанию 0x50

⚠️ Важно: После записи нового адреса устройство немедленно начинает отвечать по новому адресу. Старый адрес больше не работает. Новый адрес сохраняется в EEPROM.

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


Изменение 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

⚠️ Предупреждение: После изменения адреса устройство немедленно перестаёт отвечать по старому адресу!



Переключение на UART

Если необходимо переключиться с 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) — Период сети

Период полуволны в микросекундах (16-битное значение):

python
period_us = (AC_PERIOD_H << 8) | AC_PERIOD_L
Частота Ожидаемый период
50 Гц ~10000 мкс
60 Гц ~8333 мкс

📘 Для большинства задач достаточно регистра AC_FREQ (0x20).


CALIBRATION (0x23) — Статус калибровки

Значение Статус
0 Калибровка выполняется
1 Калибровка завершена



Операции I2C


Запись в регистр

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

Пример — задать яркость 50%:

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


Чтение из регистра

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

Пример — чтение яркости:

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

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




Примеры кода


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



Сравнение с UART

Аспект I2C UART
Преимущества Регистровый доступ, более простой код Работает с мостами
Пример записи write_byte_data(0x50, 0x10, 50) write([0x02, 0x53, 0x00, 50])
Пример чтения read_byte_data(0x50, 0x10) Отправить команду, прочитать ответ
Код ошибки Чтение из регистра ERROR Возвращается в ответе



Отладка


Поиск устройства

Raspberry Pi:

bash
i2cdetect -y 1

Ожидаемый результат — адрес 50 в таблице.

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


Распространённые проблемы

Проблема Причина Решение
Устройство не найдено Нет подтяжки Добавьте резисторы 4,7 кОм на SDA и SCL
Устройство не найдено Неверный адрес Проверьте адрес 0x50 (или изменённый адрес)
ERROR = 0xFE Недопустимый параметр level > 100, curve > 2 или адрес вне диапазона 0x08–0x77
Нестабильное соединение Длинные провода Укоротите провода или уменьшите номинал подтяжки
Нет ответа после смены адреса Используется старый адрес Подключитесь по новому адресу


Чтение/запись из командной строки (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 | Содержание | Далее: Одноплатные компьютеры →