← Интерфейс 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:
// 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
⚠️ Предупреждение: После изменения адреса устройство немедленно перестаёт отвечать по старому адресу!
Переключение на UART
Если необходимо переключиться с I2C на 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) — Период сети
Период полуволны в микросекундах (16-битное значение):
period_us = (AC_PERIOD_H << 8) | AC_PERIOD_L
| Частота | Ожидаемый период |
|---|---|
| 50 Гц | ~10000 мкс |
| 60 Гц | ~8333 мкс |
📘 Для большинства задач достаточно регистра AC_FREQ (0x20).
CALIBRATION (0x23) — Статус калибровки
| Значение | Статус |
|---|---|
| 0 | Калибровка выполняется |
| 1 | Калибровка завершена |
Операции I2C
Запись в регистр
START → [Address+W] → ACK → [Register] → ACK → [Data] → ACK → STOP
Пример — задать яркость 50%:
START → 0xA0 → ACK → 0x10 → ACK → 0x32 → ACK → STOP
Чтение из регистра
START → [Address+W] → ACK → [Register] → ACK →
RESTART → [Address+R] → ACK → [Data] → NACK → STOP
Пример — чтение яркости:
START → 0xA0 → ACK → 0x10 → ACK →
RESTART → 0xA1 → ACK → [data] → NACK → STOP
Addresses:
- Write: 0xA0 (0x50 << 1)
- Read: 0xA1 (0x50 << 1 | 1)
Примеры кода
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()
Сравнение с UART
| Аспект | I2C | UART |
|---|---|---|
| Преимущества | Регистровый доступ, более простой код | Работает с мостами |
| Пример записи | write_byte_data(0x50, 0x10, 50) |
write([0x02, 0x53, 0x00, 50]) |
| Пример чтения | read_byte_data(0x50, 0x10) |
Отправить команду, прочитать ответ |
| Код ошибки | Чтение из регистра ERROR | Возвращается в ответе |
Отладка
Поиск устройства
Raspberry Pi:
i2cdetect -y 1
Ожидаемый результат — адрес 50 в таблице.
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);
}
}
}
Распространённые проблемы
| Проблема | Причина | Решение |
|---|---|---|
| Устройство не найдено | Нет подтяжки | Добавьте резисторы 4,7 кОм на SDA и SCL |
| Устройство не найдено | Неверный адрес | Проверьте адрес 0x50 (или изменённый адрес) |
| ERROR = 0xFE | Недопустимый параметр | level > 100, curve > 2 или адрес вне диапазона 0x08–0x77 |
| Нестабильное соединение | Длинные провода | Укоротите провода или уменьшите номинал подтяжки |
| Нет ответа после смены адреса | Используется старый адрес | Подключитесь по новому адресу |
Чтение/запись из командной строки (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?
- Интерфейс UART — альтернативный способ управления
- Raspberry Pi — подробнее об одноплатных компьютерах
- Примеры кода — готовые скрипты
- FAQ — устранение неполадок
← Интерфейс UART | Содержание | Далее: Одноплатные компьютеры →