Arduino Project

Arduino Uno Rev3 & Adafruit NeoPixel Ring

Posted by Ivan on October 9, 2018

In questo semplice progetto le componenti sono fondamentalmente 2: un clone Arduino Uno Rev. 3 ed un clone Adafruit NeoPixel Ring a 24 LEDs. Sebbene il secondo sia identico sotto ogni punto di vista all’originale, la board in questione presenta invece una sola differenza: il modulo di comunicazione seriale (USB to Serial): parliamo infatti di un CH340G, prodotto dalla casa cinese WCH, molto più economico del FT232RL montato sull’originale. Sono stato quindi costretto a scaricare i driver dal sito WCH. Questi si presentano in varie sezioni:

  • Windows (.zip ed .exe, da installare)
  • Linux (.zip, da compilare)
  • macOS (.zip, contiene un file .pkg da installare)
  • Android (.zip, da scompattare)

alt text Lavorando in macOS, ho dovuto installare un semplice .pkg all’interno dello zip e riavviare, su Windows la procedura è simile, su Ubuntu ho invece dovuto installare prima gcc e make (tramite comando sudo apt install gcc make) per poi posizionarmi sulla cartella tramite comando cd POSIZIONE_CARTELLA e quindi compilare e caricare i driver col comando make load (o make per compilare e basta).

Fatto ciò, la scheda viene finalmente riconosciuta come un normale Arduino Uno (cambia solo il nome della porta seriale, in alcuni casi, come in macOS in cui prende il nome di /dev/cu.wchusbserial1460). alt text

Librerie

Il seguente progetto utilizza librerie non standard, create da Adafruit, rilasciate su Github dalla stessa, e da non molto pubblicate anche tra le librerie scaricabili tramite l’IDE di Arduino.

Una volta installate, basta includere la libreria nel progetto con #include <Adafruit_NeoPixel.h>, per poi continuare con il resto dello sketch. alt text

Variabili unsigned

Lo sketch in sé è molto più semplice di quanto possa sembrare. L’unica cosa da capire effettivamente erano alcune variabili ricorsive all’inizio di ogni ciclo void, che Adafruit consigliava di includere, pena il non funzionamento dello sketch.

In particolare, diverse volte nello sketch compaiono le variabili uint8_t, uint16_t e uint32_t. Questi valori sono rispettivamente delle variabili unsigned char (equivalente ad un byte), unsigned short e unsigned int. Per cui:

  • uint8_t = 2^8 = 256 Unsigned char
  • uint16_t = 2^16 = 65536 Unsigned short
  • uint32_t = 4294967296 Unsigned int
  • uint64_t = 18446744073709551616 Unsigned long long (Non compare nello sketch)

Difatti, scrivere uint32_t è come scrivere unsigned int nello stesso IDE. Ma qual è la differenza tra tra una variabile e la stessa variabile non firmata (unsigned)?

Prendiamo di nuovo come esempio int. Essendo una variabile a 32 bit il range di valori integer occupabili va da −2,147,483,648 a 2,147,483,647. Difatti questa variabile prende il nome di int o signed int (int firmata). Per quanto riguarda unsigned int lo stesso valore a 32 bit viene considerato solo in valori positivi, per cui in un range da 0 a 4,294,967,295.

Nel nostro caso Adafruit consiglia di usare valori unsigned poiché non lavorando in campo di valori negativi (un pin non può assumere un valore negativo) allora è bene sfruttare al massimo il campo degli integer soltanto con valori positivi, e quindi variabili positive (ovvero non firmate).

Quando ad una di queste definizioni è assegnato il valore wait è come se stiamo congelando quella variabile, non assegnandogli nessun valore.

Lo sketch

Download Clicca il tasto destro e "Scarica file collegato" sull'icona qui sopra per scaricare lo sketch

Ho diviso lo sketch in diversi cicli, in modo da commentare i vari cicli rapidamente in fase di dimostrazione per mostrare uno solo di essi.

1
#include <Adafruit_NeoPixel.h>

Per prima cosa includo le librerie, come detto prima

1
#define PIN 6

Definisce il termine PIN come valore 6

1
Adafruit_NeoPixel strip = Adafruit_NeoPixel(24, PIN, NEO_GRB + NEO_KHZ800);

