Algoritm Lampa RGB

Introducere 
În acest articol va fi prezentat un algoritm ce realizează reglarea culorii şi a intensităţii unei lămpi. E una din multiplele abordări a acestei probleme. Nu e nici cea mai bună, nici cea mai eficientă. E doar o implementare, care are ca scop iniţierea celor ce sunt la început de drum la capitoul programare.
Datele problemei sunt următoarele: avem 2 intrări şi 3 ieşiri. Cele două intrări sunt potenţiometrele de unde vrem să reglăm culoare şi intensitatea, iar fiecare ieşire va controla câte o culoare de bază.

Cum se reglează culoarea?
Foarte simplu, cu un potentiometru parcurgi culorile (curcubeului), iar cu celălalt reglezi intensitatea luminii. Dacă până aici e totul clar, problema se complică puţin când vine vorba de a implementa un algoritm care să facă asta.
Pentru început trebuie citită valoarea potenţiometrului. Convertorul AD (Analogic Digital) al microcontrolerului (ATtiny13) permite o conversie pe 10 biţi. Asta înseamnă că valoarea citită de la potenţiometru poate să varieze între 0 şi 1023. Astfel o să am la dispoziţie 1024 de valori pe care o să le pot interpreta ca fiind culori.
Din acele 1024 de culori am ales câteva nuanţe/culori reper după cum urmează: alb, roşu, galben, verde, turcuaz, albastru şi violet. Am vrut ca la capetele potenţiometrului să am alb şi am mai vrut ca să nu pierd nuanţele de la violet spre roşu, aşa că am adăugat cele două culori în tabel.
Astfel, prima culoare va fi alb, a 128-a culoare va fi roşu, a 256-a culoare va fi galben, şi aşa mai departe până la alb. Fiecărei culori reper i-am ataşat un index de la 0 la 7. Motivul pentru care nu am indexat şi albul de la sfârşit a fost acela că valoarea maximă ce se poate citi de la convertor e 1023 (sau în hexazecimal 0x03FF), ceea ce înseamnă că albul nu va putea fi atins. De aici se poate vedea că între fiecare două culori reper alăturate vor fi 128 de nuanţe.
Fiecare din aceste culori reper pot fi obţinute prin combinarea simplă a celor trei culori de bază: roşu, verde şi albastru. Galbenul se poate obţine amestecând roşu cu verde, turcoazul din verde şi albastru, iar violetul din roşu şi albastru. Albul se obţine amestecând toate cele trei culori de bază.
Tabelul de mai sus ilustrează felul în care variază procentajul culorilor de bază, în obţinerea celor 1024 de nuanţe. Astfel orice cod de culoare citit de la potenţiometru va putea fi transformat într-o combinaţie a celor trei culori de baza (Rosu, Verde si Albastru). Tabelul se termină cu roşu şi alb (culorile cu care şi începe) pentru a avea şi nuantele de la violet spre roşu şi de a putea acoperi întregul interval de valori posibile ce vin de la potenţiometru.
Pentru simplificare, tabelul de mai sus l-am codat pe biţi astfel: 
  • maximul de culoare de bază va fi înlocuit de un bit de 1
  • minimul va fi inlocuit de un bit de 0
  • excepţia o constă ultima coloană de alb care nu va fi prezentă în tabelul simplificat pentru a mă încadra în 8 biţi

Diagrama ce conţine codarea culorilor de bază pe biţi va fi transformată într-un număr binar, începând numerotarea biţilor de la stânga la dreapta, cu bitul 0. Tabelul de mai sus se va regăsi în memoria uC-ului pe 3 octeţi, în oglindă, astfel:
  • PrezentaCuloare[Rosu]       = 0xC7    (1 1 0 0 0 1 1 1)
  • PrezentaCuloare[Verde]     = 0x1D    (0 0 0 1 1 1 0 1)
  • PrezentaCuloare[Albastru] = 0x71     (0 1 1 1 0 0 0 1)
Aceşti trei octeţi de data vor conţine mai multe informaţii:
  1. Compoziţia culorilor de bază ce intră în nunanţa selectată
  2. Indexurile culorilor reper între care se află nuanţa selactată
  3. Tranziţia fiecărei culori de bază
