Sari la conținut
ELFORUM - Forumul electronistilor

Termometru PIC16F690 afisaj 7SEG+LM35


danpin

Postări Recomandate

Salutari,

am reluat de curand proiectul pe care-l abandonasem un timp...

Am avut ceva probleme la scrierea PIC-ului; cu o clona Pickit2 nu vroia de fel sa scrie programul corect, am incercat cu un programator pe serial, clona dupa un Velleman si merge dar de fiecare data cand vreau sa scriu in Pic trebuie sa-l sterg mai intai altfel imi da eroare. Am doua pic-uri amandoua se comporta la fel.

Cum spunea Fratello intr-o postare anterioara: " Multe aplicatii software (in care generam o purtatoare de 38 KHz sau un PWM variabil) imi mergeau sacadat IN SIMULARE, dar perfect in realitate !!!" asa este, in simulare digitii se vad sacadat dar in montaj functioneaza bine.

Singura chestie care nu-mi prea place este faptul ca ultimul digit dupa virgula si uneori cel al unitatilor cam "salta" aiurea, ceva de genul: 27,4; 27,8; 27,3, 26,9; 27,2; 26,7 cam asa ceva.

Rog pe cineva care se pricepe sa-mi dea un sfat cum asi putea face sa am o indicatie mai stabila, eventual la modalitatea de a face calculele mai precise la masurarea temperaturii. In simulare indicatia este stabila, in montaj nu. In motaj la iesirea lui LM35 am pus si un AO cu amplif. x2, oricum cu sau fara AO indicatia ultimilor 2 digiti tot "salta" ciudat.

In program am mai introdus si niste functii pt masurarea curentului si tensiunii pt. protectia la suprasarcina si scc.

Cam asta ar fi programul:

