# Описание компонента zh_ac_dimmer ## Общие сведения **zh_ac_dimmer** — это драйвер для ESP-IDF, реализующий фазовое управление мощностью переменного тока (AC dimming) через детектирование перехода через ноль и управление затвором тиристора (TRIAC). Компонент определяет момент перехода сетевого напряжения через ноль, измеряет период полуволны и зажигает TRIAC с программируемой задержкой, регулируя среднюю мощность, подаваемую на нагрузку. Компонент предоставляет полный набор функций для инициализации, запуска, остановки и управления уровнем затемнения (0–100 %), а также автоматическую калибровку аппаратного смещения в режиме реального времени. ### Основные возможности 1. **Детектирование перехода через ноль** — через GPIO-прерывание с измерением периода. 2. **Управление TRIAC** — через ESP-IDF gptimer с программируемой задержкой. 3. **Уровень затемнения 0–100 %** — плавная регулировка мощности нагрузки. 4. **Автоматическая калибровка** — аппаратное смещение калибруется во время работы. 5. **Фильтрация периодов** — низкочастотный фильтр для стабильного измерения частоты сети. 6. **Защитный зазор (guard band)** — гарантированное отключение TRIAC до конца полуволны. 7. **Валидация частоты** — отбрасывание помех и некорректных импульсов. 8. **Старт/стоп API** — простое управление состоянием dimmer. --- ## Внутреннее устройство ### Структура `zh_ac_dimmer_handle_t` Внутренняя структура хранит все состояние экземпляра dimmer: GPIO-пины, таймер, состояние синхронизации и текущий уровень затемнения. | Поле | Тип | Описание | | ------ | ----- | ---------- | | `zero_cross_gpio` | `gpio_num_t` | GPIO-пин для детектирования перехода через ноль | | `triac_gpio` | `gpio_num_t` | GPIO-пин для управления затвором TRIAC (выход) | | `dimmer_timer_handle` | `gptimer_handle_t` | Экземпляр gptimer для задержки зажигания TRIAC | | `prev_zero_crossing_us` | `uint64_t` | Метка времени предыдущего фронтa (мкс) | | `pulse_start_us` | `uint64_t` | Метка времени текущего фронтa для измерения ширины импульса (мкс) | | `period_us` | `uint32_t` | Отфильтрованный период полуволны в мкс (0 до первого измерения) | | `base_offset_us` | `volatile uint32_t` | Автоматически откалиброванное опережение ISR относительно нуля (мкс) | | `auto_base_samples` | `volatile uint32_t` | Количество образцов, использованных для калибровки | | `dimmer_value` | `volatile uint8_t` | Текущий уровень затемнения, 0 (выкл) .. 100 (полная мощность) | | `is_dimmer_work` | `volatile bool` | Флаг активной работы dimmer | | `last_edge_was_rising` | `bool` | Флаг полярности последнего фронта для отбрасывания дубликатов | ### Управление памятью Память выделяется через `heap_caps_calloc()` в функции `zh_ac_dimmer_init()` с флагом `MALLOC_CAP_8BIT`. Освобождение происходит в `zh_ac_dimmer_deinit()` через `heap_caps_free()`. При неудачной инициализации освобождённая память очищается через цепочку cleanup в макросе `ZH_ERROR_CHECK`. ### Структура `zh_ac_dimmer_init_config_t` Конфигурационная структура, передаваемая пользователем при инициализации. | Поле | Тип | Описание | |------|-----|----------| | `zero_cross_gpio` | `gpio_num_t` | GPIO-пин для детектирования перехода через ноль | | `triac_gpio` | `gpio_num_t` | GPIO-пин для управления затвором TRIAC | --- ## API (интерфейс) ### Инициализация и освобождение | Функция | Описание | |---------|----------| | `zh_ac_dimmer_init()` | Инициализирует драйвер: выделяет память, настраивает GPIO, устанавливает ISR-обработчик, создаёт gptimer | | `zh_ac_dimmer_deinit()` | Полностью деинициализирует драйвер: удаляет ISR, сбрасывает GPIO, удаляет таймер, освобождает память | ### Управление состоянием | Функция | Описание | |---------|----------| | `zh_ac_dimmer_start()` | Включает dimmer — переходы через ноль начинают запускать TRIAC | | `zh_ac_dimmer_stop()` | Останавливает dimmer — TRIAC не зажигается до следующего перехода через ноль | ### Установка и получение уровня | Функция | Описание | |---------|----------| | `zh_ac_dimmer_set()` | Устанавливает уровень затемнения (0–100 %), применяется на следующем полупериоде | | `zh_ac_dimmer_get()` | Возвращает текущий установленный уровень затемнения | --- ## Потокобезопасность Компонент **не является потокобезопасным**. Все поля структуры `zh_ac_dimmer_handle_t`, изменяемые как из основного потока, так и из ISR (`dimmer_value`, `is_dimmer_work`, `base_offset_us`, `auto_base_samples`), помечены как `volatile` для предотвращения оптимизаций компилятора. Синхронизация между потоками не предусмотрена — пользователь должен обеспечивать взаимное исключение на уровне приложения, если dimmer используется из нескольких задач FreeRTOS. --- ## Алгоритмы ### Калибровка аппаратного смещения При детектировании спадающего фронта импульса перехода через ноль вычисляется ширина импульса (`pulse_width`), которая делится пополам для получения опережения: ``` measured_adv = pulse_width / 2 ``` Первое измерение устанавливает `base_offset_us` напрямую. Последующие значения сглаживаются экспоненциальным скользящим средним (EMA) с коэффициентом 15/16: ``` base_offset_us = (base_offset_us * 15 + measured_adv) / 16 ``` Валидация импульса: ширина должна быть в диапазоне `[4 мкс, period_us / 4]`. Если `period_us` ещё не измерен, используется верхний предел 2000 мкс. ### Фильтрация периода полуволны Первый принятый фронт устанавливает `period_us` напрямую. Последующие значения сглаживаются EMA с коэффициентом 7/8: ``` period_us = (period_us * 7 + delta_us) / 8 ``` Если интервал между фронтами выходит за пределы `[period_min_us, period_max_us]`, состояние сбрасывается: `period_us = 0`, `auto_base_samples = 0`, TRIAC сбрасывается. Границы периода рассчитываются из настраиваемой частоты сети: ``` period_min_us = 1 000 000 / (2 * ZH_AC_DIMMER_FREQ_MAX_HZ) period_max_us = 1 000 000 / (2 * ZH_AC_DIMMER_FREQ_MIN_HZ) ``` ### Расчёт задержки зажигания TRIAC Для каждого принятого фронтa вычисляется задержка до зажигания TRIAC: 1. Вычисляется целевая задержка на основе уровня затемнения: ``` t_fire = period * (100 - dimmer_value) / 100 ``` 2. Добавляется откалиброванное смещение: ``` delay_us = t_fire + base_offset_us ``` Если `base_offset_us >= period`, смещение ограничивается значением `period`. 3. Если `delay_us <= 0`, TRIAC зажигается немедленно. 4. Вычисляется защитный зазор: `guard_us = max(period / 20, 5 мкс)`. 5. Если `delay_us > period - guard_us`, задержка урезается до `period - guard_us`. 6. Таймер запускается с вычисленной задержкой. ### Обработка ISR перехода через ноль ISR обрабатывает оба фронта сигнала: 1. **Спадающий фронт** (GPIO = 0, предыдущий фронт был возрастающий): - Измеряется ширина импульса. - Калибруется `base_offset_us` через EMA. - Действий с TRIAC не выполняется. 2. **Возрастающий фронт** (GPIO = 1): - Сохраняется метка времени `pulse_start_us`. - Если это первый фронт — только запоминается время, TRIAC сбрасывается. - Интервал сравнивается с допустимыми границами: - Если интервал слишком короткий — фронт отбрасывается как помеха. - Если интервал слишком длинный — состояние сбрасывается. - Обновляется отфильтрованный период `period_us`. - Если dimmer не работает или уровень = 0 — таймер останавливается. - Иначе — вычисляется задержка и запускается таймер для зажигания TRIAC. --- ## Примеры использования ### Базовый пример инициализации и управления ```c #include "zh_ac_dimmer.h" zh_ac_dimmer_handle_t *dimmer = NULL; void app_main(void) { // Настройка уровня логирования esp_log_level_set("zh_ac_dimmer", ESP_LOG_ERROR); // Конфигурация пинов zh_ac_dimmer_init_config_t config = ZH_AC_DIMMER_INIT_CONFIG_DEFAULT(); config.zero_cross_gpio = GPIO_NUM_16; config.triac_gpio = GPIO_NUM_17; // Инициализация и запуск zh_ac_dimmer_init(&config, &dimmer); zh_ac_dimmer_start(&dimmer); // Установка уровня затемнения 50 % zh_ac_dimmer_set(&dimmer, 50); // Чтение текущего уровня uint8_t level = 0; zh_ac_dimmer_get(&dimmer, &level); // Остановка и деинициализация zh_ac_dimmer_stop(&dimmer); zh_ac_dimmer_deinit(&dimmer); } ``` ### Динамическое изменение яркости ```c #include "zh_ac_dimmer.h" zh_ac_dimmer_handle_t *dimmer = NULL; void app_main(void) { esp_log_level_set("zh_ac_dimmer", ESP_LOG_ERROR); zh_ac_dimmer_init_config_t config = ZH_AC_DIMMER_INIT_CONFIG_DEFAULT(); config.zero_cross_gpio = GPIO_NUM_16; config.triac_gpio = GPIO_NUM_17; zh_ac_dimmer_init(&config, &dimmer); zh_ac_dimmer_start(&dimmer); // Плавное нарастание яркости от 0 до 100 % for (uint8_t i = 0; i <= 100; ++i) { zh_ac_dimmer_set(&dimmer, i); vTaskDelay(100 / portTICK_PERIOD_MS); } // Плавное затухание от 100 до 0 % for (uint8_t i = 100; i > 0; --i) { zh_ac_dimmer_set(&dimmer, i); vTaskDelay(100 / portTICK_PERIOD_MS); } zh_ac_dimmer_stop(&dimmer); zh_ac_dimmer_deinit(&dimmer); } ``` --- ## Логирование Компонент использует ESP-IDF логирование с тегом `"zh_ac_dimmer"`. ```c esp_log_level_set("zh_ac_dimmer", ESP_LOG_ERROR); ``` Доступные уровни: `ESP_LOG_ERROR`, `ESP_LOG_WARN`, `ESP_LOG_INFO`, `ESP_LOG_DEBUG`. Настройка `ESP_LOG_ERROR` оставляет только сообщения об ошибках. Уровень `ESP_LOG_INFO` добавляет сообщения об успешной инициализации, запуске и остановке. --- ## Ошибки | Код | Описание | | ----- | ---------- | | `ESP_OK` | Успешное выполнение | | `ESP_ERR_INVALID_ARG` | Неверный аргумент: NULL-указатель, некорректный GPIO, одинаковые пины, значение > 100 | | `ESP_ERR_INVALID_STATE` | Драйвер уже инициализирован (handle указывает на существующий экземпляр) | | `ESP_ERR_NO_MEM` | Не удалось выделить память для handle | | `ESP_FAIL` | Не удалось настроить GPIO, установить ISR-обработчик или создать таймер | --- ## Ограничения 1. **Один экземпляр на GPIO-пин** — инициализация второго dimmer на том же zero-cross GPIO заменяет ISR-обработчик первого экземпляра. Каждый пин должен принадлежать одному экземпляру. 2. **Не потокобезопасный** — нет внутренней синхронизации между задачами. `volatile` защищает только от оптимизаций компилятора, но не от гонки данных. 3. **Диапазон частот сети** — определяется макросами `ZH_AC_DIMMER_FREQ_MIN_HZ` (по умолчанию 30 Гц) и `ZH_AC_DIMMER_FREQ_MAX_HZ` (по умолчанию 500 Гц). Выход за эти границы приводит к сбросу состояния. 4. **Симметричный оптоизолятор** — калибровка смещения предполагает симметричный импульс перехода через ноль (`base_offset = pulse_width / 2`). Асимметрия внесёт систематическую погрешность. 5. **Защитный зазор** — минимальная задержка перед концом полуволны ограничена `max(period / 20, 5 мкс)`, что может не обеспечить достаточного времени для полного затухания TRIAC на очень низких частотах. --- ## Требования для сборки - ESP-IDF (фреймворк для ESP32) - FreeRTOS (входит в состав ESP-IDF) - Драйверы `driver/gpio` и `driver/gptimer` (входят в состав ESP-IDF) Компонент размещается в директории `components/` проекта ESP-IDF. --- ## Установка ```bash cd ваш_проект/components git clone https://github.com/aZholtikov/zh_ac_dimmer ``` ```c #include "zh_ac_dimmer.h" ```