Codul nunaţei selectate va arăta care va fi procentul din fiecare culoare de bază raportată la cele mai apropiate doua culori reper. Pentru fiecare cod de culoare/nunaţă se poate calcula poziţia bitului din variabila "PrezentaCuloare", împărţind codul culorii/nunaţei la 128 (numarul de nunaţe între două culori reper), şi aflând câtul împărţirii. Procentul culorilor de bază (Roşu, Verde, Albastru) se calculează aflând restul împărţirii codului culorii/nuanţei la 128. Deci, pentru a putea afla compoziţia de Roşu, Verde şi Albastru a unei culori trebuie să alfăm câtul şi restul împărţirii codului culorii (care poate varia între 0 şi 1023) la 128.
Două metode rapide de a afla câtul şi restul împărţirii oricărui număr la 128 sunt prezentate în continuare:

  1. Câtul împărţirii la 128 se poate calcula prin deplasarea pe biţi numărului la dreapta cu 7 biti. Numarul 128 = 2 ^ 7, astfel că prin deplasarea la dreapta cu 7 biţi se va obtine  câtul împărţirii. Microprocesorul are instructiuni speciale pentru manipularea biţilor, aşa că o astfel de operaţie se va efectua foarte rapid şi cu mai puţine instrucţiuni. (Catul = CodCuloare >> 7;)
  2. Restul împărţirii la 128 se poate calcula luând în considerare doar cei mai nesemnificativi 7 biţi din număr, adică primii 7 biţi. (Restul = CodCuloare & 0x007F;)
În tabelul de culori sunt trecute o serie de culori reper: Alb, Roşu, Galben, Verde, Turcoaz, Albastru, Violet, Roşu şi Alb. Toate celelalte nuanţe care se vor afla între două culori reper alăturate se for putea descompune în culorile de bază cu ajutorul a patru tranziţii. Astfel, pentru trecerea de la o culoare reper la alta, culorile de baza se vor afla în una din următoarele tranziţii:
Valoarea tranziţiei a fost aleasa unind cele două stări (starea curentă şi starea următoare) într-un număr binar. Astfel:
  • Tranziţia 0 = 00 (trecerea de la minim la minim)
  • Tranziţia 1 = 01 (trecerea de la maxim la minim)
  • Tranziţia 2 = 10 (trecerea de la mimin la maxim)
  • Tranziţia 3 = 11 (trecerea de la maxim la maxim)
Codarea asta este va reduce numărul de "if"-uri din funcţia care calculează compoziţia culorilor de bază din nuanţa selectată. Tranziţia se poate determina aflând starea curentă şi starea următoare pentru fiecare culoare de baza. Starea curentă se află mutând bitul de pe poziţia indicată de Câtul împărtirii Codului culorii la 128, pe poziţia 0. Astfel Starea curentă va fi 0 sau 1.
  • StareCurenta[RGB] = (PrezentaCuloare[RGB] >> Câtul) & 0x01;
Starea următoare se află aplicând aceeaşi mască, la bitul următor (Câtul+1). Formula se complică puţin pentru cazul în care Câtul este 7, adică avem o culoare cu codul mai mare de 896 (între roşu şi albul de la sfârşitul tabelului de culori). În cazul acesta, următoarea stare va fi starea dată de nuanţa reper Alb, aflată pe pozitia 0 în tabel. 
  •  if (Catul == 7) { StareUrmatoare[RGB] = 0x02; }
    else { StareUrmatoare[RGB] = (PrezentaCuloare[RGB] >> Catul) & 0x02; }
Valoarea tranziţiei se va calcula adunând starea curentă cu starea următoare.
  • Tranzitie = StareCurenta[RGB] + StareUrmatoare[RGB];
Pentru clarificare o să iau două exemple:

