ublo
bogdan's (micro)blog

bogdan » arduino: senzor de culoare

10:50 pm on Apr 29, 2019 | #more | tags:

în urma rugăminții profesorului Stamatin, de câțiva ani țin un curs la Facultatea de Fizică din Măgurele. dacă inițial numele «modelare și simulare» ascundea ecuații matematice și metode numerice, recent am înlocuit diferențialele cu Arduino, în ovațiile celor câtorva studenți care frecventează cursul. dotările limitate m-au făcut să devin creativ cu materialele de curs, iar rezultatul mi s-a părut suficient de interesant pentru a-l reproduce aici. așa că:

cum funcționează? (puțină teorie)

pe lângă simbolurile folclorice și religioase, un curcubeu poartă și dovada că lumina albă are în componență o multitudine de culori, pornind cu roșu și sfârșindu-se cu violet. când privești un obiect, o parte dintre aceste culori sunt reflectate către fotoreceptorii din retină, în timp ce restul sunt absorbiți. în felul ăsta, un obiect pare alb, dacă reflectă toate culorile înapoi către privitor, pare negru dacă nu reflectă (aproape) nimic și pare verde dacă absoarbe toate celelalte culori cu excepția culorii verzi, pe care o reflectă.

un mic experiment pe care-l poți face pentru a observa mai bine acest fenomen este să privești un obiect verde sau albastru iluminat doar de un led roșu. culoarea obiectului va fi foarte apropiată de negru, semn că obiectele verzi sau albastre absorb mare parte din lumina roșie incidentă.

lumina este radiație electromagnetică, foarte asemănătoare cu ce încălzește mâncarea în cuptorul cu microunde sau ce poartă ultimul snapchat către prietenii tăi. diferența este dată de frecvența cu care acestea oscilează: 2.450.000.000 oscilații pe secundă pentru cuptorul cu microunde, între 700.000.000 și 2.600.000.000 oscilații pe secundă pentru telefoane mobile și între 394.460.000.000.000 și 768.700.000.000.000 oscilații pe secundă pentru lumină. numerele sunt mult prea mari pentru a fi utilizate în practică, așa că în locul numărului de oscilații pe secundă se folosește lungimea de undă, mai exact distanța parcursă de undă într-un interval de timp cât durează o singură oscilație:
$$\lambda = \frac{c}{f}$$
,unde:
λ – e lungimea de undă, exprimată în metri;
c – e viteza cu care se deplasează undele, în acest caz, viteza luminii, ~3×108 metri pe secundă;
f – reprezintă numărul de oscilații pe secundă și se măsoară în herzi sau secunde-1;

fără a intra în detaliile care mă depășesc legate de nuanțe, intervalele de lungime de undă asociate fiecărei culori sunt 415 ± 35nm, 475 ± 25nm, 535 ± 35nm, 585 ± 15nm, 615 ± 15nm, 695 ± 65nm.

o ultimă formulă fizică vine din suprapunerea a două culori. atunci când oscilațiile cu frecvențe diferite se suprapun, apare fenomenul numit bătăi: pentru un observator extern va deveni dominantă o oscilație cu frecvența media aritmetică a frecvențelor oscilațiilor componente, cu amplitudinea oscilând cu frecvența cât jumătate din diferența absolută a frecvențelor oscilațiilor componente. pentru lungimi de undă, formula implică media armonică:
$$\lambda_{rezultat} = \frac {2 \lambda_{1} \lambda_{2}}{\left|\lambda_{1} \pm \lambda_{2}\right|}$$
,unde:
λrezultat – reprezintă lungimea de undă rezultată;
λ1,2 – reprezintă lungimile de undă componente;

pornind de la formula asta și de la particularitățile ochiului uman, combinând fascicule cu lumină cu lungimea de undă corespunzătoare culorilor roșu, verde și albastru poți obține majoritatea culorilor din spectrul vizibil.

cum construiesc un senzorul de culoare?

trecând peste teoria oarecum plictisitoare de mai nainte, drept senzor de culoare voi folosi un simplu fotorezistor (2 lei, 1,95 lei) – un dispozitiv care își modifică conductivitatea în funcție de cantitatea de lumină incidentă. conductivitatea fotorezistorului variază însă și cu lungimea de undă, astfel, dacă sensibilitatea maximă este în jurul culorii verzi, pentru a obține același efect, roșu și indigo au nevoie de fascicule de câteva ori mai puternice, ca în figura care urmează. un alt aspect pe care îl voi reține din foaia de catalog a fotorezistorului este timpul de răspuns maxim de 30ms.

CdS Photorezistor Sensitivity

principiul de funcționare al senzorului va fi următorul: iluminăm obiectul a cărui culoare o voi determina cu un fascicul de lumină de culoare cunoscută, măsor cât din ea se reflectă către senzor și compar cu valoarea dată de iluminarea ambientală. în funcție de rezultat, voi stabili cu exactitatea culoarea.

