Files
2026-10-18 21:08:34 +03:00

16 KiB
Raw Permalink Blame History

Описание компонента 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.

Примеры использования

Базовый пример инициализации и управления

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

Динамическое изменение яркости

#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".

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.


Установка

cd ваш_проект/components
git clone https://github.com/aZholtikov/zh_ac_dimmer
#include "zh_ac_dimmer.h"