Sari la conținut
ELFORUM - Forumul electronistilor

Invatat programare de la zero


riciu

Postări Recomandate

contratimp = când ledul A e aprins, ledul B e stins, şi invers.sincron = se aprind şi se sting amândouă odată.

 

nu era cazul de privat, se poate să nu fii singurul cu nedumerirea asta! :)

Link spre comentariu

tocmai de-aia când o să vrei să aprinzi 2 leduri în contratimp

Folosesti XOR cu masca potrivita. Cre' ca merge si in assembler... :limb: Editat de Liviu M
Link spre comentariu

la ora aia nu-mi ardea de xor! :)))))))de fapt, abia începeam să-mi dau seama că cel care mi-a zis că asm de mcu nu se pupă cu asm de z80 n-a glumit când a spus-o! :)

 

iar codul meu semăna binişor cu "cârnatul" de instrucţiuni pe fiecare bit în parte pus de PKP la atmeluri! :rade:

cel puţin, avem scuza că vrem să vedem cum reacţionează mcu la instrucţiunile respective... şi după aia sigur ne plictisim şi vrem cod mai mic, dar cu aceleaşi funcţii! :limb:

Link spre comentariu

Continuare a descrierii programului al 2 lea din postul #66, descriere efectuata in posturile: #109, #115.

 

In postul #115 am discutat despre instructiunea decizionala if ... else si despre "uneltele" oferite de compilatorul mikroC for PIC care permit accesul individual la bitii din registrii controlerului.

 

Un exemplu de asemenea "unealta" este structura:

RA0_bit

care ofera accesul la bitul 0 din portul PORTA.

 

In functie de directia pinului asociat RA0 (directie care poate fi INTRARE sau IESIRE in functie de ce valoare are bitul 0 din registrul TRISA: 1, respectiv 0) putem folosi aceasta "instructiune" RA0_bit astfel:

 - daca bitul 0 al TRISA este 0 (inseamna ca pinul asociat este Output) atunci putem folosi RA0_bit ca sa facem "set" sau "clear" pentru pinul iesire (pt controlerul PIC16F877A, pinul asociat lui RA0 este pinul 2 hardware) adica facem 1 logic pe pin sau, respectiv 0 logic pe pin. Aprindem LED-ul sau il stingem :)

 - daca bitul 0 al TRISA este 1 (inseamna ca pinul asociat este Input) atunci putem folosi RA0_bit ca sa citim valoarea pinului corespunzator lui RA0 (adica valoarea digitala de pe pinul 2 hardware). Cu alte cuvinte, aflam ce stare creaza mediul inconjurator (un utilizator care apasa un buton de ex) pe acel pin.

 

In cazul nostru specific, bitul 0 al TRISA (mikroC ne lasa sa il apelam cu "instructiunea" TRISA0_bit) este 1 deci RA0 (pinul 2 hardware) este Intrare.

 

Prin introducerea lui RA0_bit  intr-o instructiune decizionala  in acest fel:

