Lezione 6 – Corso di Elettronica Creativa con Arduino Sensor Kit

Sensore di luminosità

Inizio questa lezione con un’informazione che va a correggere quanto scritto sul sito Arduino in riferimento al Grove Light Sensor in cui erroneamente viene indicato il dispositivo come fotoresistenza, in realtà si tratta di un fototransistor e ciò, soprattutto per i neofiti può creare qualche problema di comprensione, soprattutto perché lo sketch presentato non fornisce esattamente quanto indicato.

Fotoresistenza o fototransistor?

  • Versione 1.0 del Grove-Light Sensor – usava una classica CdS LDR GL5528, cioè una vera fotoresistenza.
  • Versione 1.1 / 1.2 (quella montata sullo Shield dell’Arduino Sensor Kit) – per ragioni RoHS (il cadmio delle CdS è vietato) Seeed ha sostituito l’LDR con un LS06-S: si tratta di un sensore a fototransistor lineare (tecnicamente una fotodiodo-transistor) che “mima” la vecchia fotoresistenza ma è più rapido e lineare.

Il sito di Arduino non ha aggiornato la terminologia e continua a chiamarlo “photo-resistor”.

Ora come possiamo fare per non creare problemi a chi inizia?

Innanzi tutto se volete utilizzare una fotoresistenza vi rimando alle mie slide: Alfabeto di Arduino – Lezione 3, ma attenzione in questo caso dovrete usare una breadboard e realizzare un circuito con un resistore da 10Kohm e una fotoresistenza, in questo modo usando lo sketch presente sul sito Arduino o quelli indicati nelle mie slide tutto funzionerà ed avrete valori che variano tra 0 e circa 900, coll’esempio sul sito Arduino avrete un valore massimo più basso.

Dal punto di vista teorico cosa succede (usando una fotoresistenza):

La fotoresistenza (o LDR, Light Dependent Resistor) per rilevare l’intensità della luce:

  • la resistenza della fotoresistenza diminuisce quando l’intensità luminosa aumenta;
  • la resistenza della fotoresistenza aumenta quando l’intensità luminosa diminuisce.

L’ADC dell’Arduino la converte in un numero intero da 0 (buio) a 1023 (molta luce) quindi la lettura avviene tramite l’istruzione analogRead() per questo kit collegheremo direttamente il modulo al pin A3 e quindi nel codice scriveremo: analogRead(A3).

1// Prof. Maffucci Michele
2// Uso del sensore di luminosità
3// 27.05.2025
4 
5int sensore_luce = A3;   // pin del sensore di luminosità
6 
7void setup() {
8  analogReference(INTERNAL);   // 1,1 V; attivare PRIMA di qualsiasi analogRead
9  delay(3);                    // attesa minima per la stabilizzazione della reference
10  Serial.begin(9600);          // avvia la comunicazione seriale
11}
12 
13void loop() {
14  int luce_grezza = analogRead(sensore_luce);      // legge il valore grezzo dal pin A3
15  int luce = map(luce_grezza, 0, 1023, 0, 100);    // converte 0–1023 in 0–100 (percentuale)
16 
17Serial.print("Livello di luce: ");
18  Serial.println(luce);  // stampa il valore di luce sul Monitor Seriale
19 
20delay(1000);           // attende 1 secondo prima della prossima lettura
21}

Le cose sono simili con il Grove-Light Sensor sull’Arduino Sensor Kit, ma avrete, come dicevo, valori massimi più bassi, che raggiungo circa i 750 con la torcia dello smartphone direttamente puntata sul fototransistor. Dal punto di vista funzionale nulla cambia ma è importante aver ben presente che siamo lavorando con componenti elettronici diversi che hanno comportamenti simili.

Con lo sketch precedente otterremo sulla serial monitor questi valori:

Il fatto che il valore massimo si fermi attorno a 750 è in realtà perfettamente coerente con l’elettronica del modulo.

Quindi per ora, per chi inizia potete far finta che il componente sull’Arduino Sensor Kit è una fotoresistenza e se desiderate potete fermarvi a questo punto.

Qualche approfondimento in più sul Grove-Light Sensor

Fototransistor LS06-S
Sotto luce intensa scorre una corrente dell’ordine di qualche decina di µA, che non bastano a generare direttamente tensioni alte. L’amplificatore operazionale LM358 presente sul modulo (con resistenza di feedback di circa 47 kΩ), converte quella corrente in tensione. Pur essendo alimentato fra 0 V e 5 V, riesce a portare l’uscita solo fino a circa 5 V – 1,2 V ≈ 3,8 V.

