mars01 Postat Martie 29, 2016 Partajează Postat Martie 29, 2016 (editat) @vezi_muti multumesc pentru aprecieri! In ceea ce priveste varianta ta vreau sa fac urmatoarele precizari: Daca cititi mai mult pe Internet, pe forumurile unde se ofera cod open source, o sa vedeti ca foarte multi dintre cei care posteaza snippet-uri de cod (bucati de cod) se scuza spunand ca "codul oferit nu este elegant". Diferenta intre codul oferit de mine si cel oferit de @vezi_muti este aceea ca, codul meu este "facut pe genunchi - vorba vine" iar codul lui @vezi_muti este un cod elegant. Doar ca pentru a ajunge la un cod elegant se presupune ca stii bine ce faci si de multe ori este mai greu inteligibil pentru cineva aflat la inceput. Asadar, pentru un incepator, este mult mai usor de citit un cod scris "pas cu pas" decat un cod elegant care se foloseste de operatii mai exotice, multiple, la nivel de bit. Si asa este destul de greu uneori sa vezi logica cand se lucreaza cu biti, orice combinari ale acestora multiplica exponential dificultatea de a intelege ce se doreste a se face (vezi modulatia PWM stocata la nivel de biti de ex). In general, majoritatea programtorilor fac un fel de schita de ansamblu, un cod scris usor si nu elegant, doar pentru a verifica conceptul iar apoi se trece la rafinare, eficientizare. Este ca si cu un sculptor. Mai intai se uita la bucata de marmura, isi imagineaza cum va arata rezultatul final, si se apuca de munca mai intai cu o dalta groasa si indeparteaza in mod brut bucati de marmura ca apoi sa treaca din ce in ce la dalti mai fine pentru a crea detaliile si frumusetea care incanta ochii admiratorului. Mai intai, colegul @vezi_muti prin folosirea de operatii la nivel de biti (numai shiftari) a adus o eficienta maxima pentru ca s-a folosit de operatii care ii sunt "naturale" procesorului, pe care procesorul le face intr-o singura operatie. Pe scurt insa, operatia: LATC = (LATC << 1) | (LATC >> 7); se poate sparge in trei: shiftarea la stanga: LATC <<1 , shiftarea la dreapta LATC >> 7 si operatia OR dintre cele doua mai sus. Dpdv al logicii programului operatia OR, in acest caz particular, are o contributie doar la terminarea unui ciclu de deplasari la stanga. Intre cele doua operatii este o operatie tip OR. Ce face LATC << 1 este sa aprinda unul din LED-urile conectate pe portul C si numai unul pe o pozitie crescatoare cu 1 mereu catre stanga. Avand in vedere ca doar un bit este 1 la un moment dat ne putem da seama ca singura data cand rezutlatul LATC >> 7 va genera un bit 1 in byte-ul rezutlat este atunci cand bitul 7 este 1 adica practic la finalul shitarii la stanga. Ce aduce este, ca practic reseteaza la finalul trecerii prin cei 8 biti (prin adaugarea unei valori 1 logic pe pozitia bit0 cu ajutorul operatiei OR) in registrul LATC si aprinde LED-ul de pe bitul 0 creand astfel premisele unui ciclu nou. Daca nu ar fi aceasta operatie LATC >> 7 atunci dupa un singur ciclu de aprinderi succcesive, LED-urile ar ramane stinse. Cum spuneam este mai greu de inteles aceasta varianta daca nu esti familiar cat de cat cu aceste operatii. PS: cum puteti vedea in programele din postul anterior, se pot face efecte interesante de lumini cu LED-uri folosindu-ne doar de aceste operatii pe biti. Combinatii intre aceste operatii pe biti, asa cum arata de altfel si colegul @vezi_muti, pot aduce eficienta maxima si/sau pot crea efecte complexe pentru stelute de Craciun (ca tot sunt oamenii interesati de asa ceva in preajma sarbatorilor). Sau acum ca se aproprie Pastele, puteti "anima" oua cu lumini dinamice :) Editat Martie 29, 2016 de mars01 Link spre comentariu
Vizitator Viorel Paduraru Postat Iunie 7, 2016 Partajează Postat Iunie 7, 2016 Ca sa stim de unde plecam. Un microcontroler este un procesor la care este atasata ceva memorie flash pentru stocare program, memorie RAM pentru executie program si periferice. Analogic vorbind memoria flash la microcontrolere este cum este HDD-ul la PC iar memoria RAM la microcontrolere este la fel ca memoria RAM la PC-uri. Simplist vorbind, la pornirea microcontrolerului, se proceseaza programul din memoria flash linie dupa linie, se incarca datele din memoria flash in memoria RAM, procesorul din controler va procesa datele din RAM si va salva datele in alte locatii din memoria RAM. Ulterior aceste date vor fi folosite pentru a face o schimbare in periferice. Perifericele sunt "interfata" cu lumea exterioara. Toate procesele din controler au un singur scop: ca la finalul operatiilor sa se execute o schimbare in lumea exterioara. Ca schimbi starea unui pin dintr-un port ("portul" este o forma de organizare hardware dar si software a pinilor), sau trimiti o informatie pe un port de comunicatie sau etc se numeste ca te interfatezi cu lumea exterioara si schimbi ceva. Asadar. Simplist vorbind. Ai o parte de procesare a datelor. Adica pornind de la o situatie data, preiei informatie (poate de la lumea exterioara prin pinii controlerului sau poate intern de la un timer de ex samd), faci ceva cu informatia si apoi intr-un final controlezi perifericele ca sa faca ceva. Un concept esental este cel de "registru". Registrii sunt folositi ca sa controleze perifericele. Cu registrii configurezi perifericele, dar cu registrii trimiti si informatie catre lumea exterioara, sau dupa caz, in registri se stocheaza prima oara datele provenite din lumea exterioara, reala. Registrii sunt chestii hardware dar aceasi denumire de registri se foloseste si pentru "variabilele" (mai corect spus adrese speciale in memorie) prin care se seteaza din software acei registri. Poti sa iti imaginezi un registru ca o colectie de intrerupatoare. Pentru procesoarele uzuale in lumea "hobby" adica cele pe 8biti, registrii sunt o "colectie de 8 intrerupatoare". O grupa de 8 intrerupatoare are un nume, registrul cutare sau cutare. Fiecare "intrerupator" se poate actiona individual, independent de celalalt (ma rog, e o discutie si pe aici dar sa o lasam asa pe moment). Fiecare "intrerupator" are o anume functie, "face ceva" unui periferic. Sunt registri care actioneaza exlusiv in controler dar nu vorbim despre aceia acum. Spre ex luam un controler cum ar fi 16F877A pentru ca este plin net-ul de exemple cu acesta. Sa zicem ca vrem sa aprindem un LED si sa stingem LED-ul la un interval timp de sa zicem 500ms, asa-numit-ul "licurici". Bun. Cum facem aceasta? Tradus noi vrem ca un pin al controlerului sa isi schimbe starea de la nivel LOW (adica "zero" logic =~ 0V) la nivel HIGH (adica "unu" logic =~ 5V) in urmatoarea secventa: pin controler HIGH (~5Volti)pauza 500mspin controler LOW (~0Volti)pauza 500ms si tot asa la infinit. Faptul ca noi schimbam starea pin-ului intre HIGH si LOW inseamna ca acel pin va functiona in regim DIGITAL. Ce facem mai departe? Citim datasheet-ul pentru PIC16F877A. Search Google "16F877A pdf", download datasheet. la mine primul link este acesta de la Sparkfun, dar este OK: https://cdn.sparkfun.com/datasheets/Components/General%20IC/PIC16F877A.pdf Ne alegem un pin al controlerului care sa aprinda LED-ul, citim datasheet-ul si alegem un pin care este parte al unui asa numit "port". In cazul nostru alegem pinul 2 care este parte a PORTA. Toti pinii controlerului (cu cateva exceptii) sunt grupati in aceste porturi. In cazul 16F877A sunt grupati in porturile: A, B, C, E. Exceptiile sunt pinii de alimentare si pinii unde se conecteaza oscilatorul extern. La alte controlere doar pinii de alimentare sunt exceptii si restul pinilor sunt toti organizati in porturi. Buuun, ne alegem sa zicem pinul 2. Este notat cu RA0/AN0. Aceasta inseamna ca pinul are functii multiple. Uzual la controlere este sa poti face mai multe chestii pe acelasi pin dar nu simultan. Functiile pinului se configureaza din ... registrii asociati. RA0 arata ca pinul este parte din portul A, poate fi configurat ca intrare sau iesire digitala (adica reactioneaza doar la HIGH si LOW cand este intrare si ofera nivele de HIGH si LOW cand este configurat ca iesire) si este controlat digital de bit-ul ("intrerupatorul") 0 din registrul PORTA. Atentie: PORTA este un cuvant cheie si poate fi folosit ca atare in program. Se scrie legat, PORTA. AN0 arata ca pinul poate fi configurat si ca intrare analogica. Mai concret poate fi configurat ca intrare ADC (convertor analogic digital) adica intrarea accepta o tensiune analogica cu orice valoare intre 0 volti si 5volti. Noi vom configura pinul 2 ca si iesire digitala unde vom conecta LED-ul. Asadar deja stim ca vom lucra cu RA0 si vom trebui sa dezactivam AN0 de pe acel pin. Ne uitam dupa I/O ports (input / output ports), vad ca sunt la pagina 41. La pagina 43, tabelul 4-2 vedem ca portul A are asociati un numar de registri. Daca te uiti la poza cu pinii controlerului pe la inceputul datasheet-ului o sa vezi ca portul A are asociati pinii de la 2 la 7; nu sunt decat 5 pini in portul asta si nu 8 cum am zis dar e uzual sa mai gasesti porturi "incomplete" din cauza limitarii date de numarul de pini - sunt standarde la numarul de pini per capsula. Registrii asociati pentru portul A sunt: PORTA, TRISA, CMCON, CVRCON, ADCON1. Citesti despre fiecare registru in parte si ce face fiecare "intrerupator" mai corect spus, bit, din fiecare din registrii de mai sus. Mai pe scurt, dupa ce citesti o sa te lamuresti ca din: - PORTA, bit 0, vei schimba starea pinului 2 (pe care l-am ales noi sa il folosim) - TRISA, bit 0, vei configura pinul 2 ca si iesire digitala. - CMCON - in acest registru trebuie dezactivezi comparatorul (periferic care este conectat se pare si el la acest pin cu ceva) - ADCON1 - in acest registru trebuie sa deazctivam perifericul de intrare analogica pentru pinul 2 Va urma. frumos explicat Link spre comentariu
mars01 Postat Iunie 7, 2016 Partajează Postat Iunie 7, 2016 Multumesc! Astazi m-am intors in Romania din Arabia (doar ca sa vad ca iar au castigat comunistii alegerile si am asa o lehamite ...) asa ca in cateva zile am sa incep sa mai scriu pe aici. Link spre comentariu
Florian Ciobanu Postat Iunie 10, 2016 Partajează Postat Iunie 10, 2016 Aștept cu mare interes continuarea, foarte interesanta prezentareaCând treci prin pitești sa dai de veste, fac cinste cu o cafea sau bere,după caz Link spre comentariu
Vizitator tnt10 Postat Iunie 11, 2016 Partajează Postat Iunie 11, 2016 Excelent topicul, multumiri pentru efortul depus si timpul alocat. Link spre comentariu
allez001 Postat Iunie 18, 2016 Partajează Postat Iunie 18, 2016 multumesc mult pentru tot efortul depus !!! Link spre comentariu
mars01 Postat Iunie 28, 2016 Partajează Postat Iunie 28, 2016 (editat) Dupa o perioada mai indelungata de pauza e timpul sa mai adaugam ceva nou in acest topic. Dar mai intai un rezumat: - am invatat cum sa cream un proiect in IDE-ul (si compilatorul) mikroC - am invatat ca in procesul de a crea un fisier binar cu extensia .HEX, fisier care se programeaza intr-un controller PIC cu ajutorul unui programator cum ar fi PicKit2 (sau PicKit3), sunt mai multe etape. Etapa A) Setam cuvintele de configurare adica mai exact registrii CONFIG. In compilatorul mikroC acest lucru se face grafic si nu necesita introducerea de linii de cod ca in alte compilatoare. Prin setarea registrilor CONFIG se configureaza lucruri importante cum ar fi oscilatorul (daca este folosit cel intern - unde e cazul, sau daca unul extern), modulul WatchDog, etc Pentru detalii se citeste in datasheet-ul controller-ului folosit. Etapa B) Configuram registrii necesari pentru ce avem de facut. Toate modulele din controller, inclusiv porturile fizice (adica gruparile de pini) au asociati asa numitii registri. Registrii sunt variabile speciale de cate 8 biti fiecare, unii se pot si citi si scrie dar altii se pot numai scrie, acolo unde este cazul se poate scrie un 1 sau un 0 pe fiecare bit in parte. Fiecare bit dintr-un registru poate avea functia de control pentru ceva dintr-un modul. Sau poate oferi o informatie cu privire la starea acelui modul, periferic. Etapa C) Incepem sa scriem codul care se va gasi grupat intr-o functie principala numita void main(void) fara de care nu se poate. De aici incepe programul sa ruleze. Intotdeauna. Mai exista bucatele de cod care se executa inainte de functia main, dar aceasta este bucataria interna a compilatorului care trebuie sa aiba grija ca variabilele sa fie intotdeauna initializate si alte chestii. Dar acest lucru nu ne intereseaza pe noi, la acest nivel. In afara functei main(), se mai pot crea si alte functii dupa bunul nostru plac. Etapa D) Compilam codul scris folosind un buton special din programul tip IDE (mikroC, MPLAX) sau ruland o linie de comanda la nivel de shell. In acest fel rezulta fisierul .HEX. ************************* Observatie: exista intotdeauna un compromis cu privire la cat de multe functii putem crea intr-un program. Cu cat sunt mai multe (si mai mici) este mai bine pentru ca in primul rand codul este mai reutilizabil (apelam o functie si numai trebuie sa scrim din nou acea bucatica de cod). Este bine ca functiile sa fie mai mici deoarece are legatura cu arhitectura interna a microcontrollerelor PIC (memory banks, e bine ca functiile sa nu fie mai mari de un memory bank). Dar nu putem avea oricate functii deoarece in primul rand memoria RAM este limitata si functiile, doar pentru ca le folosim, consuma ceva RAM suplimentar. In plus, fiecare familie de PIC-uri are un numar limita de cate functii pot fi apelate una in alta (adica in functia 1 avem cateva functii 2 si in fiecare functie 2 sunt ceateva functii 3 si tot asa). Daca nu ma insele arhitectura basic (PIC-uri 10F si unele 12F si/sau 16F) are o stiva hardware (limita de functii apelate una in alta) de 2. Arhitectura Mid-range Enhanced (unele 12F si unele 16F) are o stiva hardware de 8. PIC-urile 18F au o limita mai mare (16 sau 32 numai mi-aduc aminte acum si nu stau sa caut ) ************************* - am invatat despre variabile si tipurile acestora. O variabila poate fi scrisa in memorie pe un anumit numar de biti (multipli de 8), Variabila tip CHAR se scrie pe 8 biti, variabila tip INT se scrie pe 16 biti, variabila tip LONG se scrie pe 32 de biti. Tipurile sunt de tip cu semn (adica SIGNED) sau numai pozitive adica UNSIGNED. Mai exista si tipul FLOAT (adica numere reale, cu virgula) dar daca puteti, folositi-va de subterfugii matematice (gen inmultiti cu 10000 sau 100000 si aveti grija la afisare pe unde puneti virgula) a.i sa nu folositi tipul FLOAT. Fugiti de el pentru ca ingenuncheaza controlerele PIC pe 8 biti care nu unitati matematice de lucru cu numere reale. Atat dpdv al memoriei FLASH (adica spatiul "pe HDD") cat si dpdv al memoriei RAM consumate (memoria RAM este una din chestiile cele mai valoaroase pentru controllere pentru ca este mereu prea putina ). Variabilele pot fi cele normale de care am invatat la orele de matematica sau "variabile" constante. Constantele sunt declarate gen: const float PI = 3.1415; Cuvantul cheie const arata ca ce urmeaza este ceva constant. Daca dupa declaratia de mai sus, in program incercati sa faceti ceva de genul: PI = 15; O sa primiti o eroare la compilare pentru ca schimbarea valoarii unei constante este imposibila, contrazice ideea de constanta. Variabilele pot fi transformate dintr-un tip in altul prin operatia de "casting", adica asa: int x = 3;long k;k = (long)x * 500000; Mai intai variabila x este declarata tip INT adica este stocata pe 16biti. Cand fac (long)x, variabila x va fi stocata pe 32biti (adica spatiul de stocare aferent tipului LONG). De ce faci aceasta? Va las pe voi sa descoperiti sau alti colegi generosi sa explice. Am vorbit de variabilele accesibile peste tot in program (cele globale, definite la inceputul programului) si cele accesibile doar in anumite functii (definite tot de noi) numite locale. Si altele prea multe de scris din nou. Cititi in paginile anterioare. Despre ce nu am vorbit: string-urile de caractere, arii si matrici (strans legate de un tip special de variabile, pointerii). Dar despre aceasta mai tarziu cand ajungem sa discutam despre cum afisam chestiile pe un LCD sau le trimitem prin comunicatia seriala catre un PC. - am invatat despre registri si ce sunt ei .... nu se poate programare in microcontrolere si sa nu va loviti din prima secunda de acestia ... Sunt documentati in detaliu in datasheet (cartea de capatai a programatorului embedded - atentie ca mai sunt prostii si in datasheet-uri si cel mai adesea pe langa datasheet-uri mai trebuie citite si erratele care contin informatii cu privire la ce a mers prost la nivel de silicon sau proiectare si nu se "pupa" cu ce scrie prin datasheet). - am invatat o chestie foarte importanta. Cand scrii un cod si il oferi in public (mai ales daca ceri ajutorul) FORMATATI codul, domnilor. Faceti-l "comestibil" altfel nu-l citeste nimeni decat poate cei care sunt cu adevarat dedicati ... Am invatat cum sa folosim indentarea codului (cu TAB-ruri sau cu spatii (SPACE) cum face Liviu) pentru a arata clar unde incepe si unde se termina o secventa de cod incadrata de acolade (de deschidere si de inchidere). Daca nu facem acest lucru, ne incurcam in acolade. Am invatat sa comentam codul si sa folosim spatii intre linii (obtine cu apasarea tasta ENTER) pentru a face viziible liniile de cod care au sens impreuna. Evident cel mai bine este ca acestea sa fie grupate in cate o functie. Si nu in ultimul rand, folosim butonul CODE din bara meniuri a ferestrei de editare, cand postam programe pe forum. Semnul "<>". - am invatat despre bucle (ciclul for, bucla while, bucla do-while si decizii: if-else si alte instructiuni cum ar fi switch-case) si faptul ca mai mereu avem cel putin o bucla principala in functia main(), definita in genul: while(1) { .... linii de cod ....}sau while(1==1) { .... linii de cod ....}saufor (;;) { .... linii de cod ....} - am invatat si faptul ca majoritatea compilatoarelor ofera modalitati de acces direct a cate unui singur bit dintr-un registru. Gen, putem accesa bitul 1 din portul A folosindu-ne in mikroC de cuvantul cheie RA1_bit cam asa: RA1_bit = 0; Toate aceste cuvinte cheie se gasesc intr-un fisier special pentru fiecare microcontroller in parte si acest fisier se gaseste intr-un folder in directorul de instalare al compilatorului (in general folderul are nume sugestic cum ar fi INCLUDE sau DEFINITION etc). Numele fisierului este totuna cu numele controllerelui (ex. 18F2550.h) avand insa extensia .h - am invatat putin despre modul binar de reprezentare a datelor si am discutat despre shiftare. Deja s-a adunat o gramada pana acum ... Va urma. Editat Iunie 28, 2016 de mars01 Link spre comentariu
mars01 Postat Iunie 30, 2016 Partajează Postat Iunie 30, 2016 (editat) Conceptul de "masking" (in romana, "mascare"). Imaginati-va ca aveti o pagina scrisa cu diverse litere in fata voastra. Apoi asezati deasupra acelei pagini o alta pagina, de data aceasta pagina suplimentara avand unele fante de dimensiunea unei litere. Prin suprapunerea celor doua pagini (pagina cu fante deasupra), se vor putea citi numai literele care se vad prin fante. Practic din toata pagina plina de litere, am selectat acum numai cateva litere, expuse prin fantele paginii de deasupra. Pagina de deasupra se cheama ca este o masca. In engleza, "mask". Am sa ma refer la termenii de masca si mascare si prin cuvintele in engleza: mask, respectiv masking (unele cuvinte tehnice pur si simplu trebuie sa ramana in engleza in anumit situatii). Asa ca o sa apara cand "mask" cand "masca" desemnand acelasi lucru: o masca binara. Cum spuneam in postarile anterioare, registrii sunt un concept esential pentru uC-uri (microcontrolere). Prin intermediul acestor variabile speciale (locatia lor in memorie este fixa si stabilita din fabricatie asa ca le putem vedea ca fiind asa numite constante) se pot controla/configura diversele periferice (module) sau se obtin informatii de la acele module. Pentru ca registrii sunt de fapt o colectie de cate 8 biti (discutam despre controlere pe 8 biti aici asa ca registrii sunt pe 8biti. Pentru controlere mai evoluate 16bit, 32bit si registrii pot fi mai mari. Dar nu este cazul nostru aici), iar in majoritatea situatiilor fiecare bit are o functie specifica (face ceva pe care numai el il face. De ex bitul 2 din registrul PORTA are rolul de a expune starea pinului fizic asociat) ajungem in situatia cand vrem sa schimbam starea unui bit fara sa ii afectam pe ceilalti. Compilatoarele C curente deja ofera aceasta facilitate asa cum am vazut anterior. De ex compilatorul MikroC ofera cuvantul cheie RA2_bit pentru a controla bitul 2 din PORTA. Dar este bine sa stim si noi cum putem face acest lucru, nu? Fara sa depindem de "bunatatea" compilatoarelor. Astfel apare utilitateea conceptului de masking. Practic noi luam registrul tinta (cel cu care lucram si caruia vrem sa ii schimbam un bit) si ii suprapunem o masca (mask) astfel incat sa schimbam numai si numai acel bit. Sau de ce nu?!! Putem schimba simultan nu numai un singur bit. Putem chiar si mai multi. Simultan. Foarte important acest lucru. Simultan .... *************************************** O mica paranteza. Codul: RA0_bit = 1;RA1_bit = 1;RA2_bit = 1;RA3_bit = 1;RA4_bit = 1;RA5_bit = 1;RA6_bit = 1;RA7_bit = 1; NU este chiar totuna cu: PORTA = 0b11111111; Logic poate ca este acelasi lucru ... si chiar este pentru ca rezultatul final in ambele cazuri este ca toti bitii PORTA devin 1. In primul caz facem toti bitii PORTA HIGH (1) in mod individual iar in al doilea caz, facem HIGH toti bitii PORTA dar in mod simultan. In primul caz, chiar daca se va face foarte repede, pentru noi oamenii este insesizabil, bitii PORTA sunt facuti HIGH unul dupa celalalt, secvential, incepand cu bitul 0 si terminand cu bitul 7. In al 2 lea caz, toti bitii registrului PORTA sunt facuti HIGH, simultan. Uneori acest lucru este foarte important si de aceea trebuie sa stim ce se intampla "sub capota", cum face compilatorul acest lucru. Adica procesul de masking. *************************************** Revenim. Avem obiectul pe care aplicam masca, adica registrul (sau de ce nu, o variabila oarecare) si avem si masca (mask ca in masca binara si nu ca in the Mask al Dl. Jim Carrey ). Am sa arat conceptul folosind un exemplu. Sa zicem ca vrem sa facem 1 bitul 4 din registrul PORTB. Facem o asa numita setare a bitului 4. Practic vrem sa avem ceva de genul: //Stare initiala PORTBPORTB = 0b11000001;//Si dorim ca PORTB sa devinaPORTB = 0b11010001; Mai sus am bolduit si subliniat bitul4 din PORTB (atentie numaratoarea incepe de la bitul0, adica din dreapta spre stanga) in speranta ca va fi mai vizibil. Cu alte cuvinte, dorim ca mai sus, sa lasam PORTB in pace cu exceptia bitului 4 pe care vrem sa il facem din orice stare este el (poate ca nu stim daca este 0 sau 1) in 1. Altfel spus, dorim sa setam bitul 4 din PORTB. Masca noastra va arata asa: 0b00010000 Practic pe pozitia 4 avem un 1 pentru ca noi stim urmatoarele: - cand vrem sa facem un bit 1 (adica sa il setam) facem o operatie OR cu 1 (cand faci OR cu 1, daca bitul era anterior 1 va deveni tot 1, daca bitul era anterior 0 va deveni tot 1). - cand vrem sa facem un bit 0 (adica sa il stergem) facem o operatie AND cu 0 (am asemuit operatia AND cu o inmultire, orice inmultesti cu 0 devine 0). - cand vrem sa schimbam starea unui bit in cea opusa (adica asa numitul 'toggle') facem XOR cu 1 (daca bitul era 1 el devine 0, daca era 0 el devine 1). In cazul nostru vrem sa setam bitul 4 din PORTB. Operatia este: PORTB = PORTB | 0b00010000; Citim asa: PORTB ia valoarea rezultatului operatiei OR dintre valoarea curenta a PORTB si masca binara folosita. Rezultatul este: PORTB = 0b11010001; De ce? Pentru ca operatiile binare se fac bit cu bit. // Operatorul '|' este OR la nivel de bitiPORTB = 0b11000001|masca = 0b00010000rez = 0b11010001bit7 -> 1 OR 0 = 1bit6 -> 1 OR 0 = 1bit5 -> 0 OR 0 = 0bit4 -> 0 OR 1 = 1bit3 -> 0 OR 0 = 0bit2 -> 0 OR 0 = 0bit1 -> 0 OR 0 = 0bit3 -> 1 OR 0 = 1 Si daca vrem sa setam simultan bitii 4 si 2? Atunci facem asa: PORTB = PORTB | 0b00010100; Observam ca avem cate un 1 pe pozitiile 2 si 4 din masca. Bun ... ce facem cand vrem sa stergem un bit din registru fara sa ii afectam pe restul? Sa zicem ca vrem sa stergem bitii 6 si 0 din registrul PORTB: //Stare initiala PORTBPORTB = 0b11000001;//Si dorim ca PORTB sa devinaPORTB = 0b10000000; Vom folosi operatia AND. AND o putem asemui cu inmultirea: orice inmultim cu 0 se face 0. Orice inmultim cu 1 ramane ce era inainte. Daca era 0 ramane 0 daca era 1 ramane 1. Asadar masca va fi 0 pe pozitiile unde vrem sa facem zero si va fi 1 unde vrem sa lasam bitii cum sunt. 0b10111110 Observam ca avem 0 pe pozitiile 6 si 0 adica exact unde vrem sa stergem bitii. Operatia devine: PORTB = 0b11000001; // starea initiala PORTB = PORTB & 0b10111110; Explicatie: // Operatorul '&' este AND la nivel de bitiPORTB = 0b11000001&masca = 0b10111110rez = 0b10000000bit7 -> 1 AND 0 = 1bit6 -> 1 AND 0 = 0bit5 -> 0 AND 1 = 0bit4 -> 0 AND 1 = 0bit3 -> 0 AND 1 = 0bit2 -> 0 AND 1 = 0bit1 -> 0 AND 1 = 0bit3 -> 1 AND 0 = 0 LE: Din nefericire bolduirile in casetele de CODE nu rezista si au disparut dupa ce am postat ... asa ca nu mai luati in considerare unde mentionez biti bolduiti. LLE: Luati aminte. Am folosit mai sus cuvintele AND, OR, XOR. In general, in matematica vor fi gasite ca atare. In limbajele de programare se face insa distinctie intre AND logic si AND la nivel de biti. Intre OR logic si OR la nivel de biti. Samd. Ce am folosit in acest post sunt referiri numai la nivel de biti ... In limbajele de programare exista cuvintele cheie AND, OR, XOR dar sunt folosite pentru decizii logice. Uzual AND logic este notat cu &&, iar OR logic este notat cu ||. Samd. AND la nivel de bit este notat cu un singur caracter & iar OR la nivel de biti este notat cu un singur caracter |. Samd. Cititi cu atentie manualul compilatorului pentru ca uneori se pot gasi ca atare cuvintele cheie AND, OR, NOT, XOR si trebuie sa va asigurati ce semnificatie au: logic sau bitwise. Editat Iunie 30, 2016 de mars01 Link spre comentariu
mars01 Postat Iunie 30, 2016 Partajează Postat Iunie 30, 2016 (editat) Despre directive de preprocesor am sa scriu prea putin pentru ca nici eu nu folosesc mai mult. Prima pe care o folosesc este: #include iar a -2 -a este: #define Prima am sa o discut cand vorbim despre colectiile de functii, libraries. A 2-a acum. Directivele de preprocesor sunt directive care in mod simplist inlocuiesc ce este in stanga cu ce este in dreapta inainte de compilarea efectiva. Cu alte cuvinte nu introduc cod in plus, sunt numai pentru ca noi sa ne descurcam mai usor in program. De exemplu putem face un cod de test al unei intrari uC (intrarea are un rezistor de pullup catre VCC si switch-ul catre GND deci este active LOW) asa: void main() { TRISA0_bit = 1; // facem pinul A0 intrare TRISA1_bit = 0; // facem pinul A1 iesire. Eventual punem un LED aici ... RA1_bit = 0; // starea initiala a iesirii RA1 este LOW // ANSEL, CMCON etc ii configuram aici while(1) { if (RA0_bit == 0) { delay_ms(10); if (RA0_bit == 0) { RA1_bit = 1; } } else RA1_bit = 0; }} dar este mult mai usor de inteles programul ca mai jos, desi la compilare obtinem aceleasi dimensiuni: #define PIN_INTRARE RA0_bit#define PIN_INTRARE_DIR TRISA0_bit#define PIN_LED RA1_bit#define PIN_LED_DIR TRISA1_bit#define APASAT 0#define NORMAL 1#define LED_OFF 0#define LED_ON 1#define INPUT 1#define OUTPUT 0void main() { PIN_INTRARE_DIR = INPUT; // facem pinul A0 intrare PIN_LED_DIR = OUTPUT; // facem pinul A1 iesire. Eventual punem un LED aici ... PIN_LED = LED_OFF; // starea initiala a iesirii RA1 este LOW // ANSEL, CMCON etc ii configuram aici while(1) { if (PIN_INTRARE == APASAT) { delay_ms(10); if (PIN_INTRARE == APASAT) { PIN_LED = LED_ON; } } else PIN_LED = LED_OFF; }} De retinut: - in liniile cu #define nu punem ';' (punct si virgula) cand terminam linia - linia lui #define se termina cand trecem la o alta linie (adica cu un ENTER) spre deosebire de liniile C normale care se termina cu "punct si virgula" - separam cantitatea din stanga cu cea din dreapta cu spatii (cate vrem) sau cu TAB-uri - se recomanda plasarea acestor directive in antet-ul programului adica primele instructiuni. Uneori nu este doar o recomandare ci chiar o necesitate. Mai ales la #include. Se pot crea asa numite macro-uri cu ajutorul #define. Macrourile sunt un fel de functii, diferenta este ca nu consuma din stiva hardware si ca nu introduce consum suplimentar de memorie RAM prin salvarea si restaurarea anumitor registri etc. Aceasta deoarece cand se compileaza (de fapt putin inainte dar pentru noi se intampla cand apasam butonul COMPILE) se inlocuieste ce este in stanga cu ce este in dreapta. Evident stanga si dreapta sunt dupa ce scriem cuvantul cheie #define. Exemplu de macro: #define suma(x,y) (x+y) iar cand se foloseste gen: int k,a,b;k = suma(a,b); In realitate compilatorul va compila ceva cam asa: int k,a,b;k = (a + b); pentru ca inlocuieste suma(a,b) cu (a+b) Atentie mare, mai cititi despre importanta parantezelor atunci cand se creaza macro-uri. Folosirea directivei #define sau a macrourilor construite cu #define poate genera multe, multe probleme deoarece daca nu sunteti atenti si faceti greseli ele se vor transmite in software iar compilatorul nu va oferi decat erori generice, gen "nu merge". O utilitate a directivei #define, in afara de a face programul mai "citibil" este si faptul ca poate ajuta ca orice modificare in program sa se transmita cu usurinta fara sa faceti zeci de modificari prin program. De exemplu daca modificati portul si pinul unde puneti switch-ul, apoi este mult mai usor sa modificati undeva in antetul programului, o data, decat sa tot editati prin cod si sa va intrebati unde naiba ati uitat o modificare de nu merge codul ... Si inca o utilizare ... este foarte usor sa faceti #define-uri pentru cuvinte cheie care sunt caracteristice compilatorului folosit. In acest fel, daca portati programul pe alt compilator (poate alt uC, poate un Atmega sau Arduino etc) tot ce trebuie facut este sa schimbati in antet cateva linii folosind cuvintele cheie ale noului compilator si gata. Editat Iunie 30, 2016 de mars01 Link spre comentariu
mars01 Postat Iunie 30, 2016 Partajează Postat Iunie 30, 2016 (editat) Un exemplu de aplicare a mascarii, un efect luminos Knight Ryder sau Cylon effect: // PIC18F14K22// functie de configurare a perifericelor#define delay 75void init_sys() { // dezactivare convertoare ADC ANSEL = 0; ANSELH = 0; SLRC_bit = 0; // configurez slew-rate-ul pentru iesiri la viteza maxima DAC1OE_bit = 0; // dezactivare DAC // dezactivare comparatoare C1ON_bit = 0; C2ON_bit = 0; TRISC = 0b00000000; // portul C este format tot din iesiri LATC = 0b00000000; // se porneste cu toti pinii port C in stare LOW }void main() { init_sys(); //apel functie de initializare LATC = 0b00000001; // portul C este initializat cu 0 mai putin bitul0 care este 1 delay_ms(delay); // un delay ca sa putem vedea si starea initiala // bucla infinita while (1) { while ((LATC & 0b10000000) == 0){ LATC = LATC << 1; delay_ms(delay); } while ((LATC & 0b00000001) == 0){ LATC = LATC >> 1; delay_ms(delay); } }} O particularitate a programului este ca nu foloseste nici-o variabila ... Consumul de memorie RAM este fix zero. Functia de initializare nu cred ca mai este cazul sa o descriu, am tot vorbit despre aceste lucruri pana acum. Inainte de bucla infinita initializam PORTC cu 1, adica LED-ul de pe bitul 0 al portului C este aprins. Avem nevoie ca sa avem acest pin 1 pentru ca apoi sa il putem plimba de la dreapta la stanga si tot asa. Interesant este ce gasim in bucla infinita while(1): while (1) { while ((LATC & 0b10000000) == 0){ LATC = LATC << 1; delay_ms(75); } while ((LATC & 0b00000001) == 0){ LATC = LATC >> 1; delay_ms(75); } } Tradus este asa: Cat timp bitul de pe pozitia 7 (observati ca masca are un 1 fix pe pozitia 7) este zero (adica LED-ul acela nu este inca aprins), la un interval de timp cu valoarea definita delay, shifteaza PORTC catre stanga o pozitie. Cand bitul 7 este aprins, adica prin shiftare a ajuns 1-ul pe pozitia 7, iesi din prima bucla si treci in bucla a 2 a. In acest moment PORTC = 0b10000000. Cat timp bitul de pozitia 0 este zero (adica nu a ajuns 1-ul pe acea pozitie) shifteaza PORTC catre dreapta cate o pozitie la fiecare interval de timp definit de 'delay'. Si tot asa. Am folosit registrul LATC pentru ca la uC-urile care au acest port (controlere mai noi) este indicata folosirea acestuia cand avem de-a face cu pini configurati ca iesiri digitale. Putem face programul sa shifteze nu doar un LED ci hai sa zicem doua in acelasi timp. Cum facem? In felul urmator ... modificam valoarea initiala, de pornire, adica linia: LATC = 0b00000011; // portul C este initializat cu 0 mai putin bitul0 si bitul 1 care sunt 1 Modificati-o si voi cum vreti pentru efecte diferite. //functie de configurare a perifericelor// PIC18F14K22#define delay 125void init_sys() { // dezactivare convertoare ADC ANSEL = 0; ANSELH = 0; SLRC_bit = 0; // configurez slew-rate-ul pentru iesiri la viteza maxima DAC1OE_bit = 0; // dezactivare DAC // dezactivare comparatoare C1ON_bit = 0; C2ON_bit = 0; TRISC = 0b00000000; // portul C este format tot din iesiri LATC = 0b00000000; // se porneste cu toti pinii port C in stare LOW }void main() { init_sys(); LATC = 0b00000011; // portul C este initializat cu 0 mai putin bitul0 si bitul 1 care sunt 1 delay_ms(delay); // un delay ca sa putem vedea si starea initiala // bucla infinita while (1) { while ((LATC & 0b10000000) == 0){ LATC = LATC << 1; delay_ms(delay); } while ((LATC & 0b00000001) == 0){ LATC = LATC >> 1; delay_ms(delay); } }} Editat Iunie 30, 2016 de mars01 Link spre comentariu
bobinatorul Postat Iulie 1, 2016 Partajează Postat Iulie 1, 2016 Totusi cum s-ar "traduce" LAT ? Daca TRIS se refera la cum setam portul ca intrare sau iesire LAT e un fel de mutare la stanga sau dreapta in functtie de >> sau <<?Un fel de shift register ca la 74hc595?Vine cumva de la "latch"? Link spre comentariu
mars01 Postat Iulie 1, 2016 Partajează Postat Iulie 1, 2016 (editat) LATx este un registru asociat porturilor in uC-urile din familia Enhanced PIC. Practic face ce face PORTx atunci cand este setat ca IESIRE, dar o face mai bine LE: Si da, ai intuit bine, denumirea are legatura cu latch-uri dar cu latch-uri care se gasesc in microcontrolere, pe fiecare pin care este parte a unui port I/O. Registrul LATx a fost introdus pentru a rezolva o problema numita Read-Modify-Write, despre care s-a discutat foarte mult in domeniu. Registrii LATX sunt doar registri (variabile speciale ale uC-urilor) si nu au o legatura speciala cu operatia de shiftare (operatia de shiftare este efectuata in unitatea logic aritmetrica - asa numitul procesor din uController). Registrii LATx sunt executanti, "munictorii", iar unitatea ALU (procesorul) este inginerul/manager care directioneaza ce fac executantii, muncitorii. Si ca sa fie mai clar, mai jos este un "licurici" folosind registrul LATA (registrii LATx pot fi folositi doar de uC-urile unde sunt prezenti, unele PIC16F - in general cele cu numere mai mari ca 1000, toate PIC18F etc). LED-ul licurici este pe pinul asociat bit 0 din portul A. //functie de configurare a perifericelor// PIC18F14K22#define delay 500void init_sys() { // dezactivare convertoare ADC ANSEL = 0; ANSELH = 0; SLRC_bit = 0; // configurez slew-rate-ul pentru iesiri la viteza maxima DAC1OE_bit = 0; // dezactivare DAC // dezactivare comparatoare C1ON_bit = 0; C2ON_bit = 0; TRISA = 0b00000000; // portul A este numai IESIRI LATA = 0b00000000; // se porneste cu toti pinii port A in stare LOW }void main() { init_sys(); // bucla infinita while (1) { delay_ms(delay); LATA = 0b00000001; delay_ms(delay); LATA = 0b00000000; } }} Pentru procesoarele care nu au registri LATx se va folosi in locul acestora registrul PORTx. LLE: si ca sa fie si mai clar. Pentru uC-uri din seria Midline, clasice: Cand setam un port de pini, sa zicem portul A ca IESIRI folosindu-ne de registrul TRISA: TRISA = 0b00000000; Ca sa scriem 1 pe fiecare bit (pin asociat) folosim comanda: PORTA = 0b11111111; Cand setam portul A ca INTRARI folosindu-ne de registrul TRISA: TRISA = 0b11111111; Atunci vom putea citi cu ajutorul registrului PORTA starea fiecarui pin asociat portului A. ******************************************************************************************************************* Pentru uC-uri din seria Enhanced/Advanced (adica 16F in genere cu coduri mai mari ca 1000, toate 18F): Cand setam un port de pini, sa zicem portul A ca IESIRI folosindu-ne de registrul TRISA: TRISA = 0b00000000; ca sa scriem 1 pe fiecare bit (pin asociat) folosim comanda, si atentie aici apare diferenta: LATA = 0b11111111; In rest este la fel ca la cele fara registri LATx. Cand setam portul A ca INTRARI folosindu-ne de registrul TRISA: TRISA = 0b11111111; Atunci vom putea citi cu ajutorul registrului PORTA starea fiecarui pin asociat portului A. Editat Iulie 1, 2016 de mars01 Link spre comentariu
mars01 Postat Iulie 2, 2016 Partajează Postat Iulie 2, 2016 (editat) Aparent exista o confuzie legata si de operatorul SHIFT, adica de deplasare. Operatorul '<<' sau '>>' adica shiftare (deplasare) la stanga sau la dreapta este un ... operator. Probabil ca operatorul '+', de adunare aritmetrica, ne este mai familiar ... Atunci cand avem operatia: 1 + 2 = 3 stim ca adunand o cantitate cu doua cantitati atunci obtinem trei cantitati. Operatorul '<<' sau '>>' adica operatorul de shiftare este un operator care se aplica si are sens la nivel binar dar din punct de vedere zecimal, deplasarea (shiftarea) la stanga cu o pozitie echivaleaza cu inmultirea cu 2 iar deplasarea la dreapta cu o pozitie echivaleaza cu o impartire la 2. Practic deplasarea la stanga cu n pozitii echivaleaza cu o inmultire cu 2 la puterea n (adica cu numarul de pozitii). Pentru shiftarea la dreapta cu n pozitii, dpdv decimal echivaleaza cu o impartire cu 2 la puterea n. N-am sa tot repet pentru shiftare la dreapta si pentru shiftare la stanga. Doar sa stiti ca treburile merg la fel doar ca shiftare stanga = inmultire, shiftare dreapta = impartire. De ce se intampla aceasta? Sa luam cazul unui byte (adica 8biti). Numarul pozitiv (se poate discuta si despre cand numerele pot fi si negative dar complic lucrurile) maxim care se poate stoca pe 8 biti este 255 iar numarul minim este chiar 0. Numerele binare se traduc in decimal numarand pozitia de la dreapta la stanga si inmultind bitul cu 2 la puterea pozitiei. Adica: Exemplu, avem numarul in binar:0 0 0 0 1 0 1 1128 64 32 16 8 4 2 1unde:2 la puterea 7 = 1282 la puterea 6 = 642 la puterea 5 = 322 la puterea 4 = 162 la puterea 3 = 82 la puterea 2 = 42 la puterea 1 = 22 la puterea 0 = 1deci numarul nostru tradus din binar in decimal este:128*0 + 64*0 + 32*0 + 16*0 + 8*1 + 4*0 + 2*1 + 1*1 = 11 Sa zicem ca avem numarul binar echivalent lui 9 zecimal pe care aplicam operatii de shiftare: Pos7 Pos6 Pos5 Pos4 Pos3 Pos2 Pos1 Pos0128 64 32 16 8 4 2 1Numarul zecimal 9 se scrie binar:0 0 0 0 1 0 0 1Numarul acesta shiftat la stanga cu 2 este:0 0 1 0 0 1 0 0adica tradus in zecimal este: 32*1 + 4*1 = 36 (componentele care rezulta in 0 nu le-am mai adunat ca sa nu scriu in prostie)Care este exact ce ne asteptam: o shiftare la stanga a lui 9 inseamna 9*2 => 18 care este shiftat inca o data la stanga si => 18*2 = 36 Cu alte cuvinte: x = 3;x = x >> 1; // x ia valoarea ce rezulta in umra shiftarii valorii curente a lui x o data la drapta in final x este: x initial este 3 adica:0000 0011shiftat odata la dreapta este 3:2 = 1 (matematica cu nr reale spune ca este 1.5 dar aici nu merge aceasta asa ca rezultatul este partea intreaga)adica scris in binar:0000 0001'1'-le care era cel mai in dreapta s-a 'evaporat' adica numai este prezent in byte-u nostru. 7 in decimal shiftat la stanga de 3 ori este: 7 decimal = 0000 0111shiftat la stanga 3 pozitii este: 0011 1000 adica in decimal = 56 Scrisa in C treaba aceasta: #include <stdio.h>void main(void) {unsigned int x = 7;/*acelasi lucru scris in binar:x = 0b00000111;acelasi lucru scris in hexazecimal:x = 0x07;*/x = x << 3; // x ia valoarea lui x shiftat la stanga de 3 oriprintf("valoarea lui x shiftata este: %d", x); // tipareste valoarea pe ecran PC - acest lucru pentru compilatoarele C pentru PC - asta ca se vada ceva} Rezultatul este: Asa ca ... Ori scrii asa: x = x << 3; ori scrii asa: x = x * 8; este tot una, mai putin faptul ca asa cum este scris prima data (adica cu operatorul '<<') este mai aproape de asamblare, de instructiunile native ale procesorului din uC. Editat Iulie 2, 2016 de mars01 Link spre comentariu
Liviu M Postat Iulie 2, 2016 Partajează Postat Iulie 2, 2016 mai putin faptul ca asa cum este scris prima data (adica cu operatorul '<<') este mai aproape de asamblare, de instructiunile native ale procesorului din uC. Cred ca am mai scris asta o data, intr-o discutie cu Brad. Compilatoarele "traduc" inmultirile/impartirile cu puteri ale lui doi prin shifturi. Cel putin asa face xc8.Uite codul tau de mai sus tradus de xc8. Codul C: unsigned char ucIn = 7; ucIn *= 8; Codul rezultat in urma asamblarii: 113 07F3 3007 movlw 7 114 07F4 00F0 movwf ??_main 115 07F5 0870 movf ??_main,w 116 07F6 00F1 movwf main@ucIn 117 118 ;main.c: 15: ucIn *= 8; 119 07F7 1003 clrc 120 07F8 0DF1 rlf main@ucIn,f 121 07F9 1003 clrc 122 07FA 0DF1 rlf main@ucIn,f 123 07FB 1003 clrc 124 07FC 0DF1 rlf main@ucIn,f Link spre comentariu
mars01 Postat Iulie 2, 2016 Partajează Postat Iulie 2, 2016 (editat) Nu stiu daca aceasta ai vrut sa spui, dar intr-adevar nu prea conteaza cum scrii. Inmultire cu doi sau shiftare la stanga cu 1 pozitie, intr-un final cand compilatorul termina de compilat tot shiftare ajunge. Am vrut sa punctez ca shiftarea stanga/dreapta este o operatie primara, specifica procesorului. Oricum, listing-ul postat de tine este mai mult decat clar ... Editat Iulie 2, 2016 de mars01 Link spre comentariu
Postări Recomandate
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 contAutentificare
Ai deja un cont? Autentifică-te aici.
Autentifică-te acum