if (RA0_bit == 0) {//program}

de fapt noi testam daca valoarea pinului 2 hardware stocata in bitul 0 al PORTA (adica RA0) este zero. Si daca aceasta valoare din bitul 0 este zero (adica LOW) atunci stim ca s-a apasat butonul.

Aceasta pentru ca butonul este activ LOW, adica atunci cand este apasat va genera un nivel logic LOW pe pinul 2 al controlerului. Ca doar noi am pus switch-ul intre pin si GND, nu? Cu un rezistor din acelasi pin catre Vdd pentru a tine un nivel logic 1 stabil pe intrare, pana cand switch-ul este apasat si intrarea va avea nivelul logic 0...

 

Urmeaza apelul unei functii oferite de compilatorul mikroC for PIC, si anume:

delay_ms(10);

Dar sa spun cate ceva despre "bounce / debounce".

 

Orice buton/switch mecanic este, la modul simplist explicat, un ansamblu de doua lamele elastice care atunci cand butonul este OFF sunt indepartate si atunci cand butonul este ON se ating.

 

Cand lamelele elastice sunt indepartate atunci rezistenta este infinita si se cheama ca switch-ul este OFF, deschis.

Cand lamele elastice se ating atunci rezistenta este infima, practic putem spune ca este nula si se cheama ca switch-ul este ON, inchis.

 

Atunci cand aprindem un bec intr-o camera, prin apasarea intrerupatorului, noi vedem ca "instantaneu" se aprinde bec-ul si avem lumina.

Dar in realitate nu este chiar asa.

Atentia umana obisnuita (si nu luam in seama situatia autistilor care sunt exceptia de la regula) nu poate procesa informatia, cu rezultate semnificative si corecte, cu o durata sub cca 50ms (relativ vorbind pentru ca difera de la om la om). Mai multe despre aceasta de ex aici.

 

Din aceasta cauza noi nu percepem ca in realitate, la actionarea switch-ului, pentru cca 5 milisecunde (mai mult sau mai putin functie de cat de uzat/prost este switch-ul) contactele elastice se izbesc unul de altul generand multiple opriri si porniri ale becului. Este din cauza elasticitatii acelor lamele. In realitate becul "flicare" pentru cca 5ms, dar noi nu vedem acest flicar pentru ca nu putem (in mod normal, desi unele chimicale permit chestii de genul acesta).

Acest fenomen tranzitoriu este fenomenul de "bounce" (tradus liber "saritura dintr-o parte in alta").

 

Pentru noi nu este o problema dar pentru un controler catre trece prin bucla infinita de tzashpe ori pe secunda(acel while(1){ ...} de care am discutat anterior, va aduceti aminte? ), el va discerne toate acele ON si OFF pana ce intr-un final switch-ul se va stabiliza pe pozitia ON. 

Ori aceasta este o problema pentru ca pe noi nu ne intereseaza acest fenomen tranzitoriu ci ne intereseaza stari ferme de ON sau OFF ale switch-ului.

 

Pentru a elimina efectele acestui fenomen de "bounce" al switch-ului se folosesc tehnici de ... "debouncing".

 

Una dintre tehnicile de "debouncing" - pe care am folosit-o si eu aici - este acea in care atunci cand sesizam ca switch-ul este activ, luam aminte de aceasta si asteptam cca 10 milisecunde pentru ca switch-ul sa se "linisteasca".

 

Intervalul de 10ms este unul scos din caciula, dar practic vorbind s-a observat ca majoritatea switch-urilor dupa 10 ms se "astampara". Iar 10ms (fiind sub cei 50ms amintiti anterior legat de reactia umana) vor permite in continuare senzatia ca butonul este apasat si reactia este "instantanee". E un compromis care "merge".

Dupa ce am asteptat cele 10ms, ne asteptam ca switch-ul s-a potolit cu bounce-ul si evaluam din nou starea sa. Este el ON sau OFF dupa aceste 10ms?

In cazul in care in continuare este ON atunci stim sigur ca este vorba de o actionare reala a switch-ului de catre user.

Daca switch-ul este OFF atunci avem doar un fenomen tranzitoriu si nu vom lua in seama acea activare initiala, pe care o vom considera o detectie eronata (zgomot etc).

 

Aceasta  tehnica de deboune nu este cea mai eficienta pentru ca pune controlerul sa stea degeaba pentru 10ms (functia delay_ms(10) asta face, controlerul se opreste la linia lui delay_ms(10) asteapta 10ms si apoi trece la linia imediat urmatoare).

 

Acest gen de tehnica se numeste ca este o tehnica blocanta ("blocking") care foloseste ineficient controlerul. Exista si alte tehnici non-blocante dar aceastea presupun cunostiinte despre intreruperi, care este un subiect avansat.

Dar pentru ce avem noi nevoie aceasta tehnica este mai mult decat suficienta.

 

Asadar o tehnica de debouncing a unei intrari (unde avem conectat un switch mecanic) arata cam asa in cod:

if (RA0_bit == 0) {    //daca switch-ul este ON (adica intrarea este 0)  delay_ms(10);        //asteapta 10ms ca sa ne asiguram ca switch-ul este stabil  if (RA0_bit == 0) {  //daca dupa aceste 10ms, switch-ul inca mai este ON avem o activare reala si ...     // executam in acest caz programul pentru ca switch-ul este ON)     // PROGRAM  }}

Urmeaza o explicatie cu privire la FUNCTII.

Editat de mars01
Link spre comentariu

Inainte de a vorbi de functii am sa termin totusi cu instructiunile decizionale uzuale.

 

Am discutat despre bucla while(expresie):

while (expresie este adevarata) {// executa program}

Care functioneaza cam asa:

 

Posted Image

 

Nota: Observati ca daca avem conditia falsa de la inceput, atunci nu se va trece prin bucla nici macar odata. Efectiv este ca si cum ce este in bucla  nu exista in program.

 

 

 

Mai exista o varianta de bucla while. Este bucla do ... while.

do {// executa program (se executa cel putin odata)} while (expresie este adevarata)

Posted Image

 

Observati diferenta. Chiar daca avem conditia falsa, totusi programul din bucla se executa odata pana ajunge sa fie evaluata conditia expresie. Daca acea conditie este adevarata atunci se revine la cuvantul cheie do si se executa programul din bucla pana conditia expresie devine falsa.

 

Cand expresie este falsa se continua executia programului de dupa cuvantul cheie while.

 

Un alt exemplu de intructiune decizionala (nu exista in toate limbajele de programare, unele cum este de ex Python nu o au) este instructiunea switch ... case.

 

Sintaxa este:

switch (var1) {  case expresie_constanta_1:    //instructiuni    break;  case expresie_constanta_2:    //instructiuni    break;  case expresie_constanta_3:    //instructiuni  case expresie_constanta_4:    //instructiuni    break;  default:    //instructiuni}

OBSERVATIE: luati aminte la instructiunea break. Cand aceasta instructiune este intalnita intr-o bucla de tipul: while, do ... while, for(;;) sau switch ... case se intrerupe totul, se cauta acolada de inchidere a buclei si se executa instructiunea de imediat dupa acea acolada.

Este un fel de a spune " hei, numai vreau sa termin de vazut filmul, vreau sa plec acum din sala de cinema, Star Wars ep 7 este un film prea tembel ca sa imi pierd timpul. Si pleci (eu nu am plecat ca mai eram cu prieteni :) )."

 

Instructiunea switch ... case este utila cand o variabila (cea din switch(var1) ) poate avea mai multe valori concrete si dorim sa facem ceva pentru fiecare caz.

Se evalueaza variabila din switch, si  se executa liniile de program din cazul (case) care este adevarat pana se intalneste instructiunea break. Daca nu este nici-un break pana la urmatorul case atunci se trece pur si simplu la acel case, si se executa liniile de program aferente pana se gaseste un break sau se ajunge la final.  

Luati aminte cazul expresie_constanta3. Nu are break asa ca dupa ce se executa liniile de program ale cazului expresie_constanta3, se trece automat la instructiunile cazului expresie_constanta4, care are un break asa ca se iese din instructiune.

 

Daca nici-unul dintre cazuri nu este adevarat atunci se executa ce este la default:. Cazul default: este optional si poate sa nu fie prezent.

 

Un exemplu:

char etajul = 3;switch (etajul) {  case 0:    //mergi direct la cabinet    break;  case 1:    //urci pe scari ca nu te dor picioarele prea tare    break;  case 2:    //faci miscare, este sanatoasa, urci pe scari    break;  case 3:  case 5:    //iei liftul ca scoti limba pana acolo    break;  default:    //intrebi portarul ce sa faci}

Nota: Luati aminte ca variabila etajul este de tip char. Variabila evaluata intr-o instructiune switch ... case poate fi si  tipul enumerare. Ce este tipul enumerare, alta data.

Variabila etajul, adica variabila evaluata in intructiunea switch, poate fi o variabila normala (nu constanta, faptul ca poate sa aiba mai multe valori contrazice ideea de constanta) dar nu poate fi de tipul FLOAT.

 

Stim ca, variabila etajul are valoarea 3. Se sare la case 3: vedem ca acolo nu avem nici-o instructiune dar nici break asa ca se executa instructiunile de la case 5. Care are un break deci executia se termina acolo, la acel break.

 

Intr-un final, "iei liftul ca scoti limba pana acolo" la etajul 3 (chiar daca se executa instructiunile de la case 5, totusi variabila etajul evaluata de switch are valoarea 3)

 

Urmeaza ciclul for (;;)

Va urma.

Editat de sofian
Link spre comentariu

Un alt tip de bucla este asa numitul ciclu "for".

 

Ciclul for este o bucla care permite executia controlata a unui block de instructiuni de un numar bine definit de ori. 

Tradus, stabilesti pana cand executi blocul de instructiuni dintre acoladele asociate instructiunii for.

 

Sintaxa este:

for (starea_initiala; conditie_de_indeplinit; modificare_iterator) {// instructiuni_de_executat}

Ceea ce numim stare_initiala este executat o singura data, la intrarea in bucla. Dupa aceea este evaluata conditie_de_indeplinit si daca e adevarata se executa blocul de instructiuni, daca e falsa se iese din bucla si se executa programul care este dupa acolada de inchidere. La fiecare trecere prin bucla, dupa ce se executa blocul de instructiuni dintre acolade, inainte de evaluarea conditie_de_indeplinit se executa si  modificare_iterator. Si tot asa pana conditie_de_indeplinit devine falsa.

 

Observati ca intre parantezele rotunde, cele 3 elemente sunt separate de punct si virgula ';'.

Conditie_de_indeplinit cat si modificare_iterator pot lipsi atat timp cat semnele punct si virgula raman. Evident in acest caz nu mai avem un comportament standard pentru bucla for. Practic, fara oricare dintre cele doua elemente abia amintite sau fara amandoua, bucla va fi infinita.

 

Cu alte cuvinte, cand dorim o bucla infinita putem scrie asa:

while(1){ //program}

sau

while (1 == 1){ //program}

sau

for(;;){ //program}

sau de ce nu,

for(i=1;;){ //program}

Am pus in ultima forma a ciclului for elementul stare_initiala (sub forma i = 1) dar e irelevant (nu conteaza) daca este prezent sau nu atata timp cat nu avem elementele conditie_de_indeplinit si modificare_iterator prezente. 

 

Exemplu:

int i;  //declaram variabila care tine minte numarul de treceri prin bucla forfor (i=0; i<10; i=i+1){ PORTA = 0b00000001; //bitul 0 din PORTA este setat (este 1 logic) delay_ms(100);      //se asteapta la aceasta linie 100 milisecunde PORTA = 0b00000000; //bitul 0 din PORTA este cleared (sters, este 0 logic) delay_ms(100);      //se asteapta la aceasta linie 100 milisecunde}

Bucata de program de mai sus (este doar o bucata, nu este programul tot. Pentru un program functional avem nevoie sa facem configurarea registrilor si evident avem nevoie de functia main() in care sa punem programul de executat) se explica asa:

 

A) mai intai declaram variabila impreuna cu tipul acesteia,

int i; 

care va avea functia de a tine cont de cate ori se executa blocul de instructiuni al ciclului for. Noi trebuie sa stim la ce folosim ciclul for si la cam cate iteratii (treceri prin bucla) sa ne asteptam.

 

Functie de aceasta guesstimate (estimare la modul general) ne alegem tipul variabilei.

Daca ne asteptam sa avem 10 treceri prin bucla atunci este clar ca ne este suficienta o variabila de tip unsigned char (ne aducem aminte ca unsigned char poate lua valori de la 0 la 255, atat se poate scrie pe cei 8biti asociati - deci ne incadram). Dar pentru simplitate am declarat variabila ca fiind de tip int.

Declarand variabila ca fiind int (ne aducem aminte ca declarea simpla int fara modificatorul signed sau unsigned in fata, inseamna ca de fapt am declarat totuna cu signed int) semnalam compilatorului sa aloce 16biti pentru aceasta variabila si fiindca admite si numere negative (este tip signed, nu?) atunci poate lua valori intre -32768 si 32767.

Overkill, nu-i asa? Dar cum avem suficienta memorie ... ne permitem acest lux. Daca la compilare am primi mesaje de memorie insuficienta atunci ne-am apuca sa optimizam fiecare variabila in parte ... Cateodata sa stiti ca nu prea este o chestie buna ca din prima sa optimizati la sange tipurile de variabile (adica memoria ocupata de ele prin ajustarea tipului lor). Mai bine sa fiti generosi la inceput cu tipurile de variabile decat sa va pomeniti cu bug-uri tampite datorate unui overflow al variabilei.

 

Va aduceti aminte? Cand numerele/datele stocate in variabila trec de maximul posibil de stocat in acea variabila, ele vor continua sa cresca dar pornind de la minimul posibil de stocat in variabila. La o variabila tip unsigned char care ia valori posibile de la 0 la 255, daca incercam sa scriem valoarea 256 in acea variabila tot ce vom obtine este ca in variabila vom avea valoarea 0 adica minimul posibil de stocat in ea.

 

Si daca incrementam variabila, valoarea creste pornind cu aceasta valoare noua de 0 cand noi ne asteptam ca valoarea sa fie de fapt peste 255 ... va dati seama ce varza iese mai departe.

Asa ca mai bine este ca la inceput sa fiti generosi cu variabilele. Nu prea generosi insa :), probabil ca tipul int este cel mai safe de folosit. Daca ati folosi tipul FLOAT peste tot ati avea surpriza rapid ca ramaneti fara memorie. Cum spuneam, ca sa folosim tipul FLOAT (adica numere reale, cu virgula) compilatorul foloseste niste smecherii, emulari care costa foarte mult in termeni de memorie si timp de procesor consumat.

 

Unele compilatoare accepta si declararea pe loc a variabilei de iteratie (va aduceti aminte? spuneam ca variabilele se declara intotdeauna la inceputul programului sau functiei unde sunt folosite) asa ca forma urmatoare se poate folosi:

for (int i=0; i<10; i=i+1){ PORTA = 0b00000001; //bitul 0 din PORTA este setat (este 1 logic) delay_ms(100);      //se asteapta la aceasta linie 100 milisecunde PORTA = 0b00000000; //bitul 0 din PORTA este cleared (sters, este 0 logic) delay_ms(100);      //se asteapta la aceasta linie 100 milisecunde}

Observati ca:

int i; 

a intrat intre parantezele rotunde atunci cand se enunta starea_initiala.

 

B) intre parantele rotunde avem scris:

i = 0

In acest fel noi spunem ca la intrarea in bucla for (ma rog, ciclul for sau cum vreti sa ii spuneti) primul lucru care se face este sa se initializeze variabila i cu valoarea 0 (si se face o singura data, doar la intrarea in acest ciclu).

 

C) urmatorul pas, se evalueaza daca: este i mai mic ca 10?

i < 10

Daca da (deci este adevarat), se executa ce este intre acoladele instructiunii for. Daca nu (deci este fals), se sare peste si se executa programul care este in continuarea acoladei de inchidere a instructiunii for

 

D) urmatorul pas, evident daca s-a executat blocul de instructiuni dintre acoladele lui for, (implicit imediat anterior, evaluarea conditiei a fost adevarata, nu?) atunci se executa elementul modificare_iterator:

i = i + 1

Adica, la valoarea variabilei i se adauga valoarea 1 si ce rezulta se va pune tot in variabila i. Daca variabila i a fost inainte 3 acum va fi 3+1 = 4.

 

E) urmatorul pas, se evalueaza din nou daca este adevarat:

i < 10

daca da, se executa din nou blocul de insturctiuni dintre acoladele lui for si tot asa pana cand la un moment dat i este 9, se executa blocul de instructiuni din bucla, se face i = i + 1 deci i devine 10 si cand se testeaza daca i  < 10, zbang, numai este mai mic ca 10, pentru ca este 10 ... si in acel moment se sare peste blocul de instructiuni din bucla si se executa ce este dupa acolada de inchidere a buclei for

 

Ce face bucata de program de mai sus?

Pai, pentru 10 interatii (de la 0 la 9 avem 10 iteratii, 10 treceri prin bucla) se scrie in registrul PORTA, pe bitul 0 (pinul fizic asociat in cazul 16F877A este chiar pinul 2) in mod succesiv valori de 1 si 0.

Intercalam functia delay_ms(100) dupa fiecare schimbare de stare (functie care provoaza o blocare temporara, o intarziere, a programului pentru un numar de milisecunde care este intre paranteze) pentru a face vizibila schimbarea starii pinului.

 

Practic vom avea o "clipocire" a unui eventual LED conectat pe pinul 2 hardware de un numar fix de ori, mai exact de fix 10 ori, durata cat este LED-ul ON si cat este OFF fiind data de acea valoare de intarziere gasita intre parantezele rotunde ale functiei delay_ms(x).

Ca si chestie, ceea ce face un ciclu for poate fi foarte bine facut si cu o bucla while.

De ex, cele doua sectiuni de cod de mai jos sunt echivalente:

for (int i = 0; i < 10; i = i +1) { //executa program}

face acelasi lucru cu:

int i = 0;while (i < 10){ //executa_program i = i + 1;}
Editat de mars01
Link spre comentariu

E timpul sa inchidem descrierea programului al 2 lea din postul #66.

Il repostez aici ca sa fac referire directa.

ATENTIE: este acelasi program care se gaseste in postul #66 de la pagina 5 a acestui topic.

void main() {  /*declaram o variabila numita timp, de tip unsigned int - deci poate lua valori intre 0 si 65535  Aceasta variabila va tine in ea numarul de zeci de milisecunde care va constitui perioada de temporizare.  Initializam aceasta variabila cu val 60000 care corespunde la 60000 cicluri de 10ms adica 10minute */  unsigned int timp = 60000;    /*Declaram doua constante ON si OFF ca sa ne fie mai usor de inteles programul*/  const unsigned char ON = 1;  const unsigned char OFF = 0;      /************** INITIALIZARE***************************/  CMCON = 0b00000111;  //dezactivam comparator  ADCON0 = 0;          //dezactivam ADC  CVRCON = 0;          //dezactivam referinta variabila de tensiune  TRISA = 0b00000001;  //facem INTRARE din pinul2 adica RA0; aici vom avea switch-ul conectat  TRISB = 0b00000000;  //facem IESIRE din pinul 33 adica RB0; aici vom avea LED-ul conectat  PORTB = 0b00000000;  //pornim cu LED-ul atasat pe pinul33 stins. Adica RB0 = LOW;  /**************SFARSIT INITIALIZARE********************/  /*urmeaza bucla infinita Cat timp 1 este egal cu 1(si 1 este egal cu 1 tot timpul) executa ce este intre acolade  */  while(1 == 1) {        if (RA0_bit == 0) {   //daca intrarea este LOW      delay_ms(10);       //asteptam 10 milisecunde      if (RA0_bit == 0) { //daca intrarea inca mai este LOW atunci chiar ca avem o apasare reala de switch        RB0_bit == ON;    //aprindem LED-ul                while (timp != 0) {     //cat timp timpul este diferit de zero          /*Urmatoarele doua conditii if(expresie) se folosesc pentru ca daca cumva in timpul de asteptare mai apasam          cumva pe buton, timpul trecut se reseteaza si temporizarea o ia de la inceput*/          if (RA0_bit == 0) {   //daca intrarea  este LOW            delay_ms(10);       //asteptam 10 milisecunde            if (RA0_bit == 0) { //daca intrarea inca mai este LOW atunci chiar ca avem o apasare reala de switch              timp =  60000;     //resetam timpul la valoarea initiala, 60000 * 10ms = 600000ms = 600sec = 10minute            }          }          //variabila timp va scadea cu o unitate pentru fiecare 100ms care trec          timp = timp - 1;      // sau se poate scrie timp--;          delay_ms(10);         //aceasta este intarzierea esentiala pentru cronometrarea timpului cat LED-ul sta aprins                  }        //timpul cat LED-ul trebuie sa stea ON a trecut si acum facem LED-ul OFF        RB0_bit == OFF;      }    }  }}

Recapitulam:

 

La linia 1 avem functia principala void main(), fara de care un program nu poate functiona. Ca si comparatie, aceasta functie speciala este cum este panza pentru pictor. Pictorul are nevoie de panza ca sa poata picta, este suprafata pe care picteaza.

Functia acesta are un bloc de instructiuni pe care il executa. Acest bloc de instructiuni incepe chiar de la linia 1 odata cu deschiderea acoladei si se incheie la linia 53 cu inchiderea acoladei asociate.

 

De la linia 2 la linia 4 avem un bloc de comentarii.

Comentariile sunt o linie sau mai multe de text cu rolul de a explica liniile de program aflate in proximitate.

Aceste linii de text nu sunt luate in considerare de compilator.

O linie de comentarii incepe intotdeuana cu dublu slash '//' si se incheie trecand la urmatoarea linie prin apasarea tastei CR (Enter)

Un bloc de comentarii poate fi scris incepand fiecare linie cu '//' sau mai simplu, se incepe blocul de comentarii cu '/*' (slash steluta) si se incheie blocul de comentarii cu '*/' (steluta slash). In acest caz tasta CR (Enter) numai face trecerea de la un comentariu la linie de program si este necesara prezenta secventei de simboluri '*/' pentru a inchide blocul de comentarii.

 

La linia 5 declaram o variabila, cu numele timp, de tip unsigned int (ia valori numai pozitive pentru ca este unsigned, si ia valori care se pot scrie pe 16bit adica de la 0 la 65535 pentru ca este de tipul INT) careia ii atribuim valoarea de 60000. Valoarea de 60000 apare deoarece eu stiu de la inceput ce vreau sa fac. Vreau ca o variabila sa stocheze timpul care va scadea in unitati de cate 10ms. Si cum din etapa de design am stabilit ca vreau sa am o temporizare de 10minute, daca vrem sa vedem cate unitati de 10ms se gasesc in 10 minute, vom vedea ca sunt fix 60000 (60000 * 10ms = 600 000ms = 600sec = 10min).

 

La liniile 8 si 9 declaram doua variabile care le facem constante cu ajutorul cuvantului cheie const. Dupa cum se observa odata cu declararea lor si a tipului lor facem si initializarea.

De ce?

Deoarece asa se procedeaza cu constantele, li se da o valoare care ramane aceasi pe tot parcursul programului. Daca se incearca schimbarea lor, la compilare vor fi erori. (In realitate se poate modifica valoarea lor dar aceasta presupune folosirea suplimentara a unor variabile mai deosebite denumite pointeri, subiect avansat)

Motivul aparatiilor lor, adica a acestor constante, este  din comoditate. Este mai usor sa scrii peste tot ON cand vrei sa scrii 1 si sa scrii OFF cand vrei sa scrii 0. Face mai usor de scris programul.

Alternatv, pentru a obtine un efect relativ similar, se foloseste directiva de preprocesor, #define.

 

Intre liniile 13 si 20 lucram cu registri. Practic cream contextul in care programul se va executa. Registrii sunt interfata noastra intre software si hardware. In acest caz numim registru o variabila speciala care este pe 8bit si in care fiecare bit  are o semnificatie speciala actionand ca un sui generis intrerupator pentru diverse module. O descriere exhaustiva a fiecarui registru cat si a fiecarui bit in parte se face in foaia tehnica a controlerului, denumita de aici in colo, datasheet.

 

Un alt fel de a descrie ce facem intre liniile 13 si 20 este ca acum configuram modulele hardware ale controlerului, dezactivand pe cele nenecesare si activand pe acelea necesare cat si configurandu-le pentru a face exact ce intentionam.

Toata aceasta munca se face deoarece intr-un cip foarte mic cum este un microcontroler se inghesuie o gramada de functii expuse mediului printr-un numar limitat de pini fizici, hardware.

Aproape toti pinii unui controler sunt organizati in grupe de cate 8 bucati, grupe denumite generic porturi.

Porturile au o eticheta care pleaca de la litera A mai departe.

Nu este obligatorie prezenta porturilor in ordine. Cu alte cuvinte un controller poate avea un port B (cu 8 pini sau mai putini, nu este obligatoriu ca un port sa aiba 8 pini dar 8 pini sunt maximul pentru un controller pe 8biti), un port C, dar sa lipseasca de ex portul A.

 

La linia 26 este prezenta instruciunea while (1 == 1) care este de fapt o bucla infinita care va executa la infinit (pana primeste un semnal de reset sau se intrerupe alimentarea cu energie electrica) toate instructiunile care se gasesc intre acolada deschisa tot pe linia 26 si acolada de inchidere de la linia 52.

 

La liniile 28, 29 si 30 se aplica o tehnica elementara de debouncing prin care se urmareste ca daca variabila RA0_bit devine ON (adica switch-ul prezent pe pinul hardware asociat bitului 0 din registrul PORTA este actionat) se asteapta 10ms si se evalueaza din nou starea lui RA0_bit. Daca in continuare starea este ON atunci avem o apasare reala a switch-ului.

Aici apare o imbricare de acolade si de aceea este utila folosirea tastei TAB pentru a formata codul si a il face usor de citit. Fiecare acoloda de inchidere se gaseste la acelasi "nivel" construit din tab-uri cu instructiunea care deschide acolada corespondenta.

 

Variabila RA0_bit este specifica compilatorului mikroC. Undeva in spate se face asocierea dintre RA0_bit si bitul 0 din PORTA.

In caz ca va intrebati de unde naiba aflati de aceste asocieri, pentru aceasta este bun user manual-ul compilatorului.

Daca stiti sa cititi un fisier tip c, toate aceste informatii se gasesc intr-un folder din directorul de instalare al compilatorului (in cazul meu: C:UsersPublicDocumentsMikroelektronikamikroC PRO for PICDefs), fisierul avand forma nume_controller.c (ex: 16F877A.c)

SI pentru ca RA0 a fost setat anterior ca INTRARE, valoarea lui RA0_bit va exprima starea pinului asociat, ce nivel digital are pe intrare.

 

In cazul in care (si numai atunci) avem o activare reala a switch-ului detectata anterior, se executa linia 31:

RB0_bit = ON;

Pentru ca RB0 a fost declarat anterior (in etapa de configurare) ca si IESIRE, in acest caz modificarea in program a RB0_bit va genera schimbare anivelului logic pe pinul asociat lui RB0 (daca nu ma insel, este pinul 33). Prin urmare scrierea valorii 1 in RB0_bit (sau ON ca e totuna din cauza ca ne-am "jucat" cu constantele mai devreme) este echivalenta cu aprinderea LED-ului care este conectat intre pinul RB0 si GND.

 

ATENTIE:

 

In program am scris :

RB0_bit == ON;

Observati dublul egal. Este o greseala flagranta pentru ca modul acesta de a scrie reprezinta o evaluare de genul: "este RB0_bit egal cu 1?" daca da, rezultatul este 1 daca este fals rezultatul este 0.

Acest gen de greseli pot genera functionari defectuoase ale programului, foarte greu de observat mai ales cand avem multe linii de cod. Asa ca multa atentie cand folostii operatorul de testare '==' versus operatorul de asignare (atribuire a unei valori) '='.

 

Urmeaza etapa de temporizare ...

Editat de mars01
Link spre comentariu

La linia 33 incepe etapa de temporizare:

 while (timp != 0)

Bucla while mentionata va executa blocul de instructiuni cuprins intre acolada de deschidere gasita tot pe linia 33 si acolada de inchidere gasita pe linia 46. Aceasta bucla se va executa cat timp variabila cu numele timp este diferita de zero. 

Cum noi am initializat variabila timp cu 60000 odata cu declararea acesteia, ne asteptam ca cumva variabila sa scada si la un moment dat sa devina 0 (zero). Altfel ne confruntam cu o bucla infinita iar noi nu vrem o temporizare infinita, nu?

 

Practic aceasta bucla va stabili cat timp LED-ul legat la RB0 este ON, activ (deoarece imediat la linia urmatoare acoladei de inchidere a acestei bucle while,  adica la linia 48, LED-ul este facut OFF, este stins).

 

Liniile 36, 37 si 38 fac din nou functia de "debouncing" (eliminarea influentelor oscilatiilor elastice mecanice care apar in switch la schimbarea starii sale: apar si cand este apasat dar apar si cand este relaxat, la revenire) si in cazul in care se detecteaza o apasare reala a switch-ului, timpul care a trecut pana in acel moment este resetat la valoarea initiala, adica valoarea 60000.

 

La linia 39 se pune in variabila timp din nou valoarea initiala de 60000. Este exact ce am discutat mai sus. Aecasta linie este executata daca si numai daca switch-ul este ON si dupa 10ms la a 2 a evaluare este inca ON deci avem o apasare reala de switch.

 

Liniile 40 si 41 reprezinta acoladele de inchidere a celor 2 intrusctiuni if din sectiunea anterioara de "debouncing". Atentie felul cum sunt imbricate este marcat vizual si prin tab-uri pentru a face mai usor inteligibila citirea acestui program.

 

Linia 43 adica:

timp = timp - 1;

are rolul de contor invers. Cu fiecare trecere prin aceasta bucla (bucla curenta este cea inceputa la linia 33 si este bucla cu rol de temporizare) aceata variabila scade cu 1. Iar noi vrem ca la fiecare trecere prin bucla sa se consume fix 10ms. Cand aceasta variabila va ajunge la 0, testarea in bucla while curenta va esua s ise va trece mai departe, practic temporizare a expirat. 

 

La linia 44 practic avem acel delay de 10ms care corespunde pentru fiecare unitate a variabilei timp

 

La linia 48 practic temporizarea noastra s-a terminat si vom stinge LED-ul cu comanda:

RB0_bit = OFF;

Din nou, in programul de la postul 66, chiar si aici introdusesem acel bug in care am scris:

RB0_bit == OFF; 

Ceea ce este gresit; remarcati dublul egal care nu are ce cauta aici. Corect este:

RB0_bit = OFF;

Cu un sigur egal, operatorul de asignare.

 

 

ATENTIE: bucla while (timp != 0) { ... }  nu se va mai executa odata ce timp = 0. Si variabila timp ramane .... zero la sfarsitul primei bucle. Si dupa aceea. Prin urmare programul va functiona corect odata si numai odata. Pentru ca la urmatoarea trecere prin while (1 == 1) cand se evalueaza prima oara   while (timp != 0), cum timp = 0 din etapa anterioara ... practic programul va fi inutil.

 

Deci mai avem un bug.

 

Practic dupa ce se termina bucla while (timp != 0) { ... }  noi trebuie sa facem din nou variabila timp = 60000.

 

Cu alte cuvinte, programul corect are nevoie sa fie asa, introducand o noua linie de program, linia 49:

void main() {  /*declaram o variabila numita timp, de tip unsigned int - deci poate lua valori intre 0 si 65535  Aceasta variabila va tine in ea numarul de zeci de milisecunde care va constitui perioada de temporizare.  Initializam aceasta variabila cu val 60000 care corespunde la 60000 cicluri de 10ms adica 10minute */  unsigned int timp = 60000;    /*Declaram doua constante ON si OFF ca sa ne fie mai usor de inteles programul*/  const unsigned char ON = 1;  const unsigned char OFF = 0;      /************** INITIALIZARE***************************/  CMCON = 0b00000111;  //dezactivam comparator  ADCON0 = 0;          //dezactivam ADC  CVRCON = 0;          //dezactivam referinta variabila de tensiune   TRISA = 0b00000001;  //facem INTRARE din pinul2 adica RA0; aici vom avea switch-ul conectat   TRISB = 0b00000000;  //facem IESIRE din pinul 33 adica RB0; aici vom avea LED-ul conectat  PORTB = 0b00000000;  //pornim cu LED-ul atasat pe pinul33 stins. Adica RB0 = LOW;  /**************SFARSIT INITIALIZARE********************/   /*urmeaza bucla infinita Cat timp 1 este egal cu 1(si 1 este egal cu 1 tot timpul) executa ce este intre acolade  */  while(1 == 1) {        if (RA0_bit == 0) {   //daca intrarea este LOW      delay_ms(10);       //asteptam 10 milisecunde      if (RA0_bit == 0) { //daca intrarea inca mai este LOW atunci chiar ca avem o apasare reala de switch        RB0_bit = ON;    //aprindem LED-ul                while (timp != 0) {     //cat timp timpul este diferit de zero          /*Urmatoarele doua conditii if(expresie) se folosesc pentru ca daca cumva in timpul de asteptare mai apasam          cumva pe buton, timpul trecut se reseteaza si temporizarea o ia de la inceput*/          if (RA0_bit == 0) {   //daca intrarea  este LOW            delay_ms(10);       //asteptam 10 milisecunde            if (RA0_bit == 0) { //daca intrarea inca mai este LOW atunci chiar ca avem o apasare reala de switch              timp =  60000;     //resetam timpul la valoarea initiala, 60000 * 10ms = 600000ms = 600sec = 10minute            }          }          //variabila timp va scadea cu o unitate pentru fiecare 100ms care trec          timp = timp - 1;      // sau se poate scrie timp--;          delay_ms(10);         //aceasta este intarzierea esentiala pentru cronometrarea timpului cat LED-ul sta aprins                  }        //timpul cat LED-ul trebuie sa stea ON a trecut si acum facem LED-ul OFF        RB0_bit = OFF;        timp = 60000;      }    }   }}

Luati aminte la linia 49, variabila timp isi ia din nou valoarea initiala de 60000 pregatindu-se astfel pentru un nou ciclu posibil de temporizare. Spun posibil pentru ca ciclul de temporizare se declanseaza numai daca user-ul apasa pe buton (sau un senzor PIR detecteaza radiatia infrarosie caracteristica unui organism viu sau etc etc).

 

Si am terminat cu explicarea programului din postul #66 din pagina 5 a acestui topic.

 

Intrebari? Inainte de a face un nou program?

 

 

 

LE: Observati ca:

PORTB = 0b00000000;

este totuna cu:

RB0_bit = 0;

iar

PORTB = 0b00000001;

face acelasi lucru ca si:

RB0_bit = 1;

Cum vi se pare mai usor? Normal ca folosind RB0_bit, nu? In acest fel unele compilatoare ne ajuta mai mult decat altele. Unele compilatoare se cheama ca sunt mai "prietenoase" decat altele. Aceasta evident cand ai de-a face cu un singur bit. dar daca vrei sa modifici simultan cativa biti atunci sintaxa:

PORTX = valoare_in_binar_sau_hexa_sau_cum_va_este_mai_usor;

este mai utila, se executa in simultaneitate pentru toti cei 8 biti.

Editat de mars01
Link spre comentariu

 

 

Cu fiecare trecere prin aceasta bucla (bucla curenta este cea inceputa la linia 33 si este bucla cu rol de temporizare) aceata variabila scade cu 1. Iar noi vrem ca la fiecare trecere prin bucla sa se consume fix 10ms. Cand aceasta variabila va ajunge la 0, testarea in bucla while curenta va esua s ise va trece mai departe, practic temporizare a expirat. 

 

Cat dureaza efectuarea unei operatii ?

 

De la linia 33 si pana la 44 sunt 7 operatii care se executa intr-o anumita unitate de timp (presupun) ; atunci , pe langa cele 10ms care se scad din variabila "timp" mai trece un timp ce corespunde timpului necesar efectuarii operatiilor de la linia 33 la 44 .  Probabil ca este vorba de marimi de ordinul micro secundelor dar presupunand totusi ca s-ar dori o precizie de ceas atomic ... cum se pot compensa aceste intarzieri ?

 

Sau gresesc eu pe undeva ? 

Editat de iuli09
Link spre comentariu

:)

 

Ca sa fiu sincer eram sigur ca cineva o sa remarce ca am punctat acolo folosind "fix 10ms". Pentr uviata de zi cu zi, cele 10.01 milisecunde cat poate dureaza in mod real executia, se poate spune fara probleme ca sunt "10 ms".

 

In primul rand o precizie atomica nu poate fi obtinuta cu un microcontroler. Se poate obtine o precizie mare dar presupune complicatii ca folosirea unui oscilator termostatat, termostatarea controlerului in sine, o sursa de tensiune fara variatii etc.

 

Dar ca sa raspund in spiritul intrebarii tale, pentru a face un delay exact de10ms ai nevoie sa "numeri instructiunile in limbaj de asamblare". Asta este, nu ai ce face. Te uti in listingul assembler si numeri instructiunile.

 

Odata ce stii numarul de instructiuni, stiind si faptul ca la controlerele PIC (arhitectura Harvard) tciclu (timpul de executie al unei instructiuni) este de 4 ori Tosc, afli exat cat dureaza o anumita secventa de instructiuni.

Ulterior ajustezi delay-ul incat per ansamblu sa obtii acele 10milisecunde.

 

Pentru un controler PIC cu oscilator care lucreaza la sa zicem 20MHz, timpul de executie a unei instructiuni (sunt exceptii cu instructiunile de decizie, jump) este:

Tosc = 1 / FoscTosc = 1 / 20 000 000Tosc = 0.05usTcycle = 4 * Tosc = 0.2us
Editat de mars01
Link spre comentariu

 

 

[*]timp = timp - 1; // sau se poate scrie timp--;

[*]delay_ms(10); //aceasta este intarzierea esentiala pentru cronometrarea timpului cat LED-ul sta aprins

 

Inteleg acum aceste instructiuni dar mi se pare putin ciudata, (parca nenaturala) sintaxa . 

 

Daca dupa o operatie de incrementare/decrementare urmeaza o linie "delay" ea se va referi la perioada de timp in care se va efectua un ciclu de incrementare/decrementare?  

 

Daca asa stau lucrurile , ce se intampla daca linia "delay" lipseste ? se va raporta microcontrolerul la "pasul" ceasului intern ? 

Editat de iuli09
Link spre comentariu

La un oscilator de 20 MHz o instructiune dureaza 200 ns. Functia/macroul delay_ms nu face decat sa numere de 5000 de ori pentru fiecare milisecunda primita ca parametru.Daca vrei 1000 ms delay, ai mai multe variante:- apelezi delay_ms(1000).- scrii de 100 de ori delay_ms(10).- scrii o bucla care sa numere pentru tine cele 100 de delay_ms(10).

Link spre comentariu

Am inteles (cred) cum functioneaza functia "delay" dar ma intrebam , avand in vedere softul prezentat mai sus de mars01, daca asa este sintaxa pentru efectuarea unei incrementari/decrementari careia i se aloca o anumita unitate de timp per ciclu . 

 

Adica :

 

timp = timp - 1;

 

...dupa care urmeaza functia delay_ms(valoare) care aloca un anumit interval de timp in care sa se efectueze incrementarea/decrementarea respectiva ; in cazul nostru 10 ms . Stiu ca in cele 10 ms controlerul nu sta efectiv ci numara ... 

 

Inca nu "vorbesc" limba controlerului asa ca incerc sa intelg niste chestii raportandu-ma la niste repere care nu sunt , de cele mai multe ori , cele mai potrivite dar incerc , dupa puteri , sa se inteleaga ceea ce vreau sa spun sau sau aflu .

 

Multumesc pentru ajutor !

Link spre comentariu

Pentru incrementare/decrementare nu trebuie alocat timp/delay in program.Folosesti delay doar daca ai nevoie sau daca anumite operatii impun asta(de exemplu initializarea unui display, uramata de o alta instructiune pentru acel display, impune un delay dupa initializare pe care il da producatorul acelui display)

Link spre comentariu

Creează un cont sau autentifică-te pentru a adăuga comentariu

Trebuie să fi un membru pentru a putea lăsa un comentariu.

Creează un cont

Înregistrează-te pentru un nou cont în comunitatea nostră. Este simplu!

Înregistrează un nou cont

Autentificare

Ai deja un cont? Autentifică-te aici.

Autentifică-te acum
×
×
  • Creează nouă...

Informații Importante

Am plasat cookie-uri pe dispozitivul tău pentru a îmbunătății navigarea pe acest site. Poți modifica setările cookie, altfel considerăm că ești de acord să continui.Termeni de Utilizare si Ghidări