Ora come ottenere un range “0-1023” (o quasi) con il Grove-Light Sensor – due strategie

Strategia 1
Cambiare il riferimento ADC analogReference(INTERNAL);
in questo modo il riferimento interno a 1,1 V lo stesso segnale da 0-2 V “riempie” tutta la scala (tocca il fondo scala già a ≈ 1,1 V), per contro però si perde in linearità oltre 1,1 V (valori restano saturi a 1023).

Strategia 2
Riscalare via software val_norm = raw * (1023.0 / 750.0); in questo modo non si altera l’hardware, però per contro rimane un range” reale di 0-750 sull’ADC.

Un sensore di questo tipo trova impiego in svariati ambiti: dal rilevamento di presenze al tracciamento della posizione del sole, fino alla gestione automatica dell’illuminazione.

Un esempio comune è l’uso del sensore di luce nei telefoni cellulari: quando la luce ambientale è intensa, il sensore fa aumentare la luminosità dello schermo per migliorarne la leggibilità; in condizioni di scarsa illuminazione, la luminosità viene ridotta, con conseguente risparmio energetico.

ATTENZIONE
Il valore fornito dal sensore di luminosità riflette soltanto l’andamento approssimato dell’intensità della luce e NON corrisponde ai lumen.

Gli esempi che seguono useranno analogReference(INTERNAL); ma spieghiamo esattamente a cosa serve.

analogReference(INTERNAL); è un’istruzione della libreria Arduino API che imposta quale tensione di riferimento (VREF) userà il convertitore analogico-digitale (ADC) per trasformare un segnale analogico in un numero da 0 a 1023.

L’ADC di Arduino misura una tensione di ingresso (VIN) e la confronta con un valore fisso, VREF:

  • se VIN = 0 V ⇒ conteggio = 0
  • se VIN = VREF ⇒ conteggio = 1023

Scegliere VREF più bassa aumenta la sensibilità (più millivolt per “step”), ma riduce il range massimo misurabile.

Cosa fa INTERNAL

Su un Arduino UNO la tensione di riferimento passa da 5 V (DEFAULT) a ~1,1 V.

Risultato:

  • Risoluzione: 1,1 V / 1023 ≈ 1 mV per step (invece di ~4,9 mV).
  • Range utile: tutto ciò che supera 1,1 V viene saturato a 1023.

è buona norma inserire analogReference(INTERNAL) nel setup() in questo modo:

1void setup() {
2  analogReference(INTERNAL);
3  delay(3);              // 2–5 ms per stabilizzare la band-gap
4}

Ma a cosa serve delay(3);?
Lo scopo è dare il tempo al riferimento di tensione interno (la “band-gap” da ≈ 1,1 V presente nei microcontrollori AVR) di accendersi e stabilizzarsi prima di eseguire la prima analogRead(), nello specifico:

  1. Avvio del generatore band-gap
    Quando si passa dal valore di DEFAULT (Vcc, 5 V) a INTERNAL l’hardware dell’ADC collega un piccolo condensatore al generatore band-gap. Occorrono alcuni microsecondi perché il generatore si attivi e qualche altro millisecondo perché il condensatore si carichi alla tensione esatta.
  2. Stabilità delle letture
    Se effettuate subito una lettura analogica, il riferimento non è ancora arrivato al valore finale: il primo campione risulterebbe non corretto. Dopo 2-5 ms (dato tipico dei datasheet AVR) la tensione di riferimento è già entro pochi millivolt dal valore nominale e le conversioni diventano affidabili.
  3. Sicurezza extra
    Usare 3 ms è un compromesso: abbastanza lungo per coprire la maggior parte dei casi, ma così breve da essere impercettibile nel ciclo di esecuzione.

Per approfondire questo aspetto vi rimando al datasheet dell’ATmega328P.

  • A pag. 37 e 43 del datasheet viene specificato che la band-gap interna richiede un breve tempo di avvio quando viene accesa; per questo, dopo analogReference(INTERNAL); si inserisce tipicamente un delay() di pochi millisecondi.
  • Le specifiche numeriche della band-gap – tensione nominale e altre caratteristiche di reset di sistema – sono riportate nella Tabella 28-4 (pag. 262).

Esempio con riferimento interno

