Passa al contenuto

← Libreria universale per ESP32 | Indice | Avanti: Guida framework ESP-IDF in C ed esempi →

Guida Arduino ed esempi

Prima di iniziare, leggere la panoramica della libreria: Libreria universale per ESP32

Info



Installazione della libreria


Arduino IDE

  1. Scaricare la libreria rbdimmerESP32 come archivio ZIP
  2. In Arduino IDE, selezionare: Sketch > Includi libreria > Aggiungi libreria .ZIP
  3. Selezionare il file ZIP scaricato
  4. Riavviare Arduino IDE per completare l'installazione


PlatformIO

  1. Creare un nuovo progetto o aprirne uno esistente
  2. Aggiungere la libreria al proprio platformio.ini dal repository GitHub o da un percorso locale:
ini
lib_deps =
  # GitHub repository
  https://github.com/robotdyn-dimmer/rbdimmerESP32
  # or local path
  # rbdimmer=file:///path/to/rbdimmerESP32
  1. PlatformIO installerà automaticamente la libreria alla prossima compilazione



Collegamento hardware

Le istruzioni per collegare il dimmer al microcontrollore e al carico AC sono disponibili qui:

  • Collegare il pin Zero-Cross a qualsiasi GPIO con funzionalità ISR; consultare la documentazione del proprio chip ESP32
  • Collegare il pin Dimmer a qualsiasi GPIO
  • VCC a 3,3 V. Per ESP32, VCC = 3,3 V
  • GND a GND
Info
Guida dettagliata al collegamento hardware: Collegamento hardware



Esempio di base

cpp
#include 
#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)

// Global variables. Dimmer object
rbdimmer_channel_t* dimmer_channel = NULL;

void setup() {
  Serial.begin(115200);
  delay(1000);

  Serial.println("AC Dimmer Test");

  // Dimmer lib init
  if (rbdimmer_init() != RBDIMMER_OK) {
    Serial.println("Failed to initialize AC Dimmer library");
    return;
  }

  // Zero-cross detector and phase registry
  if (rbdimmer_register_zero_cross(ZERO_CROSS_PIN, PHASE_NUM, 0) != RBDIMMER_OK) {
    Serial.println("Failed to register zero-cross detector");
    return;
  }

  // Dimmer channel. Configuration data structure.
  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
  };

  if (rbdimmer_create_channel(&config_channel, &dimmer_channel) != RBDIMMER_OK) {
    Serial.println("Failed to create dimmer channel");
    return;
  }

  Serial.println("AC Dimmer initialized successfully");
}

void loop() {
  // dimming from 10% to 90% with step 10
  for (int brightness = 10; brightness <= 90; brightness += 10) {
    Serial.printf("Setting brightness to %d%%\n", brightness);
    rbdimmer_set_level(dimmer_channel, brightness);
    delay(2000);
  }

  // Smooth transition from current level to 0 level in 5 sec
  Serial.println("Smooth transition to 0%");
  rbdimmer_set_level_transition(dimmer_channel, 0, 5000);
  delay(6000); // delay 6 sec

  // Smooth transition from current level (0) to 100 level in 5 sec
  Serial.println("Smooth transition to 100%");
  rbdimmer_set_level_transition(dimmer_channel, 100, 5000);
  delay(6000); // delay 6 sec
}



Riferimento API


Descrizione del funzionamento della libreria

Preparazione:

  1. La libreria viene inizializzata con rbdimmer_init()
  2. Il rilevatore di passaggio per lo zero viene registrato con rbdimmer_register_zero_cross()
  3. Il canale dimmer viene creato con rbdimmer_create_channel()

Controllo della dimmerizzazione:

  • Impostazione del livello di dimmerizzazione con rbdimmer_set_level(). Il livello è impostato nell'intervallo 0 (spento) ~ 100 (acceso)
  • Transizione fluida del livello con rbdimmer_set_level_transition(). Transizione graduale dal livello attuale al livello impostato in un periodo di tempo (in millisecondi, 1 s = 1000 ms)
Info
Come funziona un dimmer — articolo nel nostro blog: Principi di funzionamento del dimmer AC


Strutture dati

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;


Enumerazioni

