Zum Inhalt springen

← Universal Library for ESP32 | Inhaltsverzeichnis | Weiter: - ESP-IDF Framework C-Anleitung und Beispiele. →

Arduino-Anleitung und Beispiele

Lesen Sie zunächst die Bibliotheksübersicht: Universal library for ESP32

Neu in v2.0.0

  • Modulare interne Architektur – Die Bibliotheksinternen wurden in separate Module reorganisiert. Die öffentliche API ist unverändert; bestehende Sketche werden kompiliert und ausgeführt, ohne Änderungen zu erfordern.
  • Zero-Cross-Rauschgate – Beseitigt falsche TRIAC-Spike-Wiederauslösungen, die zuvor auf einigen Lasten zu Flimmern führten.
  • Zweipass-ISR für Mehrkanal-Synchronisation – Alle Kanäle in derselben Phase werden jetzt sortiert und in einer einzigen Halbperiode gezündet, wodurch Inter-Kanal-Timing-Jitter entfernt wird.
  • IRAM_ATTR auf allen zeitkritischen Pfaden – ISR-Handler und verwandte Funktionen werden in IRAM platziert, um Flash-Cache-Fehlgriffe während zeitempfindlicher Operationen zu vermeiden.
  • Vier neue Kconfig-Parameter – Konfigurierbar über idf.py menuconfig für ESP-IDF-Projekte. Arduino-Builds verwenden Compile-Zeit-Defaults (keine Maßnahmen erforderlich).

Anforderungen

  • Arduino ESP32 Core 3.x (getestet mit 3.0+)
  • Arduino IDE 2.x
  • Beispiele sind für Arduino IDE 2.x-Kompatibilität in Verzeichnisse pro Sketch umstrukturiert

Bibliotheksinstallation

Arduino IDE

  1. Laden Sie die rbdimmerESP32-Bibliothek als ZIP-Archiv herunter
  2. In Arduino IDE, wählen Sie: Sketch > Include Library > Add .ZIP Library
  3. Wählen Sie die heruntergeladene ZIP-Datei aus
  4. Starten Sie Arduino IDE neu, um die Installation abzuschließen

Erklärung des Bibliotheksbetriebs

Vorbereitung:

  1. Die Bibliothek wird mit rbdimmer_init() initialisiert
  2. Der Zero-Cross-Detektor wird mit rbdimmer_register_zero_cross() registriert
  3. Der Dimmer-Kanal wird mit rbdimmer_create_channel() erstellt

Dimmsteuerung:

  • Einstellen des Dimmniveaus mit rbdimmer_set_level(). Das Dimmniveau wird im Bereich 0(AUS) ~ 100(AN) eingestellt
  • Sanfte Dimmung des Niveaus mit rbdimmer_set_level_transition(). Sanfte Übergangszeit vom aktuellen Niveau zum eingestellten Niveau über einen Zeitraum (in Millisekunden, 1s=1000ms)

Datenstrukturen

rbdimmer_config_t

c
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;

Aufzählungen

rbdimmer_curve_t

Arten von Helligkeitskurven:

c
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

Bibliotheksfunktionsantworten:

c
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;

Konstanten und Makros

Konstanten in der rbdimmerESP32.h-Datei. Sie können diese Parameter ändern:

c
#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.0

Funktionen

Initialisierung und Setup

c
// 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);

Dimmsteuerung

c
// 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);

Informationsabfragen

c
// 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);

Schritt-für-Schritt-Anleitung

Implementierungsschritte für Initialisierung der Bibliothek, Phasenregistrierung, Zero-Cross-Detektor, Dimmkanal und Dimmsteuerung:

1. Bibliothek und Pins definieren

cpp
#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. Dimmer-Objekt erstellen

Für jeden Dimmer ein Objekt erstellen:

cpp
rbdimmer_channel_t* dimmer_channel = NULL;

3. Dimmer-Bibliothek-Initialisierung

Die Funktion gibt einen Status zurück:

cpp
rbdimmer_init();

4. Zero-Cross-Detektor und Phasenregistrierung

Die Funktion gibt einen Status zurück:

cpp
rbdimmer_register_zero_cross(ZERO_CROSS_PIN, PHASE_NUM, 0);

5. Dimmer-Kanal-Konfiguration und Erstellung

cpp
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. Dimmoperation

cpp
rbdimmer_set_level(dimmer_channel, level);

7. Sanfte Dimmerübergänge

cpp
rbdimmer_set_level_transition(dimmer_channel, 0, 5000);

Lösungen

Mehrkanal-Dimmer-Systeme

Die Bibliothek unterstützt mehrere unabhängige Dimmkanäle. Die Anzahl der Kanäle ist in den Bibliothekseinstellungen in der rbdimmerESP32.h-Datei begrenzt. Jeder Dimmkanal muss einen separaten Ausgangspin haben.

Beispiel für die Erstellung eines Zwei-Kanal-Systems:

cpp
#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);
}

Verwendung von Interrupt-Rückruf-Funktionen

