326 lines
16 KiB
Markdown
326 lines
16 KiB
Markdown
# Описание компонента 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"
|
||
```
|