rbdimmer_curve_t

Tipi di curve di livello:

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

Risposte delle funzioni della libreria:

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;


Costanti e macro

Costanti nel file rbdimmerESP32.h. È possibile modificare questi parametri:

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 50              // Minimum delay (us)
Warning
Non è consigliabile modificare RBDIMMER_DEFAULT_PULSE_WIDTH_US, poiché è correlato alle caratteristiche hardware del dimmer.


Funzioni

Inizializzazione e configurazione

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

Controllo della dimmerizzazione

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

Interrogazioni informative

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



Guida passo-passo

Passi di implementazione per l'inizializzazione della libreria, registrazione della fase, rilevatore di passaggio per lo zero, canale di dimmerizzazione e controllo:


1. Definire la libreria e i pin

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. Creare l'oggetto dimmer

Per ogni dimmer, creare un oggetto:

cpp
rbdimmer_channel_t* dimmer_channel = NULL;


3. Inizializzazione della libreria dimmer

La funzione restituisce uno stato:

cpp
rbdimmer_init();


4. Registrazione del rilevatore di passaggio per lo zero e della fase

La funzione restituisce uno stato:

cpp
rbdimmer_register_zero_cross(ZERO_CROSS_PIN, PHASE_NUM, 0);


5. Configurazione e creazione del canale dimmer

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. Controllo della dimmerizzazione

cpp
rbdimmer_set_level(dimmer_channel, level);


7. Transizioni fluide di dimmerizzazione

cpp
rbdimmer_set_level_transition(dimmer_channel, 0, 5000);
Tip
La funzione crea una transizione fluida suddividendola in più piccoli passi. Utilizza un task FreeRTOS; durante la transizione, il codice principale continua a essere eseguito.



Soluzioni


Sistemi dimmer multicanale

La libreria supporta più canali di dimmerizzazione indipendenti. Il numero di canali è limitato nelle impostazioni della libreria nel file rbdimmerESP32.h. Ogni canale di dimmerizzazione deve avere un pin di uscita separato.

Esempio di creazione di un sistema a due canali:

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


Utilizzo delle funzioni callback di interrupt

Le funzioni callback consentono di sincronizzare il codice con gli eventi di passaggio per lo zero. Utile per task che richiedono una sincronizzazione precisa con la rete AC.

Esempio di registrazione e gestore task FreeRTOS:

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 ...
}
Warning
Non utilizzare codice pesante nella funzione callback. Si raccomanda di usare chiamate a task FreeRTOS.


Sistemi multifase

Per i sistemi trifase, è necessario registrare un rilevatore di passaggio per lo zero separato per ogni fase:

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


Monitoraggio del funzionamento

Per il debug, è possibile utilizzare le funzioni integrate della libreria:

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



Esempi di base per la libreria dimmer AC


Controllo di base del dimmer

Descrizione: l'esempio più semplice che mostra come controllare un dimmer AC con un livello di luminosità fisso.

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


Transizione di luminosità

Mostra come creare transizioni fluide tra livelli di luminosità.

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
}


Canali dimmer multipli

Controlla due canali dimmer indipendenti separatamente.

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


Callback passaggio per lo zero

Dimostra l'uso di una funzione callback con un task FreeRTOS per elaborare in modo sicuro gli eventi di passaggio per lo zero, consentendo la sincronizzazione con l'onda AC senza aggiungere ritardi di esecuzione al gestore di interrupt.

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);
}
Questa implementazione migliora significativamente l'esempio originale perché:
  • Mantiene l'ISR (routine di servizio interrupt) estremamente breve — invia solo un messaggio alla coda
  • Sposta tutta la logica di elaborazione a un task FreeRTOS dedicato
  • Utilizza meccanismi FreeRTOS appropriati per la comunicazione sicura tra ISR e task
  • Evita problemi di timing nel rilevamento del passaggio per lo zero separando la gestione dell'interrupt dall'elaborazione

Questo approccio segue le best practice per i sistemi in tempo reale, dove i gestori di interrupt devono essere il più brevi possibile per non influire sul timing e sulla reattività del sistema.

← Libreria universale per ESP32 | Indice | Avanti: Guida framework ESP-IDF in C ed esempi →