Nella parentesi il primo parametro rappresenta il numero di pixel nell’anello strip, il secondo il numero del pin Arduino (qualunque PWM va bene - in questo caso è il 6). Il terzo valore invece sono le flags del tipo del pixel. Dato che noi abbiamo una strip GRB con bitstream a 800 KHz, il codice sarà come sopra.

  • NEO_KHZ800 = bitstream a 800 KHz (la maggior parte dei prodotti NeoPixel, tra cui i LED WS2812)
  • NEO_KHZ400 = bitstream a 400 KHz (pixel FLORA classic ‘v1’ (non v2), driver dei LED WS2811)
  • NEO_GRB = Pixel collegati per bitstream di tipo GRB (la maggior parte dei prodotti NeoPixel)
  • NEO_RGB = Pixel collegati per bitstream di tipo RGB (pixel FLORA v1, non v2)
  • NEO_RGBW = Pixel collegati per bitstream di tipo RGBW (prodotti NeoPixel RGBW)
1
2
3
4
void setup() {
	strip.begin();
	strip.show();
}

strip.begin() inizializza la strip in sé, mentre strip.show() spegne tutti i LEDs (la funzione show viene applicata a nulla, per cui i pixel sono spenti)

1
2
3
4
5
6
7
void loop() {
	cerchio();
	corsa();
	colori(20);
	arcobaleno(20);
	corsa_arcobaleno(50);
}

Applica i vari cicli, definiti in seguito tramite i vari cicli void. Il valore tra parentesi indica il numero di ripetizioni dello stesso ciclo

1
2
3
4
5
6
7
void assegna_colore(uint32_t c, uint8_t wait) {
	for(uint16_t i=0; i<strip.numPixels(); i++) {
		strip.setPixelColor(i, c);
		strip.show();
		delay(wait);
	}
}

Quella sopra è la prima delle due funzioni principali, accende tutti i pixel uno dietro l’altro con un determinato colore. Per quanto detto sopra, possiamo vedere una dichiarazione per c di tipo unsigned int, e unsigned char in pausa (wait).

Il valore c sta ad indicare il colore, il valore i il numero del pixel (che in questo anello va da 0 a 24).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void funzione_corsa(uint32_t c, uint8_t wait) {
	for (int j=0; j<10; j++) {	// 10 cicli di inseguimento
		for (int q=0; q < 3; q++) {
			for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
				strip.setPixelColor(i+q, c);	// Accende ogni terzo pixel
			}
			strip.show();

			delay(wait);

			for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
				strip.setPixelColor(i+q, 0);	// Spegne ogni terzo pixel
			}
		}
	}
}

Ho chiamato così la seconda funzione principale perché non sapevo che altro nome darle. Le luci si accendono in modo alternato (detto stile “theater-chase”), accendendo un un LED, spegnendo quello successivo, accendendo quello dopo ancora, per poi ricominciare il ciclo for. Il valore q permette appunto questo intervallo di un LED spento, poiché va aggiunto al valore di i. Il colore c viene quindi assegnato al LED ed il ciclo ricomincia.

1
2
3
4
5
6
void cerchio() {
	assegna_colore(strip.Color(255, 0, 0), 50); // Rosso
	assegna_colore(strip.Color(0, 255, 0), 50); // Verde
	assegna_colore(strip.Color(0, 0, 255), 50); // Blu
	assegna_colore(strip.Color(255, 255, 255), 50); // Bianco
}

Questa funzione mostra un accensione in senso orario, impostando il colore tramite la prima funzione principale e la funzione standard strip.color(R, G, B), t. In questo caso i valori sono byte o char, che vanno da 0 a 255, o meglio unsigned char. Questo particolare tipo di assegnazione di colori prende il nome di HEX. Posso quindi impostare i colori come in qualunque diodo RGB, nonostante la strip sia GRB. La t indica la durata della funzione (una sorta di delay).

1
2
3
4
5
void corsa() {
	funzione_corsa(strip.Color(127, 127, 127), 50); // Grigio
	funzione_corsa(strip.Color(127, 0, 0), 50); // Rosso
	funzione_corsa(strip.Color(0, 0, 127), 50); // Blu
}

Ricrea l’effetto “chase”, ovvero la rincorsa dei pixel. Non è altro che una pura applicazione della seconda funzione principale, applicando anche qui valori RGB-delay tramite la funzione strip.Color.

I cicli successivi sono invece leggermente più complessi. Applicano le funzioni ed i cicli sopra descritti assieme ad ulteriori cicli for. Ho poi definito un valore e ciclo Ruota, e tramite questo ciclo viene definita la posizione del LED sul ring in un determinato istante e viene assegnata la valore RuotaPos.

