Come eseguire più schemi con i LED WS2812 e Arduino, modificarli utilizzando un timer o premendo un pulsante, e creare una dissolvenza incrociata.
In questo nuovo articolo relativo alla programmazione della libreria FastLED, esaminiamo come eseguire più schemi e modificarli utilizzando un timer o premendo un pulsante. Quindi esamineremo la dissolvenza incrociata tra i modelli per garantire un aspetto più professionale. I
Articoli precedenti:
- LED WS2812 per Arduino – Cosa sono e come si usano
- LED WS2812 – Attività di configurazone con Arduino
- LED WS2812 e Arduino – Ora giochiamo con i colori
- LED WS2812 e Arduino – Altre animazioni sui colori
- LED WS2812 con Arduino – Gestire pattern variabili
- LED WS2812 e Arduino – Onde e sfumature variopinte
Modificare un pattern con un timer o un tasto
Immaginiamo dunque di voler mostrare diversi pattern: il sorgente di qseguito ci mostra in che modo gestirne la programmazione.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
// Programma 17 - Timer pattern #include <FastLED.h> #define NUM_LEDS 14 #define LED_PIN 6 CRGB leds[NUM_LEDS]; uint8_t patternCounter = 0; void setup() { FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS); FastLED.setBrightness(40); // up to 255 } void loop() { switch (patternCounter) { case 0: movingDots(); break; case 1: rainbowBeat(); break; case 2: redWhiteBlue(); break; } EVERY_N_SECONDS(5) { nextPattern(); } FastLED.show(); } void nextPattern() { patternCounter = (patternCounter + 1) % 3; } void movingDots() { uint16_t posBeat = beatsin16(30, 0, NUM_LEDS - 1, 0, 0); uint16_t posBeat2 = beatsin16(60, 0, NUM_LEDS - 1, 0, 0); uint16_t posBeat3 = beatsin16(30, 0, NUM_LEDS - 1, 0, 32767); uint16_t posBeat4 = beatsin16(60, 0, NUM_LEDS - 1, 0, 32767); // Onda per il colore del LED uint8_t colBeat = beatsin8(45, 0, 255, 0, 0); leds[(posBeat + posBeat2) / 2] = CHSV(colBeat, 255,255); leds[(posBeat3 + posBeat4) / 2] = CHSV(colBeat, 255,255); fadeToBlackBy(leds, NUM_LEDS, 10); } void rainbowBeat() { uint16_t beatA = beatsin16(30, 0, 255); uint16_t beatB = beatsin16(20, 0, 255); fill_rainbow(leds, NUM_LEDS, (beatA+beatB)/2, 8); } void redWhiteBlue() { uint16_t sinBeat = beatsin16(30, 0, NUM_LEDS - 1, 0, 0); uint16_t sinBeat2 = beatsin16(30, 0, NUM_LEDS - 1, 0, 21845); uint16_t sinBeat3 = beatsin16(30, 0, NUM_LEDS - 1, 0, 43690); leds[sinBeat] = CRGB::Blue; leds[sinBeat2] = CRGB::Red; leds[sinBeat3] = CRGB::White; fadeToBlackBy(leds, NUM_LEDS, 10); } |
Ci troviamo di fronte ad un programma mediamente complesso (ma che occupa solo il 17% della memoria di Arduino, 4576 bytes!). Analizziamolo con calma.
Definiamo una variabile di stato, patternCounter: ci consentirà di scegliere tra figure grafiche differenti; quindi eseguiamo il solito setup della libreria FastLED.
All’interno del loop() eseguiamo un controllo del valore del patternCounter, e a seconda del valore da esso posseduto lanciamo una funzione predefinita. Ogni 5 secondi il paternCounter viene aggiornato dall’apposita funzione, che somma 1 al valore precedente ed estrae il modulo del numero, ottenendo quindi la sequenza 0-1-2-0-1-2… che rappresenta appunto la sequenza di lancio di ciascun pattern.
Dalla riga #43 vengono definite le funzioni che rappresentano in che modo “accendere” i LED. Alla riga #54 e #55 vediamo come utilizzare le interferenze tra le onde per definire le posizioni del LED da accendere, mentre il valore calcolato alla riga #52 determina la “rotazione” dei colori.
Per cambiare il pattern ci siamo affidati al timer definito a riga #32. Ma se invece avessimo voluto eseguire il cambio del pattern attraverso un pulsante?
Nulla di più facile: prima di tutto includiamo la libreria OneButton.h, che si occupa di gestire al meglio i pulsanti; poi definiamo a 3 la costante BTN_PIN (il pin al quale colleghiamo il pulsante) e inizializziamo la libreria con il comando
1 |
btn.attachClick(nextPattern); |
A questo punto eliminiamo il timer a riga #32, e dopo la funzione FastLED.show() aggiungiamo il comando
1 |
btn.tick(); |
per tenere sotto controllo la pressione del pulsante. Il pulsante andrà collegato tra il BTN_PIN e GND, senza alcun resistore.
Per i più pigri, o per coloro che, alle prime armi, si sono persi per strada, aggiungiamo il codice sorgente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
// Programma 18 - Button pattern #include <FastLED.h> #include <OneButton.h> #define NUM_LEDS 14 #define LED_PIN 6 #define BTN_PIN 3 CRGB leds[NUM_LEDS]; uint8_t patternCounter = 0; OneButton btn = OneButton(BTN_PIN, true, true); void setup() { FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS); FastLED.setBrightness(40); // up to 255 btn.attachClick(nextPattern); } void loop() { switch (patternCounter) { case 0: movingDots(); break; case 1: rainbowBeat(); break; case 2: redWhiteBlue(); break; } FastLED.show(); btn.tick(); } void nextPattern() { patternCounter = (patternCounter + 1) % 3; } void movingDots() { uint16_t posBeat = beatsin16(30, 0, NUM_LEDS - 1, 0, 0); uint16_t posBeat2 = beatsin16(60, 0, NUM_LEDS - 1, 0, 0); uint16_t posBeat3 = beatsin16(30, 0, NUM_LEDS - 1, 0, 32767); uint16_t posBeat4 = beatsin16(60, 0, NUM_LEDS - 1, 0, 32767); // Onda per il colore del LED uint8_t colBeat = beatsin8(45, 0, 255, 0, 0); leds[(posBeat + posBeat2) / 2] = CHSV(colBeat, 255,255); leds[(posBeat3 + posBeat4) / 2] = CHSV(colBeat, 255,255); fadeToBlackBy(leds, NUM_LEDS, 10); } void rainbowBeat() { uint16_t beatA = beatsin16(30, 0, 255); uint16_t beatB = beatsin16(20, 0, 255); fill_rainbow(leds, NUM_LEDS, (beatA+beatB)/2, 8); } void redWhiteBlue() { uint16_t sinBeat = beatsin16(30, 0, NUM_LEDS - 1, 0, 0); uint16_t sinBeat2 = beatsin16(30, 0, NUM_LEDS - 1, 0, 21845); uint16_t sinBeat3 = beatsin16(30, 0, NUM_LEDS - 1, 0, 43690); leds[sinBeat] = CRGB::Blue; leds[sinBeat2] = CRGB::Red; leds[sinBeat3] = CRGB::White; fadeToBlackBy(leds, NUM_LEDS, 10); } |
Lanciamo il programma, e potremo modificare il nostro pattern al tocco di un pulsante. O magari simulando il tasto via software su di uno smartphone…
Dissolvenza incrociata tra pattern
In video editing (ma anche in audio), una dissolvenza è il passaggio da una sorgente di segnale all’altra in modo morbido.
Immaginiamo di avere due sorgenti luminose con due pattern differenti: possiamo passare dalla prima alla seconda spostando un cursore, come nelle immagini di seguito:
Per ottenere un effetto simile occorre però modificare leggermente il programma:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
// Programma 19 - Dissolvenza incrociata #include <FastLED.h> #define NUM_LEDS 14 #define LED_PIN 6 CRGB origine1[NUM_LEDS]; CRGB origine2[NUM_LEDS]; CRGB output[NUM_LEDS]; uint8_t blendAmount = 0; uint8_t patternCounter = 0; uint8_t origine1Pattern = 0; uint8_t origine2Pattern = 1; bool useOrigine1 = false; void setup() { FastLED.addLeds<WS2812B, LED_PIN, GRB>(output, NUM_LEDS); FastLED.setBrightness(50); } void loop() { EVERY_N_MILLISECONDS(10) { blend(origine1, origine2, output, NUM_LEDS, blendAmount); // Sposta il canale di uscita if (useOrigine1) { if (blendAmount < 255) blendAmount++; // Blend 'up' verso origine 2 } else { if (blendAmount > 0) blendAmount--; // Blend 'down' verso origine 1 } } EVERY_N_SECONDS(5) { nextPattern(); } runPattern(origine1Pattern, origine1); // Esegui simultaneamente entrambi i pattern runPattern(origine2Pattern, origine2); FastLED.show(); } void nextPattern() { patternCounter = (patternCounter + 1) % 3; // Il numero dopo % indica il numero dei pattern disponibili if (useOrigine1) origine1Pattern = patternCounter; // Definisce l'array di origine per il nuovopattern else origine2Pattern = patternCounter; useOrigine1 = !useOrigine1; // Inversione dell'array origine ogni 5 secondi } void runPattern(uint8_t pattern, CRGB *LEDArray) { switch (pattern) { case 0: movingDots(LEDArray); break; case 1: rainbowBeat(LEDArray); break; case 2: redWhiteBlue(LEDArray); break; } } //------- Inserire di seguito le definizioni dei pattern da eseguire -------// void movingDots(CRGB *LEDarray) { uint16_t posBeat = beatsin16(30, 0, NUM_LEDS - 1, 0, 0); uint16_t posBeat2 = beatsin16(60, 0, NUM_LEDS - 1, 0, 0); uint16_t posBeat3 = beatsin16(30, 0, NUM_LEDS - 1, 0, 32767); uint16_t posBeat4 = beatsin16(60, 0, NUM_LEDS - 1, 0, 32767); // Wave for LED color uint8_t colBeat = beatsin8(45, 0, 255, 0, 0); LEDarray[(posBeat + posBeat2) / 2] = CHSV(colBeat, 255, 255); LEDarray[(posBeat3 + posBeat4) / 2] = CHSV(colBeat, 255, 255); fadeToBlackBy(LEDarray, NUM_LEDS, 10); } void rainbowBeat(CRGB *LEDarray) { uint16_t beatA = beatsin16(30, 0, 255); uint16_t beatB = beatsin16(20, 0, 255); fill_rainbow(LEDarray, NUM_LEDS, (beatA+beatB)/2, 8); } void redWhiteBlue(CRGB *LEDarray) { uint16_t sinBeat = beatsin16(30, 0, NUM_LEDS - 1, 0, 0); uint16_t sinBeat2 = beatsin16(30, 0, NUM_LEDS - 1, 0, 21845); uint16_t sinBeat3 = beatsin16(30, 0, NUM_LEDS - 1, 0, 43690); LEDarray[sinBeat] = CRGB::Blue; LEDarray[sinBeat2] = CRGB::Red; LEDarray[sinBeat3] = CRGB::White; fadeToBlackBy(LEDarray, NUM_LEDS, 10); } |
Analizziamolo assieme.
Righe 8-10: creiamo i due pattern di ingresso ed un pattern ch rappresenta i valori di uscita.
RIghe 12-16: variabili che useremo per mixare i pattern.
Righe 18-21 il “solito” setup().
Righe 23-43: il loop() suddiviso in 3 sezioni: nella prima (Righe 25-33) viene chiamata la funzione blend() ogni 10 millisecondi, modificando il valore del missaggio (ultimo parametro) tra 0 e 255. La variabile booleana useOrigine1 determina se siamo in fase di incremento (il “potenziometro” si sposta da sinistra a destra, ed il valore cresce) o di decremento (nel qual caso il valore diminuisce). Ogni 5 secondi, invece, viene modificato il pattern sul quale si lavora, attraverso la funzione nextPattern(). Infine,ad ogni ciclo viene chiamata la funzione runPattern(), che si occupa di determinare quale dei 3 pattern dev’essere eseguito su ciascuno degli stream originali.
Righe 45-52: La funzione nextPattern().
Righe 54-66: la funzione runPattern().
Righe 68-108: Troviamo le definizioni dei pattern che avevamo già visto nei sorgenti precedenti. In questo programma, il passaggio da un pattern ad un altro non avverrà in modo temporizzato e brusco, bensì attraverso una dissolvenza incrociata tra i pattern.
Riflessioni
E anche per questa settimana abbiamo terminato. La prossima volta tenteremo di applicare quanto abbiamo appreso in teoria ad un progetto pratico vero e proprio.
Troverete i file sorgente con gli esempi proposti su GitHub come al solito.
A risentirci tra sette giorni!
Link utili
- Arduino uno
- Arduino nano
- Raspberry PI Pico
- Elegoo starter kit
- Striscia LED WS2812 (5m 30 LED/m)
- Striscia LED WS2812 (1m 144 LED/m)
- Alimentatore 5V 1.5A
Parte dell’articolo si basa sulle lezioni online di Scott Marley.