← Universal Library for ESP32 | Contents | Next: - ESP-IDF framework C guide and examples. →
Руководство и примеры Arduino
Перед началом прочитайте обзор библиотеки: Universal library for ESP32
Новое в v2.0.0
- Модульная внутренняя архитектура -- внутренняя структура библиотеки была переорганизована в отдельные модули. Публичный API не изменился; существующие скетчи компилируются и работают без изменений.
- Шумовой фильтр zero-cross -- устраняет ложные срабатывания TRIAC, вызывавшие мерцание на некоторых нагрузках.
- Двухпроходный ISR для синхронизации многоканальных систем -- все каналы на одной фазе теперь сортируются и срабатывают в одном полупериоде, устраняя межканальную временную нестабильность.
- IRAM_ATTR на всех критичных по времени путях -- обработчики ISR и связанные функции размещены в IRAM, чтобы избежать промахов кэша flash-памяти во время критичных по времени операций.
- Четыре новых параметра Kconfig -- конфигурируются через
idf.py menuconfigдля проектов ESP-IDF. Сборки Arduino используют значения по умолчанию, установленные во время компиляции (никаких действий не требуется).
Требования
- Arduino ESP32 Core 3.x (протестировано с 3.0+)
- Arduino IDE 2.x
- Примеры переструктурированы в отдельные директории для каждого скетча для совместимости с Arduino IDE 2.x
Установка библиотеки
Arduino IDE
- Скачайте библиотеку
rbdimmerESP32как ZIP-архив - В Arduino IDE выберите: Sketch > Include Library > Add .ZIP Library
- Выберите скачанный ZIP-файл
- Перезагрузите Arduino IDE для завершения установки
Пояснение операции библиотеки
Подготовка:
- Библиотека инициализируется с использованием
rbdimmer_init() - Детектор zero-crossing регистрируется с помощью
rbdimmer_register_zero_cross() - Канал диммера создается с помощью
rbdimmer_create_channel()
Управление затемнением:
- Установка уровня затемнения с
rbdimmer_set_level(). Уровень затемнения задается в диапазоне 0(OFF) ~ 100(ON) - Плавный переход уровня затемнения с
rbdimmer_set_level_transition(). Плавный переход от текущего уровня к заданному уровню в течение периода времени (в миллисекундах, 1s=1000ms)
Структуры данных
rbdimmer_config_t
typedef struct {
uint8_t gpio_pin; // Dimmer GPIO
uint8_t phase; // Phase number
uint8_t initial_level; // Initial dimming level
rbdimmer_curve_t curve_type; // Level Curve type
} rbdimmer_config_t;Перечисления
rbdimmer_curve_t
Типы кривых уровня:
typedef enum {
RBDIMMER_CURVE_LINEAR, // Linear curve
RBDIMMER_CURVE_RMS, // RMS-compensated curve (for incandescent bulbs)
RBDIMMER_CURVE_LOGARITHMIC // Logarithmic curve (for dimmable LED)
} rbdimmer_curve_t;rbdimmer_err_t
Ответы функции библиотеки:
typedef enum {
RBDIMMER_OK = 0, // Successful execution
RBDIMMER_ERR_INVALID_ARG, // Invalid argument
RBDIMMER_ERR_NO_MEMORY, // Not enough memory
RBDIMMER_ERR_NOT_FOUND, // Object not found
RBDIMMER_ERR_ALREADY_EXIST, // Object already exists
RBDIMMER_ERR_TIMER_FAILED, // Timer initialization error
RBDIMMER_ERR_GPIO_FAILED // GPIO initialization error
} rbdimmer_err_t;Константы и макросы
Константы в файле rbdimmerESP32.h. Вы можете изменить эти параметры:
#define RBDIMMER_MAX_PHASES 4 // Maximum number of phases
#define RBDIMMER_MAX_CHANNELS 8 // Maximum number of channels
#define RBDIMMER_DEFAULT_PULSE_WIDTH_US 50 // Pulse width (us)
#define RBDIMMER_MIN_DELAY_US 100 // Minimum delay (us) — raised from 50 in v2.0.0RBDIMMER_DEFAULT_PULSE_WIDTH_US, так как это связано с характеристиками оборудования диммера.Функции
Инициализация и настройка
// Initialize the library
rbdimmer_err_t rbdimmer_init(void);
// Register a zero-cross detector
rbdimmer_err_t rbdimmer_register_zero_cross(uint8_t pin, uint8_t phase, uint16_t frequency);
// Create a dimmer channel
rbdimmer_err_t rbdimmer_create_channel(rbdimmer_config_t* config, rbdimmer_channel_t** channel);
// Set callback function for zero-cross events
rbdimmer_err_t rbdimmer_set_callback(uint8_t phase, void (*callback)(void*), void* user_data);Управление затемнением
// Set dimming level
rbdimmer_err_t rbdimmer_set_level(rbdimmer_channel_t* channel, uint8_t level_percent);
// Set brightness with smooth transition
rbdimmer_err_t rbdimmer_set_level_transition(rbdimmer_channel_t* channel, uint8_t level_percent, uint32_t transition_ms);
// Set brightness curve type
rbdimmer_err_t rbdimmer_set_curve(rbdimmer_channel_t* channel, rbdimmer_curve_t curve_type);
// Activate/deactivate channel
rbdimmer_err_t rbdimmer_set_active(rbdimmer_channel_t* channel, bool active);Информационные запросы
// Get current channel brightness
uint8_t rbdimmer_get_level(rbdimmer_channel_t* channel);
// Get measured mains frequency for the specified phase
uint16_t rbdimmer_get_frequency(uint8_t phase);
// Check if channel is active
bool rbdimmer_is_active(rbdimmer_channel_t* channel);
// Get channel curve type
rbdimmer_curve_t rbdimmer_get_curve(rbdimmer_channel_t* channel);
// Get current channel delay
uint32_t rbdimmer_get_delay(rbdimmer_channel_t* channel);Пошаговое руководство
Этапы реализации инициализации библиотеки, регистрации фазы, детектора zero-cross, канала затемнения и управления затемнением:
1. Определите библиотеку и выводы
#include "rbdimmerESP32.h"
// Pins
#define ZERO_CROSS_PIN 18 // Zero-Cross pin
#define DIMMER_PIN 19 // Dimming control pin
#define PHASE_NUM 0 // Phase N (0 for single phase)2. Создайте объект диммера
Для каждого диммера создайте объект:
rbdimmer_channel_t* dimmer_channel = NULL;3. Инициализация библиотеки диммера
Функция возвращает статус:
rbdimmer_init();4. Детектор zero-cross и регистрация фазы
Функция возвращает статус:
rbdimmer_register_zero_cross(ZERO_CROSS_PIN, PHASE_NUM, 0);5. Конфигурация и создание канала диммера
rbdimmer_config_t config_channel = {
.gpio_pin = DIMMER_PIN,
.phase = PHASE_NUM,
.initial_level = 50, // Initial dimming level 50%
.curve_type = RBDIMMER_CURVE_RMS // Level Curve Selection. RMS-curve
};
rbdimmer_create_channel(&config_channel, &dimmer_channel);6. Операция затемнения
rbdimmer_set_level(dimmer_channel, level);7. Плавные переходы затемнения
rbdimmer_set_level_transition(dimmer_channel, 0, 5000);Решения
Многоканальные системы диммирования
Библиотека поддерживает несколько независимых каналов затемнения. Количество каналов ограничено в настройках библиотеки в файле rbdimmerESP32.h. Каждый канал затемнения должен иметь отдельный вывод.
Пример создания двухканальной системы:
#define ZERO_CROSS_PIN 18
#define DIMMER_PIN_1 19
#define DIMMER_PIN_2 21
#define PHASE_NUM 0
rbdimmer_channel_t* channel1 = NULL;
rbdimmer_channel_t* channel2 = NULL;
void setup() {
// Initialize library
rbdimmer_init();
// Register zero-cross detector (one per phase)
rbdimmer_register_zero_cross(ZERO_CROSS_PIN, PHASE_NUM, 0);
// Create first channel (incandescent bulbs)
rbdimmer_config_t config1 = {
.gpio_pin = DIMMER_PIN_1,
.phase = PHASE_NUM,
.initial_level = 50,
.curve_type = RBDIMMER_CURVE_RMS
};
rbdimmer_create_channel(&config1, &channel1);
// Create second channel (dimmable LED lighting)
rbdimmer_config_t config2 = {
.gpio_pin = DIMMER_PIN_2,
.phase = PHASE_NUM,
.initial_level = 50,
.curve_type = RBDIMMER_CURVE_LOGARITHMIC
};
rbdimmer_create_channel(&config2, &channel2);
}
void loop() {
// Control channels independently
rbdimmer_set_level(channel1, 75);
rbdimmer_set_level(channel2, 25);
delay(2000);
rbdimmer_set_level(channel1, 25);
rbdimmer_set_level(channel2, 75);
delay(2000);
}Использование функций обратного вызова прерывания
Функции обратного вызова позволяют вам синхронизировать ваш код с событиями zero-crossing. Это полезно для задач, требующих точной синхронизации с сетью переменного тока.
Пример регистрации и обработчика задач FreeRTOS:
// Callback function for zero-cross events
void zero_cross_callback(void* arg) {
// process zero-cross events
digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // Flashing with ZC frequency
// Any code
}
void setup() {
// ... Lib inits ...
// callback-function registry
rbdimmer_set_callback(PHASE_NUM, zero_cross_callback, NULL);
// ... any code ...
}Трехфазные системы
Для трехфазных систем отдельный детектор zero-crossing должен быть зарегистрирован для каждой фазы:
#define ZERO_CROSS_PIN_PHASE_A 18
#define ZERO_CROSS_PIN_PHASE_B 19
#define ZERO_CROSS_PIN_PHASE_C 21
#define DIMMER_PIN_PHASE_A 22
#define DIMMER_PIN_PHASE_B 23
#define DIMMER_PIN_PHASE_C 25
#define PHASE_A 0
#define PHASE_B 1
#define PHASE_C 2
void setup() {
// Lib init
rbdimmer_init();
// ZC detect registry for each phase
rbdimmer_register_zero_cross(ZERO_CROSS_PIN_PHASE_A, PHASE_A, 0);
rbdimmer_register_zero_cross(ZERO_CROSS_PIN_PHASE_B, PHASE_B, 0);
rbdimmer_register_zero_cross(ZERO_CROSS_PIN_PHASE_C, PHASE_C, 0);
// Create dimming channels for each phase
// ... dimming code ...
}Мониторинг операций
Для отладки можно использовать встроенные функции библиотеки:
void printDimmerStatus(rbdimmer_channel_t* channel) {
Serial.println("=== Dimmer Status ===");
Serial.printf("Mains frequency: %d Hz\n", rbdimmer_get_frequency(0));
Serial.printf("Brightness: %d%%\n", rbdimmer_get_level(channel));
Serial.printf("Active: %s\n", rbdimmer_is_active(channel) ? "Yes" : "No");
Serial.printf("Curve type: %d\n", rbdimmer_get_curve(channel));
Serial.printf("Delay: %d us\n", rbdimmer_get_delay(channel));
Serial.println("====================");
}Устранение неполадок: мерцание
v2.0.0 включает несколько целевых исправлений для распространенных проблем мерцания:
| Симптом | Причина | Исправление в v2.0.0 |
|---|---|---|
| Общее мерцание / случайные скачки | Срабатывание TRIAC повторно включает детектор zero-cross | Шумовой фильтр zero-cross: ZC_DEBOUNCE_US = 3000 us окно подавления после каждого zero-cross, игнорирующее ложные края |
| Мерцание при 100% яркости | Задержка срабатывания слишком близка к следующему zero-cross | MIN_DELAY_US повышен до 100 us; уровни >= 100% сопоставляются внутри с 99%, поэтому TRIAC всегда получает надлежащий импульс затвора |
| Мерцание ниже 3% яркости | TRIAC не может поддерживать ток при очень малых углах проводимости | Уровни ниже LEVEL_MIN рассматриваются как полностью ВЫКЛЮЧЕНЫ, избегая ненадежного частичного срабатывания |
| Многоканальный дрожание / мерцание | Каналы срабатывали с независимыми таймерами, вызывая конфликты планирования | Двухпроходный ISR: все каналы на одной фазе сортируются по задержке и последовательно срабатывают в одной цепочке таймера в течение каждого полупериода |
Если после обновления у вас все еще возникает мерцание, проверьте, чист ли ваш сигнал детектора zero-cross (нет звенения) и совместима ли ваша нагрузка с фазовым управлением, управляемым передней кромкой (TRIAC).
Базовые примеры для библиотеки AC Dimmer
Примеры скетчей находятся в отдельных директориях каждого скетча в examples/arduino/:
| Пример | Путь |
|---|---|
| Базовое управление диммером | examples/arduino/BasicDimmer/BasicDimmer.ino |
| Переход яркости | examples/arduino/BasicTransition/BasicTransition.ino |
| Несколько каналов диммера | examples/arduino/MultiDimmer/MultiDimmer.ino |
| Обратный вызов Zero-Cross | examples/arduino/ZCCallBack/ZCCallBack.ino |
Базовое управление диммером
Описание: самый простой пример, показывающий, как управлять AC диммером с фиксированным уровнем яркости.
Файл: examples/arduino/BasicDimmer/BasicDimmer.ino
#include
#include "rbdimmerESP32.h"
#define ZERO_CROSS_PIN 18
#define DIMMER_PIN 19
#define PHASE_NUM 0
rbdimmer_channel_t* dimmer = NULL;
void setup() {
Serial.begin(115200);
delay(1000);
// Initialize the library
rbdimmer_init();
// Register zero-cross detector
rbdimmer_register_zero_cross(ZERO_CROSS_PIN, PHASE_NUM, 0);
// Create dimmer channel with RMS curve (best for incandescent bulbs)
rbdimmer_config_t config = {
.gpio_pin = DIMMER_PIN,
.phase = PHASE_NUM,
.initial_level = 50, // Start at 50% brightness
.curve_type = RBDIMMER_CURVE_RMS
};
rbdimmer_create_channel(&config, &dimmer);
Serial.println("Dimmer initialized at 50% brightness");
}
void loop() {
// Nothing needed in the loop - dimmer maintains its state
delay(1000);
} Переход яркости
Показывает, как создать плавные переходы между уровнями яркости.
Файл: examples/arduino/BasicTransition/BasicTransition.ino
#include
#include "rbdimmerESP32.h"
#define ZERO_CROSS_PIN 18
#define DIMMER_PIN 19
#define PHASE_NUM 0
rbdimmer_channel_t* dimmer = NULL;
void setup() {
Serial.begin(115200);
delay(1000);
// Initialize dimmer
rbdimmer_init();
rbdimmer_register_zero_cross(ZERO_CROSS_PIN, PHASE_NUM, 0);
rbdimmer_config_t config = {
.gpio_pin = DIMMER_PIN,
.phase = PHASE_NUM,
.initial_level = 0, // Start with light off
.curve_type = RBDIMMER_CURVE_RMS
};
rbdimmer_create_channel(&config, &dimmer);
Serial.println("Dimmer initialized");
}
void loop() {
// Fade up to 100% over 3 seconds
Serial.println("Fading up...");
rbdimmer_set_level_transition(dimmer, 100, 3000);
delay(4000); // Wait for transition + 1 second
// Fade down to 10% over 3 seconds
Serial.println("Fading down...");
rbdimmer_set_level_transition(dimmer, 10, 3000);
delay(4000); // Wait for transition + 1 second
} Несколько каналов диммера
Управляет двумя отдельными каналами диммера независимо.
Файл: examples/arduino/MultiDimmer/MultiDimmer.ino
#include
#include "rbdimmerESP32.h"
#define ZERO_CROSS_PIN 18
#define DIMMER_PIN_1 19
#define DIMMER_PIN_2 21
#define PHASE_NUM 0
rbdimmer_channel_t* dimmer1 = NULL;
rbdimmer_channel_t* dimmer2 = NULL;
void setup() {
Serial.begin(115200);
delay(1000);
// Initialize dimmer library
rbdimmer_init();
rbdimmer_register_zero_cross(ZERO_CROSS_PIN, PHASE_NUM, 0);
// Create first dimmer channel (for incandescent bulb)
rbdimmer_config_t config1 = {
.gpio_pin = DIMMER_PIN_1,
.phase = PHASE_NUM,
.initial_level = 50,
.curve_type = RBDIMMER_CURVE_RMS
};
rbdimmer_create_channel(&config1, &dimmer1);
// Create second dimmer channel (for LED light)
rbdimmer_config_t config2 = {
.gpio_pin = DIMMER_PIN_2,
.phase = PHASE_NUM,
.initial_level = 50,
.curve_type = RBDIMMER_CURVE_LOGARITHMIC // Better for LEDs
};
rbdimmer_create_channel(&config2, &dimmer2);
Serial.println("Two dimmer channels initialized");
}
void loop() {
// Alternate level between channels
Serial.println("Channel 1: 80%, Channel 2: 20%");
rbdimmer_set_level(dimmer1, 80);
rbdimmer_set_level(dimmer2, 20);
delay(3000);
Serial.println("Channel 1: 20%, Channel 2: 80%");
rbdimmer_set_level(dimmer1, 20);
rbdimmer_set_level(dimmer2, 80);
delay(3000);
} Обратный вызов Zero-Cross
Демонстрирует использование функции обратного вызова с задачей FreeRTOS для безопасной обработки событий zero-crossing, которые можно использовать для синхронизации с формой волны переменного тока без добавления задержки выполнения кода к обработчику прерывания.
Файл: examples/arduino/ZCCallBack/ZCCallBack.ino
#include
#include "rbdimmerESP32.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#define ZERO_CROSS_PIN 18
#define DIMMER_PIN 19
#define LED_PIN 2 // Onboard LED for zero-cross visualization
#define PHASE_NUM 0
rbdimmer_channel_t* dimmer = NULL;
uint32_t zeroCount = 0;
// FreeRTOS components
QueueHandle_t zeroCrossQueue;
TaskHandle_t zeroCrossTaskHandle;
// Simple message type for our queue
typedef struct {
uint32_t timestamp;
} ZeroCrossEvent_t;
// Callback function for zero-cross events (runs in ISR context)
void zeroCrossCallback(void* arg) {
// Create event
ZeroCrossEvent_t event;
event.timestamp = millis();
// Send to queue from ISR
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(zeroCrossQueue, &event, &xHigherPriorityTaskWoken);
// If a higher priority task was woken, request context switch
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
// Task to process zero-cross events
void zeroCrossProcessingTask(void* parameter) {
ZeroCrossEvent_t event;
// Task loop - will run forever
for (;;) {
// Wait for an item from the queue
if (xQueueReceive(zeroCrossQueue, &event, portMAX_DELAY)) {
// Process the event (now we're in task context, not ISR)
// Toggle LED to visualize zero-crossing
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
// Count zero-crossing events
zeroCount++;
// Additional processing can be done here safely
// This doesn't affect the zero-cross interrupt timing
}
}
}
void setup() {
Serial.begin(115200);
delay(1000);
// Setup LED for visual zero-cross indication
pinMode(LED_PIN, OUTPUT);
// Create the queue to send events from ISR to task
zeroCrossQueue = xQueueCreate(10, sizeof(ZeroCrossEvent_t));
if (zeroCrossQueue == NULL) {
Serial.println("Error creating the queue");
while (1); // Stop execution on error
}
// Create the task to process zero-cross events
BaseType_t xReturned = xTaskCreate(
zeroCrossProcessingTask, // Task function
"ZeroCrossTask", // Task name
2048, // Stack size (bytes)
NULL, // No parameters needed
5, // Medium priority
&zeroCrossTaskHandle // Task handle
);
if (xReturned != pdPASS) {
Serial.println("Error creating the task");
while (1); // Stop execution on error
}
// Initialize dimmer
rbdimmer_init();
rbdimmer_register_zero_cross(ZERO_CROSS_PIN, PHASE_NUM, 0);
// Register zero-cross callback
rbdimmer_set_callback(PHASE_NUM, zeroCrossCallback, NULL);
// Create dimmer channel
rbdimmer_config_t config = {
.gpio_pin = DIMMER_PIN,
.phase = PHASE_NUM,
.initial_level = 60,
.curve_type = RBDIMMER_CURVE_RMS
};
rbdimmer_create_channel(&config, &dimmer);
Serial.println("Dimmer with zero-cross callback and task processing initialized");
}
void loop() {
// Print zero-cross statistics every second
static unsigned long lastPrint = 0;
if (millis() - lastPrint >= 1000) {
uint16_t frequency = rbdimmer_get_frequency(PHASE_NUM);
Serial.printf("Zero-cross count: %u, Detected frequency: %u Hz\n",
zeroCount, frequency);
lastPrint = millis();
}
delay(10);
}
- Обработчик ISR (Interrupt Service Routine) остается предельно коротким - он только отправляет сообщение в очередь
- Вся логика обработки перемещена в выделенную задачу FreeRTOS
- Используются надлежащие механизмы FreeRTOS для безопасного взаимодействия между ISR и задачей
- Предотвращаются любые проблемы с временем в обнаружении zero-crossing путем отделения обработки прерывания от обработки
Этот подход соответствует лучшим практикам для систем реального времени, где обработчики прерываний должны быть максимально короткими, чтобы избежать влияния на синхронизацию системы и отзывчивость.
Смотрите CHANGELOG.md для полного списка изменений в каждом выпуске.
← Universal Library for ESP32 | Contents | Next: - ESP-IDF framework C guide and examples. →