Exemplul 1:
Presupunem că de la convertorul AD al microcontrolerului citim valoarea 239 pentru culoare. Dacă împărţim aces număr la 128 vom obţine un cât de 1 şi un rest de 111. Câtul reprezintă indexul culorii reper, adică roşu (1), iar restul reprezintă indexul nuanţei intermediare între roşu (1) şi următoarea culoare reper, galben (2).
Pentru a putea reproduce culoarea dorită (şi selectată) trebuie verificaţi cei trei octeţi de PrezentaCuloare, corespondenţi fiecărei culori de bază.
Astfel, pentru Roşu, biţii 1 (poziţia reper) şi 2 (poziţia următoare) sunt amândoi 1, adică roşu va fi în tranziţia 3. Cu alte cuvinte, roşu va fi la intensitate maximă în culoarea selectată.
Pentru Verde, bitul de pe poziţia 1 este 0, iar cel de pe poziţia 2 este 1. De aici reiese că verdele se află undeva între minim şi maxim, pe o pantă ascendentă, adică tranziţia 2. Cum valoarea de maxim am ales-o pentru uşurinţă la valoarea 127, verdele va fi la 111 intensitate, în culoarea selectată, adică minimul (0) + indexul nuanţei intermediare (111).
 La Albastru, biţii de pe poziţiile 1 şi doi sunt ambii 0, deci avem tranziţia 0. Asta înseamnă că albastru nu va fi prezent în culoarea selectată.
Rezultă că pentru culoarea cu codul = 239 avem nevoie de:  
  • Roşu = 127 (100%)
  • Verde = 111 ( 87%)
  • Albastru = 0 ( 0%) 
Exemplu 2:
Citim valoarea 611 pentru culoare. Dacă împărţim acest număr la 128 vom obţine un cât de 4 şi un rest de 99. Câtul reprezintă indexul culorii reper, adică turcoaz (4), iar restul reprezintă indexul nuanţei intermediare între turcoaz (4) şi următoarea culoare reper, albastru (5).
Dacă ne uităm la culoarea de bază roşu vedem că se află în tranziţia 0, deci va fi la intensitate minimă.
Verdele, în schimb, va trece de la maxim la minim, pe o pantă descendentă, conform tranziţiei 1. Astfel valoarea finală pentru verde se va calcula aflând diferenţa dintre maximul de culoare şi indexul nuanţei intermediare: maximul (127)  - index nuanţă intermediară (99), adică 28.
Albastrul se va afla în tranziţia 3, deci la valoare maximă.

Rezultă că pentru culoarea cu codul = 611 avem nevoie de:  
  • Roşu = 0 (0%)
  • Verde = 28 (22%)
  • Albastru = 100 (100%) 
Intensitatea
Până aici am obţinut valorile celor trei culori de bază pentru a obţine nuanţa dorită. Urmează calcularea intensităţii luminii. Pentru asta se citeşte valoarea numerică de la celălalt potenţiometru (care variază între 0 şi 1023). Pentru a fi mai uşor de calculat am ales să folosesc doar 128 de nivele pentru intensitatea luminii, astfel încât:
  • 0 = 0% intensitate minimă
  • 127 = 100% intensitate maximă

Astfel valoarea intensităţii luminii se poate afla împărţind valoarea citită de la potenţiometru la 8, astfel încât rezultatul va fi în intervalul [0 - 127].
 
Exemplu 1: Valoare potenţiometru = 50
Câtul împărţirii la 8 = 6, rezultă că intensitatea culorii este 6
 
Exemplu 2: Valoare potenţiometru = 1000
Câtul împărţirii la 8 = 125 rezultă că intensitatea culorii este 125
 
Exemplu 3: Valoare potenţiometru = 1023
Câtul împărţirii la 8 = 127 rezultă că intensitatea culorii este 127

Lumina 
Valoarea finală a comenzii, care va fi trimisă la LED-urile RGB va fi o combinaţie între culoarea calculată şi intensitatea aleasă. Formula fiind:
  • ReferintaPWM = (Valoare culoare de bază)  x (Intensitate) / (Intensitate maximă)