1// Prof. Maffucci Michele
2// Uso del sensore di luminosità
3// 27.05.2025
4 
5void setup() {
6  analogReference(INTERNAL);   // 1,1 V; attivare PRIMA di qualsiasi analogRead
7  delay(3);                    // attesa minima per la stabilizzazione della reference
8  Serial.begin(9600);          // avvia la comunicazione seriale
9}
10 
11void loop() {
12  int raw = analogRead(A3);    // ora 1 step ≈1,07 mV
13  Serial.println(raw);         // stampa valori tra 0 e 1023
14  delay(200);
15}

Esempio: stampa il valore in mV

1void setup() {
2  analogReference(INTERNAL);   // 1,1 V; attivare PRIMA di qualsiasi analogRead
3  delay(3);                    // attesa minima per la stabilizzazione della reference
4  Serial.begin(9600);          // avvia la comunicazione seriale
5}
6 
7void loop() {
8  int raw   = analogRead(A3);
9  float vin = raw * (1.1 / 1023.0);   // Volt reali sul pin
10  Serial.println(vin, 3);             // stampa con 1 mV di risoluzione
11  delay(200);
12}

Con questo sketch potete leggere variazioni di pochi millivolt – perfetto per sensori la cui uscita non supera mai ~0,8–1 V.

Esempio: realizziamo una lampada automatica, l’intensità luminosa del LED sull’Arduino Sensor Kit aumenta al diminuire della luminosità esterna.

1/* ---------------------------------------------------------
2 * Lampada automatica - utilizzo del PWM – sensore luce su A3
3 * Reference ADC interna (1,1 V) – Arduino UNO + Sensor Kit
4 * LED pilotato sul pin 6 (PWM)   – più buio → LED più luminoso
5 * --------------------------------------------------------- */
6const byte LDR_PIN = A3;   // Light Sensor
7const byte LED_PIN = 6;    // LED sul Base Shield
8 
9void setup() {
10  analogReference(INTERNAL);   // 1,1 V; attivare PRIMA di qualsiasi analogRead
11  delay(3);                    // attesa minima per la stabilizzazione della reference
12  pinMode(LED_PIN, OUTPUT);
13}
14 
15void loop() {
16  int raw = analogRead(LDR_PIN);       // 0–1023 (satura a 1023 sopra ~1,1 V)
17  /* Con riferimento interno l’uscita dell’op-amp raggiunge 1023 già con luce
18     “media”.  Invertiamo e ristringiamo la scala per avere un controllo morbido
19     nell’intervallo 0–800 circa: */
20  raw = constrain(raw, 0, 800);        // taglia eventuali saturazioni
21  int pwm = map(raw, 800, 0,   0, 255); // buio (0) → 255   |   luce (≥800) → 0
22  analogWrite(LED_PIN, pwm);
23  delay(30);
24}

Seguire il link per maggiori informazioni sull’uso dell’istruzione constrain().

Esempio: LED che lampeggia ad una frequenza proporzionale alla luce rilevata dal sensore di luce

1/* ---------------------------------------------------------
2* Prof. Maffucci Michele
3* Blinking LED – la frequenza varia con l’intensità luminosa
4* LED sul pin 6 usato come digitale
5* Più luce → lampeggio più veloce | Più buio → lampeggio lento
6* 27.05.2025
7* --------------------------------------------------------- */
8const byte LDR_PIN = A3;
9const byte LED_PIN = 6;
10 
11// limiti del periodo di lampeggio (in millisecondi)
12const unsigned int PERIOD_MIN = 150; // luce forte ⇒ lampeggio veloce
13const unsigned int PERIOD_MAX = 2000; // buio profondo ⇒ lampeggio lento
14 
15void setup() {
16analogReference(INTERNAL); // 1,1 V
17delay(3);
18pinMode(LED_PIN, OUTPUT);
19}
20 
21void loop() {
22int raw = analogRead(LDR_PIN); // 0–1023 (satura a 1023)
23/* Per trasformare il valore luce → periodo
24Luce forte (raw alto) → periodo breve
25Luce debole (raw basso) → periodo lungo */
26raw = constrain(raw, 50, 750); // impostazione finestra utile
27unsigned int period = map(raw, 750, 50, PERIOD_MIN, PERIOD_MAX);
28 
29// lampeggio: metà periodo LED ON, metà OFF
30digitalWrite(LED_PIN, HIGH);
31delay(period / 2);
32digitalWrite(LED_PIN, LOW);
33delay(period / 2);
34}

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.