în lumina discuției de mai sus, voi alege cele trei culori standard: roșu, verde și albastru pe care le folosesc pe rând pentru a ilumina obiectul, măsurând pentru fiecare intensitatea luminii reflectate. dacă dintre cele trei culori, roșu, verde și albastru, obiectul va reflecta mai mult verde, atunci va fi verde, dacă reflectă și roșu și verde în același timp, cel mai probabil va fi galben.

pentru sursele de lumină cu culoare stabilită, voi folosi LED-uri cu lentile colorate în culorile roșu, verde și albastru (50 de lei / 500 bucăți). bineînțeles că poți utiliza mai multe culori de LED-uri. cu puțină răbdare și costuri ceva mai mari, poți găsi cel puțin 16 nuanțe diferite de LED, inclusiv infraroșu (~900nm) și ultraviolet (~280nm), însă va trebui să folosești un senzor mai bun de intensitate luminoasă (cine știe, poate un viitor articol despre asta, până atunci ai aici unul ceva mai avansat).

circuitul este simplu: conectez fiecare dintre LED-uri la unul dintre terminalele digitale 3, 4 și respectiv 5 ale Arduino, în timp ce fotorezistorul formează un divizor rezistiv împreună cu un rezistor de 47KΩ conectat către Vcc (+5V), al cărui punct intermediar e conectat la terminalul analogic A0.

Color Senzor Schematics

notă
modul de aranjare al componentelor pe placa de experimente încearcă să reproducă cât mai fidel realitatea. fotorezistorul e înconjurat de LED-uri pentru a reflecta lumina sub aproximativ același unghi, făcând ca poziția obiectului din fața senzorului să nu influențeze major rezultatul măsurătorii. orice altă poziționare e posibilă, doar că drumul optic între LED-uri și fotorezistor va avea dimensiuni și forme diferite, influențând intensitatea luminoasă recepționată de senzor.
Color Sensor

cum măsor intensitatea culorilor?

pentru început, definind niște constante și un tip de date pentru valoarea măsurată de senzor. constantele sunt:

#define RED_LED 3 // <-- terminalul la care e conectat LED-ul rosu
#define GREEN_LED 4 // <-- terminalul la care e conectat LED-ul verde
#define BLUE_LED 5 // <-- terminalul la care e conectat LED-ul albastru
#define SENSOR_PIN A0 // <-- terminalul analog care masoara tensiunea electrica pe senzor
#define SAMPLES 20 // <-- numarul de masuratori analogice; explicatia urmeaza

tipul de date de care vorbeam este o structură în care voi stoca valoarea măsurată cu LED-urile stinse, pe care o voi numi base (de la bază) și valorile pentru fiecare dintre leduri red, green, blue pentru roșu, verde și respectiv albastru. typedef e cuvântul cheie care mă ajută să definesc tipul color (de la sfârșit). tipul e definit ca o structură definită cu struct, al cărei identificator este _color și ale cărei componente sunt toate de tip întreg. definesc tipul ăsta de date pentru că devine foarte ușor de folosit când e întors sau folosit ca parametru în funcții.

typedef struct _color {
  int base;
  int red;
  int green;
  int blue;
} color;

în bucata de cod de mai sus am introdus constanta SAMPLES ca fiind 20. citirea datelor folosind convertorul analog-digital din Arduino nu e întotdeauna stabilă, așa că voi citi SAMPLES mostre, pe care le voi media pentru a obține o valoarea mai apropiată de valoarea reală a tensiunii măsurate. mai trebuie să țin cont că rezistența electrică a fotorezistorului scade odată cu creșterea intensității luminoase. cum fotorezistorul este conectat către GND, tensiunea pe acesta va fi direct proporțională cu rezistența electrică, adică va scădea la rândul ei cu creșterea intensității fluxului luminos incident. acest lucru e o mică problema în interpretarea datelor, preferând o funcție de conversie crescătoare. ca orice problemă legată de Arduino, am două variante: prima, fizică, în care inversez locul rezistorului și fotorezistorului în divizor, conectând fotorezistorul către Vcc (+5V); a doua, în cod, în care scad din 1023 (valoarea maximă produsă de convertorul analog-digital), valoarea măsurată. cum pregătirea mea de bază e de matematician, am ales-o pe a doua.

int sensor_read () {
  long output = 0; // <-- s-ar putea sa depasesc valoarea maxima pentru intregi simpli
  delay (30); // <-- mai tii minte? senzorul reactioneaza cu 30ms intarziere
  for (byte c = 0; c < SAMPLES; c++) { // <-- citesc 20 de mostre
    output += analogRead (SENSOR_PIN); // <-- adun valoarea masurata pentru calculul sumei
  }
  return 1023 - (int) (output / SAMPLES); // <-- din 1023 scad media masuratorilor
}

