Compare commits
2
Commits
862ed13e09
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bbded33b2 | ||
|
|
51fd157ed2 |
No files matched your search
@@ -1,12 +1,22 @@
|
|||||||
# ESP32 ESP-IDF component for AC dimmer
|
# ESP32 ESP-IDF component for AC dimmer (phase-angle control)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
1. Zero-crossing detection via GPIO interrupt on both edges (`GPIO_INTR_ANYEDGE`), with edge polarity determined from the current GPIO level and duplicate/glitch edge rejection. Spurious-interrupt filtering: the interval between rising edges must be within the half-cycle range derived from the frequency 30 - 500 Hz (shorter intervals are ignored, longer ones reset the timing and calibration state).
|
1. Zero-crossing detection via GPIO interrupt on both edges with edge polarity determined from the current GPIO level and duplicate/glitch edge rejection.
|
||||||
2. Measured zero-crossing half-period is low-pass filtered and used to calculate the TRIAC firing delay, so the dimmer adapts to the actual mains frequency.
|
2. Spurious-interrupt filtering based on half-cycle range derived from the mains frequency limits (30 to 500 Hz).
|
||||||
3. TRIAC gate control through ESP-IDF gptimer (1 MHz resolution) with configurable dimming level (0–100%): 0 keeps the load off, 100 fires the TRIAC immediately. The firing delay is clamped to leave a guard band (period / 20, at least 5 us) before the end of the half-cycle.
|
3. Low-pass filtered zero-crossing half-period measurement adapts the firing delay to the actual mains frequency.
|
||||||
4. Automatic runtime calibration of the hardware base offset: on each falling edge the width of the zero-crossing pulse is measured and fed into an EMA (`pulse_width / 2`), compensating for the zero-crossing detector advance relative to the physical mains zero crossing.
|
4. TRIAC gate control through ESP-IDF gptimer with 1 MHz resolution and configurable dimming level from 0 to 100 percent.
|
||||||
5. Handle-based API: init/deinit (with full GPIO, ISR and timer cleanup), start/stop, set/get dimming level.
|
5. Firing delay clamped to a guard band of at least 5 microseconds before the end of the half-cycle.
|
||||||
|
6. Automatic runtime calibration of the hardware base offset using exponential moving average of the zero-crossing pulse width.
|
||||||
|
7. Handle-based API with init, deinit, start, stop, set and get functions.
|
||||||
|
8. Memory managed through ESP-IDF heap_caps functions with full cleanup on initialization failure.
|
||||||
|
|
||||||
|
## Attention
|
||||||
|
|
||||||
|
1. Only one dimmer instance per zero-crossing GPIO pin. Initializing a second instance on the same pin replaces the ISR handler of the first one.
|
||||||
|
2. Not thread-safe. Volatile fields prevent compiler optimizations but do not provide mutual exclusion between tasks.
|
||||||
|
3. Calibration assumes a symmetric zero-crossing optocoupler pulse. Asymmetric pulses introduce a systematic offset error.
|
||||||
|
4. Supported mains frequency range is limited by the compile-time constants 30 to 500 Hz. Out-of-range intervals reset the timing and calibration state.
|
||||||
|
|
||||||
## Using
|
## Using
|
||||||
|
|
||||||
@@ -23,7 +33,9 @@ In the application, add the component:
|
|||||||
#include "zh_ac_dimmer.h"
|
#include "zh_ac_dimmer.h"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example
|
## Examples
|
||||||
|
|
||||||
|
### Basic Example: Initialize and Control Dimming Level
|
||||||
|
|
||||||
```c
|
```c
|
||||||
#include "zh_ac_dimmer.h"
|
#include "zh_ac_dimmer.h"
|
||||||
@@ -36,24 +48,136 @@ void app_main(void)
|
|||||||
zh_ac_dimmer_init_config_t config = ZH_AC_DIMMER_INIT_CONFIG_DEFAULT();
|
zh_ac_dimmer_init_config_t config = ZH_AC_DIMMER_INIT_CONFIG_DEFAULT();
|
||||||
config.zero_cross_gpio = GPIO_NUM_16;
|
config.zero_cross_gpio = GPIO_NUM_16;
|
||||||
config.triac_gpio = GPIO_NUM_17;
|
config.triac_gpio = GPIO_NUM_17;
|
||||||
zh_ac_dimmer_init(&config, &ac_dimmer_handle);
|
esp_err_t ret = zh_ac_dimmer_init(&config, &ac_dimmer_handle);
|
||||||
zh_ac_dimmer_start(&ac_dimmer_handle);
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
printf("Dimmer initialization error: %s\n", esp_err_to_name(ret));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ret = zh_ac_dimmer_start(&ac_dimmer_handle);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
printf("Dimmer start error: %s\n", esp_err_to_name(ret));
|
||||||
|
}
|
||||||
|
uint8_t val1 = 50;
|
||||||
|
ret = zh_ac_dimmer_set(&ac_dimmer_handle, val1);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
printf("Dimmer set error: %s\n", esp_err_to_name(ret));
|
||||||
|
}
|
||||||
|
uint8_t value = 0;
|
||||||
|
ret = zh_ac_dimmer_get(&ac_dimmer_handle, &value);
|
||||||
|
if (ret == ESP_OK)
|
||||||
|
{
|
||||||
|
printf("Dimmer value: %u\n", value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("Dimmer get error: %s\n", esp_err_to_name(ret));
|
||||||
|
}
|
||||||
|
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||||
|
ret = zh_ac_dimmer_stop(&ac_dimmer_handle);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
printf("Dimmer stop error: %s\n", esp_err_to_name(ret));
|
||||||
|
}
|
||||||
|
zh_ac_dimmer_deinit(&ac_dimmer_handle);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Fade In Fade Out Example
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "zh_ac_dimmer.h"
|
||||||
|
|
||||||
|
zh_ac_dimmer_handle_t *ac_dimmer_handle = 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;
|
||||||
|
esp_err_t ret = zh_ac_dimmer_init(&config, &ac_dimmer_handle);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
printf("Dimmer initialization error: %s\n", esp_err_to_name(ret));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ret = zh_ac_dimmer_start(&ac_dimmer_handle);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
printf("Dimmer start error: %s\n", esp_err_to_name(ret));
|
||||||
|
}
|
||||||
uint8_t value = 0;
|
uint8_t value = 0;
|
||||||
for (uint8_t i = 0; i <= 100; ++i)
|
for (uint8_t i = 0; i <= 100; ++i)
|
||||||
{
|
{
|
||||||
zh_ac_dimmer_set(&ac_dimmer_handle, i);
|
zh_ac_dimmer_set(&ac_dimmer_handle, i);
|
||||||
zh_ac_dimmer_get(&ac_dimmer_handle, &value);
|
zh_ac_dimmer_get(&ac_dimmer_handle, &value);
|
||||||
printf("Dimmer value: %d.\n", value);
|
printf("Dimmer value: %u\n", value);
|
||||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
}
|
}
|
||||||
for (uint8_t i = 100; i > 0; --i)
|
for (uint8_t i = 100; i > 0; --i)
|
||||||
{
|
{
|
||||||
zh_ac_dimmer_set(&ac_dimmer_handle, i);
|
zh_ac_dimmer_set(&ac_dimmer_handle, i);
|
||||||
zh_ac_dimmer_get(&ac_dimmer_handle, &value);
|
zh_ac_dimmer_get(&ac_dimmer_handle, &value);
|
||||||
printf("Dimmer value: %d.\n", value);
|
printf("Dimmer value: %u\n", value);
|
||||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
}
|
}
|
||||||
zh_ac_dimmer_stop(&ac_dimmer_handle);
|
zh_ac_dimmer_stop(&ac_dimmer_handle);
|
||||||
zh_ac_dimmer_deinit(&ac_dimmer_handle);
|
zh_ac_dimmer_deinit(&ac_dimmer_handle);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Error Handling Example
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "zh_ac_dimmer.h"
|
||||||
|
|
||||||
|
zh_ac_dimmer_handle_t *ac_dimmer_handle = 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_16;
|
||||||
|
esp_err_t ret = zh_ac_dimmer_init(&config, &ac_dimmer_handle);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
printf("Expected init error: %s\n", esp_err_to_name(ret));
|
||||||
|
}
|
||||||
|
config.triac_gpio = GPIO_NUM_17;
|
||||||
|
ret = zh_ac_dimmer_init(&config, &ac_dimmer_handle);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
printf("Dimmer initialization error: %s\n", esp_err_to_name(ret));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ret = zh_ac_dimmer_init(&config, &ac_dimmer_handle);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
printf("Expected double init error: %s\n", esp_err_to_name(ret));
|
||||||
|
}
|
||||||
|
zh_ac_dimmer_start(&ac_dimmer_handle);
|
||||||
|
ret = zh_ac_dimmer_set(&ac_dimmer_handle, 101);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
printf("Expected set range error: %s\n", esp_err_to_name(ret));
|
||||||
|
}
|
||||||
|
ret = zh_ac_dimmer_set(&ac_dimmer_handle, 75);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
printf("Dimmer set error: %s\n", esp_err_to_name(ret));
|
||||||
|
}
|
||||||
|
uint8_t value = 0;
|
||||||
|
zh_ac_dimmer_get(&ac_dimmer_handle, &value);
|
||||||
|
printf("Dimmer value: %u\n", value);
|
||||||
|
zh_ac_dimmer_stop(&ac_dimmer_handle);
|
||||||
|
zh_ac_dimmer_deinit(&ac_dimmer_handle);
|
||||||
|
}
|
||||||
|
```
|
||||||
+325
@@ -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"
|
||||||
|
```
|
||||||
Reference in new issue
Block a user