Mit Rückruf-Funktionen können Sie Ihren Code mit Zero-Cross-Ereignissen synchronisieren. Dies ist nützlich für Aufgaben, die präzise Synchronisation mit dem AC-Netz erfordern.

Beispiel für Registrierung und FreeRTOS-Task-Handler:

cpp
// 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 ...
}

Mehrphasen-Systeme

Für Drehstromsysteme muss ein separater Zero-Cross-Detektor für jede Phase registriert werden:

cpp
#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 ...
}

Betriebsüberwachung

Zum Debuggen können Sie die integrierten Bibliotheksfunktionen verwenden:

cpp
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("====================");
}

Fehlerbehebung: Flimmern

v2.0.0 enthält mehrere gezielt auf häufige Flimmerprobleme ausgerichtete Fehlerbehebungen:

Symptom Ursache v2.0.0 Fehlerbehebung
Allgemeines Flimmern / zufällige Spikes TRIAC-Einschalts-Spike löst den Zero-Cross-Detektor erneut aus Zero-Cross-Rauschgate: ZC_DEBOUNCE_US = 3000 us Blendingfenster nach jedem Zero-Cross, Ignorieren falscher Kanten
Flimmern bei 100% Helligkeit Zündverzögerung zu nah am nächsten Zero-Cross MIN_DELAY_US erhöht auf 100 us; Niveaus >= 100% werden intern auf 99% abgebildet, sodass der TRIAC immer einen ordnungsgemäßen Gate-Impuls erhält
Flimmern unter 3% Helligkeit TRIAC kann Strom bei sehr kleinen Leitungswinkeln nicht aufrechterhalten Niveaus unter LEVEL_MIN werden als vollständig AUS behandelt, wodurch unzuverlässiges Teilzünden vermieden wird
Mehrkanal-Jitter / Flimmern Kanäle mit unabhängigen Timern gezündet, was zu Planungskonflikten führt Zweipass-ISR: Alle Kanäle in derselben Phase werden nach Verzögerung sortiert und sequenziell in einer Timer-Kette innerhalb jeder Halbperiode gezündet

Wenn Sie nach dem Upgrade weiterhin Flimmern feststellen, überprüfen Sie, dass Ihr Zero-Cross-Hardware-Signal sauber ist (kein Rauschen) und dass Ihre Last mit der vorlaufenden Kante (TRIAC) Phase-Schnitt-Dimmung kompatibel ist.

Grundlegende Beispiele für die AC-Dimmer-Bibliothek

Beispiel-Sketche befinden sich in Verzeichnissen pro Sketch unter examples/arduino/:

Beispiel Pfad
Basic Dimmer Control examples/arduino/BasicDimmer/BasicDimmer.ino
Brightness Transition examples/arduino/BasicTransition/BasicTransition.ino
Multiple Dimmer Channels examples/arduino/MultiDimmer/MultiDimmer.ino
Zero-Cross Callback examples/arduino/ZCCallBack/ZCCallBack.ino

Basic Dimmer Control

Beschreibung: Das einfachste Beispiel, das zeigt, wie man einen AC-Dimmer mit einem festen Helligkeitsniveau steuert.

Datei: examples/arduino/BasicDimmer/BasicDimmer.ino

cpp
#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);
}

Brightness Transition

Zeigt, wie man sanfte Übergänge zwischen Helligkeitsstufen erstellt.

Datei: examples/arduino/BasicTransition/BasicTransition.ino

cpp
#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
}

Multiple Dimmer Channels

Steuert zwei separate Dimmer-Kanäle unabhängig.

Datei: examples/arduino/MultiDimmer/MultiDimmer.ino

cpp
#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 Callback

Demonstriert die Verwendung einer Rückruf-Funktion mit einer FreeRTOS-Task zum sicheren Verarbeiten von Zero-Cross-Ereignissen, die zur Synchronisation mit der AC-Wellenform verwendet werden kann, ohne dass die Interrupt-Handler-Ausführungsverzögerung erhöht wird.

Datei: examples/arduino/ZCCallBack/ZCCallBack.ino

cpp
#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);
}
  • Beibehalten des ISR (Interrupt Service Routine) äußerst kurz - es sendet nur eine Nachricht an eine Warteschlange
  • Verschiebung der gesamten Verarbeitungslogik auf einen dedizierten FreeRTOS-Task
  • Verwendung korrekter FreeRTOS-Mechanismen für sichere Kommunikation zwischen ISR und Task
  • Vermeidung von Timing-Problemen bei der Zero-Cross-Erkennung durch Trennung der Interrupt-Behandlung von der Verarbeitung

Dieser Ansatz folgt Best Practices für Echtzeitsysteme, bei denen Interrupt-Handler so kurz wie möglich sein sollten, um zu vermeiden, dass die Systemzeitsteuerung und Reaktionsfähigkeit beeinträchtigt wird.


Siehe CHANGELOG.md für die vollständige Liste der Änderungen in jeder Version.

← Universal Library for ESP32 | Inhaltsverzeichnis | Weiter: - ESP-IDF Framework C-Anleitung und Beispiele. →