cum analogRead produce la iesire un întreg între 0 și 1023, dacă numărul de mostre culese e mai mare de 30, valoarea maximă pentru tipul de date int (întreg stocat pe 2 octeți, având valoarea maximă aprox. 32 de mii) este depășită, rezultatul fiind trunchiat. din acest motiv am utilizat pentru sumă tipul de date long. in cadrul liniei care contine return, am folosit (int) pentru a converti output / SAMPLES din tipul de date long (întreg stocat pe 4 octeți, având valoarea maximă aprox. 2 miliarde) în tipul de date int. în mod obișnuit operația nu e necesară, conversia făcându-se automat. prefer totuși aceast mod de lucru pentru a putea urmări cu atenție tipurile de date folosite și pentru a fi sigur că memoria folosită e suficientă pentru valorile folosite.

până acum am funcția necesară măsurării unei singure valori. pentru citirea tuturor valorilor necesare voi începe cu citirea valorii de bază, urmată pe rând de aprinderea LED-ului corespunzător culorii, efectuarea unei măsurători și apoi stingerea lui, pentru fiecare culoare.

color read_color () {
  color c; // <-- color e noul tip de date definit

  c.base = sensor_read (); // <-- citesc valoarea de baza (lumina ambientala)
                           // fiecare componenta a lui color o accesez cu .nume_componenta
                           // c.base face referire la componenta base din tipul de date color
  digitalWrite (RED_LED, HIGH); // <-- aprind LED-ul rosu
  c.red = sensor_read (); // <-- citesc intensitatea fasciculului rosu reflectat
  digitalWrite (RED_LED, LOW); // <-- sting LED-ul rosu
  digitalWrite (GREEN_LED, HIGH); // <-- repet procesul pentru LED-ul verde
  c.green = sensor_read ();
  digitalWrite (GREEN_LED, LOW);
  digitalWrite (BLUE_LED, HIGH); // <-- repet procesul pentru LED-ul albastru
  c.blue = sensor_read ();
  digitalWrite (BLUE_LED, LOW);

  return c;
}

după ce citesc valorile, mi-ar plăcea să le trimit către interfața serială, pentru procesarea ulterioară și pentru că mi-e mai comod, definesc o funcție care ia ca parametru o structura din noul tip definit:

void print_color (color c) {
  Serial.print (c.base); // <-- trimit catre portul serial valorile
  Serial.print (" "); // <-- separate de cate un spatiu
  Serial.print (c.red);
  Serial.print (" ");
  Serial.print (c.green);
  Serial.print (" ");
  Serial.println (c.blue); // <-- la final, trec pe randul urmator cu "ln" din "println"
}

urmează inițializarea terminalelor Arduino și a portului serial, pentru culegerea datelor.

void setup(){
  pinMode (RED_LED, OUTPUT); // <-- configurarea terminalului RED_LED (3) ca iesire
  pinMode (GREEN_LED, OUTPUT);
  pinMode (BLUE_LED, OUTPUT);

  digitalWrite (RED_LED, LOW); // <-- stabilirea potentialului terminalului RED_LED (3) la 0V
  digitalWrite (GREEN_LED, LOW);
  digitalWrite (BLUE_LED, LOW);
  Serial.begin (9600); // <-- pornesc interfata seriala
}

și ultima bucată de cod, care citește culoarea obiectului din fața senzorului, o dată pe secundă.

void loop() {
  color c; // <-- folosesc noul tip de date definit
  c = read_color (); // <-- citesc culoarea
  print_color (c); // <-- afisez rezultatele
  delay (1000); // <-- astept o secunda (1000ms)
}

notă
rezultatele obținute nu se traduc direct în culori. o metodă simplă pentru detectarea culorii o reprezintă plasarea unei coli de hârtie în fața senzorului și determinarea valorilor pentru cele trei canale. culoarea hârtiei fiind albă, intensitatea fluxului reflectat este maximă pentru fiecare canal. folosind valorile maxime măsurate pentru fiecare canal, din care scad intensitatea de bază, pot să determin pentru fiecare culoare ce procent din fluxul inițial se reflectă. în felul ăsta pot folosi principiul descris la începutul articolului: valori egale pentru roșu și verde și o valoare mult mai mică pentru albastru, înseamnă culoarea galbenă.
$$I_{relativa, culoare} = \frac{I_{culoare} - I_{baza}}{I_{alb, culoare} - I_{alb, baza}}$$
,unde:
culoare - e una dintre culorile roșu, verde sau albastru;
Irelativa, culoare - e intensitatea masurata relativa pentru culoarea culoare; va fi un numar real intre 0 și 1;
Iculoare - e intensitatea masurata pentru pentru culoarea culoare, pentru obiectul a cărui culoare vreau să o determin; un număr întreg între 0 și 1023;
Ibaza - e intesitatea de bază măsurată pentru obiectul a căreui culoare vreau să o determin; un număr întreg între 0 și 1023;
Ialb, culoare - e intensitatea măsurată pentru culoarea culoare, având coala de hârtie în fața senzorului; un număr întreg între 0 și 1023;
Ialb, baza - e intensitatea măsurată de bază, având coala de hârtie în fața senzorului; un număr întreg între 0 și 1023.

aceast sait folosește cookie-uri pentru a îmbunătăți experiența ta, ca vizitator. în același scop, acest sait utilizează modulul Facebook pentru integrarea cu rețeaua lor socială. poți accesa aici politica mea de confidențialitate.