PWM-ul
PWM (Pulse Width Modulation) = Modularea Semnalului în Lăţime de Impuls
Acest semnal este reprezentat de o succesiune de impulsuri la care se poate controla lăţimea impulsului. Cu un semnal PWM se poate regla intensitatea unui LED, modificând perioadele în care LED-ul e aprins şi când acesta e stins. La o frecvenţă mai mare de 60 Hz, ochiul uman nu mai percepe pâlpâitul LED-ului, ci vede o medie a intensităţii luminoase.
Pentru reglarea celor trei culori de bază avem nevoie de trei semnale PWM distincte. Din păcate, microcontrolerul ATtiny13 conţine doar două canale de PWM. Cum e nevoie de trei astfel de canale, nu vom putea folosi cele două canale dedicate, aşa că va trebui să realizăm trei semnale PWM prin program. Mai întâi trebuie înţeles cât mai bine cum funcţionează acest PWM.
Pentru a realiza un semnal PWM avem nevoie de un Timer. Acest Timer nu e altceva decât o valoare ce se incrementează la intervale fixe, binecunoscute. Spre exemplu, să zicem că intervalul ales este de 1 us (micro-secundă), atunci la fiecare 1 us, valoarea timer-ului va creşte cu o unitate. Valoarea Timer-ului porneşte de la un minim şi urcă până la un maxim. Aceste valori de limită se aleg o singură dată, la început şi nu se mai schimbă. Între minim şi maxim se alege şi o valoare de referinţă, care poate fi modificată oricând pe parcursul derulării programului.
Mecanismul de generare al semnalului PWM se bazează  pe tre reguli simple:
  1. Dacă valoarea Timer-ului este mai mică decât referinţa, atunci ieşirea PWM este pe 1 logic.
  2. Dacă valoarea Timer-ului este mai mare decât referinţa, atunci ieşirea PWM este pe 0 logic.
  3. Daca valoarea Timer-ului ajunge la maxim, Timer-ul se va reseta la valoarea de minim.
Se poate observa că modificând referinţa, se schimbă lăţimea impulsului din semnalul PWM. Astfel, dacă referinţa scade, se va micşora şi perioada, în care PWM-ul va fi pe 1 logic, şi viceversa. Dacă privim pe o perioadă mai lungă de timp, semnalul PWM va arăta în felul următor:

Modificând referinţa în jos, vom obţine următorul semnal:
În concluzie
Pentru a putea lega cele prezentate mai sus cu PWM-ul, trebuie definite limitele pentru Timer astfel:
  • valoarea de minim = 1
  • valoarea de maxim = 127
  • astfel valoarea de referinţă va putea lua valori în intervalul [1...127], deci o putem obţine cu ajutorul formulei prezentate la paragraful "Lumină".
Referinta_PWM [Roşu]       = (ValoareCuloare [Roşu]) x (Intensitate) / (Intensitate maximă);
Referinta_PWM [Verde]     = (ValoareCuloare [Verde]) x (Intensitate) / (Intensitate maximă);
Referinta_PWM [Albastru] = (ValoareCuloare [Albastru]) x (Intensitate) / (Intensitate maximă);
Schema Logică
Diagrama de mai jos prezintă schema bloc a programului ce rulează pe microcontroler. Programul se împarte în două părţi principale:
  • - algoritmul de calcul al culorilor (realizat de programul principal)
  • - controlul Led-urilor (realizat de rutina de întrerupere)
Programul principal
Programul începe prin a configura regiştrii Timer-ului 0, timer care va da tactul de bază. Perioada unui tact este calculată în aşa fel încât frcvenţa PWM-ului soft să fie de peste 50Hz, pentru ca ochiul uman să nu poată sesiza pâlpâitul Led-urilor. Urmează apoi configurarea convertorului Analogic-Digital ca acesta să lucreze cu valori între 0 şi 1023, şi activarea întreruperii pentru atingerea maximului la Timerul 0.
Programul va face 50 de conversii ADC (citiri de la potenţiometrele de culoare şi intesitate sau lumină), şi va folosi media acelor citiri pentru a calcula valorile culorilor de bază şi a intensităţii (lumina).

Rutina de întrerupere pentru Timerul 0 (ISR_T0)
Această rutină devine activă imediat după activarea întreruperilor. Astfel, de fiecare dată când Timerul 0 ajunge la valoarea maximă, programul principal se va opri, făcând loc rutinei de întrerupere să se execute. După ce întreruperea s-a încheiat, programul principal îşi reia execuţia din locul unde a fost întrerupt.
Paşii făcuţi în rutina de întrerupere sunt următorii:
  • resetarea Timerului 0 la valoarea minimă;
  • incrementarea Tactului de bază;
  • compararea valorii Tactului cu punctul de maxim şi cu valorile de referinţă pentru fiecare din culorile de bază. În funcţie de rezultatul comparării, Ledurile vor fi fie stinse, fie aprinse. 
 

Niciun comentariu:

Trimiteți un comentariu