From 9bbded33b2084bcaaea973eed74610ed8cee7268 Mon Sep 17 00:00:00 2001 From: Alexey Zholtikov Date: Sun, 18 Oct 2026 21:08:34 +0300 Subject: [PATCH] doc: added wiki --- wiki_ru.md | 325 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 325 insertions(+) create mode 100644 wiki_ru.md diff --git a/wiki_ru.md b/wiki_ru.md new file mode 100644 index 0000000..1f74cd1 --- /dev/null +++ b/wiki_ru.md @@ -0,0 +1,325 @@ +# Описание компонента 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" +```