unsigned short mask(int num){ switch (num) {   case 0  : return 0xC0;                       // anod comun 0   case 1  : return 0xF9;                       // anod comun 1   case 2  : return 0xA4;                       // anod comun 2   case 3  : return 0xB0;                       // anod comun 3   case 4  : return 0x99;                       // anod comun 4   case 5  : return 0x92;                       // anod comun 5   case 6  : return 0x82;                       // anod comun 6   case 7  : return 0xF8;                       // anod comun 7   case 8  : return 0x80;                       // anod comun 8   case 9  : return 0x90;                       // anod comun 9   case 10 : return 0xFF;                       // anod comun blank   case 11 : return 0xFE;                       // anod comun seg a   case 12 : return 0xFD;                       // anod comun seg b   case 13 : return 0xFB;                       // anod comun seg c   case 14 : return 0xF7;                       // anod comun seg d   case 15 : return 0xEF;                       // anod comun seg e   case 16 : return 0xDF;                       // anod comun seg f   case 17 : return 0xBF;                       // anod comun seg g   case 18 : return 0x7F;                       // anod comun dp   case 19 : return 0xFF;                       // anod comun blank }}unsigned char ch, ADCx;unsigned char anods_index;                      // index anods (1,2,3)unsigned int Voltage, Current, digit, x=0;unsigned short anods_array[3];                  // anodsunsigned long V, A, Temperature, number, tlong;unsigned short current_duty, current_duty1;void anods () {  ++anods_index;                  // Increment anods_index with one every interrupt  if (anods_index>3)  anods_index=0;  switch (anods_index)            // Turn ON one anod every interrupt  {    case 1: PORTA.F2 = 1;    break;    case 2: PORTC.F4 = 1;    break;    case 3: PORTA.F5 = 1;    break;  } } void my_port(int ValueToSend)                 // Group various pins in one virtual port {  RB4_bit = (ValueToSend);         //lsb  RB5_bit = (ValueToSend>>=1);  RB6_bit = (ValueToSend>>=1);  RB7_bit = (ValueToSend>>=1);  RC0_bit = (ValueToSend>>=1);  RC1_bit = (ValueToSend>>=1);  RC2_bit = (ValueToSend>>=1);  RC3_bit = (ValueToSend>>=1);     //msb }void interrupt(){ if(T0IF)                               // Check Timer0 overflow interrupt flag {  PORTA.F2 = 0;                          // Turn OFF anod digit1  PORTC.F4 = 0;                          // Turn OFF anod digit2  PORTA.F5 = 0;                          // Turn OFF anod digit3  my_port(anods_array[anods_index]);  anods();  TMR0 = 100;                            // TMR0 preload  INTCON.T0IF = 0;                       // Clear timer0 interrupt flag  x=++x;                                 // Increment x with one every interrupt }} void intro()                            // Initial segments check{  for (digit=10; digit<20; digit++)  {   Delay_ms(250);   anods_array[0] = mask(digit);   anods_array[1] = mask(digit);   anods_array[2] = mask(digit);  }}void display(){ if (number<1000) {  digit = (number/100u);  if(digit<1)  {   digit=10;   anods_array[0] = mask(digit);  }  else  anods_array[0] = mask(digit);  digit = (number/10u) % 10u;  anods_array[1] = mask(digit)&0x7F;  digit = number%10u;  anods_array[2] = mask(digit); } else {  digit = (number/1000u);  anods_array[0] = mask(digit);  digit = (number/100u) % 10u;  anods_array[1] = mask(digit);  digit = (number/10)% 10u;  anods_array[2] = mask(digit); }}void temp_ADC()                           // Reading the analog inputs (ADC){  Temperature = 0;  for (ADCx=0; ADCx<5; ADCx++)  {   Temperature += ADC_Read(9);            // Reading voltage values from channel 9    //Delay_us(50);  }  Temperature = (Temperature/ADCx);       // Temperature (Voltage) calculation  tlong = (long)Temperature*2500;         // Millivolts conversion  number = (long)tlong/1023;              // 0...1023 => 0...2500mV  //Delay_us(50);}void volt_ADC()                           // Reading the analog inputs (ADC){  Voltage = 0;  for (ADCx=0; ADCx<5; ADCx++)  {   Voltage += ADC_Read(3);                // Reading voltage values from channel 3   //Delay_us(20);  }  Voltage = Voltage/ADCx;                 // Voltage calculation  V = (long)Voltage*2500;                 // Millivolts conversion  V = V/1023;                             // 0...1023 => 0...2500mV}void crt_ADC()                            // Reading the analog inputs (ADC){  Current = 0;  for (ADCx=0; ADCx<5; ADCx++)  {   Current += ADC_Read(0);                // Reading current values from channel 0   //Delay_us(20);  }  Current = Current/ADCx;                 // Current calculation  A = (long)Current*2500;                 // Millivolts conversion  A = A/1023;                             // 0..1023 => 0...2500mV}void fan(){  if (number >=350)                        // Start PWM for Temperature >35°C {  PWM1_Start();                            // Init PWM for Single Output  PWM1_Set_Duty(number*0.28);              // Set the PWM Duty Cycle  if (number >=800) PWM1_Set_Duty(255);    // Start with Max Duty Cycle for Temperature >80°C }  if (number <300)  PWM1_Stop();           // Turn OFF PWM for Temperature <30°C} void main(){  OPTION_REG = 0;  OPTION_REG = 0b10000101;  //Prescaler 1:64; TMR0 Preload = 100;                            //Actual Interrupt Time: 4.992 ms  INTCON = 0b10100000;  OSCCON = 0b01110111;                      // 8Mz; Internal oscillator  ADCON0 = 0b11100111;                      // Right justified; Vref pin  ADCON1 = 0b01010000;                      // A/D Conversion clock: Fosc/16  TRISA = 0x1B;                             // Set PORTA direction: RA2,5 output;                                             // RA0,1,3,4 input  TRISB = 0x00;                             // Set PORTB direction to be output  TRISC = 0x80;                             // Set PORTC direction: RC0-6 output;                                             // RC7 input  ANSEL = 0x0B;                             // Set analog inputs: AN0,1,3  ANSELH = 0x02;                            // Set analog inputs: AN9  PWM1_Init(20000);                         // Initialize PWM1 module at 20KHz  current_duty  = 64;                       // Initial value for current_duty; 25%  PORTC = 0x00;                             // Turn OFF PORTC  TMR0 = 100;                               // TMR0 Initial value  PORTA.F2 = 0;                             // Turn OFF anod digit1  PORTC.F4 = 0;                             // Turn OFF anod digit2  PORTA.F5 = 0;                             // Turn OFF anod digit3  intro();                                  // Initial segments check  PORTA.F2 = 0;                             // Turn OFF anod digit1  PORTC.F4 = 0;                             // Turn OFF anod digit2  PORTA.F5 = 0;                             // Turn OFF anod digit3  number = 0;                               // Initial value;  digit = 0;                                // Initial value;  anods_index = 0;                          // Initial value;  PORTC.F6 = 1;                             // Turn ON Relay for power supply  while(1)                                   //Endless loop; {   volt_ADC();                              // Read voltage   crt_ADC();                               // Read current   temp_ADC();                              // Read temperature   if((V<250) && (A>0))  PORTC.F6=0;        // Check for output short circuit   if((V>0) && (A>2000)) PORTC.F6=0;        // Check for output overcurrent   if (number >800)      PORTC.F6=0;        // Check for overtemperature   if (x>300)                               // If x=300, 300*5ms=1,5s refresh display                                             // with new values  {   x=0;                                     // Reset counter x   display();  }   fan(); }}

Termometru sursa LM35-2015.rar

Editat de danpin
Link spre comentariu

Salut,

 

Sunt mai multe lucruri care eu le-as face putin diferit dar am sa ma concentrez pe problema enuntata de tine.

 

Pentru a elimina asa zisele schimbari frecvente ale digit-ului cel mai putin semnificativ poti face asa:

 

- este recomandat ca prima masuratoare a ADC-ului sa nu o folosesti. Numai stiu in ce datasheet sau erata am citit acest lucru dar cred ca nu ai prea mult de pierdut prin aceasta. Faci acest lucru prin efectuarea unei ADC_read inainte de bucla ta in care faci media, si nu folosesti aceasta prima masuratoare.

- daca banuiesti ca zgomotul este cauza problemei atunci poti sa faci in loc de medie un oversampling sa zicem pentru inca 2 biti si apoi shiftezi la dreapta de 2 ori rezultatul si renunti la acei biti (daca nu te intereseaza o rezolutie crescuta)

- o alta modalitate este sa folosesti asa numitul running-average (se face medierea la fiecare noua masuratoare, ponderea erorii se reduce)

- sau pur si simplu, efectueaza masuratoarea temperaturii la un interval de timp mai mare, sa zicem odata la 0.5 secunde. Asa cum este acum, efectuezi masuratoarea la fiecare trecere prin bucla principala si este normal ca ultimul digit sa faca pe nebunul (la fiecare adiere pe care nici nu o simti).

Editat de mars01
Link spre comentariu
@mars01

Multumesc pt. sfaturi!

O sa ma documentez cu privire la oversampling si am sa incerc.

Am sa probez si celelalte optiuni, renuntarea la prima masuratoare sau efectuare de masuratori la intervale mai mari.

Oricum am incercat sa implementez ceva de genul, nu o masurare la un interval mai mare ci doar afisarea la un interval mai mare, 1,5s.

Ce ma intriga este faptul ca digitul zecilor sta fix, ok, cel al unitatilor sta si el fix daca valoarea nu e in apropierea lui 1 sau 9, in schimb digitul de dupa virgula ia parca valori aleatorii.

Valoarea tensiunii pe pinul AI este constanta, masurata cu multimetrul cat si cu osciloscopul, nu sunt salturi, nu vad oscilatii, zgomot....

Link spre comentariu

Sa inteleg ca nu este vorba doar de masuratoarea temperaturii. Atunci vorbim de zgomot electric. Daca vine de la ADC atunci se poate minimiza software cum am scris mai sus.

Hardware se poate minimiza zgomotul folosind referinta externa separata (eventual un regulator separat de tensiune iar pe GND-ul sau intercalata o inductanta mica).

 

Sau daca vrei, conform la datasheet, poti face si asa:

 

9.3 A/D Operation During Sleep The A/D converter module can operate during Sleep. This requires the A/D clock source to be set to the FRC option. When the RC clock source is selected, the A/D waits one instruction before starting the conversion. This allows the SLEEP instruction to be executed, thus eliminating much of the switching noise from the conversion. When the conversion is complete, the GO/DONE bit is cleared and the result is loaded into the ADRESH:ADRESL registers. If the A/D interrupt is enabled, the device awakens from Sleep. If the GIE bit (INTCON<7>) is set, the program counter is set to the interrupt vector (0004h). If GIE is clear, the next instruction is executed. If the A/D interrupt is not enabled (ADIE and PEIE bits set), the A/D module is turned off, although the ADON bit remains set.

Link spre comentariu

Pt. +Vref in montaj am folosit MCP1525, regulator de 2,5V.

Cum am mai spus, nu ma pricep prea mult la electronica/microcontrolere, ma gandeam ca poate nu am declarat bine variabilele, acei long la inceputul programului si apoi in calculul temperaturii nu stiu daca le-am folosit bine in sensul ca pierd din calcule prea multe zecimale, adica acele trunchieri ce sunt facute cand se lucreaza cu int.

Link spre comentariu

Pai problema pe care ai enuntat-o nu face referire la cat de precisa este masuratoarea ci la faptul ca zecimala este fluctuanta iar acest lucru apare din cauza zgomotului electric.

In simulare zgomotul electric nu este luat in considerare asa ca valoarea afisata este stabila.

Cum spuneam pentru ca valoarea zecimalei sa fie mai stabila ori se fac masuratorile la un interval de timp mai mare ori se minimizeaza zgomotul electric.

 

Acuratetea masuratorii este limitata de ADC-ul PIC-ului (rezolutia sa) iar repetabilitatea (precizia) este limitata de zgomotul din circuit.

Pentru marirea rezolutiei solutia este oversampling-ul (vor fi mai multe zecimale) influentata fiind si de marimea tensiunii de referinta.

O tensiune de referitna de 2.5V si un ADC pe 10 biti inseamna ca rezolutia ADC-ului este de ~24mV, tensiune care poate fi foarte usor saturata de zgomot-ul circuitului.

Editat de mars01
Link spre comentariu

Rezolutia este  ~24mV sau 2,4mv?

 

Zgomot, lipsa preciziei, una din ele sau poate amandoua, ideea este ca ultima cifra nu este stabila. Cand valoarea este apropiata de 1 sau 9 incepe sa joace si cifra unitatilor.

Editat de danpin
Link spre comentariu

:nebunrau: tot editez eu posturile ca sa nu apara greseli (litere lipsa sau mai des inversate) dar uite ca lipsa la punctul ala n-am bagat-o de seama.

 

Raspuns: 2.5V / 1024 = 0.002441V = 2.441mV

Editat de mars01
Link spre comentariu
@mars01

Ok, nici o problema, mi-am dat seama ca e o eroare de tipar, intrebarea mea a fost retorica, stiu si am inteles foarte bine cum si care-i treaba cu rezolutia, ce reprezinta si cum se calculeaza. Am citit pe internet si diverse opinii despre impartirea la 1024 sau la 1023. Cum nu sunt specialist si nici nu mi-am facut un scop in viata de a fi un perfectionist in programarea microcontrolelelor le dau dreptate tuturor, eu impart la 1023 ca cica ar fi 1023 intervale de esantionare. ​Oricum nu nconteaza pt. aplicatia mea.

In urma sugestiilor tale am studiat putin mai in profunzime chestia cu oversamplingul, mediere etc. (n-am terminat inca) si am facut urmatoarea chestie ce a dus la un rezultat bun, acum probez pe "viu" si sunt multumit. Am sa mai incerc si alte variante.

void temp_ADC()                           // Reading the analog inputs (ADC){  Temperature = 0;  for (ADCx=0; ADCx<16; ADCx++)  {   Temperature += ADC_Read(9);            // Reading voltage values from channel 9    //Delay_us(50);                       // 16 times, add results  }  result=(Temperature>>4);                // divide by 16  Temperature -= result;                  // temp.= temp-1/16  Temperature += ADC_Read(9);             // temp.=(temp-1/16)+analog read  Temperature =(Temperature>>4);          // divide by 16  tlong = (long)Temperature*2500;         // Millivolts conversion  number = (long)tlong>>11;               // 0...2046 => 0...2500mV (am o amplificare cu                                                              // 2x in motaj)  }

Ceva de genul: masor de 16 ori si adun rezultatele, apoi imart la 16 si extrag o saisprezecime din adunare, apoi adun o alta citire din ADC. Vad ca acum, cu ventilatorul care sufla pe radiatorul unde este montat senzorul LM35, indicatie este destul de stabila, am variatii lente, adica cam 2-3s, uneori mai mult, de genul:36.2 , 36.1 , 36.4 , 36.0 , 35.8   dar pare bine. Am sa mai incerc si alte variante, dati-va cu parerea, intrarea este libera.

Link spre comentariu

@mars01

Ok, nici o problema, mi-am dat seama ca e o eroare de tipar, intrebarea mea a fost retorica, stiu si am inteles foarte bine cum si care-i treaba cu rezolutia, ce reprezinta si cum se calculeaza. Am citit pe internet si diverse opinii despre impartirea la 1024 sau la 1023. Cum nu sunt specialist si nici nu mi-am facut un scop in viata de a fi un perfectionist in programarea microcontrolelelor le dau dreptate tuturor, eu impart la 1023 ca cica ar fi 1023 intervale de esantionare. ​Oricum nu nconteaza pt. aplicatia mea.

 

Se imparte la 2^N in cazul tau la 2^10 = 1024.

Pentru clarificare recomand lectura aici: https://en.wikipedia.org/wiki/Analog-to-digital_converter#Resolution

Mai sutn chestii gresite pe Wikipedia dar nu aici.

 

LE: Sau aici: http://www.ni.com/white-paper/4806/en/

 

Programarea se sprijina pe matematica iar matematica este o stiinta exacta. Sigur ca mai si gresim dar nu tebuie sa ne-o facem cu mana noastra.

 

 

LLE: Am mai citit putin postul si am inteles de unde vine confuzia.

ADC-ul pe 10biti numara de la 0 la (2^10)-1 = 1023. Dar sunt 1024 valori (1023 valori + 0). Din aceasta cauza, rezolutia in mV este de Vref/1024, in cazul tau 2.5V/1024 = 2.441mV.

 

Daca dorim sa aflam ce tensiune corespunde la o citire binara atunci se foloseste formula:

 

Vin = Vref * (ADC_read/((2^N)-1).

In cazul tau, Vin = 2.5V * (ADC_read/1023)

 

Prima data aflam rezolutia in tensiune.

A doua oara interpretam o citire binara a ADC-ului pentru a afla tensiunea la intrarea ADC-ului.

 

 

LLLE: Sper ca senzorul nu masoara temperatura la tine in casa, la ora aceasta. :)

Editat de mars01
Link spre comentariu

Ok, multumesc pt. implicare.

"LLLE: Sper ca senzorul nu masoara temperatura la tine in casa, la ora aceasta.  :)"

Nu, senzorul masura ce am spus in postarea de mai sus: "Vad ca acum, cu ventilatorul care sufla pe radiatorul unde este montat senzorul LM35, indicatie este destul de stabila, am variatii lente, adica cam 2-3s, uneori mai mult, de genul:36.2 , 36.1 , 36.4 , 36.0 , 35.8   dar pare bine."

 

M-ar interesa ca cineva cu experienta sa controleze daca am stabilit bine, sau ce se poate optimiza, in alocarea tipurilor pt. diversele variabile din program, adica daca e bine cum am pus unsigned char, unsigned long, unsigned short etc. iar pt. long daca e buna sintaxa dpdv al mikroC folosita in program.

Link spre comentariu

Sintaxa long pare in regula. Treaba cu "signed" si "unsigned" este in felul urmator "long" scris simplu este luat ca "signed" inseamna ca un bit este utilizat ca semn ( numar pozitiv sau numar negativ) , daca se pune "unsigned long" inseamna ca acel bit o sa faca parte din valoarea retinuta in long si nu mai este folosit ca semn in concluzie orice declarat cu "unsigned" nu poate contine valori negative ( de ex degeaba scrii -2 ca o sa fie transformat in 2). Deci daca nu ai nevoie de valori negative poti folosii "unsigned" care este mai rapid la operatii aritmetice dacat "signed".

Link spre comentariu

O explicatie (a tipurilor de variabile) a dat-o @bandi12.

 

Dar eu as renunta la functia anods() si de vreme ce este chemata doar in functia de intrerupere, as include corpul ei  direct in functia interrupt().

 

De ce? Pentru ca functia anods() nu intoarce nimic si nu are parametri (desi daca avea parametri se rezolva inlocuind cu pointeri). Cu exceptia situatiei cand aceasta functie ar fi inlined de catre compilator, faptul ca anods() este o functie de sine statatoare nu face decat sa ocupe niste loc in stiva aiurea. Plus ca se utilizeaza ceva cicli ceas doar ca sa faca operatiile cu stiva. Inutil, parerea mea, mai ales ca functiile care deservesc intreruperi trebuie optimizate cat se poate.

Editat de mars01
Link spre comentariu

La declararea variabilelor am pus: unsigned long Temperature, number, tlong;.

In program este corect asa?

tlong = (unsigned long)tmp*2500; 

number = (unsigned long)tlong>>11; sau este suficient doar long?
 
Am introdus in intrerupere tot ce tinea de functia anods(). Daca am inteles bine, cu anods() separat in afara buclei de intrerupere ar fi functionat asa: se executa instructiunile din intrerupere pas cu pas cand se ajunge la functia anods() se pune in stiva adresa instructiunii la care s-a ajuns se executa anods() apoi programul se intoarce sa execute ce a mai ramas in intr. dupa anods(). Corect, asa intr-o explicare simpla?
 
Am mai facut o varianta pt. achizitia si calcularea temp.:
void temp_ADC()                           // Reading the analog inputs (ADC){  Temperature = 0;  for (ADCx=0; ADCx<16; ADCx++) {   Temperature += ADC_Read(9);            // Reading voltage values from channel 9 }    } void temp_calculation(){  result=(Temperature>>4);                 // divide by 16  Temperature -= result;                   // temp.= temp.-1/16  Temperature += ADC_Read(9);              // temp.=(temp.-1/16)+analog read  tmp = Temperature;  tmp =(tmp>>4);                           // divide by 16  tlong = (unsigned long)tmp*2500;         // Millivolts conversion  number = (unsigned long)tlong>>11;       // 0...2046 => 0...2500mV (am o amplificare                                            // cu 2x in motaj)}

Ideea ar fi sa fac 16 achizitii, apoi sa mentin rezultatul precedent, sa scad 1/16 din el apoi sa adaug mereu o achizitie noua.

Am incercat pe viu si pare ca functioneaza bine.

Cele 16 achizitii le fac la inceput, in main inainte de bucla while(1).

Dintre varianta asta si cea de mai sus, dpdv teoretic care ar fi mai buna?

 
Editat de danpin
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