Files
zh_ac_dimmer/README.md
T
2026-10-18 21:07:48 +03:00

184 lines
5.8 KiB
Markdown

# ESP32 ESP-IDF component for AC dimmer (phase-angle control)
## Features
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. Spurious-interrupt filtering based on half-cycle range derived from the mains frequency limits (30 to 500 Hz).
3. Low-pass filtered zero-crossing half-period measurement adapts the firing delay to the actual mains frequency.
4. TRIAC gate control through ESP-IDF gptimer with 1 MHz resolution and configurable dimming level from 0 to 100 percent.
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
In an existing project, run the following command to install the component:
```bash
cd ../your_project/components
git clone https://github.com/aZholtikov/zh_ac_dimmer
```
In the application, add the component:
```c
#include "zh_ac_dimmer.h"
```
## Examples
### Basic Example: Initialize and Control Dimming Level
```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 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;
for (uint8_t i = 0; i <= 100; ++i)
{
zh_ac_dimmer_set(&ac_dimmer_handle, i);
zh_ac_dimmer_get(&ac_dimmer_handle, &value);
printf("Dimmer value: %u\n", value);
vTaskDelay(100 / portTICK_PERIOD_MS);
}
for (uint8_t i = 100; i > 0; --i)
{
zh_ac_dimmer_set(&ac_dimmer_handle, i);
zh_ac_dimmer_get(&ac_dimmer_handle, &value);
printf("Dimmer value: %u\n", value);
vTaskDelay(100 / portTICK_PERIOD_MS);
}
zh_ac_dimmer_stop(&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);
}
```