1
2
3
4
5
6
7
8
9
10
11
12
uint32_t Ruota(byte RuotaPos) {
	RuotaPos = 255 - RuotaPos;
	if(RuotaPos < 85) {
		return strip.Color(255 - RuotaPos * 3, 0, RuotaPos * 3);
	}
	if(RuotaPos < 170) {
		RuotaPos -= 85;
		return strip.Color(0, RuotaPos * 3, 255 - RuotaPos * 3);
	}
	RuotaPos -= 170;
	return strip.Color(RuotaPos * 3, 255 - RuotaPos * 3, 0);
}

Viene appunto inizializzato un unsigned int Ruota e definito ad un unsigned char (o byte) la posizione. Il codice sopra è copiato dal sito Adafruit, poiché specifico per il funzionamento dell’anello.

1
2
3
4
5
6
7
8
9
10
11
void colori(uint8_t wait) {
	uint16_t i, j;

	for(j=0; j<256; j++) {
		for(i=0; i<strip.numPixels(); i++) {
			strip.setPixelColor(i, Ruota((i+j) & 255));
		}
		strip.show();
		delay(wait);
	}
}

Qui appunto applichiamo un colore dinamico indistintamente a tutti i LEDs. Questo poi cambia tramite j e viene applicato tramite i. La variabile j una volta arrivata a 256 (poiché limite massimo dei valori HEX) si interrompe e ricomincia. I colori sono una transazione R - G - B, per poi ricominciare con R.

1
2
3
4
5
6
7
8
9
10
11
void arcobaleno(uint8_t wait) {
	uint16_t i, j;

	for(j=0; j<256*5; j++) { // 5 cicli di tutti i colori a ruota
		for(i=0; i< strip.numPixels(); i++) {
			strip.setPixelColor(i, Ruota(((i * 256 / strip.numPixels()) + j) & 255));
		}
		strip.show();
		delay(wait);
	}
}

Leggermente diversa dal ciclo colori. Viene infatti applicato questo ciclo, ma in un sottociclo for, che si sposta alla RuotaPos successiva, cambia colore e quindi ricomincia. Ho infatti utilizzato l’unsigned int Ruota in modo da poter definire la posizione i, moltiplicarla per 256, divederla per il numero di LEDs, per poi infine sommarla a j. Tramite strip.show() i pixel si spengono ed il ciclo ricomincia.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void corsa_arcobaleno(uint8_t wait) {
	for (int j=0; j < 256; j++) {	// ciclo di 256 colori a ruota
		for (int q=0; q < 3; q++) {
			for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
				strip.setPixelColor(i+q, Ruota( (i+j) % 255));	// Accende ogni terzo pixel
			}
			strip.show();

			delay(wait);

			for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
				strip.setPixelColor(i+q, 0);	// Spegne ogni terzo pixel
			}
		}
	}
}

Quest’ultimo è il ciclo più complesso. Ritroviamo infatti un sottociclo per il valore q tramite cui otteniamo il theater-chase, e all’interno di esso un ulteriore sotticiclo for che quindi somma il valore di i a 3, somma nel sottociclo successivo i a q e tramite la funzione Ruota assegna il colore.

Lo schema

Ho ricreato lo schema in Fritzing, sia dal punto di vista grafico che puramente circuitale.

Non essendo il componente tra quelli standard di Fritzing ho dovuto cercarlo. Fortunatamente, come riportato dalla stessa Adafruit, “Adafruit Industries is a Friend of Fritzing”. alt text

Ho quindi scaricato dalla cartella parts del master tree della repo Frtizing-Library di Adafruit il file Adafruit 24 NeoPixel Ring.fzpz.

Una volta importato, l’ho aggiunto al mio bin ed ho costruito lo schema in sé.

Il costruttore consiglia in realtà di aggiungere un capacitare da 1000 µF (quindi da 1 mF) tra Arduino ed il primo LED (a cui viene applicata la tensione) in modo da ridurre il rischio di burnout del NeoPixel, o in alternativa una resistenza da 300 - 500 Ω. Inoltre, lo stesso consiglia di minimizzare la distanza tra Arduino e la strip (probabilmente per ridurre problemi di dispersione). Ed infine consiglia di connettere la GND per prima, se possibile. alt text

Sopra viene rappresentato quindi in maniera semplicistica il migliore assemblaggio del progetto, riassumendo:

  • Pin GND dell’Adafruit NeoPixel → Uno dei pin GND di Arduino
  • Pin 5V dell’Adafruit NeoPixel → Resistenza da 300-500 Ω (330 in questo caso)→ Pin 5V di Arduino
  • Pin IN dell’Adafruit NeoPixel → Pin Digitale PWM (6 in questo caso) di Arduino

alt text

Programmi utilizzati