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

326 